MediaWiki  master
SpecialEditWatchlist.php
Go to the documentation of this file.
1 <?php
36 
50  public const EDIT_CLEAR = 1;
51  public const EDIT_RAW = 2;
52  public const EDIT_NORMAL = 3;
53 
54  protected $successMessage;
55 
56  protected $toc;
57 
58  private $badItems = [];
59 
61  private $titleParser;
62 
65 
67  private $genderCache;
68 
71 
73  private $nsInfo;
74 
77 
80 
82  private $currentMode;
83 
93  public function __construct(
98  NamespaceInfo $nsInfo = null,
101  ) {
102  parent::__construct( 'EditWatchlist', 'editmywatchlist' );
103  // This class is extended and therefor fallback to global state - T266065
104  $services = MediaWikiServices::getInstance();
105  $this->watchedItemStore = $watchedItemStore ?? $services->getWatchedItemStore();
106  $this->titleParser = $titleParser ?? $services->getTitleParser();
107  $this->genderCache = $genderCache ?? $services->getGenderCache();
108  $this->linkBatchFactory = $linkBatchFactory ?? $services->getLinkBatchFactory();
109  $this->nsInfo = $nsInfo ?? $services->getNamespaceInfo();
110  $this->wikiPageFactory = $wikiPageFactory ?? $services->getWikiPageFactory();
111  $this->watchlistManager = $watchlistManager ?? $services->getWatchlistManager();
112  }
113 
114  public function doesWrites() {
115  return true;
116  }
117 
123  public function execute( $mode ) {
124  $this->setHeaders();
125 
126  # Anons don't get a watchlist
127  $this->requireNamedUser( 'watchlistanontext' );
128 
129  $out = $this->getOutput();
130 
131  $this->checkPermissions();
132  $this->checkReadOnly();
133 
134  $this->outputHeader();
135  $out->addModuleStyles( [
136  'mediawiki.interface.helpers.styles',
137  'mediawiki.special'
138  ] );
139 
140  $mode = self::getMode( $this->getRequest(), $mode, self::EDIT_NORMAL );
141  $this->currentMode = $mode;
142  $this->outputSubtitle();
143 
144  switch ( $mode ) {
145  case self::EDIT_RAW:
146  $out->setPageTitle( $this->msg( 'watchlistedit-raw-title' ) );
147  $form = $this->getRawForm();
148  if ( $form->show() ) {
149  $out->addHTML( $this->successMessage );
150  $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
151  }
152  break;
153  case self::EDIT_CLEAR:
154  $out->setPageTitle( $this->msg( 'watchlistedit-clear-title' ) );
155  $form = $this->getClearForm();
156  if ( $form->show() ) {
157  $out->addHTML( $this->successMessage );
158  $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
159  }
160  break;
161 
162  case self::EDIT_NORMAL:
163  default:
164  $this->executeViewEditWatchlist();
165  break;
166  }
167  }
168 
172  protected function outputSubtitle() {
173  $out = $this->getOutput();
174  $out->addSubtitle(
176  'span',
177  [
178  'class' => 'mw-watchlist-owner'
179  ],
180  // Previously the watchlistfor2 message took 2 parameters.
181  // It now only takes 1 so empty string is passed.
182  // Empty string parameter can be removed when all messages
183  // are updated to not use $2
184  $this->msg( 'watchlistfor2', $this->getUser()->getName(), '' )->text()
185  ) . ' ' .
186  self::buildTools(
187  $this->getLanguage(),
188  $this->getLinkRenderer(),
189  $this->currentMode
190  )
191  );
192  }
193 
197  protected function executeViewEditWatchlist() {
198  $out = $this->getOutput();
199  $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) );
200  $form = $this->getNormalForm();
201  if ( $form->show() ) {
202  $out->addHTML( $this->successMessage );
203  $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
204  } elseif ( $this->toc !== false ) {
205  $out->prependHTML( $this->toc );
206  }
207  }
208 
215  public function getSubpagesForPrefixSearch() {
216  // SpecialWatchlist uses SpecialEditWatchlist::getMode, so new types should be added
217  // here and there - no 'edit' here, because that the default for this page
218  return [
219  'clear',
220  'raw',
221  ];
222  }
223 
231  private function extractTitles( $list ) {
232  $list = explode( "\n", trim( $list ) );
233 
234  $titles = [];
235 
236  foreach ( $list as $text ) {
237  $text = trim( $text );
238  if ( strlen( $text ) > 0 ) {
239  $title = Title::newFromText( $text );
240  if ( $title instanceof Title && $this->watchlistManager->isWatchable( $title ) ) {
241  $titles[] = $title;
242  }
243  }
244  }
245 
246  $this->genderCache->doTitlesArray( $titles );
247 
248  $list = [];
250  foreach ( $titles as $title ) {
251  $list[] = $title->getPrefixedText();
252  }
253 
254  return array_unique( $list );
255  }
256 
257  public function submitRaw( $data ) {
258  $wanted = $this->extractTitles( $data['Titles'] );
259  $current = $this->getWatchlist();
260 
261  if ( count( $wanted ) > 0 ) {
262  $toWatch = array_diff( $wanted, $current );
263  $toUnwatch = array_diff( $current, $wanted );
264  $this->watchTitles( $toWatch );
265  $this->unwatchTitles( $toUnwatch );
266  $this->getUser()->invalidateCache();
267 
268  if ( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ) {
269  $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse();
270  } else {
271  return false;
272  }
273 
274  if ( count( $toWatch ) > 0 ) {
275  $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added' )
276  ->numParams( count( $toWatch ) )->parse();
277  $this->showTitles( $toWatch, $this->successMessage );
278  }
279 
280  if ( count( $toUnwatch ) > 0 ) {
281  $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' )
282  ->numParams( count( $toUnwatch ) )->parse();
283  $this->showTitles( $toUnwatch, $this->successMessage );
284  }
285  } else {
286 
287  if ( count( $current ) === 0 ) {
288  return false;
289  }
290 
291  $this->clearUserWatchedItems( 'raw' );
292  $this->showTitles( $current, $this->successMessage );
293  }
294 
295  return true;
296  }
297 
304  public function submitClear( $data ): bool {
305  $this->clearUserWatchedItems( 'clear' );
306  return true;
307  }
308 
315  private function clearUserWatchedItems( string $messageFor ): void {
316  if ( $this->watchedItemStore->mustClearWatchedItemsUsingJobQueue( $this->getUser() ) ) {
317  $this->clearUserWatchedItemsUsingJobQueue();
318  } else {
319  $this->clearUserWatchedItemsNow( $messageFor );
320  }
321  }
322 
328  private function clearUserWatchedItemsNow( string $messageFor ): void {
329  $current = $this->getWatchlist();
330  if ( !$this->watchedItemStore->clearUserWatchedItems( $this->getUser() ) ) {
331  throw new LogicException(
332  __METHOD__ . ' should only be called when able to clear synchronously'
333  );
334  }
335  $this->successMessage = $this->msg( 'watchlistedit-' . $messageFor . '-done' )->parse();
336  $this->successMessage .= ' ' . $this->msg( 'watchlistedit-' . $messageFor . '-removed' )
337  ->numParams( count( $current ) )->parse();
338  $this->getUser()->invalidateCache();
339  $this->showTitles( $current, $this->successMessage );
340  }
341 
345  private function clearUserWatchedItemsUsingJobQueue(): void {
346  $this->watchedItemStore->clearUserWatchedItemsUsingJobQueue( $this->getUser() );
347  $this->successMessage = $this->msg( 'watchlistedit-clear-jobqueue' )->parse();
348  }
349 
359  private function showTitles( $titles, &$output ) {
360  $talk = $this->msg( 'talkpagelinktext' )->text();
361  // Do a batch existence check
362  $batch = $this->linkBatchFactory->newLinkBatch();
363  if ( count( $titles ) >= 100 ) {
364  $output = $this->msg( 'watchlistedit-too-many' )->parse();
365  return;
366  }
367  foreach ( $titles as $title ) {
368  if ( !$title instanceof Title ) {
370  }
371 
372  if ( $title instanceof Title ) {
373  $batch->addObj( $title );
374  $batch->addObj( $title->getTalkPage() );
375  }
376  }
377 
378  $batch->execute();
379 
380  // Print out the list
381  $output .= "<ul>\n";
382 
383  $linkRenderer = $this->getLinkRenderer();
384  foreach ( $titles as $title ) {
385  if ( !$title instanceof Title ) {
387  }
388 
389  if ( $title instanceof Title ) {
390  $output .= '<li>' .
391  $linkRenderer->makeLink( $title ) . ' ' .
392  $this->msg( 'parentheses' )->rawParams(
393  $linkRenderer->makeLink( $title->getTalkPage(), $talk )
394  )->escaped() .
395  "</li>\n";
396  }
397  }
398 
399  $output .= "</ul>\n";
400  }
401 
408  private function getWatchlist() {
409  $list = [];
410 
411  $watchedItems = $this->watchedItemStore->getWatchedItemsForUser(
412  $this->getUser(),
413  [ 'forWrite' => $this->getRequest()->wasPosted() ]
414  );
415 
416  if ( $watchedItems ) {
418  $titles = [];
419  foreach ( $watchedItems as $watchedItem ) {
420  $namespace = $watchedItem->getTarget()->getNamespace();
421  $dbKey = $watchedItem->getTarget()->getDBkey();
422  $title = Title::makeTitleSafe( $namespace, $dbKey );
423 
424  if ( $this->checkTitle( $title, $namespace, $dbKey )
425  && !$title->isTalkPage()
426  ) {
427  $titles[] = $title;
428  }
429  }
430 
431  $this->genderCache->doTitlesArray( $titles );
432 
433  foreach ( $titles as $title ) {
434  $list[] = $title->getPrefixedText();
435  }
436  }
437 
438  $this->cleanupWatchlist();
439 
440  return $list;
441  }
442 
449  protected function getWatchlistInfo() {
450  $titles = [];
451  $options = [ 'sort' => WatchedItemStore::SORT_ASC ];
452 
453  if ( $this->getConfig()->get( MainConfigNames::WatchlistExpiry ) ) {
454  $options[ 'sortByExpiry'] = true;
455  }
456 
457  $watchedItems = $this->watchedItemStore->getWatchedItemsForUser(
458  $this->getUser(), $options
459  );
460 
461  $lb = $this->linkBatchFactory->newLinkBatch();
462  $context = $this->getContext();
463 
464  foreach ( $watchedItems as $watchedItem ) {
465  $namespace = $watchedItem->getTarget()->getNamespace();
466  $dbKey = $watchedItem->getTarget()->getDBkey();
467  $lb->add( $namespace, $dbKey );
468  if ( !$this->nsInfo->isTalk( $namespace ) ) {
469  $titles[$namespace][$dbKey] = $watchedItem->getExpiryInDaysText( $context );
470  }
471  }
472 
473  $lb->execute();
474 
475  return $titles;
476  }
477 
486  private function checkTitle( $title, $namespace, $dbKey ) {
487  if ( $title
488  && ( $title->isExternal()
489  || $title->getNamespace() < 0
490  )
491  ) {
492  $title = false; // unrecoverable
493  }
494 
495  if ( !$title
496  || $title->getNamespace() != $namespace
497  || $title->getDBkey() != $dbKey
498  ) {
499  $this->badItems[] = [ $title, $namespace, $dbKey ];
500  }
501 
502  return (bool)$title;
503  }
504 
508  private function cleanupWatchlist() {
509  if ( $this->badItems === [] ) {
510  return; // nothing to do
511  }
512 
513  $user = $this->getUser();
514  $badItems = $this->badItems;
515  DeferredUpdates::addCallableUpdate( function () use ( $user, $badItems ) {
516  foreach ( $badItems as [ $title, $namespace, $dbKey ] ) {
517  $action = $title ? 'cleaning up' : 'deleting';
518  wfDebug( "User {$user->getName()} has broken watchlist item " .
519  "ns($namespace):$dbKey, $action." );
520 
521  // NOTE: We *know* that the title is invalid. TitleValue may refuse instantiation.
522  // XXX: We may need an InvalidTitleValue class that allows instantiation of
523  // known bad title values.
524  $this->watchedItemStore->removeWatch( $user, Title::makeTitle( (int)$namespace, $dbKey ) );
525  // Can't just do an UPDATE instead of DELETE/INSERT due to unique index
526  if ( $title ) {
527  $this->watchlistManager->addWatch( $user, $title );
528  }
529  }
530  } );
531  }
532 
541  private function watchTitles( array $targets ) {
542  return $this->watchedItemStore->addWatchBatchForUser(
543  $this->getUser(), $this->getExpandedTargets( $targets )
544  ) && $this->runWatchUnwatchCompleteHook( 'Watch', $targets );
545  }
546 
559  private function unwatchTitles( array $targets ) {
560  return $this->watchedItemStore->removeWatchBatchForUser(
561  $this->getUser(), $this->getExpandedTargets( $targets )
562  ) && $this->runWatchUnwatchCompleteHook( 'Unwatch', $targets );
563  }
564 
573  private function runWatchUnwatchCompleteHook( $action, $targets ) {
574  foreach ( $targets as $target ) {
575  $title = $target instanceof LinkTarget ?
576  Title::newFromLinkTarget( $target ) :
577  Title::newFromText( $target );
578  $page = $this->wikiPageFactory->newFromTitle( $title );
579  $user = $this->getUser();
580  if ( $action === 'Watch' ) {
581  $this->getHookRunner()->onWatchArticleComplete( $user, $page );
582  } else {
583  $this->getHookRunner()->onUnwatchArticleComplete( $user, $page );
584  }
585  }
586  return true;
587  }
588 
593  private function getExpandedTargets( array $targets ) {
594  $expandedTargets = [];
595  foreach ( $targets as $target ) {
596  if ( !$target instanceof LinkTarget ) {
597  try {
598  $target = $this->titleParser->parseTitle( $target, NS_MAIN );
599  }
600  catch ( MalformedTitleException $e ) {
601  continue;
602  }
603  }
604 
605  $ns = $target->getNamespace();
606  $dbKey = $target->getDBkey();
607  $expandedTargets[] =
608  new TitleValue( $this->nsInfo->getSubject( $ns ), $dbKey );
609  $expandedTargets[] =
610  new TitleValue( $this->nsInfo->getTalk( $ns ), $dbKey );
611  }
612  return $expandedTargets;
613  }
614 
615  public function submitNormal( $data ) {
616  $removed = [];
617 
618  foreach ( $data as $titles ) {
619  $this->unwatchTitles( $titles );
620  $removed = array_merge( $removed, $titles );
621  }
622 
623  if ( count( $removed ) > 0 ) {
624  $this->successMessage = $this->msg( 'watchlistedit-normal-done'
625  )->numParams( count( $removed ) )->parse();
626  $this->showTitles( $removed, $this->successMessage );
627 
628  return true;
629  } else {
630  return false;
631  }
632  }
633 
639  protected function getNormalForm() {
640  $fields = [];
641  $count = 0;
642 
643  // Allow subscribers to manipulate the list of watched pages (or use it
644  // to preload lots of details at once)
645  $watchlistInfo = $this->getWatchlistInfo();
646  $this->getHookRunner()->onWatchlistEditorBeforeFormRender( $watchlistInfo );
647 
648  foreach ( $watchlistInfo as $namespace => $pages ) {
649  $options = [];
650  foreach ( $pages as $dbkey => $expiryDaysText ) {
651  $title = Title::makeTitleSafe( $namespace, $dbkey );
652 
653  if ( $this->checkTitle( $title, $namespace, $dbkey ) ) {
654  $text = $this->buildRemoveLine( $title, $expiryDaysText );
655  $options[$text] = $title->getPrefixedText();
656  $count++;
657  }
658  }
659 
660  // checkTitle can filter some options out, avoid empty sections
661  if ( count( $options ) > 0 ) {
662  $fields['TitlesNs' . $namespace] = [
663  'class' => EditWatchlistCheckboxSeriesField::class,
664  'options' => $options,
665  'section' => "ns$namespace",
666  ];
667  }
668  }
669  $this->cleanupWatchlist();
670 
671  if ( count( $fields ) > 1 && $count > 30 ) {
672  $this->toc = Linker::tocIndent();
673  $tocLength = 0;
674  $contLang = $this->getContentLanguage();
675 
676  foreach ( $fields as $data ) {
677  # strip out the 'ns' prefix from the section name:
678  $ns = (int)substr( $data['section'], 2 );
679 
680  $nsText = ( $ns == NS_MAIN )
681  ? $this->msg( 'blanknamespace' )->escaped()
682  : htmlspecialchars( $contLang->getFormattedNsText( $ns ) );
683  $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText,
684  $this->getLanguage()->formatNum( ++$tocLength ), 1 ) . Linker::tocLineEnd();
685  }
686 
687  $this->toc = Linker::tocList( $this->toc );
688  } else {
689  $this->toc = false;
690  }
691 
692  $form = new EditWatchlistNormalHTMLForm( $fields, $this->getContext() );
693  $form->setTitle( $this->getPageTitle() ); // Remove subpage
694  $form->setSubmitTextMsg( 'watchlistedit-normal-submit' );
695  $form->setSubmitDestructive();
696  # Used message keys:
697  # 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
698  $form->setSubmitTooltip( 'watchlistedit-normal-submit' );
699  $form->setWrapperLegendMsg( 'watchlistedit-normal-legend' );
700  $form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() );
701  $form->setSubmitCallback( [ $this, 'submitNormal' ] );
702 
703  return $form;
704  }
705 
714  private function buildRemoveLine( $title, string $expiryDaysText = '' ): string {
715  $linkRenderer = $this->getLinkRenderer();
716  $link = $linkRenderer->makeLink( $title );
717 
718  $tools = [];
719  $tools['talk'] = $linkRenderer->makeLink(
720  $title->getTalkPage(),
721  $this->msg( 'talkpagelinktext' )->text()
722  );
723 
724  if ( $title->exists() ) {
725  $tools['history'] = $linkRenderer->makeKnownLink(
726  $title,
727  $this->msg( 'history_small' )->text(),
728  [],
729  [ 'action' => 'history' ]
730  );
731  }
732 
733  if ( $title->getNamespace() === NS_USER && !$title->isSubpage() ) {
734  $tools['contributions'] = $linkRenderer->makeKnownLink(
735  SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
736  $this->msg( 'contribslink' )->text()
737  );
738  }
739 
740  $this->getHookRunner()->onWatchlistEditorBuildRemoveLine(
741  $tools, $title, $title->isRedirect(), $this->getSkin(), $link );
742 
743  if ( $title->isRedirect() ) {
744  // Linker already makes class mw-redirect, so this is redundant
745  $link = '<span class="watchlistredir">' . $link . '</span>';
746  }
747 
748  $watchlistExpiringMessage = '';
749  if ( $this->getConfig()->get( MainConfigNames::WatchlistExpiry ) && $expiryDaysText ) {
750  $watchlistExpiringMessage = Html::element(
751  'span',
752  [ 'class' => 'mw-watchlistexpiry-msg' ],
753  $expiryDaysText
754  );
755  }
756 
757  return $link . ' ' . Html::openElement( 'span', [ 'class' => 'mw-changeslist-links' ] ) .
758  implode(
759  '',
760  array_map( static function ( $tool ) {
761  return Html::rawElement( 'span', [], $tool );
762  }, $tools )
763  ) .
764  Html::closeElement( 'span' ) .
765  $watchlistExpiringMessage;
766  }
767 
773  protected function getRawForm() {
774  $titles = implode( "\n", $this->getWatchlist() );
775  $fields = [
776  'Titles' => [
777  'type' => 'textarea',
778  'label-message' => 'watchlistedit-raw-titles',
779  'default' => $titles,
780  ],
781  ];
782  $form = new OOUIHTMLForm( $fields, $this->getContext() );
783  $form->setTitle( $this->getPageTitle( 'raw' ) ); // Reset subpage
784  $form->setSubmitTextMsg( 'watchlistedit-raw-submit' );
785  # Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit'
786  $form->setSubmitTooltip( 'watchlistedit-raw-submit' );
787  $form->setWrapperLegendMsg( 'watchlistedit-raw-legend' );
788  $form->addHeaderText( $this->msg( 'watchlistedit-raw-explain' )->parse() );
789  $form->setSubmitCallback( [ $this, 'submitRaw' ] );
790 
791  return $form;
792  }
793 
799  protected function getClearForm() {
800  $form = new OOUIHTMLForm( [], $this->getContext() );
801  $form->setTitle( $this->getPageTitle( 'clear' ) ); // Reset subpage
802  $form->setSubmitTextMsg( 'watchlistedit-clear-submit' );
803  # Used message keys: 'accesskey-watchlistedit-clear-submit', 'tooltip-watchlistedit-clear-submit'
804  $form->setSubmitTooltip( 'watchlistedit-clear-submit' );
805  $form->setWrapperLegendMsg( 'watchlistedit-clear-legend' );
806  $form->addHeaderText( $this->msg( 'watchlistedit-clear-explain' )->parse() );
807  $form->setSubmitCallback( [ $this, 'submitClear' ] );
808  $form->setSubmitDestructive();
809 
810  return $form;
811  }
812 
822  public static function getMode( $request, $par, $defaultValue = false ) {
823  $mode = strtolower( $request->getRawVal( 'action', $par ) );
824 
825  switch ( $mode ) {
826  case 'clear':
827  case self::EDIT_CLEAR:
828  return self::EDIT_CLEAR;
829  case 'raw':
830  case self::EDIT_RAW:
831  return self::EDIT_RAW;
832  case 'edit':
833  case self::EDIT_NORMAL:
834  return self::EDIT_NORMAL;
835  default:
836  return $defaultValue;
837  }
838  }
839 
849  public static function buildTools( $lang, LinkRenderer $linkRenderer = null, $selectedMode = false ) {
850  if ( !$lang instanceof Language ) {
851  // back-compat where the first parameter was $unused
852  global $wgLang;
853  $lang = $wgLang;
854  }
855  if ( !$linkRenderer ) {
856  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
857  }
858 
859  $tools = [];
860  $modes = [
861  'view' => [ 'Watchlist', false, false ],
862  'edit' => [ 'EditWatchlist', false, self::EDIT_NORMAL ],
863  'raw' => [ 'EditWatchlist', 'raw', self::EDIT_RAW ],
864  'clear' => [ 'EditWatchlist', 'clear', self::EDIT_CLEAR ],
865  ];
866 
867  foreach ( $modes as $mode => $arr ) {
868  // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw'
869  $link = $linkRenderer->makeKnownLink(
870  SpecialPage::getTitleFor( $arr[0], $arr[1] ),
871  wfMessage( "watchlisttools-{$mode}" )->text()
872  );
873  $isSelected = $selectedMode === $arr[2];
874  $classes = [
875  'mw-watchlist-toollink',
876  'mw-watchlist-toollink-' . $mode,
877  $isSelected ? 'mw-watchlist-toollink-active' :
878  'mw-watchlist-toollink-inactive'
879  ];
880  $tools[] = Html::rawElement( 'span', [
881  'class' => $classes,
882  ], $link );
883  }
884 
885  return Html::rawElement(
886  'span',
887  [ 'class' => 'mw-watchlist-toollinks mw-changeslist-links' ],
888  implode( '', $tools )
889  );
890  }
891 }
getUser()
const NS_USER
Definition: Defines.php:66
const NS_MAIN
Definition: Defines.php:64
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
getContext()
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) $wgLang
Definition: Setup.php:486
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition: WebStart.php:82
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
Extend OOUIHTMLForm purely so we can have a more sensible way of getting the section headers.
Caches user genders when needed to use correct namespace aliases.
Definition: GenderCache.php:36
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:236
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:214
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:256
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:320
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:45
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1617
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1591
static tocList( $toc, Language $lang=null)
Wraps the TOC in a div with ARIA navigation role and provides the hide/collapse JavaScript.
Definition: Linker.php:1653
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1641
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Class that generates HTML anchor link elements for pages.
A class containing constants representing the names of configuration variables.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Service for creating WikiPage objects.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Compact stacked vertical format for forms, implemented using OOUI widgets.
Provides the UI through which users can perform editing operations on their watchlist.
static buildTools( $lang, LinkRenderer $linkRenderer=null, $selectedMode=false)
Build a set of links for convenient navigation between watchlist viewing and editing modes.
getRawForm()
Get a form for editing the watchlist in "raw" mode.
WatchedItemStoreInterface $watchedItemStore
extractTitles( $list)
Extract a list of titles from a blob of text, returning (prefixed) strings; unwatchable titles are ig...
clearUserWatchedItemsNow(string $messageFor)
You should call clearUserWatchedItems() instead to decide if this should use the JobQueue.
getNormalForm()
Get the standard watchlist editing form.
unwatchTitles(array $targets)
Remove a list of titles from a user's watchlist.
doesWrites()
Indicates whether this special page may perform database writes.
cleanupWatchlist()
Attempts to clean up broken items.
executeViewEditWatchlist()
Executes an edit mode for the watchlist view, from which you can manage your watchlist.
WikiPageFactory $wikiPageFactory
runWatchUnwatchCompleteHook( $action, $targets)
clearUserWatchedItemsUsingJobQueue()
You should call clearUserWatchedItems() instead to decide if this should use the JobQueue.
WatchlistManager $watchlistManager
LinkBatchFactory $linkBatchFactory
getSubpagesForPrefixSearch()
Return an array of subpages that this special page will accept.
getWatchlistInfo()
Get a list of titles on a user's watchlist, excluding talk pages, and return as a two-dimensional arr...
checkTitle( $title, $namespace, $dbKey)
Validates watchlist entry.
getClearForm()
Get a form for clearing the watchlist.
int false $currentMode
where the value is one of the EDIT_ prefixed constants (e.g.
getExpandedTargets(array $targets)
const EDIT_CLEAR
Editing modes.
execute( $mode)
Main execution point.
__construct(WatchedItemStoreInterface $watchedItemStore=null, TitleParser $titleParser=null, GenderCache $genderCache=null, LinkBatchFactory $linkBatchFactory=null, NamespaceInfo $nsInfo=null, WikiPageFactory $wikiPageFactory=null, WatchlistManager $watchlistManager=null)
outputSubtitle()
Renders a subheader on the watchlist page.
showTitles( $titles, &$output)
Print out a list of linked titles.
submitClear( $data)
Handler for the clear form submission.
watchTitles(array $targets)
Add a list of targets to a user's watchlist.
static getMode( $request, $par, $defaultValue=false)
Determine whether we are editing the watchlist, and if so, what kind of editing operation.
buildRemoveLine( $title, string $expiryDaysText='')
Build the label for a checkbox, with a link to the title, and various additional bits.
clearUserWatchedItems(string $messageFor)
Makes a decision about using the JobQueue or not for clearing a users watchlist.
getWatchlist()
Prepare a list of titles on a user's watchlist (excluding talk pages) and return an array of (prefixe...
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
getName()
Get the name of this Special Page.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
requireNamedUser( $reasonMsg='exception-nologin-text', $titleMsg='exception-nologin')
If the user is not logged in or is a temporary user, throws UserNotLoggedIn.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getRequest()
Get the WebRequest being used for this instance.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
getLanguage()
Shortcut to get user's language.
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:40
Represents a title within MediaWiki.
Definition: Title.php:49
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:282
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:370
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:664
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:638
Shortcut to construct a special page which is unlisted by default.
A title parser service for MediaWiki.
Definition: TitleParser.php:33
if(!isset( $args[0])) $lang