MediaWiki REL1_37
SpecialEditWatchlist.php
Go to the documentation of this file.
1<?php
35
49 public const EDIT_CLEAR = 1;
50 public const EDIT_RAW = 2;
51 public const EDIT_NORMAL = 3;
52
53 protected $successMessage;
54
55 protected $toc;
56
57 private $badItems = [];
58
60 private $titleParser;
61
64
66 private $genderCache;
67
70
72 private $nsInfo;
73
76
79
89 public function __construct(
97 ) {
98 parent::__construct( 'EditWatchlist', 'editmywatchlist' );
99 // This class is extended and therefor fallback to global state - T266065
100 $services = MediaWikiServices::getInstance();
101 $this->watchedItemStore = $watchedItemStore ?? $services->getWatchedItemStore();
102 $this->titleParser = $titleParser ?? $services->getTitleParser();
103 $this->genderCache = $genderCache ?? $services->getGenderCache();
104 $this->linkBatchFactory = $linkBatchFactory ?? $services->getLinkBatchFactory();
105 $this->nsInfo = $nsInfo ?? $services->getNamespaceInfo();
106 $this->wikiPageFactory = $wikiPageFactory ?? $services->getWikiPageFactory();
107 $this->watchlistManager = $watchlistManager ?? $services->getWatchlistManager();
108 }
109
110 public function doesWrites() {
111 return true;
112 }
113
119 public function execute( $mode ) {
120 $this->setHeaders();
121
122 # Anons don't get a watchlist
123 $this->requireLogin( 'watchlistanontext' );
124
125 $out = $this->getOutput();
126
127 $this->checkPermissions();
128 $this->checkReadOnly();
129
130 $this->outputHeader();
131 $this->outputSubtitle();
132 $out->addModuleStyles( 'mediawiki.special' );
133
134 $mode = self::getMode( $this->getRequest(), $mode );
135
136 switch ( $mode ) {
137 case self::EDIT_RAW:
138 $out->setPageTitle( $this->msg( 'watchlistedit-raw-title' ) );
139 $form = $this->getRawForm();
140 if ( $form->show() ) {
141 $out->addHTML( $this->successMessage );
142 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
143 }
144 break;
145 case self::EDIT_CLEAR:
146 $out->setPageTitle( $this->msg( 'watchlistedit-clear-title' ) );
147 $form = $this->getClearForm();
148 if ( $form->show() ) {
149 $out->addHTML( $this->successMessage );
150 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
151 }
152 break;
153
155 default:
157 break;
158 }
159 }
160
164 protected function outputSubtitle() {
165 $out = $this->getOutput();
166 $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() )
167 ->rawParams(
168 self::buildTools(
169 $this->getLanguage(),
170 $this->getLinkRenderer()
171 )
172 )
173 );
174 }
175
179 protected function executeViewEditWatchlist() {
180 $out = $this->getOutput();
181 $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) );
182 $form = $this->getNormalForm();
183 if ( $form->show() ) {
184 $out->addHTML( $this->successMessage );
185 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
186 } elseif ( $this->toc !== false ) {
187 $out->prependHTML( $this->toc );
188 }
189 }
190
197 public function getSubpagesForPrefixSearch() {
198 // SpecialWatchlist uses SpecialEditWatchlist::getMode, so new types should be added
199 // here and there - no 'edit' here, because that the default for this page
200 return [
201 'clear',
202 'raw',
203 ];
204 }
205
213 private function extractTitles( $list ) {
214 $list = explode( "\n", trim( $list ) );
215 if ( !is_array( $list ) ) {
216 return [];
217 }
218
219 $titles = [];
220
221 foreach ( $list as $text ) {
222 $text = trim( $text );
223 if ( strlen( $text ) > 0 ) {
224 $title = Title::newFromText( $text );
225 if ( $title instanceof Title && $this->watchlistManager->isWatchable( $title ) ) {
226 $titles[] = $title;
227 }
228 }
229 }
230
231 $this->genderCache->doTitlesArray( $titles );
232
233 $list = [];
235 foreach ( $titles as $title ) {
236 $list[] = $title->getPrefixedText();
237 }
238
239 return array_unique( $list );
240 }
241
242 public function submitRaw( $data ) {
243 $wanted = $this->extractTitles( $data['Titles'] );
244 $current = $this->getWatchlist();
245
246 if ( count( $wanted ) > 0 ) {
247 $toWatch = array_diff( $wanted, $current );
248 $toUnwatch = array_diff( $current, $wanted );
249 $this->watchTitles( $toWatch );
250 $this->unwatchTitles( $toUnwatch );
251 $this->getUser()->invalidateCache();
252
253 if ( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ) {
254 $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse();
255 } else {
256 return false;
257 }
258
259 if ( count( $toWatch ) > 0 ) {
260 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added' )
261 ->numParams( count( $toWatch ) )->parse();
262 $this->showTitles( $toWatch, $this->successMessage );
263 }
264
265 if ( count( $toUnwatch ) > 0 ) {
266 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' )
267 ->numParams( count( $toUnwatch ) )->parse();
268 $this->showTitles( $toUnwatch, $this->successMessage );
269 }
270 } else {
271
272 if ( count( $current ) === 0 ) {
273 return false;
274 }
275
276 $this->clearUserWatchedItems( 'raw' );
277 $this->showTitles( $current, $this->successMessage );
278 }
279
280 return true;
281 }
282
289 public function submitClear( $data ): bool {
290 $this->clearUserWatchedItems( 'clear' );
291 return true;
292 }
293
300 private function clearUserWatchedItems( string $messageFor ): void {
301 if ( $this->watchedItemStore->mustClearWatchedItemsUsingJobQueue( $this->getUser() ) ) {
302 $this->clearUserWatchedItemsUsingJobQueue();
303 } else {
304 $this->clearUserWatchedItemsNow( $messageFor );
305 }
306 }
307
313 private function clearUserWatchedItemsNow( string $messageFor ): void {
314 $current = $this->getWatchlist();
315 if ( !$this->watchedItemStore->clearUserWatchedItems( $this->getUser() ) ) {
316 throw new LogicException(
317 __METHOD__ . ' should only be called when able to clear synchronously'
318 );
319 }
320 $this->successMessage = $this->msg( 'watchlistedit-' . $messageFor . '-done' )->parse();
321 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-' . $messageFor . '-removed' )
322 ->numParams( count( $current ) )->parse();
323 $this->getUser()->invalidateCache();
324 $this->showTitles( $current, $this->successMessage );
325 }
326
330 private function clearUserWatchedItemsUsingJobQueue(): void {
331 $this->watchedItemStore->clearUserWatchedItemsUsingJobQueue( $this->getUser() );
332 $this->successMessage = $this->msg( 'watchlistedit-clear-jobqueue' )->parse();
333 }
334
344 private function showTitles( $titles, &$output ) {
345 $talk = $this->msg( 'talkpagelinktext' )->text();
346 // Do a batch existence check
347 $batch = $this->linkBatchFactory->newLinkBatch();
348 if ( count( $titles ) >= 100 ) {
349 $output = $this->msg( 'watchlistedit-too-many' )->parse();
350 return;
351 }
352 foreach ( $titles as $title ) {
353 if ( !$title instanceof Title ) {
354 $title = Title::newFromText( $title );
355 }
356
357 if ( $title instanceof Title ) {
358 $batch->addObj( $title );
359 $batch->addObj( $title->getTalkPage() );
360 }
361 }
362
363 $batch->execute();
364
365 // Print out the list
366 $output .= "<ul>\n";
367
368 $linkRenderer = $this->getLinkRenderer();
369 foreach ( $titles as $title ) {
370 if ( !$title instanceof Title ) {
371 $title = Title::newFromText( $title );
372 }
373
374 if ( $title instanceof Title ) {
375 $output .= '<li>' .
376 $linkRenderer->makeLink( $title ) . ' ' .
377 $this->msg( 'parentheses' )->rawParams(
378 $linkRenderer->makeLink( $title->getTalkPage(), $talk )
379 )->escaped() .
380 "</li>\n";
381 }
382 }
383
384 $output .= "</ul>\n";
385 }
386
393 private function getWatchlist() {
394 $list = [];
395
396 $watchedItems = $this->watchedItemStore->getWatchedItemsForUser(
397 $this->getUser(),
398 [ 'forWrite' => $this->getRequest()->wasPosted() ]
399 );
400
401 if ( $watchedItems ) {
403 $titles = [];
404 foreach ( $watchedItems as $watchedItem ) {
405 $namespace = $watchedItem->getTarget()->getNamespace();
406 $dbKey = $watchedItem->getTarget()->getDBkey();
407 $title = Title::makeTitleSafe( $namespace, $dbKey );
408
409 if ( $this->checkTitle( $title, $namespace, $dbKey )
410 && !$title->isTalkPage()
411 ) {
412 $titles[] = $title;
413 }
414 }
415
416 $this->genderCache->doTitlesArray( $titles );
417
418 foreach ( $titles as $title ) {
419 $list[] = $title->getPrefixedText();
420 }
421 }
422
423 $this->cleanupWatchlist();
424
425 return $list;
426 }
427
434 protected function getWatchlistInfo() {
435 $titles = [];
436 $options = [ 'sort' => WatchedItemStore::SORT_ASC ];
437
438 if ( $this->getConfig()->get( 'WatchlistExpiry' ) ) {
439 $options[ 'sortByExpiry'] = true;
440 }
441
442 $watchedItems = $this->watchedItemStore->getWatchedItemsForUser(
443 $this->getUser(), $options
444 );
445
446 $lb = $this->linkBatchFactory->newLinkBatch();
447 $context = $this->getContext();
448
449 foreach ( $watchedItems as $watchedItem ) {
450 $namespace = $watchedItem->getTarget()->getNamespace();
451 $dbKey = $watchedItem->getTarget()->getDBkey();
452 $lb->add( $namespace, $dbKey );
453 if ( !$this->nsInfo->isTalk( $namespace ) ) {
454 $titles[$namespace][$dbKey] = $watchedItem->getExpiryInDaysText( $context );
455 }
456 }
457
458 $lb->execute();
459
460 return $titles;
461 }
462
471 private function checkTitle( $title, $namespace, $dbKey ) {
472 if ( $title
473 && ( $title->isExternal()
474 || $title->getNamespace() < 0
475 )
476 ) {
477 $title = false; // unrecoverable
478 }
479
480 if ( !$title
481 || $title->getNamespace() != $namespace
482 || $title->getDBkey() != $dbKey
483 ) {
484 $this->badItems[] = [ $title, $namespace, $dbKey ];
485 }
486
487 return (bool)$title;
488 }
489
493 private function cleanupWatchlist() {
494 if ( $this->badItems === [] ) {
495 return; // nothing to do
496 }
497
498 $user = $this->getUser();
499 $badItems = $this->badItems;
500 DeferredUpdates::addCallableUpdate( function () use ( $user, $badItems ) {
501 foreach ( $badItems as [ $title, $namespace, $dbKey ] ) {
502 $action = $title ? 'cleaning up' : 'deleting';
503 wfDebug( "User {$user->getName()} has broken watchlist item " .
504 "ns($namespace):$dbKey, $action." );
505
506 // NOTE: We *know* that the title is invalid. TitleValue may refuse instantiation.
507 // XXX: We may need an InvalidTitleValue class that allows instantiation of
508 // known bad title values.
509 $this->watchedItemStore->removeWatch( $user, Title::makeTitle( (int)$namespace, $dbKey ) );
510 // Can't just do an UPDATE instead of DELETE/INSERT due to unique index
511 if ( $title ) {
512 $this->watchlistManager->addWatch( $user, $title );
513 }
514 }
515 } );
516 }
517
526 private function watchTitles( array $targets ) {
527 return $this->watchedItemStore->addWatchBatchForUser(
528 $this->getUser(), $this->getExpandedTargets( $targets )
529 ) && $this->runWatchUnwatchCompleteHook( 'Watch', $targets );
530 }
531
544 private function unwatchTitles( array $targets ) {
545 return $this->watchedItemStore->removeWatchBatchForUser(
546 $this->getUser(), $this->getExpandedTargets( $targets )
547 ) && $this->runWatchUnwatchCompleteHook( 'Unwatch', $targets );
548 }
549
558 private function runWatchUnwatchCompleteHook( $action, $targets ) {
559 foreach ( $targets as $target ) {
560 $title = $target instanceof LinkTarget ?
561 Title::newFromLinkTarget( $target ) :
562 Title::newFromText( $target );
563 $page = $this->wikiPageFactory->newFromTitle( $title );
564 $user = $this->getUser();
565 if ( $action === 'Watch' ) {
566 $this->getHookRunner()->onWatchArticleComplete( $user, $page );
567 } else {
568 $this->getHookRunner()->onUnwatchArticleComplete( $user, $page );
569 }
570 }
571 return true;
572 }
573
578 private function getExpandedTargets( array $targets ) {
579 $expandedTargets = [];
580 foreach ( $targets as $target ) {
581 if ( !$target instanceof LinkTarget ) {
582 try {
583 $target = $this->titleParser->parseTitle( $target, NS_MAIN );
584 }
585 catch ( MalformedTitleException $e ) {
586 continue;
587 }
588 }
589
590 $ns = $target->getNamespace();
591 $dbKey = $target->getDBkey();
592 $expandedTargets[] =
593 new TitleValue( $this->nsInfo->getSubject( $ns ), $dbKey );
594 $expandedTargets[] =
595 new TitleValue( $this->nsInfo->getTalk( $ns ), $dbKey );
596 }
597 return $expandedTargets;
598 }
599
600 public function submitNormal( $data ) {
601 $removed = [];
602
603 foreach ( $data as $titles ) {
604 $this->unwatchTitles( $titles );
605 $removed = array_merge( $removed, $titles );
606 }
607
608 if ( count( $removed ) > 0 ) {
609 $this->successMessage = $this->msg( 'watchlistedit-normal-done'
610 )->numParams( count( $removed ) )->parse();
611 $this->showTitles( $removed, $this->successMessage );
612
613 return true;
614 } else {
615 return false;
616 }
617 }
618
624 protected function getNormalForm() {
625 $fields = [];
626 $count = 0;
627
628 // Allow subscribers to manipulate the list of watched pages (or use it
629 // to preload lots of details at once)
630 $watchlistInfo = $this->getWatchlistInfo();
631 $this->getHookRunner()->onWatchlistEditorBeforeFormRender( $watchlistInfo );
632
633 foreach ( $watchlistInfo as $namespace => $pages ) {
634 $options = [];
635 foreach ( $pages as $dbkey => $expiryDaysText ) {
636 $title = Title::makeTitleSafe( $namespace, $dbkey );
637
638 if ( $this->checkTitle( $title, $namespace, $dbkey ) ) {
639 $text = $this->buildRemoveLine( $title, $expiryDaysText );
640 $options[$text] = $title->getPrefixedText();
641 $count++;
642 }
643 }
644
645 // checkTitle can filter some options out, avoid empty sections
646 if ( count( $options ) > 0 ) {
647 $fields['TitlesNs' . $namespace] = [
648 'class' => EditWatchlistCheckboxSeriesField::class,
649 'options' => $options,
650 'section' => "ns$namespace",
651 ];
652 }
653 }
654 $this->cleanupWatchlist();
655
656 if ( count( $fields ) > 1 && $count > 30 ) {
657 $this->toc = Linker::tocIndent();
658 $tocLength = 0;
659 $contLang = $this->getContentLanguage();
660
661 foreach ( $fields as $data ) {
662 # strip out the 'ns' prefix from the section name:
663 $ns = substr( $data['section'], 2 );
664
665 $nsText = ( $ns == NS_MAIN )
666 ? $this->msg( 'blanknamespace' )->escaped()
667 : htmlspecialchars( $contLang->getFormattedNsText( $ns ) );
668 $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText,
669 $this->getLanguage()->formatNum( ++$tocLength ), 1 ) . Linker::tocLineEnd();
670 }
671
672 $this->toc = Linker::tocList( $this->toc );
673 } else {
674 $this->toc = false;
675 }
676
677 $context = new DerivativeContext( $this->getContext() );
678 $context->setTitle( $this->getPageTitle() ); // Remove subpage
679 $form = new EditWatchlistNormalHTMLForm( $fields, $context );
680 $form->setSubmitTextMsg( 'watchlistedit-normal-submit' );
681 $form->setSubmitDestructive();
682 # Used message keys:
683 # 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
684 $form->setSubmitTooltip( 'watchlistedit-normal-submit' );
685 $form->setWrapperLegendMsg( 'watchlistedit-normal-legend' );
686 $form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() );
687 $form->setSubmitCallback( [ $this, 'submitNormal' ] );
688
689 return $form;
690 }
691
700 private function buildRemoveLine( $title, string $expiryDaysText = '' ): string {
701 $linkRenderer = $this->getLinkRenderer();
702 $link = $linkRenderer->makeLink( $title );
703
704 $tools = [];
705 $tools['talk'] = $linkRenderer->makeLink(
706 $title->getTalkPage(),
707 $this->msg( 'talkpagelinktext' )->text()
708 );
709
710 if ( $title->exists() ) {
711 $tools['history'] = $linkRenderer->makeKnownLink(
712 $title,
713 $this->msg( 'history_small' )->text(),
714 [],
715 [ 'action' => 'history' ]
716 );
717 }
718
719 if ( $title->getNamespace() === NS_USER && !$title->isSubpage() ) {
720 $tools['contributions'] = $linkRenderer->makeKnownLink(
721 SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
722 $this->msg( 'contribslink' )->text()
723 );
724 }
725
726 $this->getHookRunner()->onWatchlistEditorBuildRemoveLine(
727 $tools, $title, $title->isRedirect(), $this->getSkin(), $link );
728
729 if ( $title->isRedirect() ) {
730 // Linker already makes class mw-redirect, so this is redundant
731 $link = '<span class="watchlistredir">' . $link . '</span>';
732 }
733
734 $watchlistExpiringMessage = '';
735 if ( $this->getConfig()->get( 'WatchlistExpiry' ) && $expiryDaysText ) {
736 $watchlistExpiringMessage = Html::element(
737 'span',
738 [ 'class' => 'mw-watchlistexpiry-msg' ],
739 $expiryDaysText
740 );
741 }
742
743 return $link . ' ' .
744 $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList( $tools ) )->escaped() .
745 $watchlistExpiringMessage;
746 }
747
753 protected function getRawForm() {
754 $titles = implode( "\n", $this->getWatchlist() );
755 $fields = [
756 'Titles' => [
757 'type' => 'textarea',
758 'label-message' => 'watchlistedit-raw-titles',
759 'default' => $titles,
760 ],
761 ];
762 $context = new DerivativeContext( $this->getContext() );
763 $context->setTitle( $this->getPageTitle( 'raw' ) ); // Reset subpage
764 $form = new OOUIHTMLForm( $fields, $context );
765 $form->setSubmitTextMsg( 'watchlistedit-raw-submit' );
766 # Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit'
767 $form->setSubmitTooltip( 'watchlistedit-raw-submit' );
768 $form->setWrapperLegendMsg( 'watchlistedit-raw-legend' );
769 $form->addHeaderText( $this->msg( 'watchlistedit-raw-explain' )->parse() );
770 $form->setSubmitCallback( [ $this, 'submitRaw' ] );
771
772 return $form;
773 }
774
780 protected function getClearForm() {
781 $context = new DerivativeContext( $this->getContext() );
782 $context->setTitle( $this->getPageTitle( 'clear' ) ); // Reset subpage
783 $form = new OOUIHTMLForm( [], $context );
784 $form->setSubmitTextMsg( 'watchlistedit-clear-submit' );
785 # Used message keys: 'accesskey-watchlistedit-clear-submit', 'tooltip-watchlistedit-clear-submit'
786 $form->setSubmitTooltip( 'watchlistedit-clear-submit' );
787 $form->setWrapperLegendMsg( 'watchlistedit-clear-legend' );
788 $form->addHeaderText( $this->msg( 'watchlistedit-clear-explain' )->parse() );
789 $form->setSubmitCallback( [ $this, 'submitClear' ] );
790 $form->setSubmitDestructive();
791
792 return $form;
793 }
794
803 public static function getMode( $request, $par ) {
804 $mode = strtolower( $request->getRawVal( 'action', $par ?? '' ) );
805
806 switch ( $mode ) {
807 case 'clear':
808 case self::EDIT_CLEAR:
809 return self::EDIT_CLEAR;
810 case 'raw':
811 case self::EDIT_RAW:
812 return self::EDIT_RAW;
813 case 'edit':
814 case self::EDIT_NORMAL:
815 return self::EDIT_NORMAL;
816 default:
817 return false;
818 }
819 }
820
829 public static function buildTools( $lang, LinkRenderer $linkRenderer = null ) {
830 if ( !$lang instanceof Language ) {
831 // back-compat where the first parameter was $unused
832 global $wgLang;
833 $lang = $wgLang;
834 }
835 if ( !$linkRenderer ) {
836 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
837 }
838
839 $tools = [];
840 $modes = [
841 'view' => [ 'Watchlist', false ],
842 'edit' => [ 'EditWatchlist', false ],
843 'raw' => [ 'EditWatchlist', 'raw' ],
844 'clear' => [ 'EditWatchlist', 'clear' ],
845 ];
846
847 foreach ( $modes as $mode => $arr ) {
848 // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw'
849 $tools[] = $linkRenderer->makeKnownLink(
850 SpecialPage::getTitleFor( $arr[0], $arr[1] ),
851 wfMessage( "watchlisttools-{$mode}" )->text()
852 );
853 }
854
855 return Html::rawElement(
856 'span',
857 [ 'class' => 'mw-watchlist-toollinks' ],
858 wfMessage( 'parentheses' )->rawParams( $lang->pipeList( $tools ) )->escaped()
859 );
860 }
861}
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()
$wgLang
Definition Setup.php:831
if(ini_get('mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition Setup.php:88
An IContextSource implementation which will inherit context from another source but allow individual ...
Extend OOUIHTMLForm purely so we can have a more sane 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:232
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition Language.php:42
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition Linker.php:1861
static tocIndent()
Add another level to the Table of Contents.
Definition Linker.php:1835
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:1897
static tocLineEnd()
End a Table Of Contents line.
Definition Linker.php:1885
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Class that generates HTML links for pages.
MediaWikiServices is the service locator for the application scope of MediaWiki.
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.
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.
static buildTools( $lang, LinkRenderer $linkRenderer=null)
Build a set of links for convenient navigation between watchlist viewing and editing modes.
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.
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.
buildRemoveLine( $title, string $expiryDaysText='')
Build the label for a checkbox, with a link to the title, and various additional bits.
static getMode( $request, $par)
Determine whether we are editing the watchlist, and if so, what kind of editing operation.
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.
requireLogin( $reasonMsg='exception-nologin-text', $titleMsg='exception-nologin')
If the user is not logged in, throws UserNotLoggedIn error.
getUser()
Shortcut to get the User executing this instance.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
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:48
isWatchable()
Can this title be added to a user's watchlist?
Definition Title.php:1282
Shortcut to construct a special page which is unlisted by default.
A title parser service for MediaWiki.
if(!isset( $args[0])) $lang