MediaWiki  REL1_20
Revision.php
Go to the documentation of this file.
00001 <?php
00026 class Revision implements IDBAccessObject {
00027         protected $mId;
00028         protected $mPage;
00029         protected $mUserText;
00030         protected $mOrigUserText;
00031         protected $mUser;
00032         protected $mMinorEdit;
00033         protected $mTimestamp;
00034         protected $mDeleted;
00035         protected $mSize;
00036         protected $mSha1;
00037         protected $mParentId;
00038         protected $mComment;
00039         protected $mText;
00040         protected $mTextRow;
00041         protected $mTitle;
00042         protected $mCurrent;
00043 
00044         // Revision deletion constants
00045         const DELETED_TEXT = 1;
00046         const DELETED_COMMENT = 2;
00047         const DELETED_USER = 4;
00048         const DELETED_RESTRICTED = 8;
00049         const SUPPRESSED_USER = 12; // convenience
00050 
00051         // Audience options for accessors
00052         const FOR_PUBLIC = 1;
00053         const FOR_THIS_USER = 2;
00054         const RAW = 3;
00055 
00068         public static function newFromId( $id, $flags = 0 ) {
00069                 return self::newFromConds( array( 'rev_id' => intval( $id ) ), $flags );
00070         }
00071 
00086         public static function newFromTitle( $title, $id = 0, $flags = null ) {
00087                 $conds = array(
00088                         'page_namespace' => $title->getNamespace(),
00089                         'page_title'     => $title->getDBkey()
00090                 );
00091                 if ( $id ) {
00092                         // Use the specified ID
00093                         $conds['rev_id'] = $id;
00094                 } else {
00095                         // Use a join to get the latest revision
00096                         $conds[] = 'rev_id=page_latest';
00097                         // Callers assume this will be up-to-date
00098                         $flags = is_int( $flags ) ? $flags : self::READ_LATEST; // b/c
00099                 }
00100                 return self::newFromConds( $conds, (int)$flags );
00101         }
00102 
00117         public static function newFromPageId( $pageId, $revId = 0, $flags = null ) {
00118                 $conds = array( 'page_id' => $pageId );
00119                 if ( $revId ) {
00120                         $conds['rev_id'] = $revId;
00121                 } else {
00122                         // Use a join to get the latest revision
00123                         $conds[] = 'rev_id = page_latest';
00124                         // Callers assume this will be up-to-date
00125                         $flags = is_int( $flags ) ? $flags : self::READ_LATEST; // b/c
00126                 }
00127                 return self::newFromConds( $conds, (int)$flags );
00128         }
00129 
00140         public static function newFromArchiveRow( $row, $overrides = array() ) {
00141                 $attribs = $overrides + array(
00142                         'page'       => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
00143                         'id'         => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
00144                         'comment'    => $row->ar_comment,
00145                         'user'       => $row->ar_user,
00146                         'user_text'  => $row->ar_user_text,
00147                         'timestamp'  => $row->ar_timestamp,
00148                         'minor_edit' => $row->ar_minor_edit,
00149                         'text_id'    => isset( $row->ar_text_id ) ? $row->ar_text_id : null,
00150                         'deleted'    => $row->ar_deleted,
00151                         'len'        => $row->ar_len,
00152                         'sha1'       => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
00153                 );
00154                 if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
00155                         // Pre-1.5 ar_text row
00156                         $attribs['text'] = self::getRevisionText( $row, 'ar_' );
00157                         if ( $attribs['text'] === false ) {
00158                                 throw new MWException( 'Unable to load text from archive row (possibly bug 22624)' );
00159                         }
00160                 }
00161                 return new self( $attribs );
00162         }
00163 
00170         public static function newFromRow( $row ) {
00171                 return new self( $row );
00172         }
00173 
00182         public static function loadFromId( $db, $id ) {
00183                 return self::loadFromConds( $db, array( 'rev_id' => intval( $id ) ) );
00184         }
00185 
00196         public static function loadFromPageId( $db, $pageid, $id = 0 ) {
00197                 $conds = array( 'rev_page' => intval( $pageid ), 'page_id'  => intval( $pageid ) );
00198                 if( $id ) {
00199                         $conds['rev_id'] = intval( $id );
00200                 } else {
00201                         $conds[] = 'rev_id=page_latest';
00202                 }
00203                 return self::loadFromConds( $db, $conds );
00204         }
00205 
00216         public static function loadFromTitle( $db, $title, $id = 0 ) {
00217                 if( $id ) {
00218                         $matchId = intval( $id );
00219                 } else {
00220                         $matchId = 'page_latest';
00221                 }
00222                 return self::loadFromConds( $db,
00223                         array( "rev_id=$matchId",
00224                                    'page_namespace' => $title->getNamespace(),
00225                                    'page_title'     => $title->getDBkey() )
00226                 );
00227         }
00228 
00239         public static function loadFromTimestamp( $db, $title, $timestamp ) {
00240                 return self::loadFromConds( $db,
00241                         array( 'rev_timestamp'  => $db->timestamp( $timestamp ),
00242                                    'page_namespace' => $title->getNamespace(),
00243                                    'page_title'     => $title->getDBkey() )
00244                 );
00245         }
00246 
00254         private static function newFromConds( $conditions, $flags = 0 ) {
00255                 $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
00256                 $rev = self::loadFromConds( $db, $conditions, $flags );
00257                 if ( is_null( $rev ) && wfGetLB()->getServerCount() > 1 ) {
00258                         if ( !( $flags & self::READ_LATEST ) ) {
00259                                 $dbw = wfGetDB( DB_MASTER );
00260                                 $rev = self::loadFromConds( $dbw, $conditions, $flags );
00261                         }
00262                 }
00263                 return $rev;
00264         }
00265 
00275         private static function loadFromConds( $db, $conditions, $flags = 0 ) {
00276                 $res = self::fetchFromConds( $db, $conditions, $flags );
00277                 if( $res ) {
00278                         $row = $res->fetchObject();
00279                         if( $row ) {
00280                                 $ret = new Revision( $row );
00281                                 return $ret;
00282                         }
00283                 }
00284                 $ret = null;
00285                 return $ret;
00286         }
00287 
00296         public static function fetchRevision( $title ) {
00297                 return self::fetchFromConds(
00298                         wfGetDB( DB_SLAVE ),
00299                         array( 'rev_id=page_latest',
00300                                    'page_namespace' => $title->getNamespace(),
00301                                    'page_title'     => $title->getDBkey() )
00302                 );
00303         }
00304 
00315         private static function fetchFromConds( $db, $conditions, $flags = 0 ) {
00316                 $fields = array_merge(
00317                         self::selectFields(),
00318                         self::selectPageFields(),
00319                         self::selectUserFields()
00320                 );
00321                 $options = array( 'LIMIT' => 1 );
00322                 if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
00323                         $options[] = 'FOR UPDATE';
00324                 }
00325                 return $db->select(
00326                         array( 'revision', 'page', 'user' ),
00327                         $fields,
00328                         $conditions,
00329                         __METHOD__,
00330                         $options,
00331                         array( 'page' => self::pageJoinCond(), 'user' => self::userJoinCond() )
00332                 );
00333         }
00334 
00341         public static function userJoinCond() {
00342                 return array( 'LEFT JOIN', array( 'rev_user != 0', 'user_id = rev_user' ) );
00343         }
00344 
00351         public static function pageJoinCond() {
00352                 return array( 'INNER JOIN', array( 'page_id = rev_page' ) );
00353         }
00354 
00360         public static function selectFields() {
00361                 return array(
00362                         'rev_id',
00363                         'rev_page',
00364                         'rev_text_id',
00365                         'rev_timestamp',
00366                         'rev_comment',
00367                         'rev_user_text',
00368                         'rev_user',
00369                         'rev_minor_edit',
00370                         'rev_deleted',
00371                         'rev_len',
00372                         'rev_parent_id',
00373                         'rev_sha1'
00374                 );
00375         }
00376 
00382         public static function selectTextFields() {
00383                 return array(
00384                         'old_text',
00385                         'old_flags'
00386                 );
00387         }
00388 
00393         public static function selectPageFields() {
00394                 return array(
00395                         'page_namespace',
00396                         'page_title',
00397                         'page_id',
00398                         'page_latest',
00399                         'page_is_redirect',
00400                         'page_len',
00401                 );
00402         }
00403 
00408         public static function selectUserFields() {
00409                 return array( 'user_name' );
00410         }
00411 
00418         public static function getParentLengths( $db, array $revIds ) {
00419                 $revLens = array();
00420                 if ( !$revIds ) {
00421                         return $revLens; // empty
00422                 }
00423                 wfProfileIn( __METHOD__ );
00424                 $res = $db->select( 'revision',
00425                         array( 'rev_id', 'rev_len' ),
00426                         array( 'rev_id' => $revIds ),
00427                         __METHOD__ );
00428                 foreach ( $res as $row ) {
00429                         $revLens[$row->rev_id] = $row->rev_len;
00430                 }
00431                 wfProfileOut( __METHOD__ );
00432                 return $revLens;
00433         }
00434 
00441         function __construct( $row ) {
00442                 if( is_object( $row ) ) {
00443                         $this->mId        = intval( $row->rev_id );
00444                         $this->mPage      = intval( $row->rev_page );
00445                         $this->mTextId    = intval( $row->rev_text_id );
00446                         $this->mComment   =         $row->rev_comment;
00447                         $this->mUser      = intval( $row->rev_user );
00448                         $this->mMinorEdit = intval( $row->rev_minor_edit );
00449                         $this->mTimestamp =         $row->rev_timestamp;
00450                         $this->mDeleted   = intval( $row->rev_deleted );
00451 
00452                         if( !isset( $row->rev_parent_id ) ) {
00453                                 $this->mParentId = is_null( $row->rev_parent_id ) ? null : 0;
00454                         } else {
00455                                 $this->mParentId  = intval( $row->rev_parent_id );
00456                         }
00457 
00458                         if( !isset( $row->rev_len ) || is_null( $row->rev_len ) ) {
00459                                 $this->mSize = null;
00460                         } else {
00461                                 $this->mSize = intval( $row->rev_len );
00462                         }
00463 
00464                         if ( !isset( $row->rev_sha1 ) ) {
00465                                 $this->mSha1 = null;
00466                         } else {
00467                                 $this->mSha1 = $row->rev_sha1;
00468                         }
00469 
00470                         if( isset( $row->page_latest ) ) {
00471                                 $this->mCurrent = ( $row->rev_id == $row->page_latest );
00472                                 $this->mTitle = Title::newFromRow( $row );
00473                         } else {
00474                                 $this->mCurrent = false;
00475                                 $this->mTitle = null;
00476                         }
00477 
00478                         // Lazy extraction...
00479                         $this->mText      = null;
00480                         if( isset( $row->old_text ) ) {
00481                                 $this->mTextRow = $row;
00482                         } else {
00483                                 // 'text' table row entry will be lazy-loaded
00484                                 $this->mTextRow = null;
00485                         }
00486 
00487                         // Use user_name for users and rev_user_text for IPs...
00488                         $this->mUserText = null; // lazy load if left null
00489                         if ( $this->mUser == 0 ) {
00490                                 $this->mUserText = $row->rev_user_text; // IP user
00491                         } elseif ( isset( $row->user_name ) ) {
00492                                 $this->mUserText = $row->user_name; // logged-in user
00493                         }
00494                         $this->mOrigUserText = $row->rev_user_text;
00495                 } elseif( is_array( $row ) ) {
00496                         // Build a new revision to be saved...
00497                         global $wgUser; // ugh
00498 
00499                         $this->mId        = isset( $row['id']         ) ? intval( $row['id']         ) : null;
00500                         $this->mPage      = isset( $row['page']       ) ? intval( $row['page']       ) : null;
00501                         $this->mTextId    = isset( $row['text_id']    ) ? intval( $row['text_id']    ) : null;
00502                         $this->mUserText  = isset( $row['user_text']  ) ? strval( $row['user_text']  ) : $wgUser->getName();
00503                         $this->mUser      = isset( $row['user']       ) ? intval( $row['user']       ) : $wgUser->getId();
00504                         $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
00505                         $this->mTimestamp = isset( $row['timestamp']  ) ? strval( $row['timestamp']  ) : wfTimestampNow();
00506                         $this->mDeleted   = isset( $row['deleted']    ) ? intval( $row['deleted']    ) : 0;
00507                         $this->mSize      = isset( $row['len']        ) ? intval( $row['len']        ) : null;
00508                         $this->mParentId  = isset( $row['parent_id']  ) ? intval( $row['parent_id']  ) : null;
00509                         $this->mSha1      = isset( $row['sha1']  )      ? strval( $row['sha1']  )      : null;
00510 
00511                         // Enforce spacing trimming on supplied text
00512                         $this->mComment   = isset( $row['comment']    ) ?  trim( strval( $row['comment'] ) ) : null;
00513                         $this->mText      = isset( $row['text']       ) ? rtrim( strval( $row['text']    ) ) : null;
00514                         $this->mTextRow   = null;
00515 
00516                         $this->mTitle     = null; # Load on demand if needed
00517                         $this->mCurrent   = false;
00518                         # If we still have no length, see it we have the text to figure it out
00519                         if ( !$this->mSize ) {
00520                                 $this->mSize = is_null( $this->mText ) ? null : strlen( $this->mText );
00521                         }
00522                         # Same for sha1
00523                         if ( $this->mSha1 === null ) {
00524                                 $this->mSha1 = is_null( $this->mText ) ? null : self::base36Sha1( $this->mText );
00525                         }
00526                 } else {
00527                         throw new MWException( 'Revision constructor passed invalid row format.' );
00528                 }
00529                 $this->mUnpatrolled = null;
00530         }
00531 
00537         public function getId() {
00538                 return $this->mId;
00539         }
00540 
00547         public function setId( $id ) {
00548                 $this->mId = $id;
00549         }
00550 
00556         public function getTextId() {
00557                 return $this->mTextId;
00558         }
00559 
00565         public function getParentId() {
00566                 return $this->mParentId;
00567         }
00568 
00574         public function getSize() {
00575                 return $this->mSize;
00576         }
00577 
00583         public function getSha1() {
00584                 return $this->mSha1;
00585         }
00586 
00594         public function getTitle() {
00595                 if( isset( $this->mTitle ) ) {
00596                         return $this->mTitle;
00597                 }
00598                 if( !is_null( $this->mId ) ) { //rev_id is defined as NOT NULL
00599                         $dbr = wfGetDB( DB_SLAVE );
00600                         $row = $dbr->selectRow(
00601                                 array( 'page', 'revision' ),
00602                                 self::selectPageFields(),
00603                                 array( 'page_id=rev_page',
00604                                            'rev_id' => $this->mId ),
00605                                 __METHOD__ );
00606                         if ( $row ) {
00607                                 $this->mTitle = Title::newFromRow( $row );
00608                         }
00609                 }
00610                 return $this->mTitle;
00611         }
00612 
00618         public function setTitle( $title ) {
00619                 $this->mTitle = $title;
00620         }
00621 
00627         public function getPage() {
00628                 return $this->mPage;
00629         }
00630 
00644         public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
00645                 if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
00646                         return 0;
00647                 } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
00648                         return 0;
00649                 } else {
00650                         return $this->mUser;
00651                 }
00652         }
00653 
00659         public function getRawUser() {
00660                 return $this->mUser;
00661         }
00662 
00676         public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
00677                 if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
00678                         return '';
00679                 } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
00680                         return '';
00681                 } else {
00682                         return $this->getRawUserText();
00683                 }
00684         }
00685 
00691         public function getRawUserText() {
00692                 if ( $this->mUserText === null ) {
00693                         $this->mUserText = User::whoIs( $this->mUser ); // load on demand
00694                         if ( $this->mUserText === false ) {
00695                                 # This shouldn't happen, but it can if the wiki was recovered
00696                                 # via importing revs and there is no user table entry yet.
00697                                 $this->mUserText = $this->mOrigUserText;
00698                         }
00699                 }
00700                 return $this->mUserText;
00701         }
00702 
00716         function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
00717                 if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
00718                         return '';
00719                 } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $user ) ) {
00720                         return '';
00721                 } else {
00722                         return $this->mComment;
00723                 }
00724         }
00725 
00731         public function getRawComment() {
00732                 return $this->mComment;
00733         }
00734 
00738         public function isMinor() {
00739                 return (bool)$this->mMinorEdit;
00740         }
00741 
00745         public function isUnpatrolled() {
00746                 if( $this->mUnpatrolled !== null ) {
00747                         return $this->mUnpatrolled;
00748                 }
00749                 $dbr = wfGetDB( DB_SLAVE );
00750                 $this->mUnpatrolled = $dbr->selectField( 'recentchanges',
00751                         'rc_id',
00752                         array( // Add redundant user,timestamp condition so we can use the existing index
00753                                 'rc_user_text'  => $this->getRawUserText(),
00754                                 'rc_timestamp'  => $dbr->timestamp( $this->getTimestamp() ),
00755                                 'rc_this_oldid' => $this->getId(),
00756                                 'rc_patrolled'  => 0
00757                         ),
00758                         __METHOD__
00759                 );
00760                 return (int)$this->mUnpatrolled;
00761         }
00762 
00768         public function isDeleted( $field ) {
00769                 return ( $this->mDeleted & $field ) == $field;
00770         }
00771 
00777         public function getVisibility() {
00778                 return (int)$this->mDeleted;
00779         }
00780 
00794         public function getText( $audience = self::FOR_PUBLIC, User $user = null ) {
00795                 if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
00796                         return '';
00797                 } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
00798                         return '';
00799                 } else {
00800                         return $this->getRawText();
00801                 }
00802         }
00803 
00810         public function revText() {
00811                 wfDeprecated( __METHOD__, '1.17' );
00812                 return $this->getText( self::FOR_THIS_USER );
00813         }
00814 
00820         public function getRawText() {
00821                 if( is_null( $this->mText ) ) {
00822                         // Revision text is immutable. Load on demand:
00823                         $this->mText = $this->loadText();
00824                 }
00825                 return $this->mText;
00826         }
00827 
00831         public function getTimestamp() {
00832                 return wfTimestamp( TS_MW, $this->mTimestamp );
00833         }
00834 
00838         public function isCurrent() {
00839                 return $this->mCurrent;
00840         }
00841 
00847         public function getPrevious() {
00848                 if( $this->getTitle() ) {
00849                         $prev = $this->getTitle()->getPreviousRevisionID( $this->getId() );
00850                         if( $prev ) {
00851                                 return self::newFromTitle( $this->getTitle(), $prev );
00852                         }
00853                 }
00854                 return null;
00855         }
00856 
00862         public function getNext() {
00863                 if( $this->getTitle() ) {
00864                         $next = $this->getTitle()->getNextRevisionID( $this->getId() );
00865                         if ( $next ) {
00866                                 return self::newFromTitle( $this->getTitle(), $next );
00867                         }
00868                 }
00869                 return null;
00870         }
00871 
00879         private function getPreviousRevisionId( $db ) {
00880                 if( is_null( $this->mPage ) ) {
00881                         return 0;
00882                 }
00883                 # Use page_latest if ID is not given
00884                 if( !$this->mId ) {
00885                         $prevId = $db->selectField( 'page', 'page_latest',
00886                                 array( 'page_id' => $this->mPage ),
00887                                 __METHOD__ );
00888                 } else {
00889                         $prevId = $db->selectField( 'revision', 'rev_id',
00890                                 array( 'rev_page' => $this->mPage, 'rev_id < ' . $this->mId ),
00891                                 __METHOD__,
00892                                 array( 'ORDER BY' => 'rev_id DESC' ) );
00893                 }
00894                 return intval( $prevId );
00895         }
00896 
00906         public static function getRevisionText( $row, $prefix = 'old_' ) {
00907                 wfProfileIn( __METHOD__ );
00908 
00909                 # Get data
00910                 $textField = $prefix . 'text';
00911                 $flagsField = $prefix . 'flags';
00912 
00913                 if( isset( $row->$flagsField ) ) {
00914                         $flags = explode( ',', $row->$flagsField );
00915                 } else {
00916                         $flags = array();
00917                 }
00918 
00919                 if( isset( $row->$textField ) ) {
00920                         $text = $row->$textField;
00921                 } else {
00922                         wfProfileOut( __METHOD__ );
00923                         return false;
00924                 }
00925 
00926                 # Use external methods for external objects, text in table is URL-only then
00927                 if ( in_array( 'external', $flags ) ) {
00928                         $url = $text;
00929                         $parts = explode( '://', $url, 2 );
00930                         if( count( $parts ) == 1 || $parts[1] == '' ) {
00931                                 wfProfileOut( __METHOD__ );
00932                                 return false;
00933                         }
00934                         $text = ExternalStore::fetchFromURL( $url );
00935                 }
00936 
00937                 // If the text was fetched without an error, convert it
00938                 if ( $text !== false ) {
00939                         if( in_array( 'gzip', $flags ) ) {
00940                                 # Deal with optional compression of archived pages.
00941                                 # This can be done periodically via maintenance/compressOld.php, and
00942                                 # as pages are saved if $wgCompressRevisions is set.
00943                                 $text = gzinflate( $text );
00944                         }
00945 
00946                         if( in_array( 'object', $flags ) ) {
00947                                 # Generic compressed storage
00948                                 $obj = unserialize( $text );
00949                                 if ( !is_object( $obj ) ) {
00950                                         // Invalid object
00951                                         wfProfileOut( __METHOD__ );
00952                                         return false;
00953                                 }
00954                                 $text = $obj->getText();
00955                         }
00956 
00957                         global $wgLegacyEncoding;
00958                         if( $text !== false && $wgLegacyEncoding
00959                                 && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags ) )
00960                         {
00961                                 # Old revisions kept around in a legacy encoding?
00962                                 # Upconvert on demand.
00963                                 # ("utf8" checked for compatibility with some broken
00964                                 #  conversion scripts 2008-12-30)
00965                                 global $wgContLang;
00966                                 $text = $wgContLang->iconv( $wgLegacyEncoding, 'UTF-8', $text );
00967                         }
00968                 }
00969                 wfProfileOut( __METHOD__ );
00970                 return $text;
00971         }
00972 
00983         public static function compressRevisionText( &$text ) {
00984                 global $wgCompressRevisions;
00985                 $flags = array();
00986 
00987                 # Revisions not marked this way will be converted
00988                 # on load if $wgLegacyCharset is set in the future.
00989                 $flags[] = 'utf-8';
00990 
00991                 if( $wgCompressRevisions ) {
00992                         if( function_exists( 'gzdeflate' ) ) {
00993                                 $text = gzdeflate( $text );
00994                                 $flags[] = 'gzip';
00995                         } else {
00996                                 wfDebug( __METHOD__ . " -- no zlib support, not compressing\n" );
00997                         }
00998                 }
00999                 return implode( ',', $flags );
01000         }
01001 
01009         public function insertOn( $dbw ) {
01010                 global $wgDefaultExternalStore;
01011 
01012                 wfProfileIn( __METHOD__ );
01013 
01014                 $data = $this->mText;
01015                 $flags = self::compressRevisionText( $data );
01016 
01017                 # Write to external storage if required
01018                 if( $wgDefaultExternalStore ) {
01019                         // Store and get the URL
01020                         $data = ExternalStore::insertToDefault( $data );
01021                         if( !$data ) {
01022                                 throw new MWException( "Unable to store text to external storage" );
01023                         }
01024                         if( $flags ) {
01025                                 $flags .= ',';
01026                         }
01027                         $flags .= 'external';
01028                 }
01029 
01030                 # Record the text (or external storage URL) to the text table
01031                 if( !isset( $this->mTextId ) ) {
01032                         $old_id = $dbw->nextSequenceValue( 'text_old_id_seq' );
01033                         $dbw->insert( 'text',
01034                                 array(
01035                                         'old_id'    => $old_id,
01036                                         'old_text'  => $data,
01037                                         'old_flags' => $flags,
01038                                 ), __METHOD__
01039                         );
01040                         $this->mTextId = $dbw->insertId();
01041                 }
01042 
01043                 if ( $this->mComment === null ) $this->mComment = "";
01044 
01045                 # Record the edit in revisions
01046                 $rev_id = isset( $this->mId )
01047                         ? $this->mId
01048                         : $dbw->nextSequenceValue( 'revision_rev_id_seq' );
01049                 $dbw->insert( 'revision',
01050                         array(
01051                                 'rev_id'         => $rev_id,
01052                                 'rev_page'       => $this->mPage,
01053                                 'rev_text_id'    => $this->mTextId,
01054                                 'rev_comment'    => $this->mComment,
01055                                 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
01056                                 'rev_user'       => $this->mUser,
01057                                 'rev_user_text'  => $this->mUserText,
01058                                 'rev_timestamp'  => $dbw->timestamp( $this->mTimestamp ),
01059                                 'rev_deleted'    => $this->mDeleted,
01060                                 'rev_len'        => $this->mSize,
01061                                 'rev_parent_id'  => is_null( $this->mParentId )
01062                                         ? $this->getPreviousRevisionId( $dbw )
01063                                         : $this->mParentId,
01064                                 'rev_sha1'       => is_null( $this->mSha1 )
01065                                         ? self::base36Sha1( $this->mText )
01066                                         : $this->mSha1
01067                         ), __METHOD__
01068                 );
01069 
01070                 $this->mId = !is_null( $rev_id ) ? $rev_id : $dbw->insertId();
01071 
01072                 wfRunHooks( 'RevisionInsertComplete', array( &$this, $data, $flags ) );
01073 
01074                 wfProfileOut( __METHOD__ );
01075                 return $this->mId;
01076         }
01077 
01083         public static function base36Sha1( $text ) {
01084                 return wfBaseConvert( sha1( $text ), 16, 36, 31 );
01085         }
01086 
01093         protected function loadText() {
01094                 wfProfileIn( __METHOD__ );
01095 
01096                 // Caching may be beneficial for massive use of external storage
01097                 global $wgRevisionCacheExpiry, $wgMemc;
01098                 $textId = $this->getTextId();
01099                 $key = wfMemcKey( 'revisiontext', 'textid', $textId );
01100                 if( $wgRevisionCacheExpiry ) {
01101                         $text = $wgMemc->get( $key );
01102                         if( is_string( $text ) ) {
01103                                 wfDebug( __METHOD__ . ": got id $textId from cache\n" );
01104                                 wfProfileOut( __METHOD__ );
01105                                 return $text;
01106                         }
01107                 }
01108 
01109                 // If we kept data for lazy extraction, use it now...
01110                 if ( isset( $this->mTextRow ) ) {
01111                         $row = $this->mTextRow;
01112                         $this->mTextRow = null;
01113                 } else {
01114                         $row = null;
01115                 }
01116 
01117                 if( !$row ) {
01118                         // Text data is immutable; check slaves first.
01119                         $dbr = wfGetDB( DB_SLAVE );
01120                         $row = $dbr->selectRow( 'text',
01121                                 array( 'old_text', 'old_flags' ),
01122                                 array( 'old_id' => $this->getTextId() ),
01123                                 __METHOD__ );
01124                 }
01125 
01126                 if( !$row && wfGetLB()->getServerCount() > 1 ) {
01127                         // Possible slave lag!
01128                         $dbw = wfGetDB( DB_MASTER );
01129                         $row = $dbw->selectRow( 'text',
01130                                 array( 'old_text', 'old_flags' ),
01131                                 array( 'old_id' => $this->getTextId() ),
01132                                 __METHOD__ );
01133                 }
01134 
01135                 $text = self::getRevisionText( $row );
01136 
01137                 # No negative caching -- negative hits on text rows may be due to corrupted slave servers
01138                 if( $wgRevisionCacheExpiry && $text !== false ) {
01139                         $wgMemc->set( $key, $text, $wgRevisionCacheExpiry );
01140                 }
01141 
01142                 wfProfileOut( __METHOD__ );
01143 
01144                 return $text;
01145         }
01146 
01161         public static function newNullRevision( $dbw, $pageId, $summary, $minor ) {
01162                 wfProfileIn( __METHOD__ );
01163 
01164                 $current = $dbw->selectRow(
01165                         array( 'page', 'revision' ),
01166                         array( 'page_latest', 'page_namespace', 'page_title',
01167                                 'rev_text_id', 'rev_len', 'rev_sha1' ),
01168                         array(
01169                                 'page_id' => $pageId,
01170                                 'page_latest=rev_id',
01171                                 ),
01172                         __METHOD__ );
01173 
01174                 if( $current ) {
01175                         $revision = new Revision( array(
01176                                 'page'       => $pageId,
01177                                 'comment'    => $summary,
01178                                 'minor_edit' => $minor,
01179                                 'text_id'    => $current->rev_text_id,
01180                                 'parent_id'  => $current->page_latest,
01181                                 'len'        => $current->rev_len,
01182                                 'sha1'       => $current->rev_sha1
01183                                 ) );
01184                         $revision->setTitle( Title::makeTitle( $current->page_namespace, $current->page_title ) );
01185                 } else {
01186                         $revision = null;
01187                 }
01188 
01189                 wfProfileOut( __METHOD__ );
01190                 return $revision;
01191         }
01192 
01203         public function userCan( $field, User $user = null ) {
01204                 return self::userCanBitfield( $this->mDeleted, $field, $user );
01205         }
01206 
01219         public static function userCanBitfield( $bitfield, $field, User $user = null ) {
01220                 if( $bitfield & $field ) { // aspect is deleted
01221                         if ( $bitfield & self::DELETED_RESTRICTED ) {
01222                                 $permission = 'suppressrevision';
01223                         } elseif ( $field & self::DELETED_TEXT ) {
01224                                 $permission = 'deletedtext';
01225                         } else {
01226                                 $permission = 'deletedhistory';
01227                         }
01228                         wfDebug( "Checking for $permission due to $field match on $bitfield\n" );
01229                         if ( $user === null ) {
01230                                 global $wgUser;
01231                                 $user = $wgUser;
01232                         }
01233                         return $user->isAllowed( $permission );
01234                 } else {
01235                         return true;
01236                 }
01237         }
01238 
01246         static function getTimestampFromId( $title, $id ) {
01247                 $dbr = wfGetDB( DB_SLAVE );
01248                 // Casting fix for DB2
01249                 if ( $id == '' ) {
01250                         $id = 0;
01251                 }
01252                 $conds = array( 'rev_id' => $id );
01253                 $conds['rev_page'] = $title->getArticleID();
01254                 $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
01255                 if ( $timestamp === false && wfGetLB()->getServerCount() > 1 ) {
01256                         # Not in slave, try master
01257                         $dbw = wfGetDB( DB_MASTER );
01258                         $timestamp = $dbw->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
01259                 }
01260                 return wfTimestamp( TS_MW, $timestamp );
01261         }
01262 
01270         static function countByPageId( $db, $id ) {
01271                 $row = $db->selectRow( 'revision', array( 'revCount' => 'COUNT(*)' ),
01272                         array( 'rev_page' => $id ), __METHOD__ );
01273                 if( $row ) {
01274                         return $row->revCount;
01275                 }
01276                 return 0;
01277         }
01278 
01286         static function countByTitle( $db, $title ) {
01287                 $id = $title->getArticleID();
01288                 if( $id ) {
01289                         return self::countByPageId( $db, $id );
01290                 }
01291                 return 0;
01292         }
01293 
01309         public static function userWasLastToEdit( $db, $pageId, $userId, $since ) {
01310                 if ( !$userId ) return false;
01311 
01312                 if ( is_int( $db ) ) {
01313                         $db = wfGetDB( $db );
01314                 }
01315 
01316                 $res = $db->select( 'revision',
01317                         'rev_user',
01318                         array(
01319                                 'rev_page' => $pageId,
01320                                 'rev_timestamp > ' . $db->addQuotes( $db->timestamp( $since ) )
01321                         ),
01322                         __METHOD__,
01323                         array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ) );
01324                 foreach ( $res as $row ) {
01325                         if ( $row->rev_user != $userId ) {
01326                                 return false;
01327                         }
01328                 }
01329                 return true;
01330         }
01331 }