MediaWiki  REL1_24
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 
00045     public $mTitle;
00046 
00048     public $mUser;
00049 
00051     public $mCheckRights;
00052 
00054     private $loaded = false;
00055 
00057     private $watched;
00058 
00060     private $timestamp;
00061 
00071     public static function fromUserTitle( $user, $title,
00072         $checkRights = WatchedItem::CHECK_USER_RIGHTS
00073     ) {
00074         $wl = new WatchedItem;
00075         $wl->mUser = $user;
00076         $wl->mTitle = $title;
00077         $wl->mCheckRights = $checkRights;
00078 
00079         return $wl;
00080     }
00081 
00086     protected function getTitle() {
00087         return $this->mTitle;
00088     }
00089 
00094     protected function getTitleNs() {
00095         return $this->getTitle()->getNamespace();
00096     }
00097 
00102     protected function getTitleDBkey() {
00103         return $this->getTitle()->getDBkey();
00104     }
00105 
00110     protected function getUserId() {
00111         return $this->mUser->getId();
00112     }
00113 
00120     private function dbCond() {
00121         return array(
00122             'wl_user' => $this->getUserId(),
00123             'wl_namespace' => $this->getTitleNs(),
00124             'wl_title' => $this->getTitleDBkey(),
00125         );
00126     }
00127 
00131     private function load() {
00132         if ( $this->loaded ) {
00133             return;
00134         }
00135         $this->loaded = true;
00136 
00137         // Only loggedin user can have a watchlist
00138         if ( $this->mUser->isAnon() ) {
00139             $this->watched = false;
00140             return;
00141         }
00142 
00143         // some pages cannot be watched
00144         if ( !$this->getTitle()->isWatchable() ) {
00145             $this->watched = false;
00146             return;
00147         }
00148 
00149         # Pages and their talk pages are considered equivalent for watching;
00150         # remember that talk namespaces are numbered as page namespace+1.
00151 
00152         $dbr = wfGetDB( DB_SLAVE );
00153         $row = $dbr->selectRow( 'watchlist', 'wl_notificationtimestamp',
00154             $this->dbCond(), __METHOD__ );
00155 
00156         if ( $row === false ) {
00157             $this->watched = false;
00158         } else {
00159             $this->watched = true;
00160             $this->timestamp = $row->wl_notificationtimestamp;
00161         }
00162     }
00163 
00169     private function isAllowed( $what ) {
00170         return !$this->mCheckRights || $this->mUser->isAllowed( $what );
00171     }
00172 
00177     public function isWatched() {
00178         if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
00179             return false;
00180         }
00181 
00182         $this->load();
00183         return $this->watched;
00184     }
00185 
00192     public function getNotificationTimestamp() {
00193         if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
00194             return false;
00195         }
00196 
00197         $this->load();
00198         if ( $this->watched ) {
00199             return $this->timestamp;
00200         } else {
00201             return false;
00202         }
00203     }
00204 
00212     public function resetNotificationTimestamp( $force = '', $oldid = 0 ) {
00213         // Only loggedin user can have a watchlist
00214         if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
00215             return;
00216         }
00217 
00218         if ( $force != 'force' ) {
00219             $this->load();
00220             if ( !$this->watched || $this->timestamp === null ) {
00221                 return;
00222             }
00223         }
00224 
00225         $title = $this->getTitle();
00226         if ( !$oldid ) {
00227             // No oldid given, assuming latest revision; clear the timestamp.
00228             $notificationTimestamp = null;
00229         } elseif ( !$title->getNextRevisionID( $oldid ) ) {
00230             // Oldid given and is the latest revision for this title; clear the timestamp.
00231             $notificationTimestamp = null;
00232         } else {
00233             // See if the version marked as read is more recent than the one we're viewing.
00234             // Call load() if it wasn't called before due to $force.
00235             $this->load();
00236 
00237             if ( $this->timestamp === null ) {
00238                 // This can only happen if $force is enabled.
00239                 $notificationTimestamp = null;
00240             } else {
00241                 // Oldid given and isn't the latest; update the timestamp.
00242                 // This will result in no further notification emails being sent!
00243                 $dbr = wfGetDB( DB_SLAVE );
00244                 $notificationTimestamp = $dbr->selectField(
00245                     'revision', 'rev_timestamp',
00246                     array( 'rev_page' => $title->getArticleID(), 'rev_id' => $oldid )
00247                 );
00248                 // We need to go one second to the future because of various strict comparisons
00249                 // throughout the codebase
00250                 $ts = new MWTimestamp( $notificationTimestamp );
00251                 $ts->timestamp->add( new DateInterval( 'PT1S' ) );
00252                 $notificationTimestamp = $ts->getTimestamp( TS_MW );
00253 
00254                 if ( $notificationTimestamp < $this->timestamp ) {
00255                     if ( $force != 'force' ) {
00256                         return;
00257                     } else {
00258                         // This is a little silly…
00259                         $notificationTimestamp = $this->timestamp;
00260                     }
00261                 }
00262             }
00263         }
00264 
00265         // If the page is watched by the user (or may be watched), update the timestamp on any
00266         // any matching rows
00267         $dbw = wfGetDB( DB_MASTER );
00268         $dbw->update( 'watchlist', array( 'wl_notificationtimestamp' => $notificationTimestamp ),
00269             $this->dbCond(), __METHOD__ );
00270         $this->timestamp = null;
00271     }
00272 
00277     public static function batchAddWatch( array $items ) {
00278         $section = new ProfileSection( __METHOD__ );
00279 
00280         if ( wfReadOnly() ) {
00281             return false;
00282         }
00283 
00284         $rows = array();
00285         foreach ( $items as $item ) {
00286             // Only loggedin user can have a watchlist
00287             if ( $item->mUser->isAnon() || !$item->isAllowed( 'editmywatchlist' ) ) {
00288                 continue;
00289             }
00290             $rows[] = array(
00291                 'wl_user' => $item->getUserId(),
00292                 'wl_namespace' => MWNamespace::getSubject( $item->getTitleNs() ),
00293                 'wl_title' => $item->getTitleDBkey(),
00294                 'wl_notificationtimestamp' => null,
00295             );
00296             // Every single watched page needs now to be listed in watchlist;
00297             // namespace:page and namespace_talk:page need separate entries:
00298             $rows[] = array(
00299                 'wl_user' => $item->getUserId(),
00300                 'wl_namespace' => MWNamespace::getTalk( $item->getTitleNs() ),
00301                 'wl_title' => $item->getTitleDBkey(),
00302                 'wl_notificationtimestamp' => null
00303             );
00304             $item->watched = true;
00305         }
00306 
00307         if ( !$rows ) {
00308             return false;
00309         }
00310 
00311         $dbw = wfGetDB( DB_MASTER );
00312         foreach ( array_chunk( $rows, 100 ) as $toInsert ) {
00313             // Use INSERT IGNORE to avoid overwriting the notification timestamp
00314             // if there's already an entry for this page
00315             $dbw->insert( 'watchlist', $toInsert, __METHOD__, 'IGNORE' );
00316         }
00317 
00318         return true;
00319     }
00320 
00325     public function addWatch() {
00326         return self::batchAddWatch( array( $this ) );
00327     }
00328 
00333     public function removeWatch() {
00334         wfProfileIn( __METHOD__ );
00335 
00336         // Only loggedin user can have a watchlist
00337         if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
00338             wfProfileOut( __METHOD__ );
00339             return false;
00340         }
00341 
00342         $success = false;
00343         $dbw = wfGetDB( DB_MASTER );
00344         $dbw->delete( 'watchlist',
00345             array(
00346                 'wl_user' => $this->getUserId(),
00347                 'wl_namespace' => MWNamespace::getSubject( $this->getTitleNs() ),
00348                 'wl_title' => $this->getTitleDBkey(),
00349             ), __METHOD__
00350         );
00351         if ( $dbw->affectedRows() ) {
00352             $success = true;
00353         }
00354 
00355         # the following code compensates the new behavior, introduced by the
00356         # enotif patch, that every single watched page needs now to be listed
00357         # in watchlist namespace:page and namespace_talk:page had separate
00358         # entries: clear them
00359         $dbw->delete( 'watchlist',
00360             array(
00361                 'wl_user' => $this->getUserId(),
00362                 'wl_namespace' => MWNamespace::getTalk( $this->getTitleNs() ),
00363                 'wl_title' => $this->getTitleDBkey(),
00364             ), __METHOD__
00365         );
00366 
00367         if ( $dbw->affectedRows() ) {
00368             $success = true;
00369         }
00370 
00371         $this->watched = false;
00372 
00373         wfProfileOut( __METHOD__ );
00374         return $success;
00375     }
00376 
00384     public static function duplicateEntries( $ot, $nt ) {
00385         WatchedItem::doDuplicateEntries( $ot->getSubjectPage(), $nt->getSubjectPage() );
00386         WatchedItem::doDuplicateEntries( $ot->getTalkPage(), $nt->getTalkPage() );
00387     }
00388 
00397     private static function doDuplicateEntries( $ot, $nt ) {
00398         $oldnamespace = $ot->getNamespace();
00399         $newnamespace = $nt->getNamespace();
00400         $oldtitle = $ot->getDBkey();
00401         $newtitle = $nt->getDBkey();
00402 
00403         $dbw = wfGetDB( DB_MASTER );
00404         $res = $dbw->select( 'watchlist', 'wl_user',
00405             array( 'wl_namespace' => $oldnamespace, 'wl_title' => $oldtitle ),
00406             __METHOD__, 'FOR UPDATE'
00407         );
00408         # Construct array to replace into the watchlist
00409         $values = array();
00410         foreach ( $res as $s ) {
00411             $values[] = array(
00412                 'wl_user' => $s->wl_user,
00413                 'wl_namespace' => $newnamespace,
00414                 'wl_title' => $newtitle
00415             );
00416         }
00417 
00418         if ( empty( $values ) ) {
00419             // Nothing to do
00420             return true;
00421         }
00422 
00423         # Perform replace
00424         # Note that multi-row replace is very efficient for MySQL but may be inefficient for
00425         # some other DBMSes, mostly due to poor simulation by us
00426         $dbw->replace(
00427             'watchlist',
00428             array( array( 'wl_user', 'wl_namespace', 'wl_title' ) ),
00429             $values,
00430             __METHOD__
00431         );
00432 
00433         return true;
00434     }
00435 }