[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |