123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890 |
- <?php
- /**
- * Implements Special:Watchlist
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
- use MediaWiki\MediaWikiServices;
- use Wikimedia\Rdbms\IResultWrapper;
- use Wikimedia\Rdbms\IDatabase;
- /**
- * A special page that lists last changes made to the wiki,
- * limited to user-defined list of titles.
- *
- * @ingroup SpecialPage
- */
- class SpecialWatchlist extends ChangesListSpecialPage {
- protected static $savedQueriesPreferenceName = 'rcfilters-wl-saved-queries';
- protected static $daysPreferenceName = 'watchlistdays';
- protected static $limitPreferenceName = 'wllimit';
- protected static $collapsedPreferenceName = 'rcfilters-wl-collapsed';
- /** @var float|int */
- private $maxDays;
- /** WatchedItemStore */
- private $watchStore;
- public function __construct( $page = 'Watchlist', $restriction = 'viewmywatchlist' ) {
- parent::__construct( $page, $restriction );
- $this->maxDays = $this->getConfig()->get( 'RCMaxAge' ) / ( 3600 * 24 );
- $this->watchStore = MediaWikiServices::getInstance()->getWatchedItemStore();
- }
- public function doesWrites() {
- return true;
- }
- /**
- * Main execution point
- *
- * @param string $subpage
- */
- function execute( $subpage ) {
- // Anons don't get a watchlist
- $this->requireLogin( 'watchlistanontext' );
- $output = $this->getOutput();
- $request = $this->getRequest();
- $this->addHelpLink( 'Help:Watching pages' );
- $output->addModuleStyles( [ 'mediawiki.special' ] );
- $output->addModules( [
- 'mediawiki.special.recentchanges',
- 'mediawiki.special.watchlist',
- ] );
- $mode = SpecialEditWatchlist::getMode( $request, $subpage );
- if ( $mode !== false ) {
- if ( $mode === SpecialEditWatchlist::EDIT_RAW ) {
- $title = SpecialPage::getTitleFor( 'EditWatchlist', 'raw' );
- } elseif ( $mode === SpecialEditWatchlist::EDIT_CLEAR ) {
- $title = SpecialPage::getTitleFor( 'EditWatchlist', 'clear' );
- } else {
- $title = SpecialPage::getTitleFor( 'EditWatchlist' );
- }
- $output->redirect( $title->getLocalURL() );
- return;
- }
- $this->checkPermissions();
- $user = $this->getUser();
- $opts = $this->getOptions();
- $config = $this->getConfig();
- if ( ( $config->get( 'EnotifWatchlist' ) || $config->get( 'ShowUpdatedMarker' ) )
- && $request->getVal( 'reset' )
- && $request->wasPosted()
- && $user->matchEditToken( $request->getVal( 'token' ) )
- ) {
- $user->clearAllNotifications();
- $output->redirect( $this->getPageTitle()->getFullURL( $opts->getChangedValues() ) );
- return;
- }
- parent::execute( $subpage );
- if ( $this->isStructuredFilterUiEnabled() ) {
- $output->addModuleStyles( [ 'mediawiki.rcfilters.highlightCircles.seenunseen.styles' ] );
- }
- }
- /**
- * @see ChangesListSpecialPage::checkStructuredFilterUiEnabled
- */
- public static function checkStructuredFilterUiEnabled( $user ) {
- if ( $user instanceof Config ) {
- wfDeprecated( __METHOD__ . ' with Config argument', '1.34' );
- $user = func_get_arg( 1 );
- }
- return !$user->getOption( 'wlenhancedfilters-disable' );
- }
- /**
- * Return an array of subpages that this special page will accept.
- *
- * @see also SpecialEditWatchlist::getSubpagesForPrefixSearch
- * @return string[] subpages
- */
- public function getSubpagesForPrefixSearch() {
- return [
- 'clear',
- 'edit',
- 'raw',
- ];
- }
- /**
- * @inheritDoc
- */
- protected function transformFilterDefinition( array $filterDefinition ) {
- if ( isset( $filterDefinition['showHideSuffix'] ) ) {
- $filterDefinition['showHide'] = 'wl' . $filterDefinition['showHideSuffix'];
- }
- return $filterDefinition;
- }
- /**
- * @inheritDoc
- * @suppress PhanUndeclaredMethod
- */
- protected function registerFilters() {
- parent::registerFilters();
- // legacy 'extended' filter
- $this->registerFilterGroup( new ChangesListBooleanFilterGroup( [
- 'name' => 'extended-group',
- 'filters' => [
- [
- 'name' => 'extended',
- 'isReplacedInStructuredUi' => true,
- 'activeValue' => false,
- 'default' => $this->getUser()->getBoolOption( 'extendwatchlist' ),
- 'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables,
- &$fields, &$conds, &$query_options, &$join_conds ) {
- $nonRevisionTypes = [ RC_LOG ];
- Hooks::run( 'SpecialWatchlistGetNonRevisionTypes', [ &$nonRevisionTypes ] );
- if ( $nonRevisionTypes ) {
- $conds[] = $dbr->makeList(
- [
- 'rc_this_oldid=page_latest',
- 'rc_type' => $nonRevisionTypes,
- ],
- LIST_OR
- );
- }
- },
- ]
- ],
- ] ) );
- if ( $this->isStructuredFilterUiEnabled() ) {
- $this->getFilterGroup( 'lastRevision' )
- ->getFilter( 'hidepreviousrevisions' )
- ->setDefault( !$this->getUser()->getBoolOption( 'extendwatchlist' ) );
- }
- $this->registerFilterGroup( new ChangesListStringOptionsFilterGroup( [
- 'name' => 'watchlistactivity',
- 'title' => 'rcfilters-filtergroup-watchlistactivity',
- 'class' => ChangesListStringOptionsFilterGroup::class,
- 'priority' => 3,
- 'isFullCoverage' => true,
- 'filters' => [
- [
- 'name' => 'unseen',
- 'label' => 'rcfilters-filter-watchlistactivity-unseen-label',
- 'description' => 'rcfilters-filter-watchlistactivity-unseen-description',
- 'cssClassSuffix' => 'watchedunseen',
- 'isRowApplicableCallable' => function ( $ctx, RecentChange $rc ) {
- return !$this->isChangeEffectivelySeen( $rc );
- },
- ],
- [
- 'name' => 'seen',
- 'label' => 'rcfilters-filter-watchlistactivity-seen-label',
- 'description' => 'rcfilters-filter-watchlistactivity-seen-description',
- 'cssClassSuffix' => 'watchedseen',
- 'isRowApplicableCallable' => function ( $ctx, RecentChange $rc ) {
- return $this->isChangeEffectivelySeen( $rc );
- }
- ],
- ],
- 'default' => ChangesListStringOptionsFilterGroup::NONE,
- 'queryCallable' => function (
- $specialPageClassName,
- $context,
- IDatabase $dbr,
- &$tables,
- &$fields,
- &$conds,
- &$query_options,
- &$join_conds,
- $selectedValues
- ) {
- if ( $selectedValues === [ 'seen' ] ) {
- $conds[] = $dbr->makeList( [
- 'wl_notificationtimestamp IS NULL',
- 'rc_timestamp < wl_notificationtimestamp'
- ], LIST_OR );
- } elseif ( $selectedValues === [ 'unseen' ] ) {
- $conds[] = $dbr->makeList( [
- 'wl_notificationtimestamp IS NOT NULL',
- 'rc_timestamp >= wl_notificationtimestamp'
- ], LIST_AND );
- }
- }
- ] ) );
- $user = $this->getUser();
- $significance = $this->getFilterGroup( 'significance' );
- $hideMinor = $significance->getFilter( 'hideminor' );
- $hideMinor->setDefault( $user->getBoolOption( 'watchlisthideminor' ) );
- $automated = $this->getFilterGroup( 'automated' );
- $hideBots = $automated->getFilter( 'hidebots' );
- $hideBots->setDefault( $user->getBoolOption( 'watchlisthidebots' ) );
- $registration = $this->getFilterGroup( 'registration' );
- $hideAnons = $registration->getFilter( 'hideanons' );
- $hideAnons->setDefault( $user->getBoolOption( 'watchlisthideanons' ) );
- $hideLiu = $registration->getFilter( 'hideliu' );
- $hideLiu->setDefault( $user->getBoolOption( 'watchlisthideliu' ) );
- // Selecting both hideanons and hideliu on watchlist preferances
- // gives mutually exclusive filters, so those are ignored
- if ( $user->getBoolOption( 'watchlisthideanons' ) &&
- !$user->getBoolOption( 'watchlisthideliu' )
- ) {
- $this->getFilterGroup( 'userExpLevel' )
- ->setDefault( 'registered' );
- }
- if ( $user->getBoolOption( 'watchlisthideliu' ) &&
- !$user->getBoolOption( 'watchlisthideanons' )
- ) {
- $this->getFilterGroup( 'userExpLevel' )
- ->setDefault( 'unregistered' );
- }
- $reviewStatus = $this->getFilterGroup( 'reviewStatus' );
- if ( $reviewStatus !== null ) {
- // Conditional on feature being available and rights
- if ( $user->getBoolOption( 'watchlisthidepatrolled' ) ) {
- $reviewStatus->setDefault( 'unpatrolled' );
- $legacyReviewStatus = $this->getFilterGroup( 'legacyReviewStatus' );
- $legacyHidePatrolled = $legacyReviewStatus->getFilter( 'hidepatrolled' );
- $legacyHidePatrolled->setDefault( true );
- }
- }
- $authorship = $this->getFilterGroup( 'authorship' );
- $hideMyself = $authorship->getFilter( 'hidemyself' );
- $hideMyself->setDefault( $user->getBoolOption( 'watchlisthideown' ) );
- $changeType = $this->getFilterGroup( 'changeType' );
- $hideCategorization = $changeType->getFilter( 'hidecategorization' );
- if ( $hideCategorization !== null ) {
- // Conditional on feature being available
- $hideCategorization->setDefault( $user->getBoolOption( 'watchlisthidecategorization' ) );
- }
- }
- /**
- * Fetch values for a FormOptions object from the WebRequest associated with this instance.
- *
- * Maps old pre-1.23 request parameters Watchlist used to use (different from Recentchanges' ones)
- * to the current ones.
- *
- * @param FormOptions $opts
- * @return FormOptions
- */
- protected function fetchOptionsFromRequest( $opts ) {
- static $compatibilityMap = [
- 'hideMinor' => 'hideminor',
- 'hideBots' => 'hidebots',
- 'hideAnons' => 'hideanons',
- 'hideLiu' => 'hideliu',
- 'hidePatrolled' => 'hidepatrolled',
- 'hideOwn' => 'hidemyself',
- ];
- $params = $this->getRequest()->getValues();
- foreach ( $compatibilityMap as $from => $to ) {
- if ( isset( $params[$from] ) ) {
- $params[$to] = $params[$from];
- unset( $params[$from] );
- }
- }
- if ( $this->getRequest()->getVal( 'action' ) == 'submit' ) {
- $allBooleansFalse = [];
- // If the user submitted the form, start with a baseline of "all
- // booleans are false", then change the ones they checked. This
- // means we ignore the defaults.
- // This is how we handle the fact that HTML forms don't submit
- // unchecked boxes.
- foreach ( $this->getLegacyShowHideFilters() as $filter ) {
- $allBooleansFalse[ $filter->getName() ] = false;
- }
- $params = $params + $allBooleansFalse;
- }
- // Not the prettiest way to achieve this… FormOptions internally depends on data sanitization
- // methods defined on WebRequest and removing this dependency would cause some code duplication.
- $request = new DerivativeRequest( $this->getRequest(), $params );
- $opts->fetchValuesFromRequest( $request );
- return $opts;
- }
- /**
- * @inheritDoc
- */
- protected function doMainQuery( $tables, $fields, $conds, $query_options,
- $join_conds, FormOptions $opts
- ) {
- $dbr = $this->getDB();
- $user = $this->getUser();
- $rcQuery = RecentChange::getQueryInfo();
- $tables = array_merge( $tables, $rcQuery['tables'], [ 'watchlist' ] );
- $fields = array_merge( $rcQuery['fields'], $fields );
- $join_conds = array_merge(
- [
- 'watchlist' => [
- 'JOIN',
- [
- 'wl_user' => $user->getId(),
- 'wl_namespace=rc_namespace',
- 'wl_title=rc_title'
- ],
- ],
- ],
- $rcQuery['joins'],
- $join_conds
- );
- $tables[] = 'page';
- $fields[] = 'page_latest';
- $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
- $fields[] = 'wl_notificationtimestamp';
- // Log entries with DELETED_ACTION must not show up unless the user has
- // the necessary rights.
- $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
- if ( !$permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
- $bitmask = LogPage::DELETED_ACTION;
- } elseif ( !$permissionManager->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) ) {
- $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
- } else {
- $bitmask = 0;
- }
- if ( $bitmask ) {
- $conds[] = $dbr->makeList( [
- 'rc_type != ' . RC_LOG,
- $dbr->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
- ], LIST_OR );
- }
- $tagFilter = $opts['tagfilter'] ? explode( '|', $opts['tagfilter'] ) : [];
- ChangeTags::modifyDisplayQuery(
- $tables,
- $fields,
- $conds,
- $join_conds,
- $query_options,
- $tagFilter
- );
- $this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts );
- if ( $this->areFiltersInConflict() ) {
- return false;
- }
- $orderByAndLimit = [
- 'ORDER BY' => 'rc_timestamp DESC',
- 'LIMIT' => $opts['limit']
- ];
- if ( in_array( 'DISTINCT', $query_options ) ) {
- // ChangeTags::modifyDisplayQuery() adds DISTINCT when filtering on multiple tags.
- // In order to prevent DISTINCT from causing query performance problems,
- // we have to GROUP BY the primary key. This in turn requires us to add
- // the primary key to the end of the ORDER BY, and the old ORDER BY to the
- // start of the GROUP BY
- $orderByAndLimit['ORDER BY'] = 'rc_timestamp DESC, rc_id DESC';
- $orderByAndLimit['GROUP BY'] = 'rc_timestamp, rc_id';
- }
- // array_merge() is used intentionally here so that hooks can, should
- // they so desire, override the ORDER BY / LIMIT condition(s)
- $query_options = array_merge( $orderByAndLimit, $query_options );
- return $dbr->select(
- $tables,
- $fields,
- $conds,
- __METHOD__,
- $query_options,
- $join_conds
- );
- }
- /**
- * Return a IDatabase object for reading
- *
- * @return IDatabase
- */
- protected function getDB() {
- return wfGetDB( DB_REPLICA, 'watchlist' );
- }
- /**
- * Output feed links.
- */
- public function outputFeedLinks() {
- $user = $this->getUser();
- $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
- if ( $wlToken ) {
- $this->addFeedLinks( [
- 'action' => 'feedwatchlist',
- 'allrev' => 1,
- 'wlowner' => $user->getName(),
- 'wltoken' => $wlToken,
- ] );
- }
- }
- /**
- * Build and output the actual changes list.
- *
- * @param IResultWrapper $rows Database rows
- * @param FormOptions $opts
- */
- public function outputChangesList( $rows, $opts ) {
- $dbr = $this->getDB();
- $user = $this->getUser();
- $output = $this->getOutput();
- $services = MediaWikiServices::getInstance();
- # Show a message about replica DB lag, if applicable
- $lag = $dbr->getSessionLagStatus()['lag'];
- if ( $lag > 0 ) {
- $output->showLagWarning( $lag );
- }
- # If no rows to display, show message before try to render the list
- if ( $rows->numRows() == 0 ) {
- $output->wrapWikiMsg(
- "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
- );
- return;
- }
- $dbr->dataSeek( $rows, 0 );
- $list = ChangesList::newFromContext( $this->getContext(), $this->filterGroups );
- $list->setWatchlistDivs();
- $list->initChangesListRows( $rows );
- if ( $user->getOption( 'watchlistunwatchlinks' ) ) {
- $list->setChangeLinePrefixer( function ( RecentChange $rc, ChangesList $cl, $grouped ) {
- // Don't show unwatch link if the line is a grouped log entry using EnhancedChangesList,
- // since EnhancedChangesList groups log entries by performer rather than by target article
- if ( $rc->mAttribs['rc_type'] == RC_LOG && $cl instanceof EnhancedChangesList &&
- $grouped ) {
- return '';
- } else {
- return $this->getLinkRenderer()
- ->makeKnownLink( $rc->getTitle(),
- $this->msg( 'watchlist-unwatch' )->text(), [
- 'class' => 'mw-unwatch-link',
- 'title' => $this->msg( 'tooltip-ca-unwatch' )->text()
- ], [ 'action' => 'unwatch' ] ) . "\u{00A0}";
- }
- } );
- }
- $dbr->dataSeek( $rows, 0 );
- if ( $this->getConfig()->get( 'RCShowWatchingUsers' )
- && $user->getOption( 'shownumberswatching' )
- ) {
- $watchedItemStore = $services->getWatchedItemStore();
- }
- $s = $list->beginRecentChangesList();
- if ( $this->isStructuredFilterUiEnabled() ) {
- $s .= $this->makeLegend();
- }
- $userShowHiddenCats = $this->getUser()->getBoolOption( 'showhiddencats' );
- $counter = 1;
- foreach ( $rows as $obj ) {
- # Make RC entry
- $rc = RecentChange::newFromRow( $obj );
- # Skip CatWatch entries for hidden cats based on user preference
- if (
- $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE &&
- !$userShowHiddenCats &&
- $rc->getParam( 'hidden-cat' )
- ) {
- continue;
- }
- $rc->counter = $counter++;
- if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) ) {
- $unseen = !$this->isChangeEffectivelySeen( $rc );
- } else {
- $unseen = false;
- }
- if ( isset( $watchedItemStore ) ) {
- $rcTitleValue = new TitleValue( (int)$obj->rc_namespace, $obj->rc_title );
- $rc->numberofWatchingusers = $watchedItemStore->countWatchers( $rcTitleValue );
- } else {
- $rc->numberofWatchingusers = 0;
- }
- // XXX: this treats pages with no unseen changes as "not on the watchlist" since
- // everything is on the watchlist and it is an easy way to make pages with unseen
- // changes appear bold. @TODO: clean this up.
- $changeLine = $list->recentChangesLine( $rc, $unseen, $counter );
- if ( $changeLine !== false ) {
- $s .= $changeLine;
- }
- }
- $s .= $list->endRecentChangesList();
- $output->addHTML( $s );
- }
- /**
- * Set the text to be displayed above the changes
- *
- * @param FormOptions $opts
- * @param int $numRows Number of rows in the result to show after this header
- */
- public function doHeader( $opts, $numRows ) {
- $user = $this->getUser();
- $out = $this->getOutput();
- $out->addSubtitle(
- $this->msg( 'watchlistfor2', $user->getName() )
- ->rawParams( SpecialEditWatchlist::buildTools(
- $this->getLanguage(),
- $this->getLinkRenderer()
- ) )
- );
- $this->setTopText( $opts );
- $form = '';
- $form .= Xml::openElement( 'form', [
- 'method' => 'get',
- 'action' => wfScript(),
- 'id' => 'mw-watchlist-form'
- ] );
- $form .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
- $form .= Xml::openElement(
- 'fieldset',
- [ 'id' => 'mw-watchlist-options', 'class' => 'cloptions' ]
- );
- $form .= Xml::element(
- 'legend', null, $this->msg( 'watchlist-options' )->text()
- );
- if ( !$this->isStructuredFilterUiEnabled() ) {
- $form .= $this->makeLegend();
- }
- $lang = $this->getLanguage();
- $timestamp = wfTimestampNow();
- $now = $lang->userTimeAndDate( $timestamp, $user );
- $wlInfo = Html::rawElement(
- 'span',
- [
- 'class' => 'wlinfo',
- 'data-params' => json_encode( [ 'from' => $timestamp, 'fromFormatted' => $now ] ),
- ],
- $this->msg( 'wlnote' )->numParams( $numRows, round( $opts['days'] * 24 ) )->params(
- $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user )
- )->parse()
- ) . "<br />\n";
- $nondefaults = $opts->getChangedValues();
- $cutofflinks = Html::rawElement(
- 'span',
- [ 'class' => 'cldays cloption' ],
- $this->msg( 'wlshowtime' ) . ' ' . $this->cutoffselector( $opts )
- );
- # Spit out some control panel links
- $links = [];
- $namesOfDisplayedFilters = [];
- foreach ( $this->getLegacyShowHideFilters() as $filterName => $filter ) {
- $namesOfDisplayedFilters[] = $filterName;
- $links[] = $this->showHideCheck(
- $nondefaults,
- $filter->getShowHide(),
- $filterName,
- $opts[ $filterName ],
- $filter->isFeatureAvailableOnStructuredUi( $this )
- );
- }
- $hiddenFields = $nondefaults;
- $hiddenFields['action'] = 'submit';
- unset( $hiddenFields['namespace'] );
- unset( $hiddenFields['invert'] );
- unset( $hiddenFields['associated'] );
- unset( $hiddenFields['days'] );
- foreach ( $namesOfDisplayedFilters as $filterName ) {
- unset( $hiddenFields[$filterName] );
- }
- # Namespace filter and put the whole form together.
- $form .= $wlInfo;
- $form .= $cutofflinks;
- $form .= Html::rawElement(
- 'span',
- [ 'class' => 'clshowhide' ],
- $this->msg( 'watchlist-hide' ) .
- $this->msg( 'colon-separator' )->escaped() .
- implode( ' ', $links )
- );
- $form .= "\n<br />\n";
- $namespaceForm = Html::namespaceSelector(
- [
- 'selected' => $opts['namespace'],
- 'all' => '',
- 'label' => $this->msg( 'namespace' )->text(),
- 'in-user-lang' => true,
- ], [
- 'name' => 'namespace',
- 'id' => 'namespace',
- 'class' => 'namespaceselector',
- ]
- ) . "\n";
- $hidden = $opts['namespace'] === '' ? ' mw-input-hidden' : '';
- $namespaceForm .= '<span class="mw-input-with-label' . $hidden . '">' . Xml::checkLabel(
- $this->msg( 'invert' )->text(),
- 'invert',
- 'nsinvert',
- $opts['invert'],
- [ 'title' => $this->msg( 'tooltip-invert' )->text() ]
- ) . "</span>\n";
- $namespaceForm .= '<span class="mw-input-with-label' . $hidden . '">' . Xml::checkLabel(
- $this->msg( 'namespace_association' )->text(),
- 'associated',
- 'nsassociated',
- $opts['associated'],
- [ 'title' => $this->msg( 'tooltip-namespace_association' )->text() ]
- ) . "</span>\n";
- $form .= Html::rawElement(
- 'span',
- [ 'class' => 'namespaceForm cloption' ],
- $namespaceForm
- );
- $form .= Xml::submitButton(
- $this->msg( 'watchlist-submit' )->text(),
- [ 'class' => 'cloption-submit' ]
- ) . "\n";
- foreach ( $hiddenFields as $key => $value ) {
- $form .= Html::hidden( $key, $value ) . "\n";
- }
- $form .= Xml::closeElement( 'fieldset' ) . "\n";
- $form .= Xml::closeElement( 'form' ) . "\n";
- // Insert a placeholder for RCFilters
- if ( $this->isStructuredFilterUiEnabled() ) {
- $rcfilterContainer = Html::element(
- 'div',
- // TODO: Remove deprecated rcfilters-container class
- [ 'class' => 'rcfilters-container mw-rcfilters-container' ]
- );
- $loadingContainer = Html::rawElement(
- 'div',
- [ 'class' => 'mw-rcfilters-spinner' ],
- Html::element(
- 'div',
- [ 'class' => 'mw-rcfilters-spinner-bounce' ]
- )
- );
- // Wrap both with rcfilters-head
- $this->getOutput()->addHTML(
- Html::rawElement(
- 'div',
- // TODO: Remove deprecated rcfilters-head class
- [ 'class' => 'rcfilters-head mw-rcfilters-head' ],
- $rcfilterContainer . $form
- )
- );
- // Add spinner
- $this->getOutput()->addHTML( $loadingContainer );
- } else {
- $this->getOutput()->addHTML( $form );
- }
- $this->setBottomText( $opts );
- }
- function cutoffselector( $options ) {
- $selected = (float)$options['days'];
- if ( $selected <= 0 ) {
- $selected = $this->maxDays;
- }
- $selectedHours = round( $selected * 24 );
- $hours = array_unique( array_filter( [
- 1,
- 2,
- 6,
- 12,
- 24,
- 72,
- 168,
- 24 * (float)$this->getUser()->getOption( 'watchlistdays', 0 ),
- 24 * $this->maxDays,
- $selectedHours
- ] ) );
- asort( $hours );
- $select = new XmlSelect( 'days', 'days', (float)( $selectedHours / 24 ) );
- foreach ( $hours as $value ) {
- if ( $value < 24 ) {
- $name = $this->msg( 'hours' )->numParams( $value )->text();
- } else {
- $name = $this->msg( 'days' )->numParams( $value / 24 )->text();
- }
- $select->addOption( $name, (float)( $value / 24 ) );
- }
- return $select->getHTML() . "\n<br />\n";
- }
- function setTopText( FormOptions $opts ) {
- $nondefaults = $opts->getChangedValues();
- $form = '';
- $user = $this->getUser();
- $numItems = $this->countItems();
- $showUpdatedMarker = $this->getConfig()->get( 'ShowUpdatedMarker' );
- // Show watchlist header
- $watchlistHeader = '';
- if ( $numItems == 0 ) {
- $watchlistHeader = $this->msg( 'nowatchlist' )->parse();
- } else {
- $watchlistHeader .= $this->msg( 'watchlist-details' )->numParams( $numItems )->parse() . "\n";
- if ( $this->getConfig()->get( 'EnotifWatchlist' )
- && $user->getOption( 'enotifwatchlistpages' )
- ) {
- $watchlistHeader .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
- }
- if ( $showUpdatedMarker ) {
- $watchlistHeader .= $this->msg(
- $this->isStructuredFilterUiEnabled() ?
- 'rcfilters-watchlist-showupdated' :
- 'wlheader-showupdated'
- )->parse() . "\n";
- }
- }
- $form .= Html::rawElement(
- 'div',
- [ 'class' => 'watchlistDetails' ],
- $watchlistHeader
- );
- if ( $numItems > 0 && $showUpdatedMarker ) {
- $form .= Xml::openElement( 'form', [ 'method' => 'post',
- 'action' => $this->getPageTitle()->getLocalURL(),
- 'id' => 'mw-watchlist-resetbutton' ] ) . "\n" .
- Xml::submitButton( $this->msg( 'enotif_reset' )->text(),
- [ 'name' => 'mw-watchlist-reset-submit' ] ) . "\n" .
- Html::hidden( 'token', $user->getEditToken() ) . "\n" .
- Html::hidden( 'reset', 'all' ) . "\n";
- foreach ( $nondefaults as $key => $value ) {
- $form .= Html::hidden( $key, $value ) . "\n";
- }
- $form .= Xml::closeElement( 'form' ) . "\n";
- }
- $this->getOutput()->addHTML( $form );
- }
- protected function showHideCheck( $options, $message, $name, $value, $inStructuredUi ) {
- $options[$name] = 1 - (int)$value;
- $attribs = [ 'class' => 'mw-input-with-label clshowhideoption cloption' ];
- if ( $inStructuredUi ) {
- $attribs[ 'data-feature-in-structured-ui' ] = true;
- }
- return Html::rawElement(
- 'span',
- $attribs,
- // not using Html::checkLabel because that would escape the contents
- Html::check( $name, (int)$value, [ 'id' => $name ] ) . Html::rawElement(
- 'label',
- $attribs + [ 'for' => $name ],
- // <nowiki/> at beginning to avoid messages with "$1 ..." being parsed as pre tags
- $this->msg( $message, '<nowiki/>' )->parse()
- )
- );
- }
- /**
- * Count the number of paired items on a user's watchlist.
- * The assumption made here is that when a subject page is watched a talk page is also watched.
- * Hence the number of individual items is halved.
- *
- * @return int
- */
- protected function countItems() {
- $store = MediaWikiServices::getInstance()->getWatchedItemStore();
- $count = $store->countWatchedItems( $this->getUser() );
- return floor( $count / 2 );
- }
- /**
- * @param RecentChange $rc
- * @return bool User viewed the revision or a newer one
- */
- protected function isChangeEffectivelySeen( RecentChange $rc ) {
- $firstUnseen = $this->getLatestNotificationTimestamp( $rc );
- return ( $firstUnseen === null || $firstUnseen > $rc->getAttribute( 'rc_timestamp' ) );
- }
- /**
- * @param RecentChange $rc
- * @return string|null TS_MW timestamp of first unseen revision or null if there isn't one
- */
- private function getLatestNotificationTimestamp( RecentChange $rc ) {
- return $this->watchStore->getLatestNotificationTimestamp(
- $rc->getAttribute( 'wl_notificationtimestamp' ),
- $this->getUser(),
- $rc->getTitle()
- );
- }
- }
|