MediaWiki  REL1_22
RecentChange.php
Go to the documentation of this file.
00001 <?php
00066 class RecentChange {
00067     var $mAttribs = array(), $mExtra = array();
00068 
00072     var $mTitle = false;
00073 
00077     private $mPerformer = false;
00078 
00082     var $mMovedToTitle = false;
00083     var $numberofWatchingusers = 0; # Dummy to prevent error message in SpecialRecentchangeslinked
00084     var $notificationtimestamp;
00085 
00086     # Factory methods
00087 
00092     public static function newFromRow( $row ) {
00093         $rc = new RecentChange;
00094         $rc->loadFromRow( $row );
00095         return $rc;
00096     }
00097 
00103     public static function newFromCurRow( $row ) {
00104         wfDeprecated( __METHOD__, '1.22' );
00105         $rc = new RecentChange;
00106         $rc->loadFromCurRow( $row );
00107         $rc->notificationtimestamp = false;
00108         $rc->numberofWatchingusers = false;
00109         return $rc;
00110     }
00111 
00118     public static function newFromId( $rcid ) {
00119         return self::newFromConds( array( 'rc_id' => $rcid ), __METHOD__ );
00120     }
00121 
00130     public static function newFromConds( $conds, $fname = __METHOD__, $options = array() ) {
00131         $dbr = wfGetDB( DB_SLAVE );
00132         $row = $dbr->selectRow( 'recentchanges', self::selectFields(), $conds, $fname, $options );
00133         if ( $row !== false ) {
00134             return self::newFromRow( $row );
00135         } else {
00136             return null;
00137         }
00138     }
00139 
00145     public static function selectFields() {
00146         return array(
00147             'rc_id',
00148             'rc_timestamp',
00149             'rc_cur_time',
00150             'rc_user',
00151             'rc_user_text',
00152             'rc_namespace',
00153             'rc_title',
00154             'rc_comment',
00155             'rc_minor',
00156             'rc_bot',
00157             'rc_new',
00158             'rc_cur_id',
00159             'rc_this_oldid',
00160             'rc_last_oldid',
00161             'rc_type',
00162             'rc_patrolled',
00163             'rc_ip',
00164             'rc_old_len',
00165             'rc_new_len',
00166             'rc_deleted',
00167             'rc_logid',
00168             'rc_log_type',
00169             'rc_log_action',
00170             'rc_params',
00171         );
00172     }
00173 
00174     # Accessors
00175 
00179     public function setAttribs( $attribs ) {
00180         $this->mAttribs = $attribs;
00181     }
00182 
00186     public function setExtra( $extra ) {
00187         $this->mExtra = $extra;
00188     }
00189 
00194     public function &getTitle() {
00195         if ( $this->mTitle === false ) {
00196             $this->mTitle = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
00197         }
00198         return $this->mTitle;
00199     }
00200 
00206     public function getPerformer() {
00207         if ( $this->mPerformer === false ) {
00208             if ( $this->mAttribs['rc_user'] ) {
00209                 $this->mPerformer = User::newFromID( $this->mAttribs['rc_user'] );
00210             } else {
00211                 $this->mPerformer = User::newFromName( $this->mAttribs['rc_user_text'], false );
00212             }
00213         }
00214         return $this->mPerformer;
00215     }
00216 
00221     public function save( $noudp = false ) {
00222         global $wgLocalInterwiki, $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker, $wgContLang;
00223 
00224         $dbw = wfGetDB( DB_MASTER );
00225         if ( !is_array( $this->mExtra ) ) {
00226             $this->mExtra = array();
00227         }
00228         $this->mExtra['lang'] = $wgLocalInterwiki;
00229 
00230         if ( !$wgPutIPinRC ) {
00231             $this->mAttribs['rc_ip'] = '';
00232         }
00233 
00234         # If our database is strict about IP addresses, use NULL instead of an empty string
00235         if ( $dbw->strictIPs() and $this->mAttribs['rc_ip'] == '' ) {
00236             unset( $this->mAttribs['rc_ip'] );
00237         }
00238 
00239         # Trim spaces on user supplied text
00240         $this->mAttribs['rc_comment'] = trim( $this->mAttribs['rc_comment'] );
00241 
00242         # Make sure summary is truncated (whole multibyte characters)
00243         $this->mAttribs['rc_comment'] = $wgContLang->truncate( $this->mAttribs['rc_comment'], 255 );
00244 
00245         # Fixup database timestamps
00246         $this->mAttribs['rc_timestamp'] = $dbw->timestamp( $this->mAttribs['rc_timestamp'] );
00247         $this->mAttribs['rc_cur_time'] = $dbw->timestamp( $this->mAttribs['rc_cur_time'] );
00248         $this->mAttribs['rc_id'] = $dbw->nextSequenceValue( 'recentchanges_rc_id_seq' );
00249 
00250         ## If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
00251         if ( $dbw->cascadingDeletes() and $this->mAttribs['rc_cur_id'] == 0 ) {
00252             unset( $this->mAttribs['rc_cur_id'] );
00253         }
00254 
00255         # Insert new row
00256         $dbw->insert( 'recentchanges', $this->mAttribs, __METHOD__ );
00257 
00258         # Set the ID
00259         $this->mAttribs['rc_id'] = $dbw->insertId();
00260 
00261         # Notify extensions
00262         wfRunHooks( 'RecentChange_save', array( &$this ) );
00263 
00264         # Notify external application via UDP
00265         if ( !$noudp ) {
00266             $this->notifyRCFeeds();
00267         }
00268 
00269         # E-mail notifications
00270         if ( $wgUseEnotif || $wgShowUpdatedMarker ) {
00271             $editor = $this->getPerformer();
00272             $title = $this->getTitle();
00273 
00274             if ( wfRunHooks( 'AbortEmailNotification', array( $editor, $title ) ) ) {
00275                 # @todo FIXME: This would be better as an extension hook
00276                 $enotif = new EmailNotification();
00277                 $enotif->notifyOnPageChange( $editor, $title,
00278                     $this->mAttribs['rc_timestamp'],
00279                     $this->mAttribs['rc_comment'],
00280                     $this->mAttribs['rc_minor'],
00281                     $this->mAttribs['rc_last_oldid'],
00282                     $this->mExtra['pageStatus'] );
00283             }
00284         }
00285     }
00286 
00290     public function notifyRC2UDP() {
00291         wfDeprecated( __METHOD__, '1.22' );
00292         $this->notifyRCFeeds();
00293     }
00294 
00299     public static function sendToUDP( $line, $address = '', $prefix = '', $port = '' ) {
00300         global $wgRC2UDPAddress, $wgRC2UDPInterwikiPrefix, $wgRC2UDPPort, $wgRC2UDPPrefix;
00301 
00302         wfDeprecated( __METHOD__, '1.22' );
00303 
00304         # Assume default for standard RC case
00305         $address = $address ? $address : $wgRC2UDPAddress;
00306         $prefix = $prefix ? $prefix : $wgRC2UDPPrefix;
00307         $port = $port ? $port : $wgRC2UDPPort;
00308 
00309         $engine = new UDPRCFeedEngine();
00310         $feed = array(
00311             'uri' => "udp://$address:$port/$prefix",
00312             'formatter' => 'IRCColourfulRCFeedFormatter',
00313             'add_interwiki_prefix' => $wgRC2UDPInterwikiPrefix,
00314         );
00315 
00316         return $engine->send( $feed, $line );
00317     }
00318 
00322     public function notifyRCFeeds() {
00323         global $wgRCFeeds;
00324 
00325         foreach ( $wgRCFeeds as $feed ) {
00326             $engine = self::getEngine( $feed['uri'] );
00327 
00328             if ( isset( $this->mExtra['actionCommentIRC'] ) ) {
00329                 $actionComment = $this->mExtra['actionCommentIRC'];
00330             } else {
00331                 $actionComment = null;
00332             }
00333 
00334             $omitBots = isset( $feed['omit_bots'] ) ? $feed['omit_bots'] : false;
00335 
00336             if (
00337                 ( $omitBots && $this->mAttribs['rc_bot'] ) ||
00338                 $this->mAttribs['rc_type'] == RC_EXTERNAL
00339             ) {
00340                 continue;
00341             }
00342 
00343             $formatter = new $feed['formatter']();
00344             $line = $formatter->getLine( $feed, $this, $actionComment );
00345 
00346             $engine->send( $feed, $line );
00347         }
00348     }
00349 
00356     private static function getEngine( $uri ) {
00357         global $wgRCEngines;
00358 
00359         $scheme = parse_url( $uri, PHP_URL_SCHEME );
00360         if ( !$scheme ) {
00361             throw new MWException( __FUNCTION__ . ": Invalid stream logger URI: '$uri'" );
00362         }
00363 
00364         if ( !isset( $wgRCEngines[$scheme] ) ) {
00365             throw new MWException( __FUNCTION__ . ": Unknown stream logger URI scheme: $scheme" );
00366         }
00367 
00368         return new $wgRCEngines[$scheme];
00369     }
00370 
00374     public static function cleanupForIRC( $text ) {
00375         wfDeprecated( __METHOD__, '1.22' );
00376         return IRCColourfulRCFeedFormatter::cleanupForIRC( $text );
00377     }
00378 
00386     public static function markPatrolled( $change, $auto = false ) {
00387         global $wgUser;
00388 
00389         $change = $change instanceof RecentChange
00390             ? $change
00391             : RecentChange::newFromId( $change );
00392 
00393         if ( !$change instanceof RecentChange ) {
00394             return null;
00395         }
00396         return $change->doMarkPatrolled( $wgUser, $auto );
00397     }
00398 
00407     public function doMarkPatrolled( User $user, $auto = false ) {
00408         global $wgUseRCPatrol, $wgUseNPPatrol;
00409         $errors = array();
00410         // If recentchanges patrol is disabled, only new pages
00411         // can be patrolled
00412         if ( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) ) {
00413             $errors[] = array( 'rcpatroldisabled' );
00414         }
00415         // Automatic patrol needs "autopatrol", ordinary patrol needs "patrol"
00416         $right = $auto ? 'autopatrol' : 'patrol';
00417         $errors = array_merge( $errors, $this->getTitle()->getUserPermissionsErrors( $right, $user ) );
00418         if ( !wfRunHooks( 'MarkPatrolled', array( $this->getAttribute( 'rc_id' ), &$user, false ) ) ) {
00419             $errors[] = array( 'hookaborted' );
00420         }
00421         // Users without the 'autopatrol' right can't patrol their
00422         // own revisions
00423         if ( $user->getName() == $this->getAttribute( 'rc_user_text' ) && !$user->isAllowed( 'autopatrol' ) ) {
00424             $errors[] = array( 'markedaspatrollederror-noautopatrol' );
00425         }
00426         if ( $errors ) {
00427             return $errors;
00428         }
00429         // If the change was patrolled already, do nothing
00430         if ( $this->getAttribute( 'rc_patrolled' ) ) {
00431             return array();
00432         }
00433         // Actually set the 'patrolled' flag in RC
00434         $this->reallyMarkPatrolled();
00435         // Log this patrol event
00436         PatrolLog::record( $this, $auto, $user );
00437         wfRunHooks( 'MarkPatrolledComplete', array( $this->getAttribute( 'rc_id' ), &$user, false ) );
00438         return array();
00439     }
00440 
00445     public function reallyMarkPatrolled() {
00446         $dbw = wfGetDB( DB_MASTER );
00447         $dbw->update(
00448             'recentchanges',
00449             array(
00450                 'rc_patrolled' => 1
00451             ),
00452             array(
00453                 'rc_id' => $this->getAttribute( 'rc_id' )
00454             ),
00455             __METHOD__
00456         );
00457         // Invalidate the page cache after the page has been patrolled
00458         // to make sure that the Patrol link isn't visible any longer!
00459         $this->getTitle()->invalidateCache();
00460         return $dbw->affectedRows();
00461     }
00462 
00481     public static function notifyEdit( $timestamp, &$title, $minor, &$user, $comment, $oldId,
00482         $lastTimestamp, $bot, $ip = '', $oldSize = 0, $newSize = 0, $newId = 0, $patrol = 0 ) {
00483         $rc = new RecentChange;
00484         $rc->mTitle = $title;
00485         $rc->mPerformer = $user;
00486         $rc->mAttribs = array(
00487             'rc_timestamp'  => $timestamp,
00488             'rc_cur_time'   => $timestamp,
00489             'rc_namespace'  => $title->getNamespace(),
00490             'rc_title'      => $title->getDBkey(),
00491             'rc_type'       => RC_EDIT,
00492             'rc_minor'      => $minor ? 1 : 0,
00493             'rc_cur_id'     => $title->getArticleID(),
00494             'rc_user'       => $user->getId(),
00495             'rc_user_text'  => $user->getName(),
00496             'rc_comment'    => $comment,
00497             'rc_this_oldid' => $newId,
00498             'rc_last_oldid' => $oldId,
00499             'rc_bot'        => $bot ? 1 : 0,
00500             'rc_ip'         => self::checkIPAddress( $ip ),
00501             'rc_patrolled'  => intval( $patrol ),
00502             'rc_new'        => 0,  # obsolete
00503             'rc_old_len'    => $oldSize,
00504             'rc_new_len'    => $newSize,
00505             'rc_deleted'    => 0,
00506             'rc_logid'      => 0,
00507             'rc_log_type'   => null,
00508             'rc_log_action' => '',
00509             'rc_params'     => ''
00510         );
00511 
00512         $rc->mExtra = array(
00513             'prefixedDBkey' => $title->getPrefixedDBkey(),
00514             'lastTimestamp' => $lastTimestamp,
00515             'oldSize'       => $oldSize,
00516             'newSize'       => $newSize,
00517             'pageStatus'   => 'changed'
00518         );
00519         $rc->save();
00520         return $rc;
00521     }
00522 
00540     public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot,
00541         $ip = '', $size = 0, $newId = 0, $patrol = 0 ) {
00542         $rc = new RecentChange;
00543         $rc->mTitle = $title;
00544         $rc->mPerformer = $user;
00545         $rc->mAttribs = array(
00546             'rc_timestamp'      => $timestamp,
00547             'rc_cur_time'       => $timestamp,
00548             'rc_namespace'      => $title->getNamespace(),
00549             'rc_title'          => $title->getDBkey(),
00550             'rc_type'           => RC_NEW,
00551             'rc_minor'          => $minor ? 1 : 0,
00552             'rc_cur_id'         => $title->getArticleID(),
00553             'rc_user'           => $user->getId(),
00554             'rc_user_text'      => $user->getName(),
00555             'rc_comment'        => $comment,
00556             'rc_this_oldid'     => $newId,
00557             'rc_last_oldid'     => 0,
00558             'rc_bot'            => $bot ? 1 : 0,
00559             'rc_ip'             => self::checkIPAddress( $ip ),
00560             'rc_patrolled'      => intval( $patrol ),
00561             'rc_new'            => 1, # obsolete
00562             'rc_old_len'        => 0,
00563             'rc_new_len'        => $size,
00564             'rc_deleted'        => 0,
00565             'rc_logid'          => 0,
00566             'rc_log_type'       => null,
00567             'rc_log_action'     => '',
00568             'rc_params'         => ''
00569         );
00570 
00571         $rc->mExtra = array(
00572             'prefixedDBkey' => $title->getPrefixedDBkey(),
00573             'lastTimestamp' => 0,
00574             'oldSize' => 0,
00575             'newSize' => $size,
00576             'pageStatus' => 'created'
00577         );
00578         $rc->save();
00579         return $rc;
00580     }
00581 
00597     public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip, $type,
00598         $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '' )
00599     {
00600         global $wgLogRestrictions;
00601         # Don't add private logs to RC!
00602         if ( isset( $wgLogRestrictions[$type] ) && $wgLogRestrictions[$type] != '*' ) {
00603             return false;
00604         }
00605         $rc = self::newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action,
00606             $target, $logComment, $params, $newId, $actionCommentIRC );
00607         $rc->save();
00608         return true;
00609     }
00610 
00626     public static function newLogEntry( $timestamp, &$title, &$user, $actionComment, $ip,
00627         $type, $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '' ) {
00628         global $wgRequest;
00629 
00630         ## Get pageStatus for email notification
00631         switch ( $type . '-' . $action ) {
00632             case 'delete-delete':
00633                 $pageStatus = 'deleted';
00634                 break;
00635             case 'move-move':
00636             case 'move-move_redir':
00637                 $pageStatus = 'moved';
00638                 break;
00639             case 'delete-restore':
00640                 $pageStatus = 'restored';
00641                 break;
00642             case 'upload-upload':
00643                 $pageStatus = 'created';
00644                 break;
00645             case 'upload-overwrite':
00646             default:
00647                 $pageStatus = 'changed';
00648                 break;
00649         }
00650 
00651         $rc = new RecentChange;
00652         $rc->mTitle = $target;
00653         $rc->mPerformer = $user;
00654         $rc->mAttribs = array(
00655             'rc_timestamp'  => $timestamp,
00656             'rc_cur_time'   => $timestamp,
00657             'rc_namespace'  => $target->getNamespace(),
00658             'rc_title'      => $target->getDBkey(),
00659             'rc_type'       => RC_LOG,
00660             'rc_minor'      => 0,
00661             'rc_cur_id'     => $target->getArticleID(),
00662             'rc_user'       => $user->getId(),
00663             'rc_user_text'  => $user->getName(),
00664             'rc_comment'    => $logComment,
00665             'rc_this_oldid' => 0,
00666             'rc_last_oldid' => 0,
00667             'rc_bot'        => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot', true ) : 0,
00668             'rc_ip'         => self::checkIPAddress( $ip ),
00669             'rc_patrolled'  => 1,
00670             'rc_new'        => 0, # obsolete
00671             'rc_old_len'    => null,
00672             'rc_new_len'    => null,
00673             'rc_deleted'    => 0,
00674             'rc_logid'      => $newId,
00675             'rc_log_type'   => $type,
00676             'rc_log_action' => $action,
00677             'rc_params'     => $params
00678         );
00679 
00680         $rc->mExtra = array(
00681             'prefixedDBkey' => $title->getPrefixedDBkey(),
00682             'lastTimestamp' => 0,
00683             'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage
00684             'pageStatus'    => $pageStatus,
00685             'actionCommentIRC' => $actionCommentIRC
00686         );
00687         return $rc;
00688     }
00689 
00695     public function loadFromRow( $row ) {
00696         $this->mAttribs = get_object_vars( $row );
00697         $this->mAttribs['rc_timestamp'] = wfTimestamp( TS_MW, $this->mAttribs['rc_timestamp'] );
00698         $this->mAttribs['rc_deleted'] = $row->rc_deleted; // MUST be set
00699     }
00700 
00707     public function loadFromCurRow( $row ) {
00708         wfDeprecated( __METHOD__, '1.22' );
00709         $this->mAttribs = array(
00710             'rc_timestamp' => wfTimestamp( TS_MW, $row->rev_timestamp ),
00711             'rc_cur_time' => $row->rev_timestamp,
00712             'rc_user' => $row->rev_user,
00713             'rc_user_text' => $row->rev_user_text,
00714             'rc_namespace' => $row->page_namespace,
00715             'rc_title' => $row->page_title,
00716             'rc_comment' => $row->rev_comment,
00717             'rc_minor' => $row->rev_minor_edit ? 1 : 0,
00718             'rc_type' => $row->page_is_new ? RC_NEW : RC_EDIT,
00719             'rc_cur_id' => $row->page_id,
00720             'rc_this_oldid' => $row->rev_id,
00721             'rc_last_oldid' => isset( $row->rc_last_oldid ) ? $row->rc_last_oldid : 0,
00722             'rc_bot' => 0,
00723             'rc_ip' => '',
00724             'rc_id' => $row->rc_id,
00725             'rc_patrolled' => $row->rc_patrolled,
00726             'rc_new' => $row->page_is_new, # obsolete
00727             'rc_old_len' => $row->rc_old_len,
00728             'rc_new_len' => $row->rc_new_len,
00729             'rc_params' => isset( $row->rc_params ) ? $row->rc_params : '',
00730             'rc_log_type' => isset( $row->rc_log_type ) ? $row->rc_log_type : null,
00731             'rc_log_action' => isset( $row->rc_log_action ) ? $row->rc_log_action : null,
00732             'rc_logid' => isset( $row->rc_logid ) ? $row->rc_logid : 0,
00733             'rc_deleted' => $row->rc_deleted // MUST be set
00734         );
00735     }
00736 
00743     public function getAttribute( $name ) {
00744         return isset( $this->mAttribs[$name] ) ? $this->mAttribs[$name] : null;
00745     }
00746 
00750     public function getAttributes() {
00751         return $this->mAttribs;
00752     }
00753 
00760     public function diffLinkTrail( $forceCur ) {
00761         if ( $this->mAttribs['rc_type'] == RC_EDIT ) {
00762             $trail = "curid=" . (int)( $this->mAttribs['rc_cur_id'] ) .
00763                 "&oldid=" . (int)( $this->mAttribs['rc_last_oldid'] );
00764             if ( $forceCur ) {
00765                 $trail .= '&diff=0';
00766             } else {
00767                 $trail .= '&diff=' . (int)( $this->mAttribs['rc_this_oldid'] );
00768             }
00769         } else {
00770             $trail = '';
00771         }
00772         return $trail;
00773     }
00774 
00782     public function getCharacterDifference( $old = 0, $new = 0 ) {
00783         if ( $old === 0 ) {
00784             $old = $this->mAttribs['rc_old_len'];
00785         }
00786         if ( $new === 0 ) {
00787             $new = $this->mAttribs['rc_new_len'];
00788         }
00789         if ( $old === null || $new === null ) {
00790             return '';
00791         }
00792         return ChangesList::showCharacterDifference( $old, $new );
00793     }
00794 
00799     public static function purgeExpiredChanges() {
00800         if ( wfReadOnly() ) {
00801             return;
00802         }
00803 
00804         $method = __METHOD__;
00805         $dbw = wfGetDB( DB_MASTER );
00806         $dbw->onTransactionIdle( function() use ( $dbw, $method ) {
00807             global $wgRCMaxAge;
00808 
00809             $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
00810             $dbw->delete(
00811                 'recentchanges',
00812                 array( 'rc_timestamp < ' . $dbw->addQuotes( $cutoff ) ),
00813                 $method
00814             );
00815         } );
00816     }
00817 
00818     private static function checkIPAddress( $ip ) {
00819         global $wgRequest;
00820         if ( $ip ) {
00821             if ( !IP::isIPAddress( $ip ) ) {
00822                 throw new MWException( "Attempt to write \"" . $ip . "\" as an IP address into recent changes" );
00823             }
00824         } else {
00825             $ip = $wgRequest->getIP();
00826             if ( !$ip ) {
00827                 $ip = '';
00828             }
00829         }
00830         return $ip;
00831     }
00832 
00842     public static function isInRCLifespan( $timestamp, $tolerance = 0 ) {
00843         global $wgRCMaxAge;
00844         return wfTimestamp( TS_UNIX, $timestamp ) > time() - $tolerance - $wgRCMaxAge;
00845     }
00846 }