MediaWiki
REL1_24
|
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 }