MediaWiki
REL1_23
|
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 00111 public static function newFromCurRow( $row ) { 00112 wfDeprecated( __METHOD__, '1.22' ); 00113 $rc = new RecentChange; 00114 $rc->loadFromCurRow( $row ); 00115 $rc->notificationtimestamp = false; 00116 $rc->numberofWatchingusers = false; 00117 00118 return $rc; 00119 } 00120 00127 public static function newFromId( $rcid ) { 00128 return self::newFromConds( array( 'rc_id' => $rcid ), __METHOD__ ); 00129 } 00130 00139 public static function newFromConds( $conds, $fname = __METHOD__, $options = array() ) { 00140 $dbr = wfGetDB( DB_SLAVE ); 00141 $row = $dbr->selectRow( 'recentchanges', self::selectFields(), $conds, $fname, $options ); 00142 if ( $row !== false ) { 00143 return self::newFromRow( $row ); 00144 } else { 00145 return null; 00146 } 00147 } 00148 00154 public static function selectFields() { 00155 return array( 00156 'rc_id', 00157 'rc_timestamp', 00158 'rc_user', 00159 'rc_user_text', 00160 'rc_namespace', 00161 'rc_title', 00162 'rc_comment', 00163 'rc_minor', 00164 'rc_bot', 00165 'rc_new', 00166 'rc_cur_id', 00167 'rc_this_oldid', 00168 'rc_last_oldid', 00169 'rc_type', 00170 'rc_source', 00171 'rc_patrolled', 00172 'rc_ip', 00173 'rc_old_len', 00174 'rc_new_len', 00175 'rc_deleted', 00176 'rc_logid', 00177 'rc_log_type', 00178 'rc_log_action', 00179 'rc_params', 00180 ); 00181 } 00182 00183 # Accessors 00184 00188 public function setAttribs( $attribs ) { 00189 $this->mAttribs = $attribs; 00190 } 00191 00195 public function setExtra( $extra ) { 00196 $this->mExtra = $extra; 00197 } 00198 00203 public function &getTitle() { 00204 if ( $this->mTitle === false ) { 00205 $this->mTitle = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] ); 00206 } 00207 00208 return $this->mTitle; 00209 } 00210 00216 public function getPerformer() { 00217 if ( $this->mPerformer === false ) { 00218 if ( $this->mAttribs['rc_user'] ) { 00219 $this->mPerformer = User::newFromID( $this->mAttribs['rc_user'] ); 00220 } else { 00221 $this->mPerformer = User::newFromName( $this->mAttribs['rc_user_text'], false ); 00222 } 00223 } 00224 00225 return $this->mPerformer; 00226 } 00227 00232 public function save( $noudp = false ) { 00233 global $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker, $wgContLang; 00234 00235 $dbw = wfGetDB( DB_MASTER ); 00236 if ( !is_array( $this->mExtra ) ) { 00237 $this->mExtra = array(); 00238 } 00239 00240 if ( !$wgPutIPinRC ) { 00241 $this->mAttribs['rc_ip'] = ''; 00242 } 00243 00244 # If our database is strict about IP addresses, use NULL instead of an empty string 00245 if ( $dbw->strictIPs() and $this->mAttribs['rc_ip'] == '' ) { 00246 unset( $this->mAttribs['rc_ip'] ); 00247 } 00248 00249 # Trim spaces on user supplied text 00250 $this->mAttribs['rc_comment'] = trim( $this->mAttribs['rc_comment'] ); 00251 00252 # Make sure summary is truncated (whole multibyte characters) 00253 $this->mAttribs['rc_comment'] = $wgContLang->truncate( $this->mAttribs['rc_comment'], 255 ); 00254 00255 # Fixup database timestamps 00256 $this->mAttribs['rc_timestamp'] = $dbw->timestamp( $this->mAttribs['rc_timestamp'] ); 00257 $this->mAttribs['rc_id'] = $dbw->nextSequenceValue( 'recentchanges_rc_id_seq' ); 00258 00259 ## If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL 00260 if ( $dbw->cascadingDeletes() and $this->mAttribs['rc_cur_id'] == 0 ) { 00261 unset( $this->mAttribs['rc_cur_id'] ); 00262 } 00263 00264 # Insert new row 00265 $dbw->insert( 'recentchanges', $this->mAttribs, __METHOD__ ); 00266 00267 # Set the ID 00268 $this->mAttribs['rc_id'] = $dbw->insertId(); 00269 00270 # Notify extensions 00271 wfRunHooks( 'RecentChange_save', array( &$this ) ); 00272 00273 # Notify external application via UDP 00274 if ( !$noudp ) { 00275 $this->notifyRCFeeds(); 00276 } 00277 00278 # E-mail notifications 00279 if ( $wgUseEnotif || $wgShowUpdatedMarker ) { 00280 $editor = $this->getPerformer(); 00281 $title = $this->getTitle(); 00282 00283 if ( wfRunHooks( 'AbortEmailNotification', array( $editor, $title ) ) ) { 00284 # @todo FIXME: This would be better as an extension hook 00285 $enotif = new EmailNotification(); 00286 $enotif->notifyOnPageChange( $editor, $title, 00287 $this->mAttribs['rc_timestamp'], 00288 $this->mAttribs['rc_comment'], 00289 $this->mAttribs['rc_minor'], 00290 $this->mAttribs['rc_last_oldid'], 00291 $this->mExtra['pageStatus'] ); 00292 } 00293 } 00294 } 00295 00299 public function notifyRC2UDP() { 00300 wfDeprecated( __METHOD__, '1.22' ); 00301 $this->notifyRCFeeds(); 00302 } 00303 00308 public static function sendToUDP( $line, $address = '', $prefix = '', $port = '' ) { 00309 global $wgRC2UDPAddress, $wgRC2UDPInterwikiPrefix, $wgRC2UDPPort, $wgRC2UDPPrefix; 00310 00311 wfDeprecated( __METHOD__, '1.22' ); 00312 00313 # Assume default for standard RC case 00314 $address = $address ? $address : $wgRC2UDPAddress; 00315 $prefix = $prefix ? $prefix : $wgRC2UDPPrefix; 00316 $port = $port ? $port : $wgRC2UDPPort; 00317 00318 $engine = new UDPRCFeedEngine(); 00319 $feed = array( 00320 'uri' => "udp://$address:$port/$prefix", 00321 'formatter' => 'IRCColourfulRCFeedFormatter', 00322 'add_interwiki_prefix' => $wgRC2UDPInterwikiPrefix, 00323 ); 00324 00325 $engine->send( $feed, $line ); 00326 } 00327 00331 public function notifyRCFeeds() { 00332 global $wgRCFeeds; 00333 00334 $performer = $this->getPerformer(); 00335 00336 foreach ( $wgRCFeeds as $feed ) { 00337 $feed += array( 00338 'omit_bots' => false, 00339 'omit_anon' => false, 00340 'omit_user' => false, 00341 'omit_minor' => false, 00342 'omit_patrolled' => false, 00343 ); 00344 00345 if ( 00346 ( $feed['omit_bots'] && $this->mAttribs['rc_bot'] ) || 00347 ( $feed['omit_anon'] && $performer->isAnon() ) || 00348 ( $feed['omit_user'] && !$performer->isAnon() ) || 00349 ( $feed['omit_minor'] && $this->mAttribs['rc_minor'] ) || 00350 ( $feed['omit_patrolled'] && $this->mAttribs['rc_patrolled'] ) || 00351 $this->mAttribs['rc_type'] == RC_EXTERNAL 00352 ) { 00353 continue; 00354 } 00355 00356 $engine = self::getEngine( $feed['uri'] ); 00357 00358 if ( isset( $this->mExtra['actionCommentIRC'] ) ) { 00359 $actionComment = $this->mExtra['actionCommentIRC']; 00360 } else { 00361 $actionComment = null; 00362 } 00363 00365 $formatter = new $feed['formatter'](); 00366 $line = $formatter->getLine( $feed, $this, $actionComment ); 00367 00368 $engine->send( $feed, $line ); 00369 } 00370 } 00371 00379 public static function getEngine( $uri ) { 00380 global $wgRCEngines; 00381 00382 $scheme = parse_url( $uri, PHP_URL_SCHEME ); 00383 if ( !$scheme ) { 00384 throw new MWException( __FUNCTION__ . ": Invalid stream logger URI: '$uri'" ); 00385 } 00386 00387 if ( !isset( $wgRCEngines[$scheme] ) ) { 00388 throw new MWException( __FUNCTION__ . ": Unknown stream logger URI scheme: $scheme" ); 00389 } 00390 00391 return new $wgRCEngines[$scheme]; 00392 } 00393 00397 public static function cleanupForIRC( $text ) { 00398 wfDeprecated( __METHOD__, '1.22' ); 00399 00400 return IRCColourfulRCFeedFormatter::cleanupForIRC( $text ); 00401 } 00402 00410 public static function markPatrolled( $change, $auto = false ) { 00411 global $wgUser; 00412 00413 $change = $change instanceof RecentChange 00414 ? $change 00415 : RecentChange::newFromId( $change ); 00416 00417 if ( !$change instanceof RecentChange ) { 00418 return null; 00419 } 00420 00421 return $change->doMarkPatrolled( $wgUser, $auto ); 00422 } 00423 00433 public function doMarkPatrolled( User $user, $auto = false ) { 00434 global $wgUseRCPatrol, $wgUseNPPatrol; 00435 $errors = array(); 00436 // If recentchanges patrol is disabled, only new pages 00437 // can be patrolled 00438 if ( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) ) { 00439 $errors[] = array( 'rcpatroldisabled' ); 00440 } 00441 // Automatic patrol needs "autopatrol", ordinary patrol needs "patrol" 00442 $right = $auto ? 'autopatrol' : 'patrol'; 00443 $errors = array_merge( $errors, $this->getTitle()->getUserPermissionsErrors( $right, $user ) ); 00444 if ( !wfRunHooks( 'MarkPatrolled', array( $this->getAttribute( 'rc_id' ), &$user, false ) ) ) { 00445 $errors[] = array( 'hookaborted' ); 00446 } 00447 // Users without the 'autopatrol' right can't patrol their 00448 // own revisions 00449 if ( $user->getName() == $this->getAttribute( 'rc_user_text' ) 00450 && !$user->isAllowed( 'autopatrol' ) 00451 ) { 00452 $errors[] = array( 'markedaspatrollederror-noautopatrol' ); 00453 } 00454 if ( $errors ) { 00455 return $errors; 00456 } 00457 // If the change was patrolled already, do nothing 00458 if ( $this->getAttribute( 'rc_patrolled' ) ) { 00459 return array(); 00460 } 00461 // Actually set the 'patrolled' flag in RC 00462 $this->reallyMarkPatrolled(); 00463 // Log this patrol event 00464 PatrolLog::record( $this, $auto, $user ); 00465 wfRunHooks( 'MarkPatrolledComplete', array( $this->getAttribute( 'rc_id' ), &$user, false ) ); 00466 00467 return array(); 00468 } 00469 00474 public function reallyMarkPatrolled() { 00475 $dbw = wfGetDB( DB_MASTER ); 00476 $dbw->update( 00477 'recentchanges', 00478 array( 00479 'rc_patrolled' => 1 00480 ), 00481 array( 00482 'rc_id' => $this->getAttribute( 'rc_id' ) 00483 ), 00484 __METHOD__ 00485 ); 00486 // Invalidate the page cache after the page has been patrolled 00487 // to make sure that the Patrol link isn't visible any longer! 00488 $this->getTitle()->invalidateCache(); 00489 00490 return $dbw->affectedRows(); 00491 } 00492 00511 public static function notifyEdit( $timestamp, &$title, $minor, &$user, $comment, $oldId, 00512 $lastTimestamp, $bot, $ip = '', $oldSize = 0, $newSize = 0, $newId = 0, $patrol = 0 ) { 00513 $rc = new RecentChange; 00514 $rc->mTitle = $title; 00515 $rc->mPerformer = $user; 00516 $rc->mAttribs = array( 00517 'rc_timestamp' => $timestamp, 00518 'rc_namespace' => $title->getNamespace(), 00519 'rc_title' => $title->getDBkey(), 00520 'rc_type' => RC_EDIT, 00521 'rc_source' => self::SRC_EDIT, 00522 'rc_minor' => $minor ? 1 : 0, 00523 'rc_cur_id' => $title->getArticleID(), 00524 'rc_user' => $user->getId(), 00525 'rc_user_text' => $user->getName(), 00526 'rc_comment' => $comment, 00527 'rc_this_oldid' => $newId, 00528 'rc_last_oldid' => $oldId, 00529 'rc_bot' => $bot ? 1 : 0, 00530 'rc_ip' => self::checkIPAddress( $ip ), 00531 'rc_patrolled' => intval( $patrol ), 00532 'rc_new' => 0, # obsolete 00533 'rc_old_len' => $oldSize, 00534 'rc_new_len' => $newSize, 00535 'rc_deleted' => 0, 00536 'rc_logid' => 0, 00537 'rc_log_type' => null, 00538 'rc_log_action' => '', 00539 'rc_params' => '' 00540 ); 00541 00542 $rc->mExtra = array( 00543 'prefixedDBkey' => $title->getPrefixedDBkey(), 00544 'lastTimestamp' => $lastTimestamp, 00545 'oldSize' => $oldSize, 00546 'newSize' => $newSize, 00547 'pageStatus' => 'changed' 00548 ); 00549 $rc->save(); 00550 00551 return $rc; 00552 } 00553 00570 public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot, 00571 $ip = '', $size = 0, $newId = 0, $patrol = 0 ) { 00572 $rc = new RecentChange; 00573 $rc->mTitle = $title; 00574 $rc->mPerformer = $user; 00575 $rc->mAttribs = array( 00576 'rc_timestamp' => $timestamp, 00577 'rc_namespace' => $title->getNamespace(), 00578 'rc_title' => $title->getDBkey(), 00579 'rc_type' => RC_NEW, 00580 'rc_source' => self::SRC_NEW, 00581 'rc_minor' => $minor ? 1 : 0, 00582 'rc_cur_id' => $title->getArticleID(), 00583 'rc_user' => $user->getId(), 00584 'rc_user_text' => $user->getName(), 00585 'rc_comment' => $comment, 00586 'rc_this_oldid' => $newId, 00587 'rc_last_oldid' => 0, 00588 'rc_bot' => $bot ? 1 : 0, 00589 'rc_ip' => self::checkIPAddress( $ip ), 00590 'rc_patrolled' => intval( $patrol ), 00591 'rc_new' => 1, # obsolete 00592 'rc_old_len' => 0, 00593 'rc_new_len' => $size, 00594 'rc_deleted' => 0, 00595 'rc_logid' => 0, 00596 'rc_log_type' => null, 00597 'rc_log_action' => '', 00598 'rc_params' => '' 00599 ); 00600 00601 $rc->mExtra = array( 00602 'prefixedDBkey' => $title->getPrefixedDBkey(), 00603 'lastTimestamp' => 0, 00604 'oldSize' => 0, 00605 'newSize' => $size, 00606 'pageStatus' => 'created' 00607 ); 00608 $rc->save(); 00609 00610 return $rc; 00611 } 00612 00628 public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip, $type, 00629 $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '' 00630 ) { 00631 global $wgLogRestrictions; 00632 00633 # Don't add private logs to RC! 00634 if ( isset( $wgLogRestrictions[$type] ) && $wgLogRestrictions[$type] != '*' ) { 00635 return false; 00636 } 00637 $rc = self::newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action, 00638 $target, $logComment, $params, $newId, $actionCommentIRC ); 00639 $rc->save(); 00640 00641 return true; 00642 } 00643 00659 public static function newLogEntry( $timestamp, &$title, &$user, $actionComment, $ip, 00660 $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '' ) { 00661 global $wgRequest; 00662 00663 ## Get pageStatus for email notification 00664 switch ( $type . '-' . $action ) { 00665 case 'delete-delete': 00666 $pageStatus = 'deleted'; 00667 break; 00668 case 'move-move': 00669 case 'move-move_redir': 00670 $pageStatus = 'moved'; 00671 break; 00672 case 'delete-restore': 00673 $pageStatus = 'restored'; 00674 break; 00675 case 'upload-upload': 00676 $pageStatus = 'created'; 00677 break; 00678 case 'upload-overwrite': 00679 default: 00680 $pageStatus = 'changed'; 00681 break; 00682 } 00683 00684 $rc = new RecentChange; 00685 $rc->mTitle = $target; 00686 $rc->mPerformer = $user; 00687 $rc->mAttribs = array( 00688 'rc_timestamp' => $timestamp, 00689 'rc_namespace' => $target->getNamespace(), 00690 'rc_title' => $target->getDBkey(), 00691 'rc_type' => RC_LOG, 00692 'rc_source' => self::SRC_LOG, 00693 'rc_minor' => 0, 00694 'rc_cur_id' => $target->getArticleID(), 00695 'rc_user' => $user->getId(), 00696 'rc_user_text' => $user->getName(), 00697 'rc_comment' => $logComment, 00698 'rc_this_oldid' => 0, 00699 'rc_last_oldid' => 0, 00700 'rc_bot' => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot', true ) : 0, 00701 'rc_ip' => self::checkIPAddress( $ip ), 00702 'rc_patrolled' => 1, 00703 'rc_new' => 0, # obsolete 00704 'rc_old_len' => null, 00705 'rc_new_len' => null, 00706 'rc_deleted' => 0, 00707 'rc_logid' => $newId, 00708 'rc_log_type' => $type, 00709 'rc_log_action' => $action, 00710 'rc_params' => $params 00711 ); 00712 00713 $rc->mExtra = array( 00714 'prefixedDBkey' => $title->getPrefixedDBkey(), 00715 'lastTimestamp' => 0, 00716 'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage 00717 'pageStatus' => $pageStatus, 00718 'actionCommentIRC' => $actionCommentIRC 00719 ); 00720 00721 return $rc; 00722 } 00723 00729 public function loadFromRow( $row ) { 00730 $this->mAttribs = get_object_vars( $row ); 00731 $this->mAttribs['rc_timestamp'] = wfTimestamp( TS_MW, $this->mAttribs['rc_timestamp'] ); 00732 $this->mAttribs['rc_deleted'] = $row->rc_deleted; // MUST be set 00733 } 00734 00741 public function loadFromCurRow( $row ) { 00742 wfDeprecated( __METHOD__, '1.22' ); 00743 $this->mAttribs = array( 00744 'rc_timestamp' => wfTimestamp( TS_MW, $row->rev_timestamp ), 00745 'rc_user' => $row->rev_user, 00746 'rc_user_text' => $row->rev_user_text, 00747 'rc_namespace' => $row->page_namespace, 00748 'rc_title' => $row->page_title, 00749 'rc_comment' => $row->rev_comment, 00750 'rc_minor' => $row->rev_minor_edit ? 1 : 0, 00751 'rc_type' => $row->page_is_new ? RC_NEW : RC_EDIT, 00752 'rc_source' => $row->page_is_new ? self::SRC_NEW : self::SRC_EDIT, 00753 'rc_cur_id' => $row->page_id, 00754 'rc_this_oldid' => $row->rev_id, 00755 'rc_last_oldid' => isset( $row->rc_last_oldid ) ? $row->rc_last_oldid : 0, 00756 'rc_bot' => 0, 00757 'rc_ip' => '', 00758 'rc_id' => $row->rc_id, 00759 'rc_patrolled' => $row->rc_patrolled, 00760 'rc_new' => $row->page_is_new, # obsolete 00761 'rc_old_len' => $row->rc_old_len, 00762 'rc_new_len' => $row->rc_new_len, 00763 'rc_params' => isset( $row->rc_params ) ? $row->rc_params : '', 00764 'rc_log_type' => isset( $row->rc_log_type ) ? $row->rc_log_type : null, 00765 'rc_log_action' => isset( $row->rc_log_action ) ? $row->rc_log_action : null, 00766 'rc_logid' => isset( $row->rc_logid ) ? $row->rc_logid : 0, 00767 'rc_deleted' => $row->rc_deleted // MUST be set 00768 ); 00769 } 00770 00777 public function getAttribute( $name ) { 00778 return isset( $this->mAttribs[$name] ) ? $this->mAttribs[$name] : null; 00779 } 00780 00784 public function getAttributes() { 00785 return $this->mAttribs; 00786 } 00787 00794 public function diffLinkTrail( $forceCur ) { 00795 if ( $this->mAttribs['rc_type'] == RC_EDIT ) { 00796 $trail = "curid=" . (int)( $this->mAttribs['rc_cur_id'] ) . 00797 "&oldid=" . (int)( $this->mAttribs['rc_last_oldid'] ); 00798 if ( $forceCur ) { 00799 $trail .= '&diff=0'; 00800 } else { 00801 $trail .= '&diff=' . (int)( $this->mAttribs['rc_this_oldid'] ); 00802 } 00803 } else { 00804 $trail = ''; 00805 } 00806 00807 return $trail; 00808 } 00809 00817 public function getCharacterDifference( $old = 0, $new = 0 ) { 00818 if ( $old === 0 ) { 00819 $old = $this->mAttribs['rc_old_len']; 00820 } 00821 if ( $new === 0 ) { 00822 $new = $this->mAttribs['rc_new_len']; 00823 } 00824 if ( $old === null || $new === null ) { 00825 return ''; 00826 } 00827 00828 return ChangesList::showCharacterDifference( $old, $new ); 00829 } 00830 00835 public static function purgeExpiredChanges() { 00836 if ( wfReadOnly() ) { 00837 return; 00838 } 00839 00840 $method = __METHOD__; 00841 $dbw = wfGetDB( DB_MASTER ); 00842 $dbw->onTransactionIdle( function () use ( $dbw, $method ) { 00843 global $wgRCMaxAge; 00844 00845 $cutoff = $dbw->timestamp( time() - $wgRCMaxAge ); 00846 $dbw->delete( 00847 'recentchanges', 00848 array( 'rc_timestamp < ' . $dbw->addQuotes( $cutoff ) ), 00849 $method 00850 ); 00851 } ); 00852 } 00853 00854 private static function checkIPAddress( $ip ) { 00855 global $wgRequest; 00856 if ( $ip ) { 00857 if ( !IP::isIPAddress( $ip ) ) { 00858 throw new MWException( "Attempt to write \"" . $ip . 00859 "\" as an IP address into recent changes" ); 00860 } 00861 } else { 00862 $ip = $wgRequest->getIP(); 00863 if ( !$ip ) { 00864 $ip = ''; 00865 } 00866 } 00867 00868 return $ip; 00869 } 00870 00880 public static function isInRCLifespan( $timestamp, $tolerance = 0 ) { 00881 global $wgRCMaxAge; 00882 00883 return wfTimestamp( TS_UNIX, $timestamp ) > time() - $tolerance - $wgRCMaxAge; 00884 } 00885 }