MediaWiki REL1_39
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
163 default:
165 break;
166 }
167 }
168
172 protected function outputSubtitle() {
173 $out = $this->getOutput();
174 $out->addSubtitle(
175 Html::element(
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 } 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( $lang, LinkRenderer $linkRenderer = null, $selectedMode = false ) {
849 if ( !$lang instanceof Language ) {
850 // back-compat where the first parameter was $unused
851 global $wgLang;
852 $lang = $wgLang;
853 }
854 if ( !$linkRenderer ) {
855 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
856 }
857
858 $tools = [];
859 $modes = [
860 'view' => [ 'Watchlist', false, false ],
861 'edit' => [ 'EditWatchlist', false, self::EDIT_NORMAL ],
862 'raw' => [ 'EditWatchlist', 'raw', self::EDIT_RAW ],
863 'clear' => [ 'EditWatchlist', 'clear', self::EDIT_CLEAR ],
864 ];
865
866 foreach ( $modes as $mode => $arr ) {
867 // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw'
868 $link = $linkRenderer->makeKnownLink(
869 SpecialPage::getTitleFor( $arr[0], $arr[1] ),
870 wfMessage( "watchlisttools-{$mode}" )->text()
871 );
872 $isSelected = $selectedMode === $arr[2];
873 $classes = [
874 'mw-watchlist-toollink',
875 'mw-watchlist-toollink-' . $mode,
876 $isSelected ? 'mw-watchlist-toollink-active' :
877 'mw-watchlist-toollink-inactive'
878 ];
879 $tools[] = Html::rawElement( 'span', [
880 'class' => $classes,
881 ], $link );
882 }
883
884 return Html::rawElement(
885 'span',
886 [ 'class' => 'mw-watchlist-toollinks mw-changeslist-links' ],
887 implode( '', $tools )
888 );
889 }
890}
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:497
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.
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
Base class for language-specific code.
Definition Language.php:53
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition Linker.php:1664
static tocIndent()
Add another level to the Table of Contents.
Definition Linker.php:1638
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:1700
static tocLineEnd()
End a Table Of Contents line.
Definition Linker.php:1688
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.
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.
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.
getLanguage()
Shortcut to get user's language.
Represents a page (or page fragment) title within MediaWiki.
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
Shortcut to construct a special page which is unlisted by default.
A title parser service for MediaWiki.
if(!isset( $args[0])) $lang