MediaWiki  REL1_23
WatchedItem.php
Go to the documentation of this file.
00001 <?php
00029 class WatchedItem {
00035     const IGNORE_USER_RIGHTS = 0;
00036 
00042     const CHECK_USER_RIGHTS = 1;
00043 
00044     var $mTitle, $mUser, $mCheckRights;
00045     private $loaded = false, $watched, $timestamp;
00046 
00056     public static function fromUserTitle( $user, $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
00057         $wl = new WatchedItem;
00058         $wl->mUser = $user;
00059         $wl->mTitle = $title;
00060         $wl->mCheckRights = $checkRights;
00061 
00062         return $wl;
00063     }
00064 
00069     protected function getTitle() {
00070         return $this->mTitle;
00071     }
00072 
00077     protected function getTitleNs() {
00078         return $this->getTitle()->getNamespace();
00079     }
00080 
00085     protected function getTitleDBkey() {
00086         return $this->getTitle()->getDBkey();
00087     }
00088 
00093     protected function getUserId() {
00094         return $this->mUser->getId();
00095     }
00096 
00103     private function dbCond() {
00104         return array(
00105             'wl_user' => $this->getUserId(),
00106             'wl_namespace' => $this->getTitleNs(),
00107             'wl_title' => $this->getTitleDBkey(),
00108         );
00109     }
00110 
00114     private function load() {
00115         if ( $this->loaded ) {
00116             return;
00117         }
00118         $this->loaded = true;
00119 
00120         // Only loggedin user can have a watchlist
00121         if ( $this->mUser->isAnon() ) {
00122             $this->watched = false;
00123             return;
00124         }
00125 
00126         // some pages cannot be watched
00127         if ( !$this->getTitle()->isWatchable() ) {
00128             $this->watched = false;
00129             return;
00130         }
00131 
00132         # Pages and their talk pages are considered equivalent for watching;
00133         # remember that talk namespaces are numbered as page namespace+1.
00134 
00135         $dbr = wfGetDB( DB_SLAVE );
00136         $row = $dbr->selectRow( 'watchlist', 'wl_notificationtimestamp',
00137             $this->dbCond(), __METHOD__ );
00138 
00139         if ( $row === false ) {
00140             $this->watched = false;
00141         } else {
00142             $this->watched = true;
00143             $this->timestamp = $row->wl_notificationtimestamp;
00144         }
00145     }
00146 
00151     private function isAllowed( $what ) {
00152         return !$this->mCheckRights || $this->mUser->isAllowed( $what );
00153     }
00154 
00159     public function isWatched() {
00160         if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
00161             return false;
00162         }
00163 
00164         $this->load();
00165         return $this->watched;
00166     }
00167 
00174     public function getNotificationTimestamp() {
00175         if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
00176             return false;
00177         }
00178 
00179         $this->load();
00180         if ( $this->watched ) {
00181             return $this->timestamp;
00182         } else {
00183             return false;
00184         }
00185     }
00186 
00194     public function resetNotificationTimestamp( $force = '', $oldid = 0 ) {
00195         // Only loggedin user can have a watchlist
00196         if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
00197             return;
00198         }
00199 
00200         if ( $force != 'force' ) {
00201             $this->load();
00202             if ( !$this->watched || $this->timestamp === null ) {
00203                 return;
00204             }
00205         }
00206 
00207         $title = $this->getTitle();
00208         if ( !$oldid ) {
00209             // No oldid given, assuming latest revision; clear the timestamp.
00210             $notificationTimestamp = null;
00211         } elseif ( !$title->getNextRevisionID( $oldid ) ) {
00212             // Oldid given and is the latest revision for this title; clear the timestamp.
00213             $notificationTimestamp = null;
00214         } else {
00215             // See if the version marked as read is more recent than the one we're viewing.
00216             // Call load() if it wasn't called before due to $force.
00217             $this->load();
00218 
00219             if ( $this->timestamp === null ) {
00220                 // This can only happen if $force is enabled.
00221                 $notificationTimestamp = null;
00222             } else {
00223                 // Oldid given and isn't the latest; update the timestamp.
00224                 // This will result in no further notification emails being sent!
00225                 $dbr = wfGetDB( DB_SLAVE );
00226                 $notificationTimestamp = $dbr->selectField(
00227                     'revision', 'rev_timestamp',
00228                     array( 'rev_page' => $title->getArticleID(), 'rev_id' => $oldid )
00229                 );
00230                 // We need to go one second to the future because of various strict comparisons
00231                 // throughout the codebase
00232                 $ts = new MWTimestamp( $notificationTimestamp );
00233                 $ts->timestamp->add( new DateInterval( 'PT1S' ) );
00234                 $notificationTimestamp = $ts->getTimestamp( TS_MW );
00235 
00236                 if ( $notificationTimestamp < $this->timestamp ) {
00237                     if ( $force != 'force' ) {
00238                         return;
00239                     } else {
00240                         // This is a little silly…
00241                         $notificationTimestamp = $this->timestamp;
00242                     }
00243                 }
00244             }
00245         }
00246 
00247         // If the page is watched by the user (or may be watched), update the timestamp on any
00248         // any matching rows
00249         $dbw = wfGetDB( DB_MASTER );
00250         $dbw->update( 'watchlist', array( 'wl_notificationtimestamp' => $notificationTimestamp ),
00251             $this->dbCond(), __METHOD__ );
00252         $this->timestamp = null;
00253     }
00254 
00260     public function addWatch() {
00261         wfProfileIn( __METHOD__ );
00262 
00263         // Only loggedin user can have a watchlist
00264         if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
00265             wfProfileOut( __METHOD__ );
00266             return false;
00267         }
00268 
00269         // Use INSERT IGNORE to avoid overwriting the notification timestamp
00270         // if there's already an entry for this page
00271         $dbw = wfGetDB( DB_MASTER );
00272         $dbw->insert( 'watchlist',
00273             array(
00274                 'wl_user' => $this->getUserId(),
00275                 'wl_namespace' => MWNamespace::getSubject( $this->getTitleNs() ),
00276                 'wl_title' => $this->getTitleDBkey(),
00277                 'wl_notificationtimestamp' => null
00278             ), __METHOD__, 'IGNORE' );
00279 
00280         // Every single watched page needs now to be listed in watchlist;
00281         // namespace:page and namespace_talk:page need separate entries:
00282         $dbw->insert( 'watchlist',
00283             array(
00284                 'wl_user' => $this->getUserId(),
00285                 'wl_namespace' => MWNamespace::getTalk( $this->getTitleNs() ),
00286                 'wl_title' => $this->getTitleDBkey(),
00287                 'wl_notificationtimestamp' => null
00288             ), __METHOD__, 'IGNORE' );
00289 
00290         $this->watched = true;
00291 
00292         wfProfileOut( __METHOD__ );
00293         return true;
00294     }
00295 
00300     public function removeWatch() {
00301         wfProfileIn( __METHOD__ );
00302 
00303         // Only loggedin user can have a watchlist
00304         if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
00305             wfProfileOut( __METHOD__ );
00306             return false;
00307         }
00308 
00309         $success = false;
00310         $dbw = wfGetDB( DB_MASTER );
00311         $dbw->delete( 'watchlist',
00312             array(
00313                 'wl_user' => $this->getUserId(),
00314                 'wl_namespace' => MWNamespace::getSubject( $this->getTitleNs() ),
00315                 'wl_title' => $this->getTitleDBkey(),
00316             ), __METHOD__
00317         );
00318         if ( $dbw->affectedRows() ) {
00319             $success = true;
00320         }
00321 
00322         # the following code compensates the new behavior, introduced by the
00323         # enotif patch, that every single watched page needs now to be listed
00324         # in watchlist namespace:page and namespace_talk:page had separate
00325         # entries: clear them
00326         $dbw->delete( 'watchlist',
00327             array(
00328                 'wl_user' => $this->getUserId(),
00329                 'wl_namespace' => MWNamespace::getTalk( $this->getTitleNs() ),
00330                 'wl_title' => $this->getTitleDBkey(),
00331             ), __METHOD__
00332         );
00333 
00334         if ( $dbw->affectedRows() ) {
00335             $success = true;
00336         }
00337 
00338         $this->watched = false;
00339 
00340         wfProfileOut( __METHOD__ );
00341         return $success;
00342     }
00343 
00351     public static function duplicateEntries( $ot, $nt ) {
00352         WatchedItem::doDuplicateEntries( $ot->getSubjectPage(), $nt->getSubjectPage() );
00353         WatchedItem::doDuplicateEntries( $ot->getTalkPage(), $nt->getTalkPage() );
00354     }
00355 
00364     private static function doDuplicateEntries( $ot, $nt ) {
00365         $oldnamespace = $ot->getNamespace();
00366         $newnamespace = $nt->getNamespace();
00367         $oldtitle = $ot->getDBkey();
00368         $newtitle = $nt->getDBkey();
00369 
00370         $dbw = wfGetDB( DB_MASTER );
00371         $res = $dbw->select( 'watchlist', 'wl_user',
00372             array( 'wl_namespace' => $oldnamespace, 'wl_title' => $oldtitle ),
00373             __METHOD__, 'FOR UPDATE'
00374         );
00375         # Construct array to replace into the watchlist
00376         $values = array();
00377         foreach ( $res as $s ) {
00378             $values[] = array(
00379                 'wl_user' => $s->wl_user,
00380                 'wl_namespace' => $newnamespace,
00381                 'wl_title' => $newtitle
00382             );
00383         }
00384 
00385         if ( empty( $values ) ) {
00386             // Nothing to do
00387             return true;
00388         }
00389 
00390         # Perform replace
00391         # Note that multi-row replace is very efficient for MySQL but may be inefficient for
00392         # some other DBMSes, mostly due to poor simulation by us
00393         $dbw->replace( 'watchlist', array( array( 'wl_user', 'wl_namespace', 'wl_title' ) ), $values, __METHOD__ );
00394         return true;
00395     }
00396 }