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 
64  private $watchedItemStore;
65 
67  private $genderCache;
68 
70  private $linkBatchFactory;
71 
73  private $nsInfo;
74 
76  private $wikiPageFactory;
77 
79  private $watchlistManager;
80 
82  private $currentMode;
83 
93  public function __construct(
94  WatchedItemStoreInterface $watchedItemStore = null,
95  TitleParser $titleParser = null,
96  GenderCache $genderCache = null,
97  LinkBatchFactory $linkBatchFactory = null,
98  NamespaceInfo $nsInfo = null,
99  WikiPageFactory $wikiPageFactory = null,
100  WatchlistManager $watchlistManager = 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  null,
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  } catch ( MalformedTitleException $e ) {
600  continue;
601  }
602  }
603 
604  $ns = $target->getNamespace();
605  $dbKey = $target->getDBkey();
606  $expandedTargets[] =
607  new TitleValue( $this->nsInfo->getSubject( $ns ), $dbKey );
608  $expandedTargets[] =
609  new TitleValue( $this->nsInfo->getTalk( $ns ), $dbKey );
610  }
611  return $expandedTargets;
612  }
613 
614  public function submitNormal( $data ) {
615  $removed = [];
616 
617  foreach ( $data as $titles ) {
618  $this->unwatchTitles( $titles );
619  $removed = array_merge( $removed, $titles );
620  }
621 
622  if ( count( $removed ) > 0 ) {
623  $this->successMessage = $this->msg( 'watchlistedit-normal-done'
624  )->numParams( count( $removed ) )->parse();
625  $this->showTitles( $removed, $this->successMessage );
626 
627  return true;
628  } else {
629  return false;
630  }
631  }
632 
638  protected function getNormalForm() {
639  $fields = [];
640  $count = 0;
641 
642  // Allow subscribers to manipulate the list of watched pages (or use it
643  // to preload lots of details at once)
644  $watchlistInfo = $this->getWatchlistInfo();
645  $this->getHookRunner()->onWatchlistEditorBeforeFormRender( $watchlistInfo );
646 
647  foreach ( $watchlistInfo as $namespace => $pages ) {
648  $options = [];
649  foreach ( $pages as $dbkey => $expiryDaysText ) {
650  $title = Title::makeTitleSafe( $namespace, $dbkey );
651 
652  if ( $this->checkTitle( $title, $namespace, $dbkey ) ) {
653  $text = $this->buildRemoveLine( $title, $expiryDaysText );
654  $options[$text] = $title->getPrefixedText();
655  $count++;
656  }
657  }
658 
659  // checkTitle can filter some options out, avoid empty sections
660  if ( count( $options ) > 0 ) {
661  $fields['TitlesNs' . $namespace] = [
662  'class' => EditWatchlistCheckboxSeriesField::class,
663  'options' => $options,
664  'section' => "ns$namespace",
665  ];
666  }
667  }
668  $this->cleanupWatchlist();
669 
670  if ( count( $fields ) > 1 && $count > 30 ) {
671  $this->toc = Linker::tocIndent();
672  $tocLength = 0;
673  $contLang = $this->getContentLanguage();
674 
675  foreach ( $fields as $data ) {
676  # strip out the 'ns' prefix from the section name:
677  $ns = (int)substr( $data['section'], 2 );
678 
679  $nsText = ( $ns == NS_MAIN )
680  ? $this->msg( 'blanknamespace' )->escaped()
681  : htmlspecialchars( $contLang->getFormattedNsText( $ns ) );
682  $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText,
683  $this->getLanguage()->formatNum( ++$tocLength ), 1 ) . Linker::tocLineEnd();
684  }
685 
686  $this->toc = Linker::tocList( $this->toc );
687  } else {
688  $this->toc = false;
689  }
690 
691  $form = new EditWatchlistNormalHTMLForm( $fields, $this->getContext() );
692  $form->setTitle( $this->getPageTitle() ); // Remove subpage
693  $form->setSubmitTextMsg( 'watchlistedit-normal-submit' );
694  $form->setSubmitDestructive();
695  # Used message keys:
696  # 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
697  $form->setSubmitTooltip( 'watchlistedit-normal-submit' );
698  $form->setWrapperLegendMsg( 'watchlistedit-normal-legend' );
699  $form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() );
700  $form->setSubmitCallback( [ $this, 'submitNormal' ] );
701 
702  return $form;
703  }
704 
713  private function buildRemoveLine( $title, string $expiryDaysText = '' ): string {
714  $linkRenderer = $this->getLinkRenderer();
715  $link = $linkRenderer->makeLink( $title );
716 
717  $tools = [];
718  $tools['talk'] = $linkRenderer->makeLink(
719  $title->getTalkPage(),
720  $this->msg( 'talkpagelinktext' )->text()
721  );
722 
723  if ( $title->exists() ) {
724  $tools['history'] = $linkRenderer->makeKnownLink(
725  $title,
726  $this->msg( 'history_small' )->text(),
727  [],
728  [ 'action' => 'history' ]
729  );
730  }
731 
732  if ( $title->getNamespace() === NS_USER && !$title->isSubpage() ) {
733  $tools['contributions'] = $linkRenderer->makeKnownLink(
734  SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
735  $this->msg( 'contribslink' )->text()
736  );
737  }
738 
739  $this->getHookRunner()->onWatchlistEditorBuildRemoveLine(
740  $tools, $title, $title->isRedirect(), $this->getSkin(), $link );
741 
742  if ( $title->isRedirect() ) {
743  // Linker already makes class mw-redirect, so this is redundant
744  $link = '<span class="watchlistredir">' . $link . '</span>';
745  }
746 
747  $watchlistExpiringMessage = '';
748  if ( $this->getConfig()->get( MainConfigNames::WatchlistExpiry ) && $expiryDaysText ) {
749  $watchlistExpiringMessage = Html::element(
750  'span',
751  [ 'class' => 'mw-watchlistexpiry-msg' ],
752  $expiryDaysText
753  );
754  }
755 
756  return $link . ' ' . Html::openElement( 'span', [ 'class' => 'mw-changeslist-links' ] ) .
757  implode(
758  '',
759  array_map( static function ( $tool ) {
760  return Html::rawElement( 'span', [], $tool );
761  }, $tools )
762  ) .
763  Html::closeElement( 'span' ) .
764  $watchlistExpiringMessage;
765  }
766 
772  protected function getRawForm() {
773  $titles = implode( "\n", $this->getWatchlist() );
774  $fields = [
775  'Titles' => [
776  'type' => 'textarea',
777  'label-message' => 'watchlistedit-raw-titles',
778  'default' => $titles,
779  ],
780  ];
781  $form = new OOUIHTMLForm( $fields, $this->getContext() );
782  $form->setTitle( $this->getPageTitle( 'raw' ) ); // Reset subpage
783  $form->setSubmitTextMsg( 'watchlistedit-raw-submit' );
784  # Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit'
785  $form->setSubmitTooltip( 'watchlistedit-raw-submit' );
786  $form->setWrapperLegendMsg( 'watchlistedit-raw-legend' );
787  $form->addHeaderText( $this->msg( 'watchlistedit-raw-explain' )->parse() );
788  $form->setSubmitCallback( [ $this, 'submitRaw' ] );
789 
790  return $form;
791  }
792 
798  protected function getClearForm() {
799  $form = new OOUIHTMLForm( [], $this->getContext() );
800  $form->setTitle( $this->getPageTitle( 'clear' ) ); // Reset subpage
801  $form->setSubmitTextMsg( 'watchlistedit-clear-submit' );
802  # Used message keys: 'accesskey-watchlistedit-clear-submit', 'tooltip-watchlistedit-clear-submit'
803  $form->setSubmitTooltip( 'watchlistedit-clear-submit' );
804  $form->setWrapperLegendMsg( 'watchlistedit-clear-legend' );
805  $form->addHeaderText( $this->msg( 'watchlistedit-clear-explain' )->parse() );
806  $form->setSubmitCallback( [ $this, 'submitClear' ] );
807  $form->setSubmitDestructive();
808 
809  return $form;
810  }
811 
821  public static function getMode( $request, $par, $defaultValue = false ) {
822  $mode = strtolower( $request->getRawVal( 'action', $par ?? '' ) );
823 
824  switch ( $mode ) {
825  case 'clear':
826  case self::EDIT_CLEAR:
827  return self::EDIT_CLEAR;
828  case 'raw':
829  case self::EDIT_RAW:
830  return self::EDIT_RAW;
831  case 'edit':
832  case self::EDIT_NORMAL:
833  return self::EDIT_NORMAL;
834  default:
835  return $defaultValue;
836  }
837  }
838 
848  public static function buildTools( $unused, LinkRenderer $linkRenderer = null, $selectedMode = false ) {
849  if ( !$linkRenderer ) {
850  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
851  }
852 
853  $tools = [];
854  $modes = [
855  'view' => [ 'Watchlist', false, false ],
856  'edit' => [ 'EditWatchlist', false, self::EDIT_NORMAL ],
857  'raw' => [ 'EditWatchlist', 'raw', self::EDIT_RAW ],
858  'clear' => [ 'EditWatchlist', 'clear', self::EDIT_CLEAR ],
859  ];
860 
861  foreach ( $modes as $mode => $arr ) {
862  // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw'
863  $link = $linkRenderer->makeKnownLink(
864  SpecialPage::getTitleFor( $arr[0], $arr[1] ),
865  wfMessage( "watchlisttools-{$mode}" )->text()
866  );
867  $isSelected = $selectedMode === $arr[2];
868  $classes = [
869  'mw-watchlist-toollink',
870  'mw-watchlist-toollink-' . $mode,
871  $isSelected ? 'mw-watchlist-toollink-active' :
872  'mw-watchlist-toollink-inactive'
873  ];
874  $tools[] = Html::rawElement( 'span', [
875  'class' => $classes,
876  ], $link );
877  }
878 
879  return Html::rawElement(
880  'span',
881  [ 'class' => 'mw-watchlist-toollinks mw-changeslist-links' ],
882  implode( '', $tools )
883  );
884  }
885 }
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_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:34
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
static tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1608
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1582
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:1644
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1632
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.
Service locator for MediaWiki core services.
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.
getRawForm()
Get a form for editing the watchlist in "raw" mode.
static buildTools( $unused, LinkRenderer $linkRenderer=null, $selectedMode=false)
Build a set of links for convenient navigation between watchlist viewing and editing modes.
getNormalForm()
Get the standard watchlist editing form.
doesWrites()
Indicates whether this special page may perform database writes.
executeViewEditWatchlist()
Executes an edit mode for the watchlist view, from which you can manage your watchlist.
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...
getClearForm()
Get a form for clearing the watchlist.
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.
submitClear( $data)
Handler for the clear form submission.
static getMode( $request, $par, $defaultValue=false)
Determine whether we are editing the watchlist, and if so, what kind of editing operation.
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.
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:40
Represents a title within MediaWiki.
Definition: Title.php:52
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:285
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:373
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:667
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:641
Shortcut to construct a special page which is unlisted by default.
A title parser service for MediaWiki.
Definition: TitleParser.php:33