MediaWiki
REL1_24
|
00001 <?php 00063 class RecentChange { 00064 // Constants for the rc_source field. Extensions may also have 00065 // their own source constants. 00066 const SRC_EDIT = 'mw.edit'; 00067 const SRC_NEW = 'mw.new'; 00068 const SRC_LOG = 'mw.log'; 00069 const SRC_EXTERNAL = 'mw.external'; // obsolete 00070 00071 public $mAttribs = array(); 00072 public $mExtra = array(); 00073 00077 public $mTitle = false; 00078 00082 private $mPerformer = false; 00083 00084 public $numberofWatchingusers = 0; # Dummy to prevent error message in SpecialRecentChangesLinked 00085 public $notificationtimestamp; 00086 00090 public $counter = -1; 00091 00092 # Factory methods 00093 00098 public static function newFromRow( $row ) { 00099 $rc = new RecentChange; 00100 $rc->loadFromRow( $row ); 00101 00102 return $rc; 00103 } 00104 00112 public static function parseToRCType( $type ) { 00113 if ( is_array( $type ) ) { 00114 $retval = array(); 00115 foreach ( $type as $t ) { 00116 $retval[] = RecentChange::parseToRCType( $t ); 00117 } 00118 00119 return $retval; 00120 } 00121 00122 switch ( $type ) { 00123 case 'edit': 00124 return RC_EDIT; 00125 case 'new': 00126 return RC_NEW; 00127 case 'log': 00128 return RC_LOG; 00129 case 'external': 00130 return RC_EXTERNAL; 00131 default: 00132 throw new MWException( "Unknown type '$type'" ); 00133 } 00134 } 00135 00142 public static function parseFromRCType( $rcType ) { 00143 switch ( $rcType ) { 00144 case RC_EDIT: 00145 $type = 'edit'; 00146 break; 00147 case RC_NEW: 00148 $type = 'new'; 00149 break; 00150 case RC_LOG: 00151 $type = 'log'; 00152 break; 00153 case RC_EXTERNAL: 00154 $type = 'external'; 00155 break; 00156 default: 00157 $type = "$rcType"; 00158 } 00159 00160 return $type; 00161 } 00162 00169 public static function newFromId( $rcid ) { 00170 return self::newFromConds( array( 'rc_id' => $rcid ), __METHOD__ ); 00171 } 00172 00181 public static function newFromConds( $conds, $fname = __METHOD__, $options = array() ) { 00182 $dbr = wfGetDB( DB_SLAVE ); 00183 $row = $dbr->selectRow( 'recentchanges', self::selectFields(), $conds, $fname, $options ); 00184 if ( $row !== false ) { 00185 return self::newFromRow( $row ); 00186 } else { 00187 return null; 00188 } 00189 } 00190 00196 public static function selectFields() { 00197 return array( 00198 'rc_id', 00199 'rc_timestamp', 00200 'rc_user', 00201 'rc_user_text', 00202 'rc_namespace', 00203 'rc_title', 00204 'rc_comment', 00205 'rc_minor', 00206 'rc_bot', 00207 'rc_new', 00208 'rc_cur_id', 00209 'rc_this_oldid', 00210 'rc_last_oldid', 00211 'rc_type', 00212 'rc_source', 00213 'rc_patrolled', 00214 'rc_ip', 00215 'rc_old_len', 00216 'rc_new_len', 00217 'rc_deleted', 00218 'rc_logid', 00219 'rc_log_type', 00220 'rc_log_action', 00221 'rc_params', 00222 ); 00223 } 00224 00225 # Accessors 00226 00230 public function setAttribs( $attribs ) { 00231 $this->mAttribs = $attribs; 00232 } 00233 00237 public function setExtra( $extra ) { 00238 $this->mExtra = $extra; 00239 } 00240 00244 public function &getTitle() { 00245 if ( $this->mTitle === false ) { 00246 $this->mTitle = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] ); 00247 } 00248 00249 return $this->mTitle; 00250 } 00251 00257 public function getPerformer() { 00258 if ( $this->mPerformer === false ) { 00259 if ( $this->mAttribs['rc_user'] ) { 00260 $this->mPerformer = User::newFromID( $this->mAttribs['rc_user'] ); 00261 } else { 00262 $this->mPerformer = User::newFromName( $this->mAttribs['rc_user_text'], false ); 00263 } 00264 } 00265 00266 return $this->mPerformer; 00267 } 00268 00273 public function save( $noudp = false ) { 00274 global $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker, $wgContLang; 00275 00276 $dbw = wfGetDB( DB_MASTER ); 00277 if ( !is_array( $this->mExtra ) ) { 00278 $this->mExtra = array(); 00279 } 00280 00281 if ( !$wgPutIPinRC ) { 00282 $this->mAttribs['rc_ip'] = ''; 00283 } 00284 00285 # If our database is strict about IP addresses, use NULL instead of an empty string 00286 if ( $dbw->strictIPs() and $this->mAttribs['rc_ip'] == '' ) { 00287 unset( $this->mAttribs['rc_ip'] ); 00288 } 00289 00290 # Trim spaces on user supplied text 00291 $this->mAttribs['rc_comment'] = trim( $this->mAttribs['rc_comment'] ); 00292 00293 # Make sure summary is truncated (whole multibyte characters) 00294 $this->mAttribs['rc_comment'] = $wgContLang->truncate( $this->mAttribs['rc_comment'], 255 ); 00295 00296 # Fixup database timestamps 00297 $this->mAttribs['rc_timestamp'] = $dbw->timestamp( $this->mAttribs['rc_timestamp'] ); 00298 $this->mAttribs['rc_id'] = $dbw->nextSequenceValue( 'recentchanges_rc_id_seq' ); 00299 00300 ## If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL 00301 if ( $dbw->cascadingDeletes() and $this->mAttribs['rc_cur_id'] == 0 ) { 00302 unset( $this->mAttribs['rc_cur_id'] ); 00303 } 00304 00305 # Insert new row 00306 $dbw->insert( 'recentchanges', $this->mAttribs, __METHOD__ ); 00307 00308 # Set the ID 00309 $this->mAttribs['rc_id'] = $dbw->insertId(); 00310 00311 # Notify extensions 00312 wfRunHooks( 'RecentChange_save', array( &$this ) ); 00313 00314 # Notify external application via UDP 00315 if ( !$noudp ) { 00316 $this->notifyRCFeeds(); 00317 } 00318 00319 # E-mail notifications 00320 if ( $wgUseEnotif || $wgShowUpdatedMarker ) { 00321 $editor = $this->getPerformer(); 00322 $title = $this->getTitle(); 00323 00324 if ( wfRunHooks( 'AbortEmailNotification', array( $editor, $title, $this ) ) ) { 00325 # @todo FIXME: This would be better as an extension hook 00326 $enotif = new EmailNotification(); 00327 $enotif->notifyOnPageChange( $editor, $title, 00328 $this->mAttribs['rc_timestamp'], 00329 $this->mAttribs['rc_comment'], 00330 $this->mAttribs['rc_minor'], 00331 $this->mAttribs['rc_last_oldid'], 00332 $this->mExtra['pageStatus'] ); 00333 } 00334 } 00335 } 00336 00341 public function notifyRCFeeds( array $feeds = null ) { 00342 global $wgRCFeeds; 00343 if ( $feeds === null ) { 00344 $feeds = $wgRCFeeds; 00345 } 00346 00347 $performer = $this->getPerformer(); 00348 00349 foreach ( $feeds as $feed ) { 00350 $feed += array( 00351 'omit_bots' => false, 00352 'omit_anon' => false, 00353 'omit_user' => false, 00354 'omit_minor' => false, 00355 'omit_patrolled' => false, 00356 ); 00357 00358 if ( 00359 ( $feed['omit_bots'] && $this->mAttribs['rc_bot'] ) || 00360 ( $feed['omit_anon'] && $performer->isAnon() ) || 00361 ( $feed['omit_user'] && !$performer->isAnon() ) || 00362 ( $feed['omit_minor'] && $this->mAttribs['rc_minor'] ) || 00363 ( $feed['omit_patrolled'] && $this->mAttribs['rc_patrolled'] ) || 00364 $this->mAttribs['rc_type'] == RC_EXTERNAL 00365 ) { 00366 continue; 00367 } 00368 00369 $engine = self::getEngine( $feed['uri'] ); 00370 00371 if ( isset( $this->mExtra['actionCommentIRC'] ) ) { 00372 $actionComment = $this->mExtra['actionCommentIRC']; 00373 } else { 00374 $actionComment = null; 00375 } 00376 00378 $formatter = is_object( $feed['formatter'] ) ? $feed['formatter'] : new $feed['formatter'](); 00379 $line = $formatter->getLine( $feed, $this, $actionComment ); 00380 00381 $engine->send( $feed, $line ); 00382 } 00383 } 00384 00392 public static function getEngine( $uri ) { 00393 global $wgRCEngines; 00394 00395 $scheme = parse_url( $uri, PHP_URL_SCHEME ); 00396 if ( !$scheme ) { 00397 throw new MWException( __FUNCTION__ . ": Invalid stream logger URI: '$uri'" ); 00398 } 00399 00400 if ( !isset( $wgRCEngines[$scheme] ) ) { 00401 throw new MWException( __FUNCTION__ . ": Unknown stream logger URI scheme: $scheme" ); 00402 } 00403 00404 return new $wgRCEngines[$scheme]; 00405 } 00406 00414 public static function markPatrolled( $change, $auto = false ) { 00415 global $wgUser; 00416 00417 $change = $change instanceof RecentChange 00418 ? $change 00419 : RecentChange::newFromId( $change ); 00420 00421 if ( !$change instanceof RecentChange ) { 00422 return null; 00423 } 00424 00425 return $change->doMarkPatrolled( $wgUser, $auto ); 00426 } 00427 00437 public function doMarkPatrolled( User $user, $auto = false ) { 00438 global $wgUseRCPatrol, $wgUseNPPatrol; 00439 $errors = array(); 00440 // If recentchanges patrol is disabled, only new pages 00441 // can be patrolled 00442 if ( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) ) { 00443 $errors[] = array( 'rcpatroldisabled' ); 00444 } 00445 // Automatic patrol needs "autopatrol", ordinary patrol needs "patrol" 00446 $right = $auto ? 'autopatrol' : 'patrol'; 00447 $errors = array_merge( $errors, $this->getTitle()->getUserPermissionsErrors( $right, $user ) ); 00448 if ( !wfRunHooks( 'MarkPatrolled', array( $this->getAttribute( 'rc_id' ), &$user, false ) ) ) { 00449 $errors[] = array( 'hookaborted' ); 00450 } 00451 // Users without the 'autopatrol' right can't patrol their 00452 // own revisions 00453 if ( $user->getName() == $this->getAttribute( 'rc_user_text' ) 00454 && !$user->isAllowed( 'autopatrol' ) 00455 ) { 00456 $errors[] = array( 'markedaspatrollederror-noautopatrol' ); 00457 } 00458 if ( $errors ) { 00459 return $errors; 00460 } 00461 // If the change was patrolled already, do nothing 00462 if ( $this->getAttribute( 'rc_patrolled' ) ) { 00463 return array(); 00464 } 00465 // Actually set the 'patrolled' flag in RC 00466 $this->reallyMarkPatrolled(); 00467 // Log this patrol event 00468 PatrolLog::record( $this, $auto, $user ); 00469 wfRunHooks( 'MarkPatrolledComplete', array( $this->getAttribute( 'rc_id' ), &$user, false ) ); 00470 00471 return array(); 00472 } 00473 00478 public function reallyMarkPatrolled() { 00479 $dbw = wfGetDB( DB_MASTER ); 00480 $dbw->update( 00481 'recentchanges', 00482 array( 00483 'rc_patrolled' => 1 00484 ), 00485 array( 00486 'rc_id' => $this->getAttribute( 'rc_id' ) 00487 ), 00488 __METHOD__ 00489 ); 00490 // Invalidate the page cache after the page has been patrolled 00491 // to make sure that the Patrol link isn't visible any longer! 00492 $this->getTitle()->invalidateCache(); 00493 00494 return $dbw->affectedRows(); 00495 } 00496 00515 public static function notifyEdit( $timestamp, &$title, $minor, &$user, $comment, $oldId, 00516 $lastTimestamp, $bot, $ip = '', $oldSize = 0, $newSize = 0, $newId = 0, $patrol = 0 ) { 00517 $rc = new RecentChange; 00518 $rc->mTitle = $title; 00519 $rc->mPerformer = $user; 00520 $rc->mAttribs = array( 00521 'rc_timestamp' => $timestamp, 00522 'rc_namespace' => $title->getNamespace(), 00523 'rc_title' => $title->getDBkey(), 00524 'rc_type' => RC_EDIT, 00525 'rc_source' => self::SRC_EDIT, 00526 'rc_minor' => $minor ? 1 : 0, 00527 'rc_cur_id' => $title->getArticleID(), 00528 'rc_user' => $user->getId(), 00529 'rc_user_text' => $user->getName(), 00530 'rc_comment' => $comment, 00531 'rc_this_oldid' => $newId, 00532 'rc_last_oldid' => $oldId, 00533 'rc_bot' => $bot ? 1 : 0, 00534 'rc_ip' => self::checkIPAddress( $ip ), 00535 'rc_patrolled' => intval( $patrol ), 00536 'rc_new' => 0, # obsolete 00537 'rc_old_len' => $oldSize, 00538 'rc_new_len' => $newSize, 00539 'rc_deleted' => 0, 00540 'rc_logid' => 0, 00541 'rc_log_type' => null, 00542 'rc_log_action' => '', 00543 'rc_params' => '' 00544 ); 00545 00546 $rc->mExtra = array( 00547 'prefixedDBkey' => $title->getPrefixedDBkey(), 00548 'lastTimestamp' => $lastTimestamp, 00549 'oldSize' => $oldSize, 00550 'newSize' => $newSize, 00551 'pageStatus' => 'changed' 00552 ); 00553 $rc->save(); 00554 00555 return $rc; 00556 } 00557 00574 public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot, 00575 $ip = '', $size = 0, $newId = 0, $patrol = 0 ) { 00576 $rc = new RecentChange; 00577 $rc->mTitle = $title; 00578 $rc->mPerformer = $user; 00579 $rc->mAttribs = array( 00580 'rc_timestamp' => $timestamp, 00581 'rc_namespace' => $title->getNamespace(), 00582 'rc_title' => $title->getDBkey(), 00583 'rc_type' => RC_NEW, 00584 'rc_source' => self::SRC_NEW, 00585 'rc_minor' => $minor ? 1 : 0, 00586 'rc_cur_id' => $title->getArticleID(), 00587 'rc_user' => $user->getId(), 00588 'rc_user_text' => $user->getName(), 00589 'rc_comment' => $comment, 00590 'rc_this_oldid' => $newId, 00591 'rc_last_oldid' => 0, 00592 'rc_bot' => $bot ? 1 : 0, 00593 'rc_ip' => self::checkIPAddress( $ip ), 00594 'rc_patrolled' => intval( $patrol ), 00595 'rc_new' => 1, # obsolete 00596 'rc_old_len' => 0, 00597 'rc_new_len' => $size, 00598 'rc_deleted' => 0, 00599 'rc_logid' => 0, 00600 'rc_log_type' => null, 00601 'rc_log_action' => '', 00602 'rc_params' => '' 00603 ); 00604 00605 $rc->mExtra = array( 00606 'prefixedDBkey' => $title->getPrefixedDBkey(), 00607 'lastTimestamp' => 0, 00608 'oldSize' => 0, 00609 'newSize' => $size, 00610 'pageStatus' => 'created' 00611 ); 00612 $rc->save(); 00613 00614 return $rc; 00615 } 00616 00632 public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip, $type, 00633 $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '' 00634 ) { 00635 global $wgLogRestrictions; 00636 00637 # Don't add private logs to RC! 00638 if ( isset( $wgLogRestrictions[$type] ) && $wgLogRestrictions[$type] != '*' ) { 00639 return false; 00640 } 00641 $rc = self::newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action, 00642 $target, $logComment, $params, $newId, $actionCommentIRC ); 00643 $rc->save(); 00644 00645 return true; 00646 } 00647 00663 public static function newLogEntry( $timestamp, &$title, &$user, $actionComment, $ip, 00664 $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '' ) { 00665 global $wgRequest; 00666 00667 ## Get pageStatus for email notification 00668 switch ( $type . '-' . $action ) { 00669 case 'delete-delete': 00670 $pageStatus = 'deleted'; 00671 break; 00672 case 'move-move': 00673 case 'move-move_redir': 00674 $pageStatus = 'moved'; 00675 break; 00676 case 'delete-restore': 00677 $pageStatus = 'restored'; 00678 break; 00679 case 'upload-upload': 00680 $pageStatus = 'created'; 00681 break; 00682 case 'upload-overwrite': 00683 default: 00684 $pageStatus = 'changed'; 00685 break; 00686 } 00687 00688 $rc = new RecentChange; 00689 $rc->mTitle = $target; 00690 $rc->mPerformer = $user; 00691 $rc->mAttribs = array( 00692 'rc_timestamp' => $timestamp, 00693 'rc_namespace' => $target->getNamespace(), 00694 'rc_title' => $target->getDBkey(), 00695 'rc_type' => RC_LOG, 00696 'rc_source' => self::SRC_LOG, 00697 'rc_minor' => 0, 00698 'rc_cur_id' => $target->getArticleID(), 00699 'rc_user' => $user->getId(), 00700 'rc_user_text' => $user->getName(), 00701 'rc_comment' => $logComment, 00702 'rc_this_oldid' => 0, 00703 'rc_last_oldid' => 0, 00704 'rc_bot' => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot', true ) : 0, 00705 'rc_ip' => self::checkIPAddress( $ip ), 00706 'rc_patrolled' => 1, 00707 'rc_new' => 0, # obsolete 00708 'rc_old_len' => null, 00709 'rc_new_len' => null, 00710 'rc_deleted' => 0, 00711 'rc_logid' => $newId, 00712 'rc_log_type' => $type, 00713 'rc_log_action' => $action, 00714 'rc_params' => $params 00715 ); 00716 00717 $rc->mExtra = array( 00718 'prefixedDBkey' => $title->getPrefixedDBkey(), 00719 'lastTimestamp' => 0, 00720 'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage 00721 'pageStatus' => $pageStatus, 00722 'actionCommentIRC' => $actionCommentIRC 00723 ); 00724 00725 return $rc; 00726 } 00727 00733 public function loadFromRow( $row ) { 00734 $this->mAttribs = get_object_vars( $row ); 00735 $this->mAttribs['rc_timestamp'] = wfTimestamp( TS_MW, $this->mAttribs['rc_timestamp'] ); 00736 $this->mAttribs['rc_deleted'] = $row->rc_deleted; // MUST be set 00737 } 00738 00745 public function getAttribute( $name ) { 00746 return isset( $this->mAttribs[$name] ) ? $this->mAttribs[$name] : null; 00747 } 00748 00752 public function getAttributes() { 00753 return $this->mAttribs; 00754 } 00755 00762 public function diffLinkTrail( $forceCur ) { 00763 if ( $this->mAttribs['rc_type'] == RC_EDIT ) { 00764 $trail = "curid=" . (int)( $this->mAttribs['rc_cur_id'] ) . 00765 "&oldid=" . (int)( $this->mAttribs['rc_last_oldid'] ); 00766 if ( $forceCur ) { 00767 $trail .= '&diff=0'; 00768 } else { 00769 $trail .= '&diff=' . (int)( $this->mAttribs['rc_this_oldid'] ); 00770 } 00771 } else { 00772 $trail = ''; 00773 } 00774 00775 return $trail; 00776 } 00777 00785 public function getCharacterDifference( $old = 0, $new = 0 ) { 00786 if ( $old === 0 ) { 00787 $old = $this->mAttribs['rc_old_len']; 00788 } 00789 if ( $new === 0 ) { 00790 $new = $this->mAttribs['rc_new_len']; 00791 } 00792 if ( $old === null || $new === null ) { 00793 return ''; 00794 } 00795 00796 return ChangesList::showCharacterDifference( $old, $new ); 00797 } 00798 00803 public static function purgeExpiredChanges() { 00804 if ( wfReadOnly() ) { 00805 return; 00806 } 00807 00808 $method = __METHOD__; 00809 $dbw = wfGetDB( DB_MASTER ); 00810 $dbw->onTransactionIdle( function () use ( $dbw, $method ) { 00811 global $wgRCMaxAge; 00812 00813 $cutoff = $dbw->timestamp( time() - $wgRCMaxAge ); 00814 $dbw->delete( 00815 'recentchanges', 00816 array( 'rc_timestamp < ' . $dbw->addQuotes( $cutoff ) ), 00817 $method 00818 ); 00819 } ); 00820 } 00821 00822 private static function checkIPAddress( $ip ) { 00823 global $wgRequest; 00824 if ( $ip ) { 00825 if ( !IP::isIPAddress( $ip ) ) { 00826 throw new MWException( "Attempt to write \"" . $ip . 00827 "\" as an IP address into recent changes" ); 00828 } 00829 } else { 00830 $ip = $wgRequest->getIP(); 00831 if ( !$ip ) { 00832 $ip = ''; 00833 } 00834 } 00835 00836 return $ip; 00837 } 00838 00848 public static function isInRCLifespan( $timestamp, $tolerance = 0 ) { 00849 global $wgRCMaxAge; 00850 00851 return wfTimestamp( TS_UNIX, $timestamp ) > time() - $tolerance - $wgRCMaxAge; 00852 } 00853 }