70 parent::__construct(
'EditWatchlist',
'editmywatchlist' );
72 $this->isWatchlistExpiryEnabled = $this->
getConfig()->get(
'WatchlistExpiry' );
80 if ( !$this->titleParser ) {
81 $this->titleParser = MediaWikiServices::getInstance()->getTitleParser();
98 # Anons don't get a watchlist
108 $out->addModuleStyles(
'mediawiki.special' );
110 # B/C: $mode used to be waaay down the parameter list, and the first parameter
112 if ( $mode instanceof
User ) {
113 $args = func_get_args();
114 if ( count(
$args ) >= 4 ) {
122 $out->setPageTitle( $this->
msg(
'watchlistedit-raw-title' ) );
124 if ( $form->show() ) {
125 $out->addHTML( $this->successMessage );
130 $out->setPageTitle( $this->
msg(
'watchlistedit-clear-title' ) );
132 if ( $form->show() ) {
133 $out->addHTML( $this->successMessage );
165 $out->setPageTitle( $this->
msg(
'watchlistedit-normal-title' ) );
167 if ( $form->show() ) {
168 $out->addHTML( $this->successMessage );
170 } elseif ( $this->toc !==
false ) {
171 $out->prependHTML( $this->toc );
172 $out->addModuleStyles(
'mediawiki.toc.styles' );
199 $list = explode(
"\n", trim( $list ) );
200 if ( !is_array( $list ) ) {
206 foreach ( $list as $text ) {
207 $text = trim( $text );
208 if ( strlen( $text ) > 0 ) {
209 $title = Title::newFromText( $text );
216 MediaWikiServices::getInstance()->getGenderCache()->doTitlesArray( $titles );
220 foreach ( $titles as
$title ) {
221 $list[] =
$title->getPrefixedText();
224 return array_unique( $list );
231 if ( count( $wanted ) > 0 ) {
232 $toWatch = array_diff( $wanted, $current );
233 $toUnwatch = array_diff( $current, $wanted );
236 $this->
getUser()->invalidateCache();
238 if ( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ) {
239 $this->successMessage = $this->
msg(
'watchlistedit-raw-done' )->parse();
244 if ( count( $toWatch ) > 0 ) {
245 $this->successMessage .=
' ' . $this->
msg(
'watchlistedit-raw-added' )
246 ->numParams( count( $toWatch ) )->parse();
247 $this->
showTitles( $toWatch, $this->successMessage );
250 if ( count( $toUnwatch ) > 0 ) {
251 $this->successMessage .=
' ' . $this->
msg(
'watchlistedit-raw-removed' )
252 ->numParams( count( $toUnwatch ) )->parse();
253 $this->
showTitles( $toUnwatch, $this->successMessage );
257 if ( count( $current ) === 0 ) {
262 $this->
showTitles( $current, $this->successMessage );
286 if ( $this->watchedItemStore->mustClearWatchedItemsUsingJobQueue( $this->
getUser() ) ) {
287 $this->clearUserWatchedItemsUsingJobQueue();
289 $this->clearUserWatchedItemsNow( $messageFor );
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'
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 );
316 $this->watchedItemStore->clearUserWatchedItemsUsingJobQueue( $this->
getUser() );
317 $this->successMessage = $this->msg(
'watchlistedit-clear-jobqueue' )->parse();
330 $talk = $this->msg(
'talkpagelinktext' )->text();
333 if ( count( $titles ) >= 100 ) {
334 $output = $this->msg(
'watchlistedit-too-many' )->parse();
337 foreach ( $titles as
$title ) {
344 $batch->addObj(
$title->getTalkPage() );
353 $linkRenderer = $this->getLinkRenderer();
354 foreach ( $titles as
$title ) {
361 $linkRenderer->makeLink(
$title ) .
' ' .
362 $this->msg(
'parentheses' )->rawParams(
363 $linkRenderer->makeLink(
$title->getTalkPage(), $talk )
369 $output .=
"</ul>\n";
381 $watchedItems = $this->watchedItemStore->getWatchedItemsForUser(
383 [
'forWrite' => $this->getRequest()->wasPosted() ]
386 if ( $watchedItems ) {
389 foreach ( $watchedItems as $watchedItem ) {
390 $namespace = $watchedItem->getLinkTarget()->getNamespace();
391 $dbKey = $watchedItem->getLinkTarget()->getDBkey();
392 $title = Title::makeTitleSafe( $namespace, $dbKey );
394 if ( $this->checkTitle(
$title, $namespace, $dbKey )
401 MediaWikiServices::getInstance()->getGenderCache()->doTitlesArray( $titles );
403 foreach ( $titles as
$title ) {
404 $list[] =
$title->getPrefixedText();
408 $this->cleanupWatchlist();
421 $services = MediaWikiServices::getInstance();
424 if ( $this->isWatchlistExpiryEnabled ) {
425 $options[
'sortByExpiry'] =
true;
428 $watchedItems = $this->watchedItemStore->getWatchedItemsForUser(
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 );
460 ||
$title->getNamespace() < 0
467 ||
$title->getNamespace() != $namespace
468 ||
$title->getDBkey() != $dbKey
470 $this->badItems[] = [
$title, $namespace, $dbKey ];
480 if ( $this->badItems === [] ) {
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." );
496 $this->watchedItemStore->removeWatch( $user, Title::makeTitle( (
int)$namespace, $dbKey ) );
499 $user->addWatch(
$title );
514 return $this->watchedItemStore->addWatchBatchForUser(
515 $this->
getUser(), $this->getExpandedTargets( $targets )
516 ) && $this->runWatchUnwatchCompleteHook(
'Watch', $targets );
532 return $this->watchedItemStore->removeWatchBatchForUser(
533 $this->
getUser(), $this->getExpandedTargets( $targets )
534 ) && $this->runWatchUnwatchCompleteHook(
'Unwatch', $targets );
546 foreach ( $targets as $target ) {
548 Title::newFromTitleValue( $target ) :
549 Title::newFromText( $target );
550 $page = WikiPage::factory(
$title );
552 if ( $action ===
'Watch' ) {
553 $this->getHookRunner()->onWatchArticleComplete( $user, $page );
555 $this->getHookRunner()->onUnwatchArticleComplete( $user, $page );
566 $expandedTargets = [];
567 $services = MediaWikiServices::getInstance();
568 foreach ( $targets as $target ) {
571 $target = $this->titleParser->parseTitle( $target,
NS_MAIN );
578 $ns = $target->getNamespace();
579 $dbKey = $target->getDBkey();
581 new TitleValue( $services->getNamespaceInfo()->getSubject( $ns ), $dbKey );
583 new TitleValue( $services->getNamespaceInfo()->getTalk( $ns ), $dbKey );
585 return $expandedTargets;
591 foreach ( $data as $titles ) {
592 $this->unwatchTitles( $titles );
593 $removed = array_merge( $removed, $titles );
596 if ( count( $removed ) > 0 ) {
597 $this->successMessage = $this->msg(
'watchlistedit-normal-done'
598 )->numParams( count( $removed ) )->parse();
599 $this->showTitles( $removed, $this->successMessage );
618 $watchlistInfo = $this->getWatchlistInfo();
619 $this->getHookRunner()->onWatchlistEditorBeforeFormRender( $watchlistInfo );
621 foreach ( $watchlistInfo as $namespace => $pages ) {
623 foreach ( $pages as $dbkey => $expiryDaysText ) {
624 $title = Title::makeTitleSafe( $namespace, $dbkey );
626 if ( $this->checkTitle(
$title, $namespace, $dbkey ) ) {
627 $text = $this->buildRemoveLine(
$title, $expiryDaysText );
628 $options[$text] =
$title->getPrefixedText();
634 if ( count( $options ) > 0 ) {
635 $fields[
'TitlesNs' . $namespace] = [
636 'class' => EditWatchlistCheckboxSeriesField::class,
637 'options' => $options,
638 'section' =>
"ns$namespace",
642 $this->cleanupWatchlist();
644 if ( count( $fields ) > 1 && $count > 30 ) {
647 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
649 foreach ( $fields as $data ) {
650 # strip out the 'ns' prefix from the section name:
651 $ns = substr( $data[
'section'], 2 );
654 ? $this->msg(
'blanknamespace' )->escaped()
655 : htmlspecialchars( $contLang->getFormattedNsText( $ns ) );
656 $this->toc .=
Linker::tocLine(
"editwatchlist-{$data['section']}", $nsText,
666 $context->setTitle( $this->getPageTitle() );
668 $form->setSubmitTextMsg(
'watchlistedit-normal-submit' );
669 $form->setSubmitDestructive();
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' ] );
689 $linkRenderer = $this->getLinkRenderer();
690 $link = $linkRenderer->makeLink(
$title );
693 $tools[
'talk'] = $linkRenderer->makeLink(
695 $this->msg(
'talkpagelinktext' )->text()
699 $tools[
'history'] = $linkRenderer->makeKnownLink(
701 $this->msg(
'history_small' )->text(),
703 [
'action' =>
'history' ]
708 $tools[
'contributions'] = $linkRenderer->makeKnownLink(
710 $this->msg(
'contribslink' )->text()
714 $this->getHookRunner()->onWatchlistEditorBuildRemoveLine(
715 $tools,
$title,
$title->isRedirect(), $this->getSkin(), $link );
717 if (
$title->isRedirect() ) {
719 $link =
'<span class="watchlistredir">' . $link .
'</span>';
722 $watchlistExpiringMessage =
'';
723 if ( $this->isWatchlistExpiryEnabled && $expiryDaysText ) {
726 [
'class' =>
'watchlistexpiry-msg' ],
732 $this->msg(
'parentheses' )->rawParams( $this->getLanguage()->pipeList( $tools ) )->escaped() .
733 $watchlistExpiringMessage;
742 $titles = implode(
"\n", $this->getWatchlist() );
745 'type' =>
'textarea',
746 'label-message' =>
'watchlistedit-raw-titles',
747 'default' => $titles,
751 $context->setTitle( $this->getPageTitle(
'raw' ) );
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' ] );
770 $context->setTitle( $this->getPageTitle(
'clear' ) );
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();
791 public static function getMode( $request, $par ) {
792 $mode = strtolower( $request->getVal(
'action', $par ??
'' ) );
796 case self::EDIT_CLEAR:
797 return self::EDIT_CLEAR;
800 return self::EDIT_RAW;
802 case self::EDIT_NORMAL:
803 return self::EDIT_NORMAL;
823 if ( !$linkRenderer ) {
824 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
829 'view' => [
'Watchlist', false ],
830 'edit' => [
'EditWatchlist', false ],
831 'raw' => [
'EditWatchlist',
'raw' ],
832 'clear' => [
'EditWatchlist',
'clear' ],
835 foreach ( $modes as $mode => $arr ) {
837 $tools[] = $linkRenderer->makeKnownLink(
839 wfMessage(
"watchlisttools-{$mode}" )->text()
843 return Html::rawElement(
845 [
'class' =>
'mw-watchlist-toollinks' ],
846 wfMessage(
'parentheses' )->rawParams(
$lang->pipeList( $tools ) )->escaped()
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.
if(ini_get('mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
An IContextSource implementation which will inherit context from another source but allow individual ...
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Class representing a list of titles The execute() method checks them all for existence and adds them ...
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
static tocIndent()
Add another level to the Table of Contents.
static tocList( $toc, Language $lang=null)
Wraps the TOC in a div with ARIA navigation role and provides the hide/collapse JavaScript.
static tocLineEnd()
End a Table Of Contents line.
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.
isWatchable()
Can this title be added to a user's watchlist?
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,...
A title parser service for MediaWiki.
if(!isset( $args[0])) $lang