PageRenderTime 54ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/extensions/CollabWatchlist/includes/CollabWatchlistEditor.php

https://github.com/ChuguluGames/mediawiki-svn
PHP | 1341 lines | 976 code | 52 blank | 313 comment | 127 complexity | 7a9fb8713b1270b469317b1ee0fc00d0 MD5 | raw file
  1. <?php
  2. /**
  3. * Provides the UI through which users can perform editing
  4. * operations on collaborative watchlists
  5. *
  6. * @ingroup CollabWatchlist
  7. * @author Rob Church <robchur@gmail.com>
  8. * @author Florian Hackenberger <f.hackenberger@chello.at>
  9. */
  10. class CollabWatchlistEditor {
  11. /**
  12. * Editing modes
  13. */
  14. const EDIT_CLEAR = 1;
  15. const CATEGORIES_EDIT_RAW = 2;
  16. const EDIT_NORMAL = 3;
  17. const TAGS_EDIT_RAW = 4;
  18. const SET_TAGS = 5;
  19. const UNSET_TAGS = 6;
  20. const USERS_EDIT_RAW = 7;
  21. const NEW_LIST = 8;
  22. const DELETE_LIST = 9;
  23. /**
  24. * Main execution point
  25. *
  26. * @param $rlId Collaborative watchlist id
  27. * @param $listIdsAndNames An array mapping from list id to list name
  28. * @param $output OutputPage
  29. * @param $request WebRequest
  30. * @param $mode int
  31. */
  32. public function execute( $rlId, $listIdsAndNames, $output, $request, $mode ) {
  33. global $wgUser, $wgCollabWatchlistPermissionDeniedPage;
  34. if ( wfReadOnly() ) {
  35. $output->readOnlyPage();
  36. return;
  37. }
  38. if ( ( $mode === self::EDIT_CLEAR ||
  39. $mode === self::CATEGORIES_EDIT_RAW ||
  40. $mode === self::USERS_EDIT_RAW ||
  41. $mode === self::EDIT_NORMAL ||
  42. $mode === self::TAGS_EDIT_RAW ||
  43. $mode === self::DELETE_LIST ) && ( !isset( $rlId ) || $rlId === 0 ) ) {
  44. $thisTitle = SpecialPage::getTitleFor( 'CollabWatchlist' );
  45. $output->redirect( $thisTitle->getLocalURL() );
  46. return;
  47. }
  48. $permissionDeniedTarget = Title::newFromText( $wgCollabWatchlistPermissionDeniedPage )->getLocalURL();
  49. switch( $mode ) {
  50. case self::EDIT_CLEAR:
  51. // The "Clear" link scared people too much.
  52. // Pass on to the raw editor, from which it's very easy to clear.
  53. case self::CATEGORIES_EDIT_RAW:
  54. $output->setPageTitle( $listIdsAndNames[$rlId] . ' ' . wfMsg( 'collabwatchlistedit-raw-title' ) );
  55. if ( $request->wasPosted() ) {
  56. if ( ! $this->checkToken( $request, $wgUser, $rlId ) ) {
  57. $output->redirect( $permissionDeniedTarget );
  58. break;
  59. }
  60. $wanted = $this->extractCollabWatchlistCategories( $request->getText( 'titles' ) );
  61. $current = $this->getCollabWatchlistCategories( $rlId );
  62. if ( count( $wanted ) > 0 ) {
  63. $toWatch = array_diff( $wanted, $current );
  64. $toUnwatch = array_diff( $current, $wanted );
  65. $toWatch = $this->watchTitles( $toWatch, $rlId );
  66. $this->unwatchTitles( $toUnwatch, $rlId, $wgUser );
  67. if ( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 )
  68. $output->addHTML( wfMsgExt( 'collabwatchlistedit-raw-done', 'parse' ) );
  69. if ( ( $count = count( $toWatch ) ) > 0 ) {
  70. $output->addHTML( wfMsgExt( 'collabwatchlistedit-raw-added', 'parse', $count ) );
  71. $this->showTitles( $toWatch, $output, $wgUser->getSkin() );
  72. }
  73. if ( ( $count = count( $toUnwatch ) ) > 0 ) {
  74. $output->addHTML( wfMsgExt( 'collabwatchlistedit-raw-removed', 'parse', $count ) );
  75. $this->showTitles( $toUnwatch, $output, $wgUser->getSkin() );
  76. }
  77. } else {
  78. $this->clearCollabWatchlist( $rlId );
  79. $output->addHTML( wfMsgExt( 'collabwatchlistedit-raw-removed', 'parse', count( $current ) ) );
  80. $this->showTitles( $current, $output, $wgUser->getSkin() );
  81. }
  82. }
  83. $this->showRawForm( $output, $rlId, $listIdsAndNames[$rlId] );
  84. break;
  85. case self::USERS_EDIT_RAW:
  86. $output->setPageTitle( $listIdsAndNames[$rlId] . ' ' . wfMsg( 'collabwatchlistedit-users-raw-title' ) );
  87. if ( $request->wasPosted() ) {
  88. if ( ! $this->checkToken( $request, $wgUser, $rlId ) ) {
  89. $output->redirect( $permissionDeniedTarget );
  90. break;
  91. }
  92. $wanted = $this->extractCollabWatchlistUsers( $request->getText( 'titles' ) );
  93. $current = $this->getCollabWatchlistUsers( $rlId );
  94. $isOwnerCb = create_function( '$a', 'return stripos($a, "' . COLLABWATCHLISTUSER_OWNER_TEXT . ' ' . '") === 0;' );
  95. $wantedOwners = array_filter( $wanted, $isOwnerCb );
  96. if ( count( $wantedOwners ) < 1 ) {
  97. // Make sure there is at least one owner left
  98. $currentOwners = array_filter( $current, $isOwnerCb );
  99. $reAddedOwner = current( $currentOwners );
  100. $wanted[] = $reAddedOwner;
  101. list( $type, $typeText, $titleText ) = $this->extractTypeTypeTextAndUsername( $reAddedOwner );
  102. $output->addHTML( wfMsgExt( 'collabwatchlistedit-users-last-owner', 'parse' ) );
  103. $this->showTitles( array( $titleText ), $output, $wgUser->getSkin() );
  104. }
  105. if ( count( $wanted ) > 0 ) {
  106. $toAdd = array_diff( $wanted, $current );
  107. $toDel = array_diff( $current, $wanted );
  108. $toAdd = $this->addUsers( $toAdd, $rlId );
  109. $this->delUsers( $toDel, $rlId );
  110. if ( count( $toAdd ) > 0 || count( $toDel ) > 0 )
  111. $output->addHTML( wfMsgExt( 'collabwatchlistedit-users-raw-done', 'parse' ) );
  112. if ( ( $count = count( $toAdd ) ) > 0 ) {
  113. $output->addHTML( wfMsgExt( 'collabwatchlistedit-users-raw-added', 'parse', $count ) );
  114. $this->showTitles( $toAdd, $output, $wgUser->getSkin() );
  115. }
  116. if ( ( $count = count( $toDel ) ) > 0 ) {
  117. $output->addHTML( wfMsgExt( 'collabwatchlistedit-users-raw-removed', 'parse', $count ) );
  118. $this->showTitles( $toDel, $output, $wgUser->getSkin() );
  119. }
  120. } else {
  121. $this->clearCollabWatchlist( $rlId );
  122. $output->addHTML( wfMsgExt( 'collabwatchlistedit-users-raw-removed', 'parse', count( $current ) ) );
  123. $this->showTitles( $current, $output, $wgUser->getSkin() );
  124. }
  125. }
  126. $this->showUsersRawForm( $output, $rlId, $listIdsAndNames[$rlId] );
  127. break;
  128. case self::TAGS_EDIT_RAW:
  129. $output->setPageTitle( $listIdsAndNames[$rlId] . ' ' . wfMsg( 'collabwatchlistedit-tags-raw-title' ) );
  130. if ( $request->wasPosted() ) {
  131. if ( ! $this->checkToken( $request, $wgUser, $rlId ) ) {
  132. $output->redirect( $permissionDeniedTarget );
  133. break;
  134. }
  135. $wanted = $this->extractCollabWatchlistTags( $request->getText( 'titles' ) );
  136. $current = $this->getCollabWatchlistTags( $rlId );
  137. if ( count( $wanted ) > 0 ) {
  138. $newTags = array_diff_assoc( $wanted, $current );
  139. $removeTags = array_diff_assoc( $current, $wanted );
  140. $this->removeTags( array_keys( $removeTags ), $rlId );
  141. $this->addTags( $newTags, $rlId );
  142. if ( count( $newTags ) > 0 || count( $removeTags ) > 0 )
  143. $output->addHTML( wfMsgExt( 'collabwatchlistedit-tags-raw-done', 'parse' ) );
  144. if ( ( $count = count( $newTags ) ) > 0 ) {
  145. $output->addHTML( wfMsgExt( 'collabwatchlistedit-tags-raw-added', 'parse', $count ) );
  146. $this->showTagList( $newTags, $output, $wgUser->getSkin() );
  147. }
  148. if ( ( $count = count( $removeTags ) ) > 0 ) {
  149. $output->addHTML( wfMsgExt( 'collabwatchlistedit-tags-raw-removed', 'parse', $count ) );
  150. $this->showTagList( $removeTags, $output, $wgUser->getSkin() );
  151. }
  152. } else {
  153. $this->clearCollabWatchlist( $rlId );
  154. $output->addHTML( wfMsgExt( 'collabwatchlistedit-tags-raw-removed', 'parse', count( $current ) ) );
  155. $this->showTagList( $current, $output, $wgUser->getSkin() );
  156. }
  157. }
  158. $this->showTagsRawForm( $output, $rlId, $listIdsAndNames[$rlId] );
  159. break;
  160. case self::EDIT_NORMAL:
  161. $output->setPageTitle( $listIdsAndNames[$rlId] . ' ' . wfMsg( 'collabwatchlistedit-normal-title' ) );
  162. if ( $request->wasPosted() ) {
  163. if ( ! $this->checkToken( $request, $wgUser, $rlId ) ) {
  164. $output->redirect( $permissionDeniedTarget );
  165. break;
  166. }
  167. $titles = $this->extractCollabWatchlistCategories( $request->getArray( 'titles' ) );
  168. $this->unwatchTitles( $titles, $rlId, $wgUser );
  169. $output->addHTML( wfMsgExt( 'collabwatchlistedit-normal-done', 'parse',
  170. $GLOBALS['wgLang']->formatNum( count( $titles ) ) ) );
  171. $this->showTitles( $titles, $output, $wgUser->getSkin() );
  172. }
  173. $this->showNormalForm( $output, $rlId );
  174. break;
  175. case self::SET_TAGS:
  176. $redirTarget = SpecialPage::getTitleFor( 'CollabWatchlist' )->getLocalUrl();
  177. if ( $request->wasPosted() ) {
  178. $rlId = $request->getInt( 'collabwatchlist', -1 );
  179. if ( ! $this->checkPermissions( $wgUser, $rlId, array( COLLABWATCHLISTUSER_USER, COLLABWATCHLISTUSER_OWNER ) ) ) {
  180. $output->redirect( $permissionDeniedTarget );
  181. break;
  182. }
  183. $redirTarget = $request->getText( 'redirTarget', $redirTarget );
  184. $tagToAdd = $request->getText( 'collabwatchlisttag' );
  185. $tagcomment = $request->getText( 'tagcomment' );
  186. $setPatrolled = $request->getBool( 'setpatrolled', false );
  187. $pagesToTag = array();
  188. if ( strlen( $tagToAdd ) !== 0 && $rlId !== -1 ) {
  189. $postValues = $request->getValues();
  190. foreach ( $postValues as $key => $value ) {
  191. if ( stripos( $key, 'collaborative-watchlist-addtag-' ) === 0 ) {
  192. $pageRevRcId = explode( '|', $value );
  193. if ( count( $pageRevRcId ) < 3 ) {
  194. continue;
  195. }
  196. $pagesToTag[$pageRevRcId[0]][] = array( 'rev_id' => $pageRevRcId[1], 'rc_id' => $pageRevRcId[2] );
  197. }
  198. }
  199. $this->setTags( $pagesToTag, $tagToAdd, $wgUser->getId(), $rlId, $tagcomment, $setPatrolled );
  200. }
  201. }
  202. $output->redirect( $redirTarget );
  203. break;
  204. case self::UNSET_TAGS:
  205. $rlId = $request->getInt( 'collabwatchlist', -1 );
  206. if ( ! $this->checkPermissions( $wgUser, $rlId, array( COLLABWATCHLISTUSER_USER, COLLABWATCHLISTUSER_OWNER ) ) ) {
  207. $output->redirect( $permissionDeniedTarget );
  208. break;
  209. }
  210. $redirTarget = SpecialPage::getTitleFor( 'CollabWatchlist' )->getLocalUrl();
  211. $redirTarget = $request->getText( 'redirTarget', $redirTarget );
  212. $page = $request->getText( 'collabwatchlistpage' );
  213. $tagToDel = $request->getText( 'collabwatchlisttag' );
  214. $rcId = $request->getInt( 'collabwatchlistrcid', -1 );
  215. if ( strlen( $page ) !== 0 && strlen( $tagToDel ) !== 0 && $rlId !== -1 && $rcId !== -1 ) {
  216. $pagesToUntag[$page][] = array( 'rc_id' => $rcId );
  217. $this->unsetTags( $pagesToUntag, $tagToDel, $wgUser->getId(), $rlId );
  218. }
  219. $output->redirect( $redirTarget );
  220. break;
  221. case self::NEW_LIST:
  222. if ( $request->wasPosted() ) {
  223. $redirTarget = SpecialPage::getTitleFor( 'CollabWatchlist' )->getLocalUrl();
  224. $listId = $this->createNewList( $request->getText( 'listname' ) );
  225. if ( isset( $listId ) ) {
  226. $output->redirect( $redirTarget );
  227. } else {
  228. $output->addHTML( wfMsgExt( 'collabwatchlistnew-name-exists', 'parse' ) );
  229. }
  230. } else {
  231. $this->showNewListForm( $output );
  232. }
  233. break;
  234. case self::DELETE_LIST:
  235. $output->setPageTitle( $listIdsAndNames[$rlId] . ' ' . wfMsg( 'collabwatchlistdelete-title' ) );
  236. $rlId = $request->getInt( 'collabwatchlist', -1 );
  237. if ( $request->wasPosted() ) {
  238. if ( ! $this->checkToken( $request, $wgUser, $rlId ) ) {
  239. $output->redirect( $permissionDeniedTarget );
  240. break;
  241. }
  242. $this->deleteList( $rlId );
  243. $redirTarget = SpecialPage::getTitleFor( 'CollabWatchlist' )->getLocalUrl();
  244. $output->redirect( $redirTarget );
  245. } else {
  246. $this->showDeleteListForm( $output, $rlId );
  247. }
  248. break;
  249. }
  250. }
  251. /**
  252. * Check the edit token from a form submission
  253. *
  254. * @param $request WebRequest
  255. * @param $user User
  256. * @param $rlId Id of the collaborative watchlist to check users against
  257. * @param $memberTypes Which types of members are allowed
  258. * @return bool
  259. */
  260. private function checkToken( $request, $user, $rlId, $memberTypes = array( COLLABWATCHLISTUSER_OWNER ) ) {
  261. $tokenOk = $user->matchEditToken( $request->getVal( 'token' ), 'watchlistedit' ) && $request->getVal( 'collabwatchlist' ) !== 0;
  262. if ( $tokenOk === false )
  263. return $tokenOk;
  264. return $this->checkPermissions( $user, $rlId, $memberTypes );
  265. }
  266. private function checkPermissions( $user, $rlId, $memberTypes = array( COLLABWATCHLISTUSER_OWNER ) ) {
  267. // Check permissions
  268. $dbr = wfGetDB( DB_MASTER );
  269. $res = $dbr->select( 'collabwatchlistuser',
  270. 'COUNT(*) AS count',
  271. array( 'rl_id' => $rlId, 'user_id' => $user->getId(), 'rlu_type' => $memberTypes ),
  272. __METHOD__
  273. );
  274. $row = $dbr->fetchObject( $res );
  275. return $row->count >= 1;
  276. }
  277. /**
  278. * Extract a list of categories from a blob of text, returning
  279. * (prefixed) strings
  280. *
  281. * @param $list mixed
  282. * @return array
  283. */
  284. private function extractCollabWatchlistCategories( $list ) {
  285. $titles = array();
  286. if ( !is_array( $list ) ) {
  287. $list = explode( "\n", trim( $list ) );
  288. if ( !is_array( $list ) )
  289. return array();
  290. }
  291. foreach ( $list as $text ) {
  292. $subtract = false;
  293. $text = trim( $text );
  294. $titleText = $text;
  295. if ( stripos( $text, '- ' ) === 0 ) {
  296. $subtract = true;
  297. $titleText = trim( substr( $text, 2 ) );
  298. }
  299. if ( strlen( $text ) > 0 ) {
  300. $title = Title::newFromText( $titleText );
  301. if ( $title instanceof Title && $title->isWatchable() ) {
  302. $titles[] = $subtract ? '- ' . $title->getPrefixedText() : $title->getPrefixedText();
  303. }
  304. }
  305. }
  306. return array_unique( $titles );
  307. }
  308. private function extractTypeTypeTextAndUsername( $typeAndUsernameStr ) {
  309. $type = COLLABWATCHLISTUSER_USER;
  310. $typeText = COLLABWATCHLISTUSER_USER_TEXT;
  311. $text = trim( $typeAndUsernameStr );
  312. $titleText = $text;
  313. if ( stripos( $text, COLLABWATCHLISTUSER_OWNER_TEXT . ' ' ) === 0 ) {
  314. $type = COLLABWATCHLISTUSER_OWNER;
  315. $typeText = COLLABWATCHLISTUSER_OWNER_TEXT;
  316. $titleText = trim( substr( $text, strlen( COLLABWATCHLISTUSER_OWNER_TEXT . ' ' ) ) );
  317. } else if ( stripos( $text, COLLABWATCHLISTUSER_USER_TEXT . ' ' ) === 0 ) {
  318. $type = COLLABWATCHLISTUSER_USER;
  319. $typeText = COLLABWATCHLISTUSER_USER_TEXT;
  320. $titleText = trim( substr( $text, strlen( COLLABWATCHLISTUSER_USER_TEXT . ' ' ) ) );
  321. } else if ( stripos( $text, COLLABWATCHLISTUSER_TRUSTED_EDITOR_TEXT . ' ' ) === 0 ) {
  322. $type = COLLABWATCHLISTUSER_TRUSTED_EDITOR;
  323. $typeText = COLLABWATCHLISTUSER_TRUSTED_EDITOR_TEXT;
  324. $titleText = trim( substr( $text, strlen( COLLABWATCHLISTUSER_TRUSTED_EDITOR_TEXT . ' ' ) ) );
  325. }
  326. return array( $type, $typeText, $titleText );
  327. }
  328. /**
  329. * Extract a list of users from a blob of text, returning
  330. * (prefixed) strings
  331. *
  332. * @param $list mixed
  333. * @return array
  334. */
  335. private function extractCollabWatchlistUsers( $list ) {
  336. $titles = array();
  337. if ( !is_array( $list ) ) {
  338. $list = explode( "\n", trim( $list ) );
  339. if ( !is_array( $list ) )
  340. return array();
  341. }
  342. foreach ( $list as $text ) {
  343. list( $type, $typeText, $titleText ) = $this->extractTypeTypeTextAndUsername( $text );
  344. if ( strlen( $text ) > 0 ) {
  345. $user = User::newFromName( $titleText );
  346. if ( $user instanceof User ) {
  347. $titles[] = $typeText . ' ' . $user->getName();
  348. }
  349. }
  350. }
  351. return array_unique( $titles );
  352. }
  353. /**
  354. * Extract a list of tags from a blob of text, returning
  355. * (prefixed) strings
  356. *
  357. * @param $list mixed
  358. * @return array
  359. */
  360. private function extractCollabWatchlistTags( $list ) {
  361. $tags = array();
  362. if ( !is_array( $list ) ) {
  363. $list = explode( "\n", trim( $list ) );
  364. if ( !is_array( $list ) )
  365. return array();
  366. }
  367. foreach ( $list as $text ) {
  368. $subtract = false;
  369. $text = trim( $text );
  370. if ( strlen( $text ) > 0 ) {
  371. $pipepos = stripos( $text, '|' );
  372. $description = '';
  373. if ( $pipepos > 0 ) {
  374. if ( ( $pipepos + 1 ) < strlen( $text ) )
  375. $description = trim( substr( $text, $pipepos + 1 ) );
  376. $text = trim( substr( $text, 0, $pipepos ) );
  377. }
  378. $tags[$text] = $description;
  379. }
  380. }
  381. return $tags;
  382. }
  383. /**
  384. * Print out a list of linked titles
  385. *
  386. * $titles can be an array of strings or Title objects; the former
  387. * is preferred, since Titles are very memory-heavy
  388. *
  389. * @param $titles An array of strings, or Title objects
  390. * @param $output OutputPage
  391. * @param $skin Skin
  392. */
  393. private function showTitles( $titles, $output, $skin ) {
  394. $talk = wfMsgHtml( 'talkpagelinktext' );
  395. // Do a batch existence check
  396. $batch = new LinkBatch();
  397. foreach ( $titles as $title ) {
  398. if ( !$title instanceof Title )
  399. $title = Title::newFromText( $title );
  400. if ( $title instanceof Title ) {
  401. $batch->addObj( $title );
  402. $batch->addObj( $title->getTalkPage() );
  403. }
  404. }
  405. $batch->execute();
  406. // Print out the list
  407. $output->addHTML( "<ul>\n" );
  408. foreach ( $titles as $title ) {
  409. if ( !$title instanceof Title )
  410. $title = Title::newFromText( $title );
  411. if ( $title instanceof Title ) {
  412. $output->addHTML( "<li>" . $skin->link( $title )
  413. . ' (' . $skin->link( $title->getTalkPage(), $talk ) . ")</li>\n" );
  414. }
  415. }
  416. $output->addHTML( "</ul>\n" );
  417. }
  418. /**
  419. * Print out a list of tags with description
  420. *
  421. * $titles can be an array of strings or Title objects; the former
  422. * is preferred, since Titles are very memory-heavy
  423. *
  424. * @param $tagsAndDesc An array of strings mapping from tag name to description
  425. * @param $output OutputPage
  426. * @param $skin Skin
  427. */
  428. private function showTagList( $tagsAndDesc, $output, $skin ) {
  429. // Print out the list
  430. $output->addHTML( "<ul>\n" );
  431. foreach ( $tagsAndDesc as $title => $description ) {
  432. $output->addHTML( "<li>" . $title
  433. . ' (' . $description . ")</li>\n" );
  434. }
  435. $output->addHTML( "</ul>\n" );
  436. }
  437. /**
  438. * Count the number of categories on a collaborative watchlist
  439. *
  440. * @param $rlId Collaborative watchlist id
  441. * @return int
  442. */
  443. private function countCollabWatchlistCategories( $rlId ) {
  444. $dbr = wfGetDB( DB_MASTER );
  445. $res = $dbr->select( 'collabwatchlistcategory', 'COUNT(*) AS count', array( 'rl_id' => $rlId ), __METHOD__ );
  446. $row = $dbr->fetchObject( $res );
  447. return $row->count;
  448. }
  449. /**
  450. * Count the number of users on a collaborative watchlist
  451. *
  452. * @param $rlId Collaborative watchlist id
  453. * @return int
  454. */
  455. private function countCollabWatchlistUsers( $rlId ) {
  456. $dbr = wfGetDB( DB_MASTER );
  457. $res = $dbr->select( 'collabwatchlistuser', 'COUNT(*) AS count', array( 'rl_id' => $rlId ), __METHOD__ );
  458. $row = $dbr->fetchObject( $res );
  459. return $row->count;
  460. }
  461. /**
  462. * Count the number of tags on a collaborative watchlist
  463. *
  464. * @param $rlId Collaborative watchlist id
  465. * @return int
  466. */
  467. private function countCollabWatchlistTags( $rlId ) {
  468. $dbr = wfGetDB( DB_MASTER );
  469. $res = $dbr->select( 'collabwatchlisttag', 'COUNT(*) AS count', array( 'rl_id' => $rlId ), __METHOD__ );
  470. $row = $dbr->fetchObject( $res );
  471. return $row->count;
  472. }
  473. /**
  474. * Count the number of set edit tags on a collaborative watchlist
  475. *
  476. * @param $rlId Collaborative watchlist id
  477. * @return int
  478. */
  479. private function countCollabWatchlistSetTags( $rlId ) {
  480. $dbr = wfGetDB( DB_MASTER );
  481. $res = $dbr->select( 'collabwatchlistrevisiontag', 'COUNT(*) AS count', array( 'rl_id' => $rlId ), __METHOD__ );
  482. $row = $dbr->fetchObject( $res );
  483. return $row->count;
  484. }
  485. /**
  486. * Prepare a list of categories on a collaborative watchlist
  487. * and return an array of (prefixed) strings
  488. *
  489. * @param $rlId Collaborative watchlist id
  490. * @return array
  491. */
  492. private function getCollabWatchlistCategories( $rlId ) {
  493. $list = array();
  494. $dbr = wfGetDB( DB_MASTER );
  495. $res = $dbr->select(
  496. array( 'collabwatchlistcategory', 'page' ),
  497. array( 'page_title', 'page_namespace', 'subtract' ),
  498. array(
  499. 'rl_id' => $rlId,
  500. ),
  501. __METHOD__, array(),
  502. # Join conditions
  503. array( 'page' => array( 'JOIN', 'page.page_id = collabwatchlistcategory.cat_page_id' ) )
  504. );
  505. if ( $res->numRows() > 0 ) {
  506. foreach ( $res as $row ) {
  507. $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
  508. if ( $title instanceof Title && !$title->isTalkPage() )
  509. $list[] = $row->subtract ? '- ' . $title->getPrefixedText() : $title->getPrefixedText();
  510. }
  511. }
  512. return $list;
  513. }
  514. /**
  515. * Prepare a list of users on a collaborative watchlist
  516. * and return an array of (prefixed) strings
  517. *
  518. * @param $rlId Collaborative watchlist id
  519. * @return array
  520. */
  521. private function getCollabWatchlistUsers( $rlId ) {
  522. $list = array();
  523. $dbr = wfGetDB( DB_MASTER );
  524. $res = $dbr->select(
  525. array( 'collabwatchlistuser', 'user' ),
  526. array( 'user_name', 'rlu_type' ),
  527. array(
  528. 'rl_id' => $rlId,
  529. ),
  530. __METHOD__, array(),
  531. # Join conditions
  532. array( 'user' => array( 'JOIN', 'user.user_id = collabwatchlistuser.user_id' ) )
  533. );
  534. if ( $res->numRows() > 0 ) {
  535. foreach ( $res as $row ) {
  536. $typeText = fnCollabWatchlistUserTypeToText( $row->rlu_type );
  537. $list[] = $typeText . ' ' . $row->user_name;
  538. }
  539. }
  540. return $list;
  541. }
  542. /**
  543. * Prepare a list of tags on a collaborative watchlist
  544. * and return an array of tag names mapping to tag descriptions
  545. *
  546. * @param $rlId Collaborative watchlist id
  547. * @return array
  548. */
  549. private function getCollabWatchlistTags( $rlId ) {
  550. $list = array();
  551. $dbr = wfGetDB( DB_MASTER );
  552. $res = $dbr->select(
  553. array( 'collabwatchlisttag' ),
  554. array( 'rt_name', 'rt_description' ),
  555. array(
  556. 'rl_id' => $rlId,
  557. ), __METHOD__
  558. );
  559. if ( $res->numRows() > 0 ) {
  560. foreach ( $res as $row ) {
  561. $list[$row->rt_name] = $row->rt_description;
  562. }
  563. }
  564. return $list;
  565. }
  566. /**
  567. * Get a list of categories on collaborative watchlist, excluding talk pages,
  568. * and return as a two-dimensional array with namespace and title which
  569. * maps to an array with 'redirect' and 'subtract' keys.
  570. *
  571. * @param $rlId Collaborative watchlist id
  572. * @return array
  573. */
  574. private function getWatchlistInfo( $rlId ) {
  575. $titles = array();
  576. $dbr = wfGetDB( DB_MASTER );
  577. $res = $dbr->select(
  578. array( 'collabwatchlistcategory', 'page' ),
  579. array( 'page_title', 'page_namespace', 'page_id', 'page_len', 'page_is_redirect', 'subtract' ),
  580. array(
  581. 'rl_id' => $rlId,
  582. ),
  583. __METHOD__, array(),
  584. # Join conditions
  585. array( 'page' => array( 'JOIN', 'page.page_id = collabwatchlistcategory.cat_page_id' ) )
  586. );
  587. if ( $res && $dbr->numRows( $res ) > 0 ) {
  588. $cache = LinkCache::singleton();
  589. foreach ( $res as $row ) {
  590. $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
  591. if ( $title instanceof Title ) {
  592. // Update the link cache while we're at it
  593. if ( $row->page_id ) {
  594. $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect );
  595. } else {
  596. $cache->addBadLinkObj( $title );
  597. }
  598. // Ignore non-talk
  599. if ( !$title->isTalkPage() )
  600. $titles[$row->page_namespace][$row->page_title] = array( 'redirect' => $row->page_is_redirect, 'subtract' => $row->subtract );
  601. }
  602. }
  603. }
  604. return $titles;
  605. }
  606. /**
  607. * Show a message indicating the number of categories on the collaborative watchlist,
  608. * and return this count for additional checking
  609. *
  610. * @param $output OutputPage
  611. * @param $rlId The id of the collaborative watchlist
  612. * @return int
  613. */
  614. private function showItemCount( $output, $rlId ) {
  615. if ( ( $count = $this->countCollabWatchlistCategories( $rlId ) ) > 0 ) {
  616. $output->addHTML( wfMsgExt( 'collabwatchlistedit-numitems', 'parse',
  617. $GLOBALS['wgLang']->formatNum( $count ) ) );
  618. } else {
  619. $output->addHTML( wfMsgExt( 'collabwatchlistedit-noitems', 'parse' ) );
  620. }
  621. return $count;
  622. }
  623. /**
  624. * Show a message indicating the number of categories on the collaborative watchlist,
  625. * and return this count for additional checking
  626. *
  627. * @param $output OutputPage
  628. * @param $rlId The id of the collaborative watchlist
  629. * @return int
  630. */
  631. private function showTagItemCount( $output, $rlId ) {
  632. if ( ( $count = $this->countCollabWatchlistTags( $rlId ) ) > 0 ) {
  633. $output->addHTML( wfMsgExt( 'collabwatchlistedit-tags-numitems', 'parse',
  634. $GLOBALS['wgLang']->formatNum( $count ) ) );
  635. } else {
  636. $output->addHTML( wfMsgExt( 'collabwatchlistedit-tags-noitems', 'parse' ) );
  637. }
  638. return $count;
  639. }
  640. /**
  641. * Show a message indicating the number of set tags for edits on the collaborative watchlist,
  642. * and return this count for additional checking
  643. *
  644. * @param $output OutputPage
  645. * @param $rlId The id of the collaborative watchlist
  646. * @return int
  647. */
  648. private function showSetTagsItemCount( $output, $rlId ) {
  649. if ( ( $count = $this->countCollabWatchlistSetTags( $rlId ) ) > 0 ) {
  650. $output->addHTML( wfMsgExt( 'collabwatchlistedit-set-tags-numitems', 'parse',
  651. $GLOBALS['wgLang']->formatNum( $count ) ) );
  652. } else {
  653. $output->addHTML( wfMsgExt( 'collabwatchlistedit-set-tags-noitems', 'parse' ) );
  654. }
  655. return $count;
  656. }
  657. /**
  658. * Show a message indicating the number of categories on the collaborative watchlist,
  659. * and return this count for additional checking
  660. *
  661. * @param $output OutputPage
  662. * @param $rlId The id of the collaborative watchlist
  663. * @return int
  664. */
  665. private function showUserItemCount( $output, $rlId ) {
  666. if ( ( $count = $this->countCollabWatchlistUsers( $rlId ) ) > 0 ) {
  667. $output->addHTML( wfMsgExt( 'collabwatchlistedit-users-numitems', 'parse',
  668. $GLOBALS['wgLang']->formatNum( $count ) ) );
  669. } else {
  670. $output->addHTML( wfMsgExt( 'collabwatchlistedit-users-noitems', 'parse' ) );
  671. }
  672. return $count;
  673. }
  674. /**
  675. * Remove all categories from a collaborative watchlist
  676. *
  677. * @param $rlId Collaborative watchlist if
  678. */
  679. private function clearCollabWatchlist( $rlId ) {
  680. $dbw = wfGetDB( DB_MASTER );
  681. $dbw->delete( 'collabwatchlistcategory', array( 'rl_id' => $rlId ), __METHOD__ );
  682. }
  683. /**
  684. * Add a list of categories to a collaborative watchlist
  685. *
  686. * $titles is an array of strings, prefixed with '- ', if the
  687. * category is subtracted
  688. *
  689. * @param $titles An array of strings
  690. * @param $rlId The id of the collaborative watchlist
  691. */
  692. private function watchTitles( $titles, $rlId ) {
  693. $dbw = wfGetDB( DB_MASTER );
  694. $rows = array();
  695. $added = array();
  696. foreach ( $titles as $title ) {
  697. $subtract = false;
  698. $title = trim( $title );
  699. $titleText = $title;
  700. if ( stripos( $title, '- ' ) === 0 ) {
  701. $subtract = true;
  702. $titleText = trim( substr( $title, 2 ) );
  703. }
  704. $titleObj = Title::newFromText( $titleText );
  705. if ( $titleObj instanceof Title && $titleObj->exists() ) {
  706. $rows[] = array(
  707. 'rl_id' => $rlId,
  708. 'cat_page_id' => $titleObj->getArticleID(),
  709. 'subtract' => $subtract,
  710. );
  711. $added[] = $title;
  712. }
  713. }
  714. $dbw->insert( 'collabwatchlistcategory', $rows, __METHOD__, 'IGNORE' );
  715. return $added;
  716. }
  717. /**
  718. * Add a list of users to a collaborative watchlist
  719. *
  720. * $titles is an array of strings, prefixed with the user type text and ' '
  721. *
  722. * @param $titles An array of strings
  723. * @param $rlId The id of the collaborative watchlist
  724. */
  725. private function addUsers( $users, $rlId ) {
  726. $dbw = wfGetDB( DB_MASTER );
  727. $rows = array();
  728. $added = array();
  729. foreach ( $users as $userString ) {
  730. list( $type, $typeText, $titleText ) = $this->extractTypeTypeTextAndUsername( $userString );
  731. $user = User::newFromName( $titleText );
  732. if ( $user instanceof User && $user->getId() !== 0 ) {
  733. $rows[] = array(
  734. 'rl_id' => $rlId,
  735. 'user_id' => $user->getId(),
  736. 'rlu_type' => $type,
  737. );
  738. $added[] = $userString;
  739. }
  740. }
  741. $dbw->insert( 'collabwatchlistuser', $rows, __METHOD__, 'IGNORE' );
  742. return $added;
  743. }
  744. private function setTags( $titlesAndTagInfo, $tag, $userId, $rlId, $comment, $setPatrolled = false ) {
  745. // XXX Attach a hook to delete tags from the collabwatchlistrevisiontag table as soon as the actual tags are deleted from the change_tags table
  746. $allowedTagsAndInfo = $this->getCollabWatchlistTags( $rlId );
  747. if ( !array_key_exists( $tag, $allowedTagsAndInfo ) ) {
  748. return false;
  749. }
  750. $dbw = wfGetDB( DB_MASTER );
  751. foreach ( $titlesAndTagInfo as $title => $infos ) {
  752. $rcIds = array();
  753. // Add entries for the tag to the change_tags table
  754. // optionally mark edit as patrolled
  755. foreach ( $infos as $infoKey => $info ) {
  756. ChangeTags::addTags( $tag, $info['rc_id'], $info['rev_id'] );
  757. $rcIds[] = $info['rc_id'];
  758. if ( $setPatrolled ) {
  759. RecentChange::markPatrolled( $info['rc_id'] );
  760. }
  761. }
  762. // Add the tagged revisions to the collaborative watchlist
  763. $sql = 'INSERT IGNORE INTO collabwatchlistrevisiontag (ct_rc_id, ct_tag, rl_id, user_id, rrt_comment)
  764. SELECT ct_rc_id, ct_tag, ' . $dbw->strencode( $rlId ) . ',' .
  765. $dbw->strencode( $userId ) . ',' .
  766. $dbw->addQuotes( $comment ) . ' FROM change_tag WHERE ct_tag = ? AND ct_rc_id ';
  767. if ( count( $rcIds ) > 1 ) {
  768. $sql .= 'IN (' . $dbw->makeList( $rcIds ) . ')';
  769. $params = array( $tag );
  770. } else {
  771. $sql .= '= ?';
  772. $params = array( $tag, $rcIds[0] );
  773. }
  774. $prepSql = $dbw->prepare( $sql );
  775. $res = $dbw->execute( $prepSql, $params );
  776. $dbw->freePrepared( $prepSql );
  777. return true;
  778. }
  779. }
  780. private function unsetTags( $titlesAndTagInfo, $tag, $userId, $rlId ) {
  781. $dbw = wfGetDB( DB_MASTER );
  782. foreach ( $titlesAndTagInfo as $title => $infos ) {
  783. $rcIds = array();
  784. foreach ( $infos as $infoKey => $info ) {
  785. // XXX Remove entries for the tag from the change_tags table
  786. // ChangeTags::addTags($tag, $info['rc_id'], $info['rev_id']);
  787. $rcIds[] = $info['rc_id'];
  788. }
  789. // Remove the tag from the collaborative watchlist
  790. $sql = 'DELETE FROM collabwatchlistrevisiontag WHERE ct_tag = ? AND ct_rc_id ';
  791. if ( count( $rcIds ) > 1 ) {
  792. $sql .= 'IN (' . $dbw->makeList( $rcIds ) . ')';
  793. $params = array( $tag );
  794. } else {
  795. $sql .= '= ?';
  796. $params = array( $tag, $rcIds[0] );
  797. }
  798. $prepSql = $dbw->prepare( $sql );
  799. $res = $dbw->execute( $prepSql, $params );
  800. $dbw->freePrepared( $prepSql );
  801. }
  802. }
  803. /**
  804. * Add a list of tags to a collaborative watchlist
  805. *
  806. * $titles is an array of strings
  807. *
  808. * @param $titles An array of strings (tag names) mapping to tag descriptions
  809. * @param $rlId The id of the collaborative watchlist
  810. */
  811. private function addTags( $titles, $rlId ) {
  812. $dbw = wfGetDB( DB_MASTER );
  813. $rows = array();
  814. foreach ( $titles as $title => $description ) {
  815. $rows[] = array(
  816. 'rl_id' => $rlId,
  817. 'rt_name' => $title,
  818. 'rt_description' => $description,
  819. );
  820. }
  821. $dbw->insert( 'collabwatchlisttag', $rows, __METHOD__, 'IGNORE' );
  822. }
  823. /**
  824. * Remove a list of categories from a collaborative watchlist
  825. *
  826. * $titles is an array of strings, prefixed with '- ', if the
  827. * category is subtracted
  828. *
  829. * @param $titles An array of strings
  830. * @param $rlId The id of the collaborative watchlist
  831. */
  832. private function unwatchTitles( $titles, $rlId, $user ) {
  833. $dbw = wfGetDB( DB_MASTER );
  834. foreach ( $titles as $title ) {
  835. $subtract = false;
  836. $title = trim( $title );
  837. $titleText = $title;
  838. if ( stripos( $title, '- ' ) === 0 ) {
  839. $subtract = true;
  840. $titleText = trim( substr( $title, 2 ) );
  841. }
  842. $title = Title::newFromText( $titleText );
  843. if ( $title instanceof Title ) {
  844. $dbw->delete(
  845. 'collabwatchlistcategory',
  846. array(
  847. 'rl_id' => $rlId,
  848. 'cat_page_id' => $title->getArticleID(),
  849. 'subtract' => $subtract,
  850. ),
  851. __METHOD__
  852. );
  853. $article = new Article( $title );
  854. // XXX Check if we can simply rename the hook, or if we need to register it
  855. wfRunHooks( 'UnwatchArticleComplete', array( &$user, &$article ) );
  856. }
  857. }
  858. }
  859. /**
  860. * Remove a list of users from a collaborative watchlist
  861. *
  862. * $titles is an array of strings, prefixed with the user type text and ' '
  863. *
  864. * @param $titles An array of strings
  865. * @param $rlId The id of the collaborative watchlist
  866. */
  867. private function delUsers( $users, $rlId ) {
  868. $dbw = wfGetDB( DB_MASTER );
  869. foreach ( $users as $userString ) {
  870. list( $type, $typeText, $titleText ) = $this->extractTypeTypeTextAndUsername( $userString );
  871. $user = User::newFromName( $titleText );
  872. if ( $user instanceof User && $user->getId() !== 0 ) {
  873. $dbw->delete(
  874. 'collabwatchlistuser',
  875. array(
  876. 'rl_id' => $rlId,
  877. 'user_id' => $user->getId(),
  878. 'rlu_type' => $type,
  879. ),
  880. __METHOD__
  881. );
  882. }
  883. }
  884. // XXX Check if we can simply rename the hook, or if we need to register it
  885. // wfRunHooks('UnwatchArticleComplete',array(&$user,&$article));
  886. }
  887. /**
  888. * Remove a list of tags from a collaborative watchlist
  889. *
  890. * $titles is an array of strings
  891. *
  892. * @param $titles An array of strings
  893. * @param $rlId The id of the collaborative watchlist
  894. */
  895. private function removeTags( $titles, $rlId ) {
  896. $dbw = wfGetDB( DB_MASTER );
  897. foreach ( $titles as $title ) {
  898. $dbw->delete(
  899. 'collabwatchlisttag',
  900. array(
  901. 'rl_id' => $rlId,
  902. 'rt_name' => $title,
  903. ),
  904. __METHOD__
  905. );
  906. // $article = new Article($title);
  907. // XXX Check if we can simply rename the hook, or if we need to register it
  908. // wfRunHooks('UnwatchArticleComplete',array(&$user,&$article));
  909. }
  910. }
  911. /**
  912. * Show the standard collaborative watchlist editing form
  913. *
  914. * @param $output OutputPage
  915. * @param $rlId Collaborative watchlist id
  916. */
  917. private function showNormalForm( $output, $rlId ) {
  918. global $wgUser;
  919. if ( ( $count = $this->showItemCount( $output, $rlId ) ) > 0 ) {
  920. $self = SpecialPage::getTitleFor( 'CollabWatchlist' );
  921. $form = Xml::openElement( 'form', array( 'method' => 'post',
  922. 'action' => $self->getLocalUrl( array( 'action' => 'edit' ) ) ) );
  923. $form .= Html::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
  924. $form .= Html::hidden( 'collabwatchlist', $rlId );
  925. $form .= "<fieldset>\n<legend>" . wfMsgHtml( 'collabwatchlistedit-normal-legend' ) . "</legend>";
  926. $form .= wfMsgExt( 'collabwatchlistedit-normal-explain', 'parse' );
  927. $form .= $this->buildRemoveList( $rlId, $wgUser->getSkin() );
  928. $form .= '<p>' . Xml::submitButton( wfMsg( 'collabwatchlistedit-normal-submit' ) ) . '</p>';
  929. $form .= '</fieldset></form>';
  930. $output->addHTML( $form );
  931. }
  932. }
  933. private function showNewListForm( $output ) {
  934. global $wgUser;
  935. $self = SpecialPage::getTitleFor( 'CollabWatchlist' );
  936. $form = Xml::openElement( 'form', array( 'method' => 'post',
  937. 'action' => $self->getLocalUrl( array( 'action' => 'newList' ) ) ) );
  938. $form .= Html::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
  939. $form .= "<fieldset>\n<legend>" . wfMsgHtml( 'collabwatchlistnew-legend' ) . "</legend>";
  940. $form .= wfMsgExt( 'collabwatchlistnew-explain', 'parse' );
  941. $form .= Xml::label( wfMsg( 'collabwatchlistnew-name' ), 'listname' ) . '&nbsp;' . Xml::input( 'listname' ) . '&nbsp;';
  942. $form .= '<p>' . Xml::submitButton( wfMsg( 'collabwatchlistnew-submit' ) ) . '</p>';
  943. $form .= '</fieldset></form>';
  944. $output->addHTML( $form );
  945. }
  946. private function showDeleteListForm( $output, $rlId ) {
  947. global $wgUser;
  948. $self = SpecialPage::getTitleFor( 'CollabWatchlist' );
  949. $form = Xml::openElement( 'form', array( 'method' => 'post',
  950. 'action' => $self->getLocalUrl( array( 'action' => 'delete' ) ) ) );
  951. $form .= Html::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
  952. $form .= Html::hidden( 'collabwatchlist', $rlId );
  953. $form .= "<fieldset>\n<legend>" . wfMsgHtml( 'collabwatchlistdelete-legend' ) . "</legend>";
  954. $form .= wfMsgExt( 'collabwatchlistdelete-explain', 'parse' );
  955. $this->showUserItemCount( $output, $rlId );
  956. $this->showSetTagsItemCount( $output, $rlId );
  957. $form .= '<p>' . Xml::submitButton( wfMsg( 'collabwatchlistdelete-submit' ) ) . '</p>';
  958. $form .= '</fieldset></form>';
  959. $output->addHTML( $form );
  960. }
  961. private function createNewList( $name ) {
  962. global $wgUser;
  963. if ( !isset( $name ) || empty( $name ) )
  964. return;
  965. $dbw = wfGetDB( DB_MASTER );
  966. $dbw->begin();
  967. try {
  968. $rl_id = $dbw->nextSequenceValue( 'collabwatchlist_rl_id_seq' );
  969. $dbw->insert( 'collabwatchlist', array(
  970. 'rl_id' => $rl_id,
  971. 'rl_name' => $name,
  972. 'rl_start' => wfTimestamp( TS_ISO_8601 ),
  973. ), __METHOD__, 'IGNORE' );
  974. $affected = $dbw->affectedRows();
  975. if ( $affected ) {
  976. $newid = $dbw->insertId();
  977. } else {
  978. return;
  979. }
  980. $rlu_id = $dbw->nextSequenceValue( 'collabwatchlistuser_rlu_id_seq' );
  981. $dbw->insert( 'collabwatchlistuser', array(
  982. 'rlu_id' => $rlu_id,
  983. 'rl_id' => $newid,
  984. 'user_id' => $wgUser->getId(),
  985. 'rlu_type' => COLLABWATCHLISTUSER_OWNER,
  986. ), __METHOD__, 'IGNORE' );
  987. $affected = $dbw->affectedRows();
  988. if ( ! $affected ) {
  989. $dbw->rollback();
  990. return;
  991. }
  992. $dbw->commit();
  993. return $newid;
  994. } catch ( Exception $e ) {
  995. $dbw->rollback();
  996. }
  997. }
  998. private function deleteList( $rlId ) {
  999. if ( !isset( $rlId ) || empty( $rlId ) )
  1000. return;
  1001. $dbw = wfGetDB( DB_MASTER );
  1002. $dbw->begin();
  1003. try {
  1004. $dbw->delete( 'collabwatchlistrevisiontag', array(
  1005. 'rl_id' => $rlId,
  1006. ), __METHOD__ );
  1007. $dbw->delete( 'collabwatchlisttag', array(
  1008. 'rl_id' => $rlId,
  1009. ), __METHOD__ );
  1010. $dbw->delete( 'collabwatchlistcategory', array(
  1011. 'rl_id' => $rlId,
  1012. ), __METHOD__ );
  1013. $dbw->delete( 'collabwatchlistuser', array(
  1014. 'rl_id' => $rlId,
  1015. ), __METHOD__ );
  1016. $dbw->delete( 'collabwatchlist', array(
  1017. 'rl_id' => $rlId,
  1018. ), __METHOD__ );
  1019. $affected = $dbw->affectedRows();
  1020. if ( ! $affected ) {
  1021. $dbw->rollback();
  1022. return;
  1023. }
  1024. $dbw->commit();
  1025. return $rlId;
  1026. } catch ( Exception $e ) {
  1027. $dbw->rollback();
  1028. }
  1029. }
  1030. /**
  1031. * Build the part of the standard collaborative watchlist editing form with the actual
  1032. * title selection checkboxes and stuff. Also generates a table of
  1033. * contents if there's more than one heading.
  1034. *
  1035. * @param $rlId The id of the collaborative watchlist
  1036. * @param $skin Skin (really, Linker)
  1037. */
  1038. private function buildRemoveList( $rlId, $skin ) {
  1039. $list = "";
  1040. $toc = $skin->tocIndent();
  1041. $tocLength = 0;
  1042. foreach ( $this->getWatchlistInfo( $rlId ) as $namespace => $pages ) {
  1043. $tocLength++;
  1044. $heading = htmlspecialchars( $this->getNamespaceHeading( $namespace ) );
  1045. $anchor = "editwatchlist-ns" . $namespace;
  1046. $list .= $skin->makeHeadLine( 2, ">", $anchor, $heading, "" );
  1047. $toc .= $skin->tocLine( $anchor, $heading, $tocLength, 1 ) . $skin->tocLineEnd();
  1048. $list .= "<ul>\n";
  1049. foreach ( $pages as $dbkey => $info ) {
  1050. $title = Title::makeTitleSafe( $namespace, $dbkey );
  1051. $list .= $this->buildRemoveLine( $title, $info, $skin );
  1052. }
  1053. $list .= "</ul>\n";
  1054. }
  1055. // ISSUE: omit the TOC if the total number of titles is low?
  1056. if ( $tocLength > 1 ) {
  1057. $list = $skin->tocList( $toc ) . $list;
  1058. }
  1059. return $list;
  1060. }
  1061. /**
  1062. * Get the correct "heading" for a namespace
  1063. *
  1064. * @param $namespace int
  1065. * @return string
  1066. */
  1067. private function getNamespaceHeading( $namespace ) {
  1068. return $namespace == NS_MAIN
  1069. ? wfMsgHtml( 'blanknamespace' )
  1070. : htmlspecialchars( $GLOBALS['wgContLang']->getFormattedNsText( $namespace ) );
  1071. }
  1072. /**
  1073. * Build a single list item containing a check box selecting a title
  1074. * and a link to that title, with various additional bits
  1075. *
  1076. * @param $title Title
  1077. * @param $info array with info about the category ('redirect' and 'subtract' keys)
  1078. * @param $skin Skin
  1079. * @return string
  1080. */
  1081. private function buildRemoveLine( $title, $catInfo, $skin ) {
  1082. global $wgLang;
  1083. $link = $skin->link( $title );
  1084. if ( $catInfo['redirect'] )
  1085. $link = '<span class="watchlistredir">' . $link . '</span>';
  1086. $tools[] = $skin->link( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) );
  1087. if ( $title->exists() ) {
  1088. $tools[] = $skin->link(
  1089. $title,
  1090. wfMsgHtml( 'history_short' ),
  1091. array(),
  1092. array( 'action' => 'history' ),
  1093. array( 'known', 'noclasses' )
  1094. );
  1095. }
  1096. if ( $title->getNamespace() == NS_USER && !$title->isSubpage() ) {
  1097. $tools[] = $skin->link(
  1098. SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
  1099. wfMsgHtml( 'contributions' ),
  1100. array(),
  1101. array(),
  1102. array( 'known', 'noclasses' )
  1103. );
  1104. }
  1105. return "<li>"
  1106. . ( $catInfo['subtract'] ? '<span class="collabwatchlistsubtract">- </span>' : '' )
  1107. . Xml::check( 'titles[]', false, array( 'value' => $catInfo['subtract'] ? '- ' . $title->getPrefixedText() : $title->getPrefixedText() ) )
  1108. . $link . " (" . $wgLang->pipeList( $tools ) . ")" . "</li>\n";
  1109. }
  1110. /**
  1111. * Show a form for editing the watchlist in "raw" mode
  1112. *
  1113. * @param $output OutputPage
  1114. * @param $rlId Collaborative watchlist id
  1115. * @param $rlName Collaborative watchlist name
  1116. */
  1117. private function showRawForm( $output, $rlId, $rlName ) {
  1118. global $wgUser;
  1119. $this->showItemCount( $output, $rlId );
  1120. $self = SpecialPage::getTitleFor( 'CollabWatchlist' );
  1121. $form = Xml::openElement( 'form', array( 'method' => 'post',
  1122. 'action' => $self->getLocalUrl( array( 'action' => 'rawCategories' ) ) ) );
  1123. $form .= Html::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
  1124. $form .= Html::hidden( 'collabwatchlist', $rlId );
  1125. $form .= '<fieldset><legend>' . $rlName . ' ' . wfMsgHtml( 'watchlistedit-raw-legend' ) . '</legend>';
  1126. $form .= wfMsgExt( 'watchlistedit-raw-explain', 'parse' );
  1127. $form .= Xml::label( wfMsg( 'watchlistedit-raw-titles' ), 'titles' );
  1128. $form .= "<br />\n";
  1129. $form .= Xml::openElement( 'textarea', array( 'id' => 'titles', 'name' => 'titles',
  1130. 'rows' => $wgUser->getIntOption( 'rows' ), 'cols' => $wgUser->getIntOption( 'cols' ) ) );
  1131. $categories = $this->getCollabWatchlistCategories( $rlId );
  1132. foreach ( $categories as $category )
  1133. $form .= htmlspecialchars( $category ) . "\n";
  1134. $form .= '</textarea>';
  1135. $form .= '<p>' . Xml::submitButton( wfMsg( 'watchlistedit-raw-submit' ) ) . '</p>';
  1136. $form .= '</fieldset></form>';
  1137. $output->addHTML( $form );
  1138. }
  1139. /**
  1140. * Show a form for editing the tags of a collaborative watchlist in "raw" mode
  1141. *
  1142. * @param $output OutputPage
  1143. * @param $rlId Collaborative watchlist id
  1144. * @param $rlName Collaborative watchlist name
  1145. */
  1146. private function showTagsRawForm( $output, $rlId, $rlName ) {
  1147. global $wgUser;
  1148. $this->showTagItemCount( $output, $rlId );
  1149. $self = SpecialPage::getTitleFor( 'CollabWatchlist' );
  1150. $form = Xml::openElement( 'form', array( 'method' => 'post',
  1151. 'action' => $self->getLocalUrl( array( 'action' => 'rawTags' ) ) ) );
  1152. $form .= Html::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
  1153. $form .= Html::hidden( 'collabwatchlist', $rlId );
  1154. $form .= '<fieldset><legend>' . $rlName . ' ' . wfMsgHtml( 'collabwatchlistedit-tags-raw-legend' ) . '</legend>';
  1155. $form .= wfMsgExt( 'collabwatchlistedit-tags-raw-explain', 'parse' );
  1156. $form .= Xml::label( wfMsg( 'collabwatchlistedit-tags-raw-titles' ), 'titles' );
  1157. $form .= "<br />\n";
  1158. $form .= Xml::openElement( 'textarea', array( 'id' => 'titles', 'name' => 'titles',
  1159. 'rows' => $wgUser->getIntOption( 'rows' ), 'cols' => $wgUser->getIntOption( 'cols' ) ) );
  1160. $tags = $this->getCollabWatchlistTags( $rlId );
  1161. foreach ( $tags as $tag => $description )
  1162. $form .= htmlspecialchars( $tag ) . "|" . $description . "\n";
  1163. $form .= '</textarea>';
  1164. $form .= '<p>' . Xml::submitButton( wfMsg( 'collabwatchlistedit-tags-raw-submit' ) ) . '</p>';
  1165. $form .= '</fieldset></form>';
  1166. $output->addHTML( $form );
  1167. }
  1168. /**
  1169. * Show a form for editing the users of a collaborative watchlist in "raw" mode
  1170. *
  1171. * @param $output OutputPage
  1172. * @param $rlId Collaborative watchlist id
  1173. * @param $rlName Collaborative watchlist name
  1174. */
  1175. private function showUsersRawForm( $output, $rlId, $rlName ) {
  1176. global $wgUser;
  1177. $this->showUserItemCount( $output, $rlId );
  1178. $self = SpecialPage::getTitleFor( 'CollabWatchlist' );
  1179. $form = Xml::openElement( 'form', array( 'method' => 'post',
  1180. 'action' => $self->getLocalUrl( array( 'action' => 'rawUsers' ) ) ) );
  1181. $form .= Html::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
  1182. $form .= Html::hidden( 'collabwatchlist', $rlId );
  1183. $form .= '<fieldset><legend>' . $rlName . ' ' . wfMsgHtml( 'collabwatchlistedit-users-raw-legend' ) . '</legend>';
  1184. $form .= wfMsgExt( 'collabwatchlistedit-users-raw-explain', 'parse' );
  1185. $form .= Xml::label( wfMsg( 'collabwatchlistedit-users-raw-titles' ), 'titles' );
  1186. $form .= "<br />\n";
  1187. $form .= Xml::openElement( 'textarea', array( 'id' => 'titles', 'name' => 'titles',
  1188. 'rows' => $wgUser->getIntOption( 'rows' ), 'cols' => $wgUser->getIntOption( 'cols' ) ) );
  1189. $users = $this->getCollabWatchlistUsers( $rlId );
  1190. foreach ( $users as $userString )
  1191. $form .= htmlspecialchars( $userString ) . "\n";
  1192. $form .= '</textarea>';
  1193. $form .= '<p>' . Xml::submitButton( wfMsg( 'collabwatchlistedit-users-raw-submit' ) ) . '</p>';
  1194. $form .= '</fieldset></form>';
  1195. $output->addHTML( $form );
  1196. }
  1197. /**
  1198. * Determine whether we are editing the watchlist, and if so, what
  1199. * kind of editing operation
  1200. *
  1201. * @param $request WebRequest
  1202. * @param $par mixed
  1203. * @return int
  1204. */
  1205. public static function getMode( $request, $par ) {
  1206. $mode = strtolower( $request->getVal( 'action', $par ) );
  1207. switch( $mode ) {
  1208. case 'clear':
  1209. return self::EDIT_CLEAR;
  1210. case 'rawcategories':
  1211. return self::CATEGORIES_EDIT_RAW;
  1212. case 'rawtags':
  1213. return self::TAGS_EDIT_RAW;
  1214. case 'edit':
  1215. return self::EDIT_NORMAL;
  1216. case 'settags':
  1217. return self::SET_TAGS;
  1218. case 'unsettags':
  1219. return self::UNSET_TAGS;
  1220. case 'rawusers':
  1221. return self::USERS_EDIT_RAW;
  1222. case 'newlist':
  1223. return self::NEW_LIST;
  1224. case 'delete':
  1225. return self::DELETE_LIST;
  1226. default:
  1227. return false;
  1228. }
  1229. }
  1230. /**
  1231. * Build a set of links for convenient navigation
  1232. * between collaborative watchlist viewing and editing modes
  1233. *
  1234. * @param $listIdsAndNames An array mapping from list ids to list names
  1235. * @param $skin Skin to use
  1236. * @return string
  1237. */
  1238. public static function buildTools( $listIdsAndNames, $skin ) {
  1239. global $wgLang, $wgUser;
  1240. $modes = array( 'view' => false, 'delete' => 'delete', 'edit' => 'edit',
  1241. 'rawCategories' => 'rawCategories', 'rawTags' => 'rawTags',
  1242. 'rawUsers' => 'rawUsers' );
  1243. $r = '';
  1244. // Insert link for new list
  1245. $r .= $skin->link(
  1246. SpecialPage::getTitleFor( 'CollabWatchlist', 'newList' ),
  1247. wfMsgHtml( "collabwatchlisttools-newList" ),
  1248. array(),
  1249. array(),
  1250. array( 'known', 'noclasses' )
  1251. ) . '<br />';
  1252. if ( !isset( $listIdsAndNames ) || empty( $listIdsAndNames ) )
  1253. return $r;
  1254. foreach ( $listIdsAndNames as $listId => $listName ) {
  1255. $tools = array();
  1256. foreach ( $modes as $mode => $subpage ) {
  1257. // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw'
  1258. $tools[] = $skin->link(
  1259. SpecialPage::getTitleFor( 'CollabWatchlist', $subpage ),
  1260. wfMsgHtml( "collabwatchlisttools-{$mode}" ),
  1261. array(),
  1262. array( 'collabwatchlist' => $listId ),
  1263. array( 'known', 'noclasses' )
  1264. );
  1265. }
  1266. $r .= $listName . ' ' . $wgLang->pipeList( $tools ) . '<br />';
  1267. }
  1268. return $r;
  1269. }
  1270. /** Returns a URL for unsetting a specific tag on a specific edit on a given list
  1271. *
  1272. * @param String $redirUrl The url to redirect after the tag was removed
  1273. * @param String $pageName The name of the page the tag is set on
  1274. * @param int $rlId The id of the collab watchlist
  1275. * @param String $tag The tag to remove
  1276. * @param int $rcId The id of the edit in the recent changes
  1277. * @return String an URL string
  1278. */
  1279. public static function getUnsetTagUrl( $redirUrl, $pageName, $rlId, $tag, $rcId ) {
  1280. return SpecialPage::getTitleFor( 'CollabWatchlist' )->getLocalUrl( array(
  1281. 'action' => 'unsetTags',
  1282. 'redirTarget' => $redirUrl,
  1283. 'collabwatchlisttag' => $tag,
  1284. 'collabwatchlist' => $rlId,
  1285. 'collabwatchlistpage' => $pageName,
  1286. 'collabwatchlistrcid' => $rcId
  1287. ) );
  1288. }
  1289. }