MediaWiki REL1_35
SpecialEditWatchlist.php
Go to the documentation of this file.
1<?php
32
46 public const EDIT_CLEAR = 1;
47 public const EDIT_RAW = 2;
48 public const EDIT_NORMAL = 3;
49
50 protected $successMessage;
51
52 protected $toc;
53
54 private $badItems = [];
55
59 private $titleParser;
60
65
68
70 parent::__construct( 'EditWatchlist', 'editmywatchlist' );
71 $this->watchedItemStore = $watchedItemStore;
72 $this->isWatchlistExpiryEnabled = $this->getConfig()->get( 'WatchlistExpiry' );
73 }
74
79 private function initServices() {
80 if ( !$this->titleParser ) {
81 $this->titleParser = MediaWikiServices::getInstance()->getTitleParser();
82 }
83 }
84
85 public function doesWrites() {
86 return true;
87 }
88
94 public function execute( $mode ) {
95 $this->initServices();
96 $this->setHeaders();
97
98 # Anons don't get a watchlist
99 $this->requireLogin( 'watchlistanontext' );
100
101 $out = $this->getOutput();
102
103 $this->checkPermissions();
104 $this->checkReadOnly();
105
106 $this->outputHeader();
107 $this->outputSubtitle();
108 $out->addModuleStyles( 'mediawiki.special' );
109
110 # B/C: $mode used to be waaay down the parameter list, and the first parameter
111 # was $wgUser
112 if ( $mode instanceof User ) {
113 $args = func_get_args();
114 if ( count( $args ) >= 4 ) {
115 $mode = $args[3];
116 }
117 }
118 $mode = self::getMode( $this->getRequest(), $mode );
119
120 switch ( $mode ) {
121 case self::EDIT_RAW:
122 $out->setPageTitle( $this->msg( 'watchlistedit-raw-title' ) );
123 $form = $this->getRawForm();
124 if ( $form->show() ) {
125 $out->addHTML( $this->successMessage );
126 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
127 }
128 break;
129 case self::EDIT_CLEAR:
130 $out->setPageTitle( $this->msg( 'watchlistedit-clear-title' ) );
131 $form = $this->getClearForm();
132 if ( $form->show() ) {
133 $out->addHTML( $this->successMessage );
134 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
135 }
136 break;
137
139 default:
141 break;
142 }
143 }
144
148 protected function outputSubtitle() {
149 $out = $this->getOutput();
150 $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() )
151 ->rawParams(
152 self::buildTools(
153 $this->getLanguage(),
154 $this->getLinkRenderer()
155 )
156 )
157 );
158 }
159
163 protected function executeViewEditWatchlist() {
164 $out = $this->getOutput();
165 $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) );
166 $form = $this->getNormalForm();
167 if ( $form->show() ) {
168 $out->addHTML( $this->successMessage );
169 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
170 } elseif ( $this->toc !== false ) {
171 $out->prependHTML( $this->toc );
172 $out->addModuleStyles( 'mediawiki.toc.styles' );
173 }
174 }
175
182 public function getSubpagesForPrefixSearch() {
183 // SpecialWatchlist uses SpecialEditWatchlist::getMode, so new types should be added
184 // here and there - no 'edit' here, because that the default for this page
185 return [
186 'clear',
187 'raw',
188 ];
189 }
190
198 private function extractTitles( $list ) {
199 $list = explode( "\n", trim( $list ) );
200 if ( !is_array( $list ) ) {
201 return [];
202 }
203
204 $titles = [];
205
206 foreach ( $list as $text ) {
207 $text = trim( $text );
208 if ( strlen( $text ) > 0 ) {
209 $title = Title::newFromText( $text );
210 if ( $title instanceof Title && $title->isWatchable() ) {
211 $titles[] = $title;
212 }
213 }
214 }
215
216 MediaWikiServices::getInstance()->getGenderCache()->doTitlesArray( $titles );
217
218 $list = [];
220 foreach ( $titles as $title ) {
221 $list[] = $title->getPrefixedText();
222 }
223
224 return array_unique( $list );
225 }
226
227 public function submitRaw( $data ) {
228 $wanted = $this->extractTitles( $data['Titles'] );
229 $current = $this->getWatchlist();
230
231 if ( count( $wanted ) > 0 ) {
232 $toWatch = array_diff( $wanted, $current );
233 $toUnwatch = array_diff( $current, $wanted );
234 $this->watchTitles( $toWatch );
235 $this->unwatchTitles( $toUnwatch );
236 $this->getUser()->invalidateCache();
237
238 if ( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ) {
239 $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse();
240 } else {
241 return false;
242 }
243
244 if ( count( $toWatch ) > 0 ) {
245 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added' )
246 ->numParams( count( $toWatch ) )->parse();
247 $this->showTitles( $toWatch, $this->successMessage );
248 }
249
250 if ( count( $toUnwatch ) > 0 ) {
251 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' )
252 ->numParams( count( $toUnwatch ) )->parse();
253 $this->showTitles( $toUnwatch, $this->successMessage );
254 }
255 } else {
256
257 if ( count( $current ) === 0 ) {
258 return false;
259 }
260
261 $this->clearUserWatchedItems( 'raw' );
262 $this->showTitles( $current, $this->successMessage );
263 }
264
265 return true;
266 }
267
274 public function submitClear( $data ): bool {
275 $this->clearUserWatchedItems( 'clear' );
276 return true;
277 }
278
285 private function clearUserWatchedItems( string $messageFor ): void {
286 if ( $this->watchedItemStore->mustClearWatchedItemsUsingJobQueue( $this->getUser() ) ) {
287 $this->clearUserWatchedItemsUsingJobQueue();
288 } else {
289 $this->clearUserWatchedItemsNow( $messageFor );
290 }
291 }
292
298 private function clearUserWatchedItemsNow( string $messageFor ): void {
299 $current = $this->getWatchlist();
300 if ( !$this->watchedItemStore->clearUserWatchedItems( $this->getUser() ) ) {
301 throw new LogicException(
302 __METHOD__ . ' should only be called when able to clear synchronously'
303 );
304 }
305 $this->successMessage = $this->msg( 'watchlistedit-' . $messageFor . '-done' )->parse();
306 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-' . $messageFor . '-removed' )
307 ->numParams( count( $current ) )->parse();
308 $this->getUser()->invalidateCache();
309 $this->showTitles( $current, $this->successMessage );
310 }
311
315 private function clearUserWatchedItemsUsingJobQueue(): void {
316 $this->watchedItemStore->clearUserWatchedItemsUsingJobQueue( $this->getUser() );
317 $this->successMessage = $this->msg( 'watchlistedit-clear-jobqueue' )->parse();
318 }
319
329 private function showTitles( $titles, &$output ) {
330 $talk = $this->msg( 'talkpagelinktext' )->text();
331 // Do a batch existence check
332 $batch = new LinkBatch();
333 if ( count( $titles ) >= 100 ) {
334 $output = $this->msg( 'watchlistedit-too-many' )->parse();
335 return;
336 }
337 foreach ( $titles as $title ) {
338 if ( !$title instanceof Title ) {
339 $title = Title::newFromText( $title );
340 }
341
342 if ( $title instanceof Title ) {
343 $batch->addObj( $title );
344 $batch->addObj( $title->getTalkPage() );
345 }
346 }
347
348 $batch->execute();
349
350 // Print out the list
351 $output .= "<ul>\n";
352
353 $linkRenderer = $this->getLinkRenderer();
354 foreach ( $titles as $title ) {
355 if ( !$title instanceof Title ) {
356 $title = Title::newFromText( $title );
357 }
358
359 if ( $title instanceof Title ) {
360 $output .= '<li>' .
361 $linkRenderer->makeLink( $title ) . ' ' .
362 $this->msg( 'parentheses' )->rawParams(
363 $linkRenderer->makeLink( $title->getTalkPage(), $talk )
364 )->escaped() .
365 "</li>\n";
366 }
367 }
368
369 $output .= "</ul>\n";
370 }
371
378 private function getWatchlist() {
379 $list = [];
380
381 $watchedItems = $this->watchedItemStore->getWatchedItemsForUser(
382 $this->getUser(),
383 [ 'forWrite' => $this->getRequest()->wasPosted() ]
384 );
385
386 if ( $watchedItems ) {
388 $titles = [];
389 foreach ( $watchedItems as $watchedItem ) {
390 $namespace = $watchedItem->getLinkTarget()->getNamespace();
391 $dbKey = $watchedItem->getLinkTarget()->getDBkey();
392 $title = Title::makeTitleSafe( $namespace, $dbKey );
393
394 if ( $this->checkTitle( $title, $namespace, $dbKey )
395 && !$title->isTalkPage()
396 ) {
397 $titles[] = $title;
398 }
399 }
400
401 MediaWikiServices::getInstance()->getGenderCache()->doTitlesArray( $titles );
402
403 foreach ( $titles as $title ) {
404 $list[] = $title->getPrefixedText();
405 }
406 }
407
408 $this->cleanupWatchlist();
409
410 return $list;
411 }
412
419 protected function getWatchlistInfo() {
420 $titles = [];
421 $services = MediaWikiServices::getInstance();
422 $options = [ 'sort' => WatchedItemStore::SORT_ASC ];
423
424 if ( $this->isWatchlistExpiryEnabled ) {
425 $options[ 'sortByExpiry'] = true;
426 }
427
428 $watchedItems = $this->watchedItemStore->getWatchedItemsForUser(
429 $this->getUser(), $options
430 );
431
432 $lb = new LinkBatch();
433 $context = $this->getContext();
434
435 foreach ( $watchedItems as $watchedItem ) {
436 $namespace = $watchedItem->getLinkTarget()->getNamespace();
437 $dbKey = $watchedItem->getLinkTarget()->getDBkey();
438 $lb->add( $namespace, $dbKey );
439 if ( !$services->getNamespaceInfo()->isTalk( $namespace ) ) {
440 $titles[$namespace][$dbKey] = $watchedItem->getExpiryInDaysText( $context );
441 }
442 }
443
444 $lb->execute();
445
446 return $titles;
447 }
448
457 private function checkTitle( $title, $namespace, $dbKey ) {
458 if ( $title
459 && ( $title->isExternal()
460 || $title->getNamespace() < 0
461 )
462 ) {
463 $title = false; // unrecoverable
464 }
465
466 if ( !$title
467 || $title->getNamespace() != $namespace
468 || $title->getDBkey() != $dbKey
469 ) {
470 $this->badItems[] = [ $title, $namespace, $dbKey ];
471 }
472
473 return (bool)$title;
474 }
475
479 private function cleanupWatchlist() {
480 if ( $this->badItems === [] ) {
481 return; // nothing to do
482 }
483
484 $user = $this->getUser();
485 $badItems = $this->badItems;
486 DeferredUpdates::addCallableUpdate( function () use ( $user, $badItems ) {
487 foreach ( $badItems as $row ) {
488 list( $title, $namespace, $dbKey ) = $row;
489 $action = $title ? 'cleaning up' : 'deleting';
490 wfDebug( "User {$user->getName()} has broken watchlist item " .
491 "ns($namespace):$dbKey, $action." );
492
493 // NOTE: We *know* that the title is invalid. TitleValue may refuse instantiation.
494 // XXX: We may need an InvalidTitleValue class that allows instantiation of
495 // known bad title values.
496 $this->watchedItemStore->removeWatch( $user, Title::makeTitle( (int)$namespace, $dbKey ) );
497 // Can't just do an UPDATE instead of DELETE/INSERT due to unique index
498 if ( $title ) {
499 $user->addWatch( $title );
500 }
501 }
502 } );
503 }
504
513 private function watchTitles( array $targets ) {
514 return $this->watchedItemStore->addWatchBatchForUser(
515 $this->getUser(), $this->getExpandedTargets( $targets )
516 ) && $this->runWatchUnwatchCompleteHook( 'Watch', $targets );
517 }
518
531 private function unwatchTitles( array $targets ) {
532 return $this->watchedItemStore->removeWatchBatchForUser(
533 $this->getUser(), $this->getExpandedTargets( $targets )
534 ) && $this->runWatchUnwatchCompleteHook( 'Unwatch', $targets );
535 }
536
545 private function runWatchUnwatchCompleteHook( $action, $targets ) {
546 foreach ( $targets as $target ) {
547 $title = $target instanceof TitleValue ?
548 Title::newFromTitleValue( $target ) :
549 Title::newFromText( $target );
550 $page = WikiPage::factory( $title );
551 $user = $this->getUser();
552 if ( $action === 'Watch' ) {
553 $this->getHookRunner()->onWatchArticleComplete( $user, $page );
554 } else {
555 $this->getHookRunner()->onUnwatchArticleComplete( $user, $page );
556 }
557 }
558 return true;
559 }
560
565 private function getExpandedTargets( array $targets ) {
566 $expandedTargets = [];
567 $services = MediaWikiServices::getInstance();
568 foreach ( $targets as $target ) {
569 if ( !$target instanceof LinkTarget ) {
570 try {
571 $target = $this->titleParser->parseTitle( $target, NS_MAIN );
572 }
573 catch ( MalformedTitleException $e ) {
574 continue;
575 }
576 }
577
578 $ns = $target->getNamespace();
579 $dbKey = $target->getDBkey();
580 $expandedTargets[] =
581 new TitleValue( $services->getNamespaceInfo()->getSubject( $ns ), $dbKey );
582 $expandedTargets[] =
583 new TitleValue( $services->getNamespaceInfo()->getTalk( $ns ), $dbKey );
584 }
585 return $expandedTargets;
586 }
587
588 public function submitNormal( $data ) {
589 $removed = [];
590
591 foreach ( $data as $titles ) {
592 $this->unwatchTitles( $titles );
593 $removed = array_merge( $removed, $titles );
594 }
595
596 if ( count( $removed ) > 0 ) {
597 $this->successMessage = $this->msg( 'watchlistedit-normal-done'
598 )->numParams( count( $removed ) )->parse();
599 $this->showTitles( $removed, $this->successMessage );
600
601 return true;
602 } else {
603 return false;
604 }
605 }
606
612 protected function getNormalForm() {
613 $fields = [];
614 $count = 0;
615
616 // Allow subscribers to manipulate the list of watched pages (or use it
617 // to preload lots of details at once)
618 $watchlistInfo = $this->getWatchlistInfo();
619 $this->getHookRunner()->onWatchlistEditorBeforeFormRender( $watchlistInfo );
620
621 foreach ( $watchlistInfo as $namespace => $pages ) {
622 $options = [];
623 foreach ( $pages as $dbkey => $expiryDaysText ) {
624 $title = Title::makeTitleSafe( $namespace, $dbkey );
625
626 if ( $this->checkTitle( $title, $namespace, $dbkey ) ) {
627 $text = $this->buildRemoveLine( $title, $expiryDaysText );
628 $options[$text] = $title->getPrefixedText();
629 $count++;
630 }
631 }
632
633 // checkTitle can filter some options out, avoid empty sections
634 if ( count( $options ) > 0 ) {
635 $fields['TitlesNs' . $namespace] = [
636 'class' => EditWatchlistCheckboxSeriesField::class,
637 'options' => $options,
638 'section' => "ns$namespace",
639 ];
640 }
641 }
642 $this->cleanupWatchlist();
643
644 if ( count( $fields ) > 1 && $count > 30 ) {
645 $this->toc = Linker::tocIndent();
646 $tocLength = 0;
647 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
648
649 foreach ( $fields as $data ) {
650 # strip out the 'ns' prefix from the section name:
651 $ns = substr( $data['section'], 2 );
652
653 $nsText = ( $ns == NS_MAIN )
654 ? $this->msg( 'blanknamespace' )->escaped()
655 : htmlspecialchars( $contLang->getFormattedNsText( $ns ) );
656 $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText,
657 $this->getLanguage()->formatNum( ++$tocLength ), 1 ) . Linker::tocLineEnd();
658 }
659
660 $this->toc = Linker::tocList( $this->toc );
661 } else {
662 $this->toc = false;
663 }
664
665 $context = new DerivativeContext( $this->getContext() );
666 $context->setTitle( $this->getPageTitle() ); // Remove subpage
667 $form = new EditWatchlistNormalHTMLForm( $fields, $context );
668 $form->setSubmitTextMsg( 'watchlistedit-normal-submit' );
669 $form->setSubmitDestructive();
670 # Used message keys:
671 # 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
672 $form->setSubmitTooltip( 'watchlistedit-normal-submit' );
673 $form->setWrapperLegendMsg( 'watchlistedit-normal-legend' );
674 $form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() );
675 $form->setSubmitCallback( [ $this, 'submitNormal' ] );
676
677 return $form;
678 }
679
688 private function buildRemoveLine( $title, string $expiryDaysText = '' ): string {
689 $linkRenderer = $this->getLinkRenderer();
690 $link = $linkRenderer->makeLink( $title );
691
692 $tools = [];
693 $tools['talk'] = $linkRenderer->makeLink(
694 $title->getTalkPage(),
695 $this->msg( 'talkpagelinktext' )->text()
696 );
697
698 if ( $title->exists() ) {
699 $tools['history'] = $linkRenderer->makeKnownLink(
700 $title,
701 $this->msg( 'history_small' )->text(),
702 [],
703 [ 'action' => 'history' ]
704 );
705 }
706
707 if ( $title->getNamespace() == NS_USER && !$title->isSubpage() ) {
708 $tools['contributions'] = $linkRenderer->makeKnownLink(
709 SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
710 $this->msg( 'contribslink' )->text()
711 );
712 }
713
714 $this->getHookRunner()->onWatchlistEditorBuildRemoveLine(
715 $tools, $title, $title->isRedirect(), $this->getSkin(), $link );
716
717 if ( $title->isRedirect() ) {
718 // Linker already makes class mw-redirect, so this is redundant
719 $link = '<span class="watchlistredir">' . $link . '</span>';
720 }
721
722 $watchlistExpiringMessage = '';
723 if ( $this->isWatchlistExpiryEnabled && $expiryDaysText ) {
724 $watchlistExpiringMessage = Html::element(
725 'span',
726 [ 'class' => 'watchlistexpiry-msg' ],
727 $expiryDaysText
728 );
729 }
730
731 return $link . ' ' .
732 $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList( $tools ) )->escaped() .
733 $watchlistExpiringMessage;
734 }
735
741 protected function getRawForm() {
742 $titles = implode( "\n", $this->getWatchlist() );
743 $fields = [
744 'Titles' => [
745 'type' => 'textarea',
746 'label-message' => 'watchlistedit-raw-titles',
747 'default' => $titles,
748 ],
749 ];
750 $context = new DerivativeContext( $this->getContext() );
751 $context->setTitle( $this->getPageTitle( 'raw' ) ); // Reset subpage
752 $form = new OOUIHTMLForm( $fields, $context );
753 $form->setSubmitTextMsg( 'watchlistedit-raw-submit' );
754 # Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit'
755 $form->setSubmitTooltip( 'watchlistedit-raw-submit' );
756 $form->setWrapperLegendMsg( 'watchlistedit-raw-legend' );
757 $form->addHeaderText( $this->msg( 'watchlistedit-raw-explain' )->parse() );
758 $form->setSubmitCallback( [ $this, 'submitRaw' ] );
759
760 return $form;
761 }
762
768 protected function getClearForm() {
769 $context = new DerivativeContext( $this->getContext() );
770 $context->setTitle( $this->getPageTitle( 'clear' ) ); // Reset subpage
771 $form = new OOUIHTMLForm( [], $context );
772 $form->setSubmitTextMsg( 'watchlistedit-clear-submit' );
773 # Used message keys: 'accesskey-watchlistedit-clear-submit', 'tooltip-watchlistedit-clear-submit'
774 $form->setSubmitTooltip( 'watchlistedit-clear-submit' );
775 $form->setWrapperLegendMsg( 'watchlistedit-clear-legend' );
776 $form->addHeaderText( $this->msg( 'watchlistedit-clear-explain' )->parse() );
777 $form->setSubmitCallback( [ $this, 'submitClear' ] );
778 $form->setSubmitDestructive();
779
780 return $form;
781 }
782
791 public static function getMode( $request, $par ) {
792 $mode = strtolower( $request->getVal( 'action', $par ?? '' ) );
793
794 switch ( $mode ) {
795 case 'clear':
796 case self::EDIT_CLEAR:
797 return self::EDIT_CLEAR;
798 case 'raw':
799 case self::EDIT_RAW:
800 return self::EDIT_RAW;
801 case 'edit':
802 case self::EDIT_NORMAL:
803 return self::EDIT_NORMAL;
804 default:
805 return false;
806 }
807 }
808
817 public static function buildTools( $lang, LinkRenderer $linkRenderer = null ) {
818 if ( !$lang instanceof Language ) {
819 // back-compat where the first parameter was $unused
820 global $wgLang;
821 $lang = $wgLang;
822 }
823 if ( !$linkRenderer ) {
824 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
825 }
826
827 $tools = [];
828 $modes = [
829 'view' => [ 'Watchlist', false ],
830 'edit' => [ 'EditWatchlist', false ],
831 'raw' => [ 'EditWatchlist', 'raw' ],
832 'clear' => [ 'EditWatchlist', 'clear' ],
833 ];
834
835 foreach ( $modes as $mode => $arr ) {
836 // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw'
837 $tools[] = $linkRenderer->makeKnownLink(
838 SpecialPage::getTitleFor( $arr[0], $arr[1] ),
839 wfMessage( "watchlisttools-{$mode}" )->text()
840 );
841 }
842
843 return Html::rawElement(
844 'span',
845 [ 'class' => 'mw-watchlist-toollinks' ],
846 wfMessage( 'parentheses' )->rawParams( $lang->pipeList( $tools ) )->escaped()
847 );
848 }
849}
getUser()
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:781
if(ini_get('mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition Setup.php:85
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.
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition Html.php:231
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition Language.php:41
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:35
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition Linker.php:1701
static tocIndent()
Add another level to the Table of Contents.
Definition Linker.php:1675
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:1737
static tocLineEnd()
End a Table Of Contents line.
Definition Linker.php:1725
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.
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.
__construct(WatchedItemStoreInterface $watchedItemStore)
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.
runWatchUnwatchCompleteHook( $action, $targets)
clearUserWatchedItemsUsingJobQueue()
You should call clearUserWatchedItems() instead to decide if this should use the JobQueue.
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...
bool $isWatchlistExpiryEnabled
Watchlist Expiry flag.
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.
initServices()
Initialize any services we'll need (unless it has already been provided via a setter).
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.
getConfig()
Shortcut to get main config object.
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:42
isWatchable()
Can this title be added to a user's watchlist?
Definition Title.php:1239
Shortcut to construct a special page which is unlisted by default.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
const NS_USER
Definition Defines.php:72
const NS_MAIN
Definition Defines.php:70
A title parser service for MediaWiki.
if( $line===false) $args
Definition mcc.php:124
if(!isset( $args[0])) $lang