diff options
Diffstat (limited to 'includes/WatchedItem.php')
-rw-r--r-- | includes/WatchedItem.php | 194 |
1 files changed, 145 insertions, 49 deletions
diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php index 1e07e7c7..ab136b89 100644 --- a/includes/WatchedItem.php +++ b/includes/WatchedItem.php @@ -41,19 +41,36 @@ class WatchedItem { */ const CHECK_USER_RIGHTS = 1; - var $mTitle, $mUser, $mCheckRights; - private $loaded = false, $watched, $timestamp; + /** @var Title */ + public $mTitle; + + /** @var User */ + public $mUser; + + /** @var int */ + public $mCheckRights; + + /** @var bool */ + private $loaded = false; + + /** @var bool */ + private $watched; + + /** @var string */ + private $timestamp; /** * Create a WatchedItem object with the given user and title * @since 1.22 $checkRights parameter added - * @param $user User: the user to use for (un)watching - * @param $title Title: the title we're going to (un)watch - * @param $checkRights int: Whether to check the 'viewmywatchlist' and 'editmywatchlist' rights. + * @param User $user The user to use for (un)watching + * @param Title $title The title we're going to (un)watch + * @param int $checkRights Whether to check the 'viewmywatchlist' and 'editmywatchlist' rights. * Pass either WatchedItem::IGNORE_USER_RIGHTS or WatchedItem::CHECK_USER_RIGHTS. - * @return WatchedItem object + * @return WatchedItem */ - public static function fromUserTitle( $user, $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) { + public static function fromUserTitle( $user, $title, + $checkRights = WatchedItem::CHECK_USER_RIGHTS + ) { $wl = new WatchedItem; $wl->mUser = $user; $wl->mTitle = $title; @@ -70,16 +87,26 @@ class WatchedItem { return $this->mTitle; } - /** Helper to retrieve the title namespace */ + /** + * Helper to retrieve the title namespace + * @return int + */ protected function getTitleNs() { return $this->getTitle()->getNamespace(); } - /** Helper to retrieve the title DBkey */ + /** + * Helper to retrieve the title DBkey + * @return string + */ protected function getTitleDBkey() { return $this->getTitle()->getDBkey(); } - /** Helper to retrieve the user id */ + + /** + * Helper to retrieve the user id + * @return int + */ protected function getUserId() { return $this->mUser->getId(); } @@ -113,6 +140,12 @@ class WatchedItem { return; } + // some pages cannot be watched + if ( !$this->getTitle()->isWatchable() ) { + $this->watched = false; + return; + } + # Pages and their talk pages are considered equivalent for watching; # remember that talk namespaces are numbered as page namespace+1. @@ -130,7 +163,8 @@ class WatchedItem { /** * Check permissions - * @param $what string: 'viewmywatchlist' or 'editmywatchlist' + * @param string $what 'viewmywatchlist' or 'editmywatchlist' + * @return bool */ private function isAllowed( $what ) { return !$this->mCheckRights || $this->mUser->isAllowed( $what ); @@ -152,8 +186,8 @@ class WatchedItem { /** * Get the notification timestamp of this entry. * - * @return false|null|string: false if the page is not watched, the value of - * the wl_notificationtimestamp field otherwise + * @return bool|null|string False if the page is not watched, the value of + * the wl_notificationtimestamp field otherwise */ public function getNotificationTimestamp() { if ( !$this->isAllowed( 'viewmywatchlist' ) ) { @@ -171,10 +205,11 @@ class WatchedItem { /** * Reset the notification timestamp of this entry * - * @param $force Whether to force the write query to be executed even if the - * page is not watched or the notification timestamp is already NULL. + * @param bool $force Whether to force the write query to be executed even if the + * page is not watched or the notification timestamp is already NULL. + * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed. */ - public function resetNotificationTimestamp( $force = '' ) { + public function resetNotificationTimestamp( $force = '', $oldid = 0 ) { // Only loggedin user can have a watchlist if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) { return; @@ -187,56 +222,111 @@ class WatchedItem { } } + $title = $this->getTitle(); + if ( !$oldid ) { + // No oldid given, assuming latest revision; clear the timestamp. + $notificationTimestamp = null; + } elseif ( !$title->getNextRevisionID( $oldid ) ) { + // Oldid given and is the latest revision for this title; clear the timestamp. + $notificationTimestamp = null; + } else { + // See if the version marked as read is more recent than the one we're viewing. + // Call load() if it wasn't called before due to $force. + $this->load(); + + if ( $this->timestamp === null ) { + // This can only happen if $force is enabled. + $notificationTimestamp = null; + } else { + // Oldid given and isn't the latest; update the timestamp. + // This will result in no further notification emails being sent! + $dbr = wfGetDB( DB_SLAVE ); + $notificationTimestamp = $dbr->selectField( + 'revision', 'rev_timestamp', + array( 'rev_page' => $title->getArticleID(), 'rev_id' => $oldid ) + ); + // We need to go one second to the future because of various strict comparisons + // throughout the codebase + $ts = new MWTimestamp( $notificationTimestamp ); + $ts->timestamp->add( new DateInterval( 'PT1S' ) ); + $notificationTimestamp = $ts->getTimestamp( TS_MW ); + + if ( $notificationTimestamp < $this->timestamp ) { + if ( $force != 'force' ) { + return; + } else { + // This is a little silly… + $notificationTimestamp = $this->timestamp; + } + } + } + } + // If the page is watched by the user (or may be watched), update the timestamp on any // any matching rows $dbw = wfGetDB( DB_MASTER ); - $dbw->update( 'watchlist', array( 'wl_notificationtimestamp' => null ), + $dbw->update( 'watchlist', array( 'wl_notificationtimestamp' => $notificationTimestamp ), $this->dbCond(), __METHOD__ ); $this->timestamp = null; } /** - * Given a title and user (assumes the object is setup), add the watch to the - * database. + * @param WatchedItem[] $items * @return bool */ - public function addWatch() { - wfProfileIn( __METHOD__ ); + public static function batchAddWatch( array $items ) { + $section = new ProfileSection( __METHOD__ ); - // Only loggedin user can have a watchlist - if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) { - wfProfileOut( __METHOD__ ); + if ( wfReadOnly() ) { return false; } - // Use INSERT IGNORE to avoid overwriting the notification timestamp - // if there's already an entry for this page - $dbw = wfGetDB( DB_MASTER ); - $dbw->insert( 'watchlist', - array( - 'wl_user' => $this->getUserId(), - 'wl_namespace' => MWNamespace::getSubject( $this->getTitleNs() ), - 'wl_title' => $this->getTitleDBkey(), + $rows = array(); + foreach ( $items as $item ) { + // Only loggedin user can have a watchlist + if ( $item->mUser->isAnon() || !$item->isAllowed( 'editmywatchlist' ) ) { + continue; + } + $rows[] = array( + 'wl_user' => $item->getUserId(), + 'wl_namespace' => MWNamespace::getSubject( $item->getTitleNs() ), + 'wl_title' => $item->getTitleDBkey(), + 'wl_notificationtimestamp' => null, + ); + // Every single watched page needs now to be listed in watchlist; + // namespace:page and namespace_talk:page need separate entries: + $rows[] = array( + 'wl_user' => $item->getUserId(), + 'wl_namespace' => MWNamespace::getTalk( $item->getTitleNs() ), + 'wl_title' => $item->getTitleDBkey(), 'wl_notificationtimestamp' => null - ), __METHOD__, 'IGNORE' ); + ); + $item->watched = true; + } - // Every single watched page needs now to be listed in watchlist; - // namespace:page and namespace_talk:page need separate entries: - $dbw->insert( 'watchlist', - array( - 'wl_user' => $this->getUserId(), - 'wl_namespace' => MWNamespace::getTalk( $this->getTitleNs() ), - 'wl_title' => $this->getTitleDBkey(), - 'wl_notificationtimestamp' => null - ), __METHOD__, 'IGNORE' ); + if ( !$rows ) { + return false; + } - $this->watched = true; + $dbw = wfGetDB( DB_MASTER ); + foreach ( array_chunk( $rows, 100 ) as $toInsert ) { + // Use INSERT IGNORE to avoid overwriting the notification timestamp + // if there's already an entry for this page + $dbw->insert( 'watchlist', $toInsert, __METHOD__, 'IGNORE' ); + } - wfProfileOut( __METHOD__ ); return true; } /** + * Given a title and user (assumes the object is setup), add the watch to the database. + * @return bool + */ + public function addWatch() { + return self::batchAddWatch( array( $this ) ); + } + + /** * Same as addWatch, only the opposite. * @return bool */ @@ -288,8 +378,8 @@ class WatchedItem { * Check if the given title already is watched by the user, and if so * add watches on a new title. To be used for page renames and such. * - * @param $ot Title: page title to duplicate entries from, if present - * @param $nt Title: page title to add watches on + * @param Title $ot Page title to duplicate entries from, if present + * @param Title $nt Page title to add watches on */ public static function duplicateEntries( $ot, $nt ) { WatchedItem::doDuplicateEntries( $ot->getSubjectPage(), $nt->getSubjectPage() ); @@ -299,8 +389,8 @@ class WatchedItem { /** * Handle duplicate entries. Backend for duplicateEntries(). * - * @param $ot Title - * @param $nt Title + * @param Title $ot + * @param Title $nt * * @return bool */ @@ -333,7 +423,13 @@ class WatchedItem { # Perform replace # Note that multi-row replace is very efficient for MySQL but may be inefficient for # some other DBMSes, mostly due to poor simulation by us - $dbw->replace( 'watchlist', array( array( 'wl_user', 'wl_namespace', 'wl_title' ) ), $values, __METHOD__ ); + $dbw->replace( + 'watchlist', + array( array( 'wl_user', 'wl_namespace', 'wl_title' ) ), + $values, + __METHOD__ + ); + return true; } } |