[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/specials/ -> SpecialWatchlist.php (source)

   1  <?php
   2  /**
   3   * Implements Special:Watchlist
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @ingroup SpecialPage
  22   */
  23  
  24  /**
  25   * A special page that lists last changes made to the wiki,
  26   * limited to user-defined list of titles.
  27   *
  28   * @ingroup SpecialPage
  29   */
  30  class SpecialWatchlist extends ChangesListSpecialPage {
  31  	public function __construct( $page = 'Watchlist', $restriction = 'viewmywatchlist' ) {
  32          parent::__construct( $page, $restriction );
  33      }
  34  
  35      /**
  36       * Main execution point
  37       *
  38       * @param string $subpage
  39       */
  40  	function execute( $subpage ) {
  41          // Anons don't get a watchlist
  42          $this->requireLogin( 'watchlistanontext' );
  43  
  44          $output = $this->getOutput();
  45          $request = $this->getRequest();
  46  
  47          $mode = SpecialEditWatchlist::getMode( $request, $subpage );
  48          if ( $mode !== false ) {
  49              if ( $mode === SpecialEditWatchlist::EDIT_RAW ) {
  50                  $title = SpecialPage::getTitleFor( 'EditWatchlist', 'raw' );
  51              } elseif ( $mode === SpecialEditWatchlist::EDIT_CLEAR ) {
  52                  $title = SpecialPage::getTitleFor( 'EditWatchlist', 'clear' );
  53              } else {
  54                  $title = SpecialPage::getTitleFor( 'EditWatchlist' );
  55              }
  56  
  57              $output->redirect( $title->getLocalURL() );
  58  
  59              return;
  60          }
  61  
  62          $this->checkPermissions();
  63  
  64          $user = $this->getUser();
  65          $opts = $this->getOptions();
  66  
  67          $config = $this->getConfig();
  68          if ( ( $config->get( 'EnotifWatchlist' ) || $config->get( 'ShowUpdatedMarker' ) )
  69              && $request->getVal( 'reset' )
  70              && $request->wasPosted()
  71          ) {
  72              $user->clearAllNotifications();
  73              $output->redirect( $this->getPageTitle()->getFullURL( $opts->getChangedValues() ) );
  74  
  75              return;
  76          }
  77  
  78          parent::execute( $subpage );
  79      }
  80  
  81      /**
  82       * Return an array of subpages beginning with $search that this special page will accept.
  83       *
  84       * @param string $search Prefix to search for
  85       * @param int $limit Maximum number of results to return
  86       * @return string[] Matching subpages
  87       */
  88  	public function prefixSearchSubpages( $search, $limit = 10 ) {
  89          // See also SpecialEditWatchlist::prefixSearchSubpages
  90          return self::prefixSearchArray(
  91              $search,
  92              $limit,
  93              array(
  94                  'clear',
  95                  'edit',
  96                  'raw',
  97              )
  98          );
  99      }
 100  
 101      /**
 102       * Get a FormOptions object containing the default options
 103       *
 104       * @return FormOptions
 105       */
 106  	public function getDefaultOptions() {
 107          $opts = parent::getDefaultOptions();
 108          $user = $this->getUser();
 109  
 110          $opts->add( 'days', $user->getOption( 'watchlistdays' ), FormOptions::FLOAT );
 111  
 112          $opts->add( 'hideminor', $user->getBoolOption( 'watchlisthideminor' ) );
 113          $opts->add( 'hidebots', $user->getBoolOption( 'watchlisthidebots' ) );
 114          $opts->add( 'hideanons', $user->getBoolOption( 'watchlisthideanons' ) );
 115          $opts->add( 'hideliu', $user->getBoolOption( 'watchlisthideliu' ) );
 116          $opts->add( 'hidepatrolled', $user->getBoolOption( 'watchlisthidepatrolled' ) );
 117          $opts->add( 'hidemyself', $user->getBoolOption( 'watchlisthideown' ) );
 118  
 119          $opts->add( 'extended', $user->getBoolOption( 'extendwatchlist' ) );
 120  
 121          return $opts;
 122      }
 123  
 124      /**
 125       * Get custom show/hide filters
 126       *
 127       * @return array Map of filter URL param names to properties (msg/default)
 128       */
 129  	protected function getCustomFilters() {
 130          if ( $this->customFilters === null ) {
 131              $this->customFilters = parent::getCustomFilters();
 132              wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ), '1.23' );
 133          }
 134  
 135          return $this->customFilters;
 136      }
 137  
 138      /**
 139       * Fetch values for a FormOptions object from the WebRequest associated with this instance.
 140       *
 141       * Maps old pre-1.23 request parameters Watchlist used to use (different from Recentchanges' ones)
 142       * to the current ones.
 143       *
 144       * @param FormOptions $opts
 145       * @return FormOptions
 146       */
 147  	protected function fetchOptionsFromRequest( $opts ) {
 148          static $compatibilityMap = array(
 149              'hideMinor' => 'hideminor',
 150              'hideBots' => 'hidebots',
 151              'hideAnons' => 'hideanons',
 152              'hideLiu' => 'hideliu',
 153              'hidePatrolled' => 'hidepatrolled',
 154              'hideOwn' => 'hidemyself',
 155          );
 156  
 157          $params = $this->getRequest()->getValues();
 158          foreach ( $compatibilityMap as $from => $to ) {
 159              if ( isset( $params[$from] ) ) {
 160                  $params[$to] = $params[$from];
 161                  unset( $params[$from] );
 162              }
 163          }
 164  
 165          // Not the prettiest way to achieve this… FormOptions internally depends on data sanitization
 166          // methods defined on WebRequest and removing this dependency would cause some code duplication.
 167          $request = new DerivativeRequest( $this->getRequest(), $params );
 168          $opts->fetchValuesFromRequest( $request );
 169  
 170          return $opts;
 171      }
 172  
 173      /**
 174       * Return an array of conditions depending of options set in $opts
 175       *
 176       * @param FormOptions $opts
 177       * @return array
 178       */
 179  	public function buildMainQueryConds( FormOptions $opts ) {
 180          $dbr = $this->getDB();
 181          $conds = parent::buildMainQueryConds( $opts );
 182  
 183          // Calculate cutoff
 184          if ( $opts['days'] > 0 ) {
 185              $conds[] = 'rc_timestamp > ' .
 186                  $dbr->addQuotes( $dbr->timestamp( time() - intval( $opts['days'] * 86400 ) ) );
 187          }
 188  
 189          return $conds;
 190      }
 191  
 192      /**
 193       * Process the query
 194       *
 195       * @param array $conds
 196       * @param FormOptions $opts
 197       * @return bool|ResultWrapper Result or false (for Recentchangeslinked only)
 198       */
 199  	public function doMainQuery( $conds, $opts ) {
 200          $dbr = $this->getDB();
 201          $user = $this->getUser();
 202  
 203          # Toggle watchlist content (all recent edits or just the latest)
 204          if ( $opts['extended'] ) {
 205              $limitWatchlist = $user->getIntOption( 'wllimit' );
 206              $usePage = false;
 207          } else {
 208              # Top log Ids for a page are not stored
 209              $nonRevisionTypes = array( RC_LOG );
 210              wfRunHooks( 'SpecialWatchlistGetNonRevisionTypes', array( &$nonRevisionTypes ) );
 211              if ( $nonRevisionTypes ) {
 212                  $conds[] = $dbr->makeList(
 213                      array(
 214                          'rc_this_oldid=page_latest',
 215                          'rc_type' => $nonRevisionTypes,
 216                      ),
 217                      LIST_OR
 218                  );
 219              }
 220              $limitWatchlist = 0;
 221              $usePage = true;
 222          }
 223  
 224          $tables = array( 'recentchanges', 'watchlist' );
 225          $fields = RecentChange::selectFields();
 226          $query_options = array( 'ORDER BY' => 'rc_timestamp DESC' );
 227          $join_conds = array(
 228              'watchlist' => array(
 229                  'INNER JOIN',
 230                  array(
 231                      'wl_user' => $user->getId(),
 232                      'wl_namespace=rc_namespace',
 233                      'wl_title=rc_title'
 234                  ),
 235              ),
 236          );
 237  
 238          if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) ) {
 239              $fields[] = 'wl_notificationtimestamp';
 240          }
 241          if ( $limitWatchlist ) {
 242              $query_options['LIMIT'] = $limitWatchlist;
 243          }
 244  
 245          $rollbacker = $user->isAllowed( 'rollback' );
 246          if ( $usePage || $rollbacker ) {
 247              $tables[] = 'page';
 248              $join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' );
 249              if ( $rollbacker ) {
 250                  $fields[] = 'page_latest';
 251              }
 252          }
 253  
 254          // Log entries with DELETED_ACTION must not show up unless the user has
 255          // the necessary rights.
 256          if ( !$user->isAllowed( 'deletedhistory' ) ) {
 257              $bitmask = LogPage::DELETED_ACTION;
 258          } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
 259              $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
 260          } else {
 261              $bitmask = 0;
 262          }
 263          if ( $bitmask ) {
 264              $conds[] = $dbr->makeList( array(
 265                  'rc_type != ' . RC_LOG,
 266                  $dbr->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
 267              ), LIST_OR );
 268          }
 269  
 270          ChangeTags::modifyDisplayQuery(
 271              $tables,
 272              $fields,
 273              $conds,
 274              $join_conds,
 275              $query_options,
 276              ''
 277          );
 278  
 279          $this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts );
 280  
 281          return $dbr->select(
 282              $tables,
 283              $fields,
 284              $conds,
 285              __METHOD__,
 286              $query_options,
 287              $join_conds
 288          );
 289      }
 290  
 291  	protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) {
 292          return parent::runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts )
 293              && wfRunHooks(
 294                  'SpecialWatchlistQuery',
 295                  array( &$conds, &$tables, &$join_conds, &$fields, $opts ),
 296                  '1.23'
 297              );
 298      }
 299  
 300      /**
 301       * Return a DatabaseBase object for reading
 302       *
 303       * @return DatabaseBase
 304       */
 305  	protected function getDB() {
 306          return wfGetDB( DB_SLAVE, 'watchlist' );
 307      }
 308  
 309      /**
 310       * Output feed links.
 311       */
 312  	public function outputFeedLinks() {
 313          $user = $this->getUser();
 314          $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
 315          if ( $wlToken ) {
 316              $this->addFeedLinks( array(
 317                  'action' => 'feedwatchlist',
 318                  'allrev' => 1,
 319                  'wlowner' => $user->getName(),
 320                  'wltoken' => $wlToken,
 321              ) );
 322          }
 323      }
 324  
 325      /**
 326       * Build and output the actual changes list.
 327       *
 328       * @param ResultWrapper $rows Database rows
 329       * @param FormOptions $opts
 330       */
 331  	public function outputChangesList( $rows, $opts ) {
 332          $dbr = $this->getDB();
 333          $user = $this->getUser();
 334          $output = $this->getOutput();
 335  
 336          # Show a message about slave lag, if applicable
 337          $lag = wfGetLB()->safeGetLag( $dbr );
 338          if ( $lag > 0 ) {
 339              $output->showLagWarning( $lag );
 340          }
 341  
 342          # If no rows to display, show message before try to render the list
 343          if ( $rows->numRows() == 0 ) {
 344              $output->wrapWikiMsg(
 345                  "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
 346              );
 347              return;
 348          }
 349  
 350          $dbr->dataSeek( $rows, 0 );
 351  
 352          $list = ChangesList::newFromContext( $this->getContext() );
 353          $list->setWatchlistDivs();
 354          $list->initChangesListRows( $rows );
 355          $dbr->dataSeek( $rows, 0 );
 356  
 357          $s = $list->beginRecentChangesList();
 358          $counter = 1;
 359          foreach ( $rows as $obj ) {
 360              # Make RC entry
 361              $rc = RecentChange::newFromRow( $obj );
 362              $rc->counter = $counter++;
 363  
 364              if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) ) {
 365                  $updated = $obj->wl_notificationtimestamp;
 366              } else {
 367                  $updated = false;
 368              }
 369  
 370              if ( $this->getConfig()->get( 'RCShowWatchingUsers' ) && $user->getOption( 'shownumberswatching' ) ) {
 371                  $rc->numberofWatchingusers = $dbr->selectField( 'watchlist',
 372                      'COUNT(*)',
 373                      array(
 374                          'wl_namespace' => $obj->rc_namespace,
 375                          'wl_title' => $obj->rc_title,
 376                      ),
 377                      __METHOD__ );
 378              } else {
 379                  $rc->numberofWatchingusers = 0;
 380              }
 381  
 382              $changeLine = $list->recentChangesLine( $rc, $updated, $counter );
 383              if ( $changeLine !== false ) {
 384                  $s .= $changeLine;
 385              }
 386          }
 387          $s .= $list->endRecentChangesList();
 388  
 389          $output->addHTML( $s );
 390      }
 391  
 392      /**
 393       * Set the text to be displayed above the changes
 394       *
 395       * @param FormOptions $opts
 396       * @param int $numRows Number of rows in the result to show after this header
 397       */
 398  	public function doHeader( $opts, $numRows ) {
 399          $user = $this->getUser();
 400  
 401          $this->getOutput()->addSubtitle(
 402              $this->msg( 'watchlistfor2', $user->getName() )
 403                  ->rawParams( SpecialEditWatchlist::buildTools( null ) )
 404          );
 405  
 406          $this->setTopText( $opts );
 407  
 408          $lang = $this->getLanguage();
 409          $wlInfo = '';
 410          if ( $opts['days'] > 0 ) {
 411              $timestamp = wfTimestampNow();
 412              $wlInfo = $this->msg( 'wlnote' )->numParams( $numRows, round( $opts['days'] * 24 ) )->params(
 413                  $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user )
 414              )->parse() . "<br />\n";
 415          }
 416  
 417          $nondefaults = $opts->getChangedValues();
 418          $cutofflinks = $this->cutoffLinks( $opts['days'], $nondefaults ) . "<br />\n";
 419  
 420          # Spit out some control panel links
 421          $filters = array(
 422              'hideminor' => 'rcshowhideminor',
 423              'hidebots' => 'rcshowhidebots',
 424              'hideanons' => 'rcshowhideanons',
 425              'hideliu' => 'rcshowhideliu',
 426              'hidemyself' => 'rcshowhidemine',
 427              'hidepatrolled' => 'rcshowhidepatr'
 428          );
 429          foreach ( $this->getCustomFilters() as $key => $params ) {
 430              $filters[$key] = $params['msg'];
 431          }
 432          // Disable some if needed
 433          if ( !$user->useNPPatrol() ) {
 434              unset( $filters['hidepatrolled'] );
 435          }
 436  
 437          $links = array();
 438          foreach ( $filters as $name => $msg ) {
 439              $links[] = $this->showHideLink( $nondefaults, $msg, $name, $opts[$name] );
 440          }
 441  
 442          $hiddenFields = $nondefaults;
 443          unset( $hiddenFields['namespace'] );
 444          unset( $hiddenFields['invert'] );
 445          unset( $hiddenFields['associated'] );
 446  
 447          # Create output
 448          $form = '';
 449  
 450          # Namespace filter and put the whole form together.
 451          $form .= $wlInfo;
 452          $form .= $cutofflinks;
 453          $form .= $lang->pipeList( $links ) . "\n";
 454          $form .= "<hr />\n<p>";
 455          $form .= Html::namespaceSelector(
 456              array(
 457                  'selected' => $opts['namespace'],
 458                  'all' => '',
 459                  'label' => $this->msg( 'namespace' )->text()
 460              ), array(
 461                  'name' => 'namespace',
 462                  'id' => 'namespace',
 463                  'class' => 'namespaceselector',
 464              )
 465          ) . '&#160;';
 466          $form .= Xml::checkLabel(
 467              $this->msg( 'invert' )->text(),
 468              'invert',
 469              'nsinvert',
 470              $opts['invert'],
 471              array( 'title' => $this->msg( 'tooltip-invert' )->text() )
 472          ) . '&#160;';
 473          $form .= Xml::checkLabel(
 474              $this->msg( 'namespace_association' )->text(),
 475              'associated',
 476              'nsassociated',
 477              $opts['associated'],
 478              array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() )
 479          ) . '&#160;';
 480          $form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "</p>\n";
 481          foreach ( $hiddenFields as $key => $value ) {
 482              $form .= Html::hidden( $key, $value ) . "\n";
 483          }
 484          $form .= Xml::closeElement( 'fieldset' ) . "\n";
 485          $form .= Xml::closeElement( 'form' ) . "\n";
 486          $this->getOutput()->addHTML( $form );
 487  
 488          $this->setBottomText( $opts );
 489      }
 490  
 491  	function setTopText( FormOptions $opts ) {
 492          $nondefaults = $opts->getChangedValues();
 493          $form = "";
 494          $user = $this->getUser();
 495  
 496          $dbr = $this->getDB();
 497          $numItems = $this->countItems( $dbr );
 498          $showUpdatedMarker = $this->getConfig()->get( 'ShowUpdatedMarker' );
 499  
 500          // Show watchlist header
 501          $form .= "<p>";
 502          if ( $numItems == 0 ) {
 503              $form .= $this->msg( 'nowatchlist' )->parse() . "\n";
 504          } else {
 505              $form .= $this->msg( 'watchlist-details' )->numParams( $numItems )->parse() . "\n";
 506              if ( $this->getConfig()->get( 'EnotifWatchlist' ) && $user->getOption( 'enotifwatchlistpages' ) ) {
 507                  $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
 508              }
 509              if ( $showUpdatedMarker ) {
 510                  $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
 511              }
 512          }
 513          $form .= "</p>";
 514  
 515          if ( $numItems > 0 && $showUpdatedMarker ) {
 516              $form .= Xml::openElement( 'form', array( 'method' => 'post',
 517                  'action' => $this->getPageTitle()->getLocalURL(),
 518                  'id' => 'mw-watchlist-resetbutton' ) ) . "\n" .
 519              Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . "\n" .
 520              Html::hidden( 'reset', 'all' ) . "\n";
 521              foreach ( $nondefaults as $key => $value ) {
 522                  $form .= Html::hidden( $key, $value ) . "\n";
 523              }
 524              $form .= Xml::closeElement( 'form' ) . "\n";
 525          }
 526  
 527          $form .= Xml::openElement( 'form', array(
 528              'method' => 'post',
 529              'action' => $this->getPageTitle()->getLocalURL(),
 530              'id' => 'mw-watchlist-form'
 531          ) );
 532          $form .= Xml::fieldset(
 533              $this->msg( 'watchlist-options' )->text(),
 534              false,
 535              array( 'id' => 'mw-watchlist-options' )
 536          );
 537  
 538          $form .= SpecialRecentChanges::makeLegend( $this->getContext() );
 539  
 540          $this->getOutput()->addHTML( $form );
 541      }
 542  
 543  	protected function showHideLink( $options, $message, $name, $value ) {
 544          $label = $this->msg( $value ? 'show' : 'hide' )->escaped();
 545          $options[$name] = 1 - (int)$value;
 546  
 547          return $this->msg( $message )
 548              ->rawParams( Linker::linkKnown( $this->getPageTitle(), $label, array(), $options ) )
 549              ->escaped();
 550      }
 551  
 552  	protected function hoursLink( $h, $options = array() ) {
 553          $options['days'] = ( $h / 24.0 );
 554  
 555          return Linker::linkKnown(
 556              $this->getPageTitle(),
 557              $this->getLanguage()->formatNum( $h ),
 558              array(),
 559              $options
 560          );
 561      }
 562  
 563  	protected function daysLink( $d, $options = array() ) {
 564          $options['days'] = $d;
 565          $message = $d ? $this->getLanguage()->formatNum( $d )
 566              : $this->msg( 'watchlistall2' )->escaped();
 567  
 568          return Linker::linkKnown(
 569              $this->getPageTitle(),
 570              $message,
 571              array(),
 572              $options
 573          );
 574      }
 575  
 576      /**
 577       * Returns html
 578       *
 579       * @param int $days This gets overwritten, so is not used
 580       * @param array $options Query parameters for URL
 581       * @return string
 582       */
 583  	protected function cutoffLinks( $days, $options = array() ) {
 584          $hours = array( 1, 2, 6, 12 );
 585          $days = array( 1, 3, 7 );
 586          $i = 0;
 587          foreach ( $hours as $h ) {
 588              $hours[$i++] = $this->hoursLink( $h, $options );
 589          }
 590          $i = 0;
 591          foreach ( $days as $d ) {
 592              $days[$i++] = $this->daysLink( $d, $options );
 593          }
 594  
 595          return $this->msg( 'wlshowlast' )->rawParams(
 596              $this->getLanguage()->pipeList( $hours ),
 597              $this->getLanguage()->pipeList( $days ),
 598              $this->daysLink( 0, $options ) )->parse();
 599      }
 600  
 601      /**
 602       * Count the number of items on a user's watchlist
 603       *
 604       * @param DatabaseBase $dbr A database connection
 605       * @return int
 606       */
 607  	protected function countItems( $dbr ) {
 608          # Fetch the raw count
 609          $rows = $dbr->select( 'watchlist', array( 'count' => 'COUNT(*)' ),
 610              array( 'wl_user' => $this->getUser()->getId() ), __METHOD__ );
 611          $row = $dbr->fetchObject( $rows );
 612          $count = $row->count;
 613  
 614          return floor( $count / 2 );
 615      }
 616  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1