MediaWiki
REL1_20
|
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 }