62 parent::__construct(
'EditWatchlist',
'editmywatchlist' );
70 if ( !$this->titleParser ) {
71 $this->titleParser = MediaWikiServices::getInstance()->getTitleParser();
88 # Anons don't get a watchlist
98 $out->addModuleStyles(
'mediawiki.special' );
100 # B/C: $mode used to be waaay down the parameter list, and the first parameter
102 if ( $mode instanceof
User ) {
103 $args = func_get_args();
104 if ( count(
$args ) >= 4 ) {
112 $out->setPageTitle( $this->
msg(
'watchlistedit-raw-title' ) );
114 if ( $form->show() ) {
115 $out->addHTML( $this->successMessage );
120 $out->setPageTitle( $this->
msg(
'watchlistedit-clear-title' ) );
122 if ( $form->show() ) {
123 $out->addHTML( $this->successMessage );
155 $out->setPageTitle( $this->
msg(
'watchlistedit-normal-title' ) );
157 if ( $form->show() ) {
158 $out->addHTML( $this->successMessage );
160 } elseif ( $this->toc !==
false ) {
161 $out->prependHTML( $this->toc );
162 $out->addModuleStyles(
'mediawiki.toc.styles' );
189 $list = explode(
"\n", trim( $list ) );
190 if ( !is_array( $list ) ) {
196 foreach ( $list as $text ) {
197 $text = trim( $text );
198 if ( strlen( $text ) > 0 ) {
199 $title = Title::newFromText( $text );
206 MediaWikiServices::getInstance()->getGenderCache()->doTitlesArray( $titles );
210 foreach ( $titles as
$title ) {
211 $list[] =
$title->getPrefixedText();
214 return array_unique( $list );
221 if ( count( $wanted ) > 0 ) {
222 $toWatch = array_diff( $wanted, $current );
223 $toUnwatch = array_diff( $current, $wanted );
226 $this->
getUser()->invalidateCache();
228 if ( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ) {
229 $this->successMessage = $this->
msg(
'watchlistedit-raw-done' )->parse();
234 if ( count( $toWatch ) > 0 ) {
235 $this->successMessage .=
' ' . $this->
msg(
'watchlistedit-raw-added' )
236 ->numParams( count( $toWatch ) )->parse();
237 $this->
showTitles( $toWatch, $this->successMessage );
240 if ( count( $toUnwatch ) > 0 ) {
241 $this->successMessage .=
' ' . $this->
msg(
'watchlistedit-raw-removed' )
242 ->numParams( count( $toUnwatch ) )->parse();
243 $this->
showTitles( $toUnwatch, $this->successMessage );
247 if ( count( $current ) === 0 ) {
252 $this->
showTitles( $current, $this->successMessage );
261 $this->
showTitles( $current, $this->successMessage );
270 $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
271 if ( $watchedItemStore->clearUserWatchedItems( $this->getUser() ) ) {
272 $this->successMessage = $this->
msg(
'watchlistedit-' . $messageFor .
'-done' )->parse();
273 $this->successMessage .=
' ' . $this->
msg(
'watchlistedit-' . $messageFor .
'-removed' )
274 ->numParams( count( $current ) )->parse();
275 $this->
getUser()->invalidateCache();
277 $watchedItemStore->clearUserWatchedItemsUsingJobQueue( $this->
getUser() );
278 $this->successMessage = $this->
msg(
'watchlistedit-clear-jobqueue' )->parse();
292 $talk = $this->
msg(
'talkpagelinktext' )->text();
295 if ( count( $titles ) >= 100 ) {
296 $output = $this->
msg(
'watchlistedit-too-many' )->parse();
299 foreach ( $titles as
$title ) {
306 $batch->addObj(
$title->getTalkPage() );
316 foreach ( $titles as
$title ) {
324 $this->
msg(
'parentheses' )->rawParams(
331 $output .=
"</ul>\n";
343 $watchedItems = MediaWikiServices::getInstance()->getWatchedItemStore()->getWatchedItemsForUser(
345 [
'forWrite' => $this->
getRequest()->wasPosted() ]
348 if ( $watchedItems ) {
351 foreach ( $watchedItems as $watchedItem ) {
352 $namespace = $watchedItem->getLinkTarget()->getNamespace();
353 $dbKey = $watchedItem->getLinkTarget()->getDBkey();
354 $title = Title::makeTitleSafe( $namespace, $dbKey );
363 MediaWikiServices::getInstance()->getGenderCache()->doTitlesArray( $titles );
365 foreach ( $titles as
$title ) {
366 $list[] =
$title->getPrefixedText();
383 $services = MediaWikiServices::getInstance();
385 $watchedItems = $services->getWatchedItemStore()
390 foreach ( $watchedItems as $watchedItem ) {
391 $namespace = $watchedItem->getLinkTarget()->getNamespace();
392 $dbKey = $watchedItem->getLinkTarget()->getDBkey();
393 $lb->add( $namespace, $dbKey );
394 if ( !$services->getNamespaceInfo()->isTalk( $namespace ) ) {
395 $titles[$namespace][$dbKey] = 1;
415 ||
$title->getNamespace() < 0
422 ||
$title->getNamespace() != $namespace
423 ||
$title->getDBkey() != $dbKey
425 $this->badItems[] = [
$title, $namespace, $dbKey ];
435 if ( $this->badItems === [] ) {
441 DeferredUpdates::addCallableUpdate(
function () use ( $user,
$badItems ) {
442 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
444 list(
$title, $namespace, $dbKey ) = $row;
445 $action =
$title ?
'cleaning up' :
'deleting';
446 wfDebug(
"User {$user->getName()} has broken watchlist item " .
447 "ns($namespace):$dbKey, $action.\n" );
449 $store->removeWatch( $user,
new TitleValue( (
int)$namespace, $dbKey ) );
452 $user->addWatch(
$title );
467 return MediaWikiServices::getInstance()->getWatchedItemStore()
485 return MediaWikiServices::getInstance()->getWatchedItemStore()
499 foreach ( $targets as $target ) {
501 Title::newFromTitleValue( $target ) :
502 Title::newFromText( $target );
503 $page = WikiPage::factory(
$title );
504 Hooks::run( $action .
'ArticleComplete', [ $this->
getUser(), &$page ] );
514 $expandedTargets = [];
515 $services = MediaWikiServices::getInstance();
516 foreach ( $targets as $target ) {
519 $target = $this->titleParser->parseTitle( $target,
NS_MAIN );
526 $ns = $target->getNamespace();
527 $dbKey = $target->getDBkey();
529 new TitleValue( $services->getNamespaceInfo()->getSubject( $ns ), $dbKey );
531 new TitleValue( $services->getNamespaceInfo()->getTalk( $ns ), $dbKey );
533 return $expandedTargets;
539 foreach ( $data as $titles ) {
541 $removed = array_merge( $removed, $titles );
544 if ( count( $removed ) > 0 ) {
545 $this->successMessage = $this->
msg(
'watchlistedit-normal-done'
546 )->numParams( count( $removed ) )->parse();
547 $this->
showTitles( $removed, $this->successMessage );
568 'WatchlistEditorBeforeFormRender',
572 foreach ( $watchlistInfo as $namespace => $pages ) {
575 foreach ( array_keys( $pages ) as $dbkey ) {
576 $title = Title::makeTitleSafe( $namespace, $dbkey );
580 $options[$text] =
$title->getPrefixedText();
586 if ( count( $options ) > 0 ) {
587 $fields[
'TitlesNs' . $namespace] = [
588 'class' => EditWatchlistCheckboxSeriesField::class,
589 'options' => $options,
590 'section' =>
"ns$namespace",
596 if ( count( $fields ) > 1 && $count > 30 ) {
599 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
601 foreach ( $fields as $data ) {
602 # strip out the 'ns' prefix from the section name:
603 $ns = substr( $data[
'section'], 2 );
606 ? $this->
msg(
'blanknamespace' )->escaped()
607 : htmlspecialchars( $contLang->getFormattedNsText( $ns ) );
608 $this->toc .=
Linker::tocLine(
"editwatchlist-{$data['section']}", $nsText,
620 $form->setSubmitTextMsg(
'watchlistedit-normal-submit' );
621 $form->setSubmitDestructive();
623 # 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
624 $form->setSubmitTooltip(
'watchlistedit-normal-submit' );
625 $form->setWrapperLegendMsg(
'watchlistedit-normal-legend' );
626 $form->addHeaderText( $this->
msg(
'watchlistedit-normal-explain' )->parse() );
627 $form->setSubmitCallback( [ $this,
'submitNormal' ] );
645 $this->msg(
'talkpagelinktext' )->text()
651 $this->
msg(
'history_small' )->text(),
653 [
'action' =>
'history' ]
660 $this->msg(
'contribslink' )->text()
665 'WatchlistEditorBuildRemoveLine',
666 [ &$tools,
$title,
$title->isRedirect(), $this->getSkin(), &$link ]
669 if (
$title->isRedirect() ) {
671 $link =
'<span class="watchlistredir">' . $link .
'</span>';
675 $this->
msg(
'parentheses' )->rawParams( $this->
getLanguage()->pipeList( $tools ) )->escaped();
687 'type' =>
'textarea',
688 'label-message' =>
'watchlistedit-raw-titles',
689 'default' => $titles,
695 $form->setSubmitTextMsg(
'watchlistedit-raw-submit' );
696 # Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit'
697 $form->setSubmitTooltip(
'watchlistedit-raw-submit' );
698 $form->setWrapperLegendMsg(
'watchlistedit-raw-legend' );
699 $form->addHeaderText( $this->
msg(
'watchlistedit-raw-explain' )->parse() );
700 $form->setSubmitCallback( [ $this,
'submitRaw' ] );
714 $form->setSubmitTextMsg(
'watchlistedit-clear-submit' );
715 # Used message keys: 'accesskey-watchlistedit-clear-submit', 'tooltip-watchlistedit-clear-submit'
716 $form->setSubmitTooltip(
'watchlistedit-clear-submit' );
717 $form->setWrapperLegendMsg(
'watchlistedit-clear-legend' );
718 $form->addHeaderText( $this->
msg(
'watchlistedit-clear-explain' )->parse() );
719 $form->setSubmitCallback( [ $this,
'submitClear' ] );
720 $form->setSubmitDestructive();
733 public static function getMode( $request, $par ) {
734 $mode = strtolower( $request->getVal(
'action', $par ) );
766 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
771 'view' => [
'Watchlist', false ],
772 'edit' => [
'EditWatchlist', false ],
773 'raw' => [
'EditWatchlist',
'raw' ],
774 'clear' => [
'EditWatchlist',
'clear' ],
777 foreach ( $modes as $mode => $arr ) {
781 wfMessage(
"watchlisttools-{$mode}" )->text()
785 return Html::rawElement(
787 [
'class' =>
'mw-watchlist-toollinks' ],
788 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.
An IContextSource implementation which will inherit context from another source but allow individual ...
Internationalisation code.
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 table 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.
extractTitles( $list)
Extract a list of titles from a blob of text, returning (prefixed) strings; unwatchable titles are ig...
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.
runWatchUnwatchCompleteHook( $action, $targets)
getSubpagesForPrefixSearch()
Return an array of subpages that this special page will accept.
clearUserWatchedItems( $current, $messageFor)
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.
buildRemoveLine( $title)
Build the label for a checkbox, with a link to the title, and various additional bits.
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.
watchTitles(array $targets)
Add a list of targets to a user's watchlist.
static getMode( $request, $par)
Determine whether we are editing the watchlist, and if so, what kind of editing operation.
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,...
getContext()
Gets the context this SpecialPage is executed in.
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.
getPageTitle( $subpage=false)
Get a self-referential title object.
getLanguage()
Shortcut to get user's language.
MediaWiki Linker LinkRenderer null $linkRenderer
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