[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/ -> WatchedItem.php (source)

   1  <?php
   2  /**
   3   * Accessor and mutator for watchlist entries.
   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 Watchlist
  22   */
  23  
  24  /**
  25   * Representation of a pair of user and title for watchlist entries.
  26   *
  27   * @ingroup Watchlist
  28   */
  29  class WatchedItem {
  30      /**
  31       * Constant to specify that user rights 'editmywatchlist' and
  32       * 'viewmywatchlist' should not be checked.
  33       * @since 1.22
  34       */
  35      const IGNORE_USER_RIGHTS = 0;
  36  
  37      /**
  38       * Constant to specify that user rights 'editmywatchlist' and
  39       * 'viewmywatchlist' should be checked.
  40       * @since 1.22
  41       */
  42      const CHECK_USER_RIGHTS = 1;
  43  
  44      /** @var Title */
  45      public $mTitle;
  46  
  47      /** @var User */
  48      public $mUser;
  49  
  50      /** @var int */
  51      public $mCheckRights;
  52  
  53      /** @var bool */
  54      private $loaded = false;
  55  
  56      /** @var bool */
  57      private $watched;
  58  
  59      /** @var string */
  60      private $timestamp;
  61  
  62      /**
  63       * Create a WatchedItem object with the given user and title
  64       * @since 1.22 $checkRights parameter added
  65       * @param User $user The user to use for (un)watching
  66       * @param Title $title The title we're going to (un)watch
  67       * @param int $checkRights Whether to check the 'viewmywatchlist' and 'editmywatchlist' rights.
  68       *     Pass either WatchedItem::IGNORE_USER_RIGHTS or WatchedItem::CHECK_USER_RIGHTS.
  69       * @return WatchedItem
  70       */
  71  	public static function fromUserTitle( $user, $title,
  72          $checkRights = WatchedItem::CHECK_USER_RIGHTS
  73      ) {
  74          $wl = new WatchedItem;
  75          $wl->mUser = $user;
  76          $wl->mTitle = $title;
  77          $wl->mCheckRights = $checkRights;
  78  
  79          return $wl;
  80      }
  81  
  82      /**
  83       * Title being watched
  84       * @return Title
  85       */
  86  	protected function getTitle() {
  87          return $this->mTitle;
  88      }
  89  
  90      /**
  91       * Helper to retrieve the title namespace
  92       * @return int
  93       */
  94  	protected function getTitleNs() {
  95          return $this->getTitle()->getNamespace();
  96      }
  97  
  98      /**
  99       * Helper to retrieve the title DBkey
 100       * @return string
 101       */
 102  	protected function getTitleDBkey() {
 103          return $this->getTitle()->getDBkey();
 104      }
 105  
 106      /**
 107       * Helper to retrieve the user id
 108       * @return int
 109       */
 110  	protected function getUserId() {
 111          return $this->mUser->getId();
 112      }
 113  
 114      /**
 115       * Return an array of conditions to select or update the appropriate database
 116       * row.
 117       *
 118       * @return array
 119       */
 120  	private function dbCond() {
 121          return array(
 122              'wl_user' => $this->getUserId(),
 123              'wl_namespace' => $this->getTitleNs(),
 124              'wl_title' => $this->getTitleDBkey(),
 125          );
 126      }
 127  
 128      /**
 129       * Load the object from the database
 130       */
 131  	private function load() {
 132          if ( $this->loaded ) {
 133              return;
 134          }
 135          $this->loaded = true;
 136  
 137          // Only loggedin user can have a watchlist
 138          if ( $this->mUser->isAnon() ) {
 139              $this->watched = false;
 140              return;
 141          }
 142  
 143          // some pages cannot be watched
 144          if ( !$this->getTitle()->isWatchable() ) {
 145              $this->watched = false;
 146              return;
 147          }
 148  
 149          # Pages and their talk pages are considered equivalent for watching;
 150          # remember that talk namespaces are numbered as page namespace+1.
 151  
 152          $dbr = wfGetDB( DB_SLAVE );
 153          $row = $dbr->selectRow( 'watchlist', 'wl_notificationtimestamp',
 154              $this->dbCond(), __METHOD__ );
 155  
 156          if ( $row === false ) {
 157              $this->watched = false;
 158          } else {
 159              $this->watched = true;
 160              $this->timestamp = $row->wl_notificationtimestamp;
 161          }
 162      }
 163  
 164      /**
 165       * Check permissions
 166       * @param string $what 'viewmywatchlist' or 'editmywatchlist'
 167       * @return bool
 168       */
 169  	private function isAllowed( $what ) {
 170          return !$this->mCheckRights || $this->mUser->isAllowed( $what );
 171      }
 172  
 173      /**
 174       * Is mTitle being watched by mUser?
 175       * @return bool
 176       */
 177  	public function isWatched() {
 178          if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
 179              return false;
 180          }
 181  
 182          $this->load();
 183          return $this->watched;
 184      }
 185  
 186      /**
 187       * Get the notification timestamp of this entry.
 188       *
 189       * @return bool|null|string False if the page is not watched, the value of
 190       *   the wl_notificationtimestamp field otherwise
 191       */
 192  	public function getNotificationTimestamp() {
 193          if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
 194              return false;
 195          }
 196  
 197          $this->load();
 198          if ( $this->watched ) {
 199              return $this->timestamp;
 200          } else {
 201              return false;
 202          }
 203      }
 204  
 205      /**
 206       * Reset the notification timestamp of this entry
 207       *
 208       * @param bool $force Whether to force the write query to be executed even if the
 209       *    page is not watched or the notification timestamp is already NULL.
 210       * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
 211       */
 212  	public function resetNotificationTimestamp( $force = '', $oldid = 0 ) {
 213          // Only loggedin user can have a watchlist
 214          if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
 215              return;
 216          }
 217  
 218          if ( $force != 'force' ) {
 219              $this->load();
 220              if ( !$this->watched || $this->timestamp === null ) {
 221                  return;
 222              }
 223          }
 224  
 225          $title = $this->getTitle();
 226          if ( !$oldid ) {
 227              // No oldid given, assuming latest revision; clear the timestamp.
 228              $notificationTimestamp = null;
 229          } elseif ( !$title->getNextRevisionID( $oldid ) ) {
 230              // Oldid given and is the latest revision for this title; clear the timestamp.
 231              $notificationTimestamp = null;
 232          } else {
 233              // See if the version marked as read is more recent than the one we're viewing.
 234              // Call load() if it wasn't called before due to $force.
 235              $this->load();
 236  
 237              if ( $this->timestamp === null ) {
 238                  // This can only happen if $force is enabled.
 239                  $notificationTimestamp = null;
 240              } else {
 241                  // Oldid given and isn't the latest; update the timestamp.
 242                  // This will result in no further notification emails being sent!
 243                  $dbr = wfGetDB( DB_SLAVE );
 244                  $notificationTimestamp = $dbr->selectField(
 245                      'revision', 'rev_timestamp',
 246                      array( 'rev_page' => $title->getArticleID(), 'rev_id' => $oldid )
 247                  );
 248                  // We need to go one second to the future because of various strict comparisons
 249                  // throughout the codebase
 250                  $ts = new MWTimestamp( $notificationTimestamp );
 251                  $ts->timestamp->add( new DateInterval( 'PT1S' ) );
 252                  $notificationTimestamp = $ts->getTimestamp( TS_MW );
 253  
 254                  if ( $notificationTimestamp < $this->timestamp ) {
 255                      if ( $force != 'force' ) {
 256                          return;
 257                      } else {
 258                          // This is a little silly…
 259                          $notificationTimestamp = $this->timestamp;
 260                      }
 261                  }
 262              }
 263          }
 264  
 265          // If the page is watched by the user (or may be watched), update the timestamp on any
 266          // any matching rows
 267          $dbw = wfGetDB( DB_MASTER );
 268          $dbw->update( 'watchlist', array( 'wl_notificationtimestamp' => $notificationTimestamp ),
 269              $this->dbCond(), __METHOD__ );
 270          $this->timestamp = null;
 271      }
 272  
 273      /**
 274       * @param WatchedItem[] $items
 275       * @return bool
 276       */
 277  	public static function batchAddWatch( array $items ) {
 278          $section = new ProfileSection( __METHOD__ );
 279  
 280          if ( wfReadOnly() ) {
 281              return false;
 282          }
 283  
 284          $rows = array();
 285          foreach ( $items as $item ) {
 286              // Only loggedin user can have a watchlist
 287              if ( $item->mUser->isAnon() || !$item->isAllowed( 'editmywatchlist' ) ) {
 288                  continue;
 289              }
 290              $rows[] = array(
 291                  'wl_user' => $item->getUserId(),
 292                  'wl_namespace' => MWNamespace::getSubject( $item->getTitleNs() ),
 293                  'wl_title' => $item->getTitleDBkey(),
 294                  'wl_notificationtimestamp' => null,
 295              );
 296              // Every single watched page needs now to be listed in watchlist;
 297              // namespace:page and namespace_talk:page need separate entries:
 298              $rows[] = array(
 299                  'wl_user' => $item->getUserId(),
 300                  'wl_namespace' => MWNamespace::getTalk( $item->getTitleNs() ),
 301                  'wl_title' => $item->getTitleDBkey(),
 302                  'wl_notificationtimestamp' => null
 303              );
 304              $item->watched = true;
 305          }
 306  
 307          if ( !$rows ) {
 308              return false;
 309          }
 310  
 311          $dbw = wfGetDB( DB_MASTER );
 312          foreach ( array_chunk( $rows, 100 ) as $toInsert ) {
 313              // Use INSERT IGNORE to avoid overwriting the notification timestamp
 314              // if there's already an entry for this page
 315              $dbw->insert( 'watchlist', $toInsert, __METHOD__, 'IGNORE' );
 316          }
 317  
 318          return true;
 319      }
 320  
 321      /**
 322       * Given a title and user (assumes the object is setup), add the watch to the database.
 323       * @return bool
 324       */
 325  	public function addWatch() {
 326          return self::batchAddWatch( array( $this ) );
 327      }
 328  
 329      /**
 330       * Same as addWatch, only the opposite.
 331       * @return bool
 332       */
 333  	public function removeWatch() {
 334          wfProfileIn( __METHOD__ );
 335  
 336          // Only loggedin user can have a watchlist
 337          if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
 338              wfProfileOut( __METHOD__ );
 339              return false;
 340          }
 341  
 342          $success = false;
 343          $dbw = wfGetDB( DB_MASTER );
 344          $dbw->delete( 'watchlist',
 345              array(
 346                  'wl_user' => $this->getUserId(),
 347                  'wl_namespace' => MWNamespace::getSubject( $this->getTitleNs() ),
 348                  'wl_title' => $this->getTitleDBkey(),
 349              ), __METHOD__
 350          );
 351          if ( $dbw->affectedRows() ) {
 352              $success = true;
 353          }
 354  
 355          # the following code compensates the new behavior, introduced by the
 356          # enotif patch, that every single watched page needs now to be listed
 357          # in watchlist namespace:page and namespace_talk:page had separate
 358          # entries: clear them
 359          $dbw->delete( 'watchlist',
 360              array(
 361                  'wl_user' => $this->getUserId(),
 362                  'wl_namespace' => MWNamespace::getTalk( $this->getTitleNs() ),
 363                  'wl_title' => $this->getTitleDBkey(),
 364              ), __METHOD__
 365          );
 366  
 367          if ( $dbw->affectedRows() ) {
 368              $success = true;
 369          }
 370  
 371          $this->watched = false;
 372  
 373          wfProfileOut( __METHOD__ );
 374          return $success;
 375      }
 376  
 377      /**
 378       * Check if the given title already is watched by the user, and if so
 379       * add watches on a new title. To be used for page renames and such.
 380       *
 381       * @param Title $ot Page title to duplicate entries from, if present
 382       * @param Title $nt Page title to add watches on
 383       */
 384  	public static function duplicateEntries( $ot, $nt ) {
 385          WatchedItem::doDuplicateEntries( $ot->getSubjectPage(), $nt->getSubjectPage() );
 386          WatchedItem::doDuplicateEntries( $ot->getTalkPage(), $nt->getTalkPage() );
 387      }
 388  
 389      /**
 390       * Handle duplicate entries. Backend for duplicateEntries().
 391       *
 392       * @param Title $ot
 393       * @param Title $nt
 394       *
 395       * @return bool
 396       */
 397  	private static function doDuplicateEntries( $ot, $nt ) {
 398          $oldnamespace = $ot->getNamespace();
 399          $newnamespace = $nt->getNamespace();
 400          $oldtitle = $ot->getDBkey();
 401          $newtitle = $nt->getDBkey();
 402  
 403          $dbw = wfGetDB( DB_MASTER );
 404          $res = $dbw->select( 'watchlist', 'wl_user',
 405              array( 'wl_namespace' => $oldnamespace, 'wl_title' => $oldtitle ),
 406              __METHOD__, 'FOR UPDATE'
 407          );
 408          # Construct array to replace into the watchlist
 409          $values = array();
 410          foreach ( $res as $s ) {
 411              $values[] = array(
 412                  'wl_user' => $s->wl_user,
 413                  'wl_namespace' => $newnamespace,
 414                  'wl_title' => $newtitle
 415              );
 416          }
 417  
 418          if ( empty( $values ) ) {
 419              // Nothing to do
 420              return true;
 421          }
 422  
 423          # Perform replace
 424          # Note that multi-row replace is very efficient for MySQL but may be inefficient for
 425          # some other DBMSes, mostly due to poor simulation by us
 426          $dbw->replace(
 427              'watchlist',
 428              array( array( 'wl_user', 'wl_namespace', 'wl_title' ) ),
 429              $values,
 430              __METHOD__
 431          );
 432  
 433          return true;
 434      }
 435  }


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