[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/ -> Revision.php (source)

   1  <?php
   2  /**
   3   * Representation of a page version.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   */
  22  
  23  /**
  24   * @todo document
  25   */
  26  class Revision implements IDBAccessObject {
  27      protected $mId;
  28  
  29      /**
  30       * @var int|null
  31       */
  32      protected $mPage;
  33      protected $mUserText;
  34      protected $mOrigUserText;
  35      protected $mUser;
  36      protected $mMinorEdit;
  37      protected $mTimestamp;
  38      protected $mDeleted;
  39      protected $mSize;
  40      protected $mSha1;
  41      protected $mParentId;
  42      protected $mComment;
  43      protected $mText;
  44      protected $mTextId;
  45  
  46      /**
  47       * @var stdClass|null
  48       */
  49      protected $mTextRow;
  50  
  51      /**
  52       * @var null|Title
  53       */
  54      protected $mTitle;
  55      protected $mCurrent;
  56      protected $mContentModel;
  57      protected $mContentFormat;
  58  
  59      /**
  60       * @var Content|null|bool
  61       */
  62      protected $mContent;
  63  
  64      /**
  65       * @var null|ContentHandler
  66       */
  67      protected $mContentHandler;
  68  
  69      /**
  70       * @var int
  71       */
  72      protected $mQueryFlags = 0;
  73  
  74      // Revision deletion constants
  75      const DELETED_TEXT = 1;
  76      const DELETED_COMMENT = 2;
  77      const DELETED_USER = 4;
  78      const DELETED_RESTRICTED = 8;
  79      const SUPPRESSED_USER = 12; // convenience
  80  
  81      // Audience options for accessors
  82      const FOR_PUBLIC = 1;
  83      const FOR_THIS_USER = 2;
  84      const RAW = 3;
  85  
  86      /**
  87       * Load a page revision from a given revision ID number.
  88       * Returns null if no such revision can be found.
  89       *
  90       * $flags include:
  91       *      Revision::READ_LATEST  : Select the data from the master
  92       *      Revision::READ_LOCKING : Select & lock the data from the master
  93       *
  94       * @param int $id
  95       * @param int $flags (optional)
  96       * @return Revision|null
  97       */
  98  	public static function newFromId( $id, $flags = 0 ) {
  99          return self::newFromConds( array( 'rev_id' => intval( $id ) ), $flags );
 100      }
 101  
 102      /**
 103       * Load either the current, or a specified, revision
 104       * that's attached to a given title. If not attached
 105       * to that title, will return null.
 106       *
 107       * $flags include:
 108       *      Revision::READ_LATEST  : Select the data from the master
 109       *      Revision::READ_LOCKING : Select & lock the data from the master
 110       *
 111       * @param Title $title
 112       * @param int $id (optional)
 113       * @param int $flags Bitfield (optional)
 114       * @return Revision|null
 115       */
 116  	public static function newFromTitle( $title, $id = 0, $flags = 0 ) {
 117          $conds = array(
 118              'page_namespace' => $title->getNamespace(),
 119              'page_title' => $title->getDBkey()
 120          );
 121          if ( $id ) {
 122              // Use the specified ID
 123              $conds['rev_id'] = $id;
 124              return self::newFromConds( $conds, (int)$flags );
 125          } else {
 126              // Use a join to get the latest revision
 127              $conds[] = 'rev_id=page_latest';
 128              $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
 129              return self::loadFromConds( $db, $conds, $flags );
 130          }
 131      }
 132  
 133      /**
 134       * Load either the current, or a specified, revision
 135       * that's attached to a given page ID.
 136       * Returns null if no such revision can be found.
 137       *
 138       * $flags include:
 139       *      Revision::READ_LATEST  : Select the data from the master (since 1.20)
 140       *      Revision::READ_LOCKING : Select & lock the data from the master
 141       *
 142       * @param int $pageId
 143       * @param int $revId (optional)
 144       * @param int $flags Bitfield (optional)
 145       * @return Revision|null
 146       */
 147  	public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) {
 148          $conds = array( 'page_id' => $pageId );
 149          if ( $revId ) {
 150              $conds['rev_id'] = $revId;
 151          } else {
 152              // Use a join to get the latest revision
 153              $conds[] = 'rev_id = page_latest';
 154          }
 155          return self::newFromConds( $conds, (int)$flags );
 156      }
 157  
 158      /**
 159       * Make a fake revision object from an archive table row. This is queried
 160       * for permissions or even inserted (as in Special:Undelete)
 161       * @todo FIXME: Should be a subclass for RevisionDelete. [TS]
 162       *
 163       * @param object $row
 164       * @param array $overrides
 165       *
 166       * @throws MWException
 167       * @return Revision
 168       */
 169  	public static function newFromArchiveRow( $row, $overrides = array() ) {
 170          global $wgContentHandlerUseDB;
 171  
 172          $attribs = $overrides + array(
 173              'page'       => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
 174              'id'         => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
 175              'comment'    => $row->ar_comment,
 176              'user'       => $row->ar_user,
 177              'user_text'  => $row->ar_user_text,
 178              'timestamp'  => $row->ar_timestamp,
 179              'minor_edit' => $row->ar_minor_edit,
 180              'text_id'    => isset( $row->ar_text_id ) ? $row->ar_text_id : null,
 181              'deleted'    => $row->ar_deleted,
 182              'len'        => $row->ar_len,
 183              'sha1'       => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
 184              'content_model'   => isset( $row->ar_content_model ) ? $row->ar_content_model : null,
 185              'content_format'  => isset( $row->ar_content_format ) ? $row->ar_content_format : null,
 186          );
 187  
 188          if ( !$wgContentHandlerUseDB ) {
 189              unset( $attribs['content_model'] );
 190              unset( $attribs['content_format'] );
 191          }
 192  
 193          if ( !isset( $attribs['title'] )
 194              && isset( $row->ar_namespace )
 195              && isset( $row->ar_title ) ) {
 196  
 197              $attribs['title'] = Title::makeTitle( $row->ar_namespace, $row->ar_title );
 198          }
 199  
 200          if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
 201              // Pre-1.5 ar_text row
 202              $attribs['text'] = self::getRevisionText( $row, 'ar_' );
 203              if ( $attribs['text'] === false ) {
 204                  throw new MWException( 'Unable to load text from archive row (possibly bug 22624)' );
 205              }
 206          }
 207          return new self( $attribs );
 208      }
 209  
 210      /**
 211       * @since 1.19
 212       *
 213       * @param object $row
 214       * @return Revision
 215       */
 216  	public static function newFromRow( $row ) {
 217          return new self( $row );
 218      }
 219  
 220      /**
 221       * Load a page revision from a given revision ID number.
 222       * Returns null if no such revision can be found.
 223       *
 224       * @param DatabaseBase $db
 225       * @param int $id
 226       * @return Revision|null
 227       */
 228  	public static function loadFromId( $db, $id ) {
 229          return self::loadFromConds( $db, array( 'rev_id' => intval( $id ) ) );
 230      }
 231  
 232      /**
 233       * Load either the current, or a specified, revision
 234       * that's attached to a given page. If not attached
 235       * to that page, will return null.
 236       *
 237       * @param DatabaseBase $db
 238       * @param int $pageid
 239       * @param int $id
 240       * @return Revision|null
 241       */
 242  	public static function loadFromPageId( $db, $pageid, $id = 0 ) {
 243          $conds = array( 'rev_page' => intval( $pageid ), 'page_id' => intval( $pageid ) );
 244          if ( $id ) {
 245              $conds['rev_id'] = intval( $id );
 246          } else {
 247              $conds[] = 'rev_id=page_latest';
 248          }
 249          return self::loadFromConds( $db, $conds );
 250      }
 251  
 252      /**
 253       * Load either the current, or a specified, revision
 254       * that's attached to a given page. If not attached
 255       * to that page, will return null.
 256       *
 257       * @param DatabaseBase $db
 258       * @param Title $title
 259       * @param int $id
 260       * @return Revision|null
 261       */
 262  	public static function loadFromTitle( $db, $title, $id = 0 ) {
 263          if ( $id ) {
 264              $matchId = intval( $id );
 265          } else {
 266              $matchId = 'page_latest';
 267          }
 268          return self::loadFromConds( $db,
 269              array(
 270                  "rev_id=$matchId",
 271                  'page_namespace' => $title->getNamespace(),
 272                  'page_title' => $title->getDBkey()
 273              )
 274          );
 275      }
 276  
 277      /**
 278       * Load the revision for the given title with the given timestamp.
 279       * WARNING: Timestamps may in some circumstances not be unique,
 280       * so this isn't the best key to use.
 281       *
 282       * @param DatabaseBase $db
 283       * @param Title $title
 284       * @param string $timestamp
 285       * @return Revision|null
 286       */
 287  	public static function loadFromTimestamp( $db, $title, $timestamp ) {
 288          return self::loadFromConds( $db,
 289              array(
 290                  'rev_timestamp' => $db->timestamp( $timestamp ),
 291                  'page_namespace' => $title->getNamespace(),
 292                  'page_title' => $title->getDBkey()
 293              )
 294          );
 295      }
 296  
 297      /**
 298       * Given a set of conditions, fetch a revision.
 299       *
 300       * @param array $conditions
 301       * @param int $flags (optional)
 302       * @return Revision|null
 303       */
 304  	private static function newFromConds( $conditions, $flags = 0 ) {
 305          $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
 306          $rev = self::loadFromConds( $db, $conditions, $flags );
 307          if ( $rev === null && wfGetLB()->getServerCount() > 1 ) {
 308              if ( !( $flags & self::READ_LATEST ) ) {
 309                  $dbw = wfGetDB( DB_MASTER );
 310                  $rev = self::loadFromConds( $dbw, $conditions, $flags );
 311              }
 312          }
 313          if ( $rev ) {
 314              $rev->mQueryFlags = $flags;
 315          }
 316          return $rev;
 317      }
 318  
 319      /**
 320       * Given a set of conditions, fetch a revision from
 321       * the given database connection.
 322       *
 323       * @param DatabaseBase $db
 324       * @param array $conditions
 325       * @param int $flags (optional)
 326       * @return Revision|null
 327       */
 328  	private static function loadFromConds( $db, $conditions, $flags = 0 ) {
 329          $res = self::fetchFromConds( $db, $conditions, $flags );
 330          if ( $res ) {
 331              $row = $res->fetchObject();
 332              if ( $row ) {
 333                  $ret = new Revision( $row );
 334                  return $ret;
 335              }
 336          }
 337          $ret = null;
 338          return $ret;
 339      }
 340  
 341      /**
 342       * Return a wrapper for a series of database rows to
 343       * fetch all of a given page's revisions in turn.
 344       * Each row can be fed to the constructor to get objects.
 345       *
 346       * @param Title $title
 347       * @return ResultWrapper
 348       */
 349  	public static function fetchRevision( $title ) {
 350          return self::fetchFromConds(
 351              wfGetDB( DB_SLAVE ),
 352              array(
 353                  'rev_id=page_latest',
 354                  'page_namespace' => $title->getNamespace(),
 355                  'page_title' => $title->getDBkey()
 356              )
 357          );
 358      }
 359  
 360      /**
 361       * Given a set of conditions, return a ResultWrapper
 362       * which will return matching database rows with the
 363       * fields necessary to build Revision objects.
 364       *
 365       * @param DatabaseBase $db
 366       * @param array $conditions
 367       * @param int $flags (optional)
 368       * @return ResultWrapper
 369       */
 370  	private static function fetchFromConds( $db, $conditions, $flags = 0 ) {
 371          $fields = array_merge(
 372              self::selectFields(),
 373              self::selectPageFields(),
 374              self::selectUserFields()
 375          );
 376          $options = array( 'LIMIT' => 1 );
 377          if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
 378              $options[] = 'FOR UPDATE';
 379          }
 380          return $db->select(
 381              array( 'revision', 'page', 'user' ),
 382              $fields,
 383              $conditions,
 384              __METHOD__,
 385              $options,
 386              array( 'page' => self::pageJoinCond(), 'user' => self::userJoinCond() )
 387          );
 388      }
 389  
 390      /**
 391       * Return the value of a select() JOIN conds array for the user table.
 392       * This will get user table rows for logged-in users.
 393       * @since 1.19
 394       * @return array
 395       */
 396  	public static function userJoinCond() {
 397          return array( 'LEFT JOIN', array( 'rev_user != 0', 'user_id = rev_user' ) );
 398      }
 399  
 400      /**
 401       * Return the value of a select() page conds array for the page table.
 402       * This will assure that the revision(s) are not orphaned from live pages.
 403       * @since 1.19
 404       * @return array
 405       */
 406  	public static function pageJoinCond() {
 407          return array( 'INNER JOIN', array( 'page_id = rev_page' ) );
 408      }
 409  
 410      /**
 411       * Return the list of revision fields that should be selected to create
 412       * a new revision.
 413       * @return array
 414       */
 415  	public static function selectFields() {
 416          global $wgContentHandlerUseDB;
 417  
 418          $fields = array(
 419              'rev_id',
 420              'rev_page',
 421              'rev_text_id',
 422              'rev_timestamp',
 423              'rev_comment',
 424              'rev_user_text',
 425              'rev_user',
 426              'rev_minor_edit',
 427              'rev_deleted',
 428              'rev_len',
 429              'rev_parent_id',
 430              'rev_sha1',
 431          );
 432  
 433          if ( $wgContentHandlerUseDB ) {
 434              $fields[] = 'rev_content_format';
 435              $fields[] = 'rev_content_model';
 436          }
 437  
 438          return $fields;
 439      }
 440  
 441      /**
 442       * Return the list of revision fields that should be selected to create
 443       * a new revision from an archive row.
 444       * @return array
 445       */
 446  	public static function selectArchiveFields() {
 447          global $wgContentHandlerUseDB;
 448          $fields = array(
 449              'ar_id',
 450              'ar_page_id',
 451              'ar_rev_id',
 452              'ar_text',
 453              'ar_text_id',
 454              'ar_timestamp',
 455              'ar_comment',
 456              'ar_user_text',
 457              'ar_user',
 458              'ar_minor_edit',
 459              'ar_deleted',
 460              'ar_len',
 461              'ar_parent_id',
 462              'ar_sha1',
 463          );
 464  
 465          if ( $wgContentHandlerUseDB ) {
 466              $fields[] = 'ar_content_format';
 467              $fields[] = 'ar_content_model';
 468          }
 469          return $fields;
 470      }
 471  
 472      /**
 473       * Return the list of text fields that should be selected to read the
 474       * revision text
 475       * @return array
 476       */
 477  	public static function selectTextFields() {
 478          return array(
 479              'old_text',
 480              'old_flags'
 481          );
 482      }
 483  
 484      /**
 485       * Return the list of page fields that should be selected from page table
 486       * @return array
 487       */
 488  	public static function selectPageFields() {
 489          return array(
 490              'page_namespace',
 491              'page_title',
 492              'page_id',
 493              'page_latest',
 494              'page_is_redirect',
 495              'page_len',
 496          );
 497      }
 498  
 499      /**
 500       * Return the list of user fields that should be selected from user table
 501       * @return array
 502       */
 503  	public static function selectUserFields() {
 504          return array( 'user_name' );
 505      }
 506  
 507      /**
 508       * Do a batched query to get the parent revision lengths
 509       * @param DatabaseBase $db
 510       * @param array $revIds
 511       * @return array
 512       */
 513  	public static function getParentLengths( $db, array $revIds ) {
 514          $revLens = array();
 515          if ( !$revIds ) {
 516              return $revLens; // empty
 517          }
 518          wfProfileIn( __METHOD__ );
 519          $res = $db->select( 'revision',
 520              array( 'rev_id', 'rev_len' ),
 521              array( 'rev_id' => $revIds ),
 522              __METHOD__ );
 523          foreach ( $res as $row ) {
 524              $revLens[$row->rev_id] = $row->rev_len;
 525          }
 526          wfProfileOut( __METHOD__ );
 527          return $revLens;
 528      }
 529  
 530      /**
 531       * Constructor
 532       *
 533       * @param object|array $row Either a database row or an array
 534       * @throws MWException
 535       * @access private
 536       */
 537  	function __construct( $row ) {
 538          if ( is_object( $row ) ) {
 539              $this->mId = intval( $row->rev_id );
 540              $this->mPage = intval( $row->rev_page );
 541              $this->mTextId = intval( $row->rev_text_id );
 542              $this->mComment = $row->rev_comment;
 543              $this->mUser = intval( $row->rev_user );
 544              $this->mMinorEdit = intval( $row->rev_minor_edit );
 545              $this->mTimestamp = $row->rev_timestamp;
 546              $this->mDeleted = intval( $row->rev_deleted );
 547  
 548              if ( !isset( $row->rev_parent_id ) ) {
 549                  $this->mParentId = null;
 550              } else {
 551                  $this->mParentId = intval( $row->rev_parent_id );
 552              }
 553  
 554              if ( !isset( $row->rev_len ) ) {
 555                  $this->mSize = null;
 556              } else {
 557                  $this->mSize = intval( $row->rev_len );
 558              }
 559  
 560              if ( !isset( $row->rev_sha1 ) ) {
 561                  $this->mSha1 = null;
 562              } else {
 563                  $this->mSha1 = $row->rev_sha1;
 564              }
 565  
 566              if ( isset( $row->page_latest ) ) {
 567                  $this->mCurrent = ( $row->rev_id == $row->page_latest );
 568                  $this->mTitle = Title::newFromRow( $row );
 569              } else {
 570                  $this->mCurrent = false;
 571                  $this->mTitle = null;
 572              }
 573  
 574              if ( !isset( $row->rev_content_model ) ) {
 575                  $this->mContentModel = null; # determine on demand if needed
 576              } else {
 577                  $this->mContentModel = strval( $row->rev_content_model );
 578              }
 579  
 580              if ( !isset( $row->rev_content_format ) ) {
 581                  $this->mContentFormat = null; # determine on demand if needed
 582              } else {
 583                  $this->mContentFormat = strval( $row->rev_content_format );
 584              }
 585  
 586              // Lazy extraction...
 587              $this->mText = null;
 588              if ( isset( $row->old_text ) ) {
 589                  $this->mTextRow = $row;
 590              } else {
 591                  // 'text' table row entry will be lazy-loaded
 592                  $this->mTextRow = null;
 593              }
 594  
 595              // Use user_name for users and rev_user_text for IPs...
 596              $this->mUserText = null; // lazy load if left null
 597              if ( $this->mUser == 0 ) {
 598                  $this->mUserText = $row->rev_user_text; // IP user
 599              } elseif ( isset( $row->user_name ) ) {
 600                  $this->mUserText = $row->user_name; // logged-in user
 601              }
 602              $this->mOrigUserText = $row->rev_user_text;
 603          } elseif ( is_array( $row ) ) {
 604              // Build a new revision to be saved...
 605              global $wgUser; // ugh
 606  
 607              # if we have a content object, use it to set the model and type
 608              if ( !empty( $row['content'] ) ) {
 609                  // @todo when is that set? test with external store setup! check out insertOn() [dk]
 610                  if ( !empty( $row['text_id'] ) ) {
 611                      throw new MWException( "Text already stored in external store (id {$row['text_id']}), " .
 612                          "can't serialize content object" );
 613                  }
 614  
 615                  $row['content_model'] = $row['content']->getModel();
 616                  # note: mContentFormat is initializes later accordingly
 617                  # note: content is serialized later in this method!
 618                  # also set text to null?
 619              }
 620  
 621              $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
 622              $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
 623              $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
 624              $this->mUserText = isset( $row['user_text'] )
 625                  ? strval( $row['user_text'] ) : $wgUser->getName();
 626              $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
 627              $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
 628              $this->mTimestamp = isset( $row['timestamp'] )
 629                  ? strval( $row['timestamp'] ) : wfTimestampNow();
 630              $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
 631              $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
 632              $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
 633              $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
 634  
 635              $this->mContentModel = isset( $row['content_model'] )
 636                  ? strval( $row['content_model'] ) : null;
 637              $this->mContentFormat = isset( $row['content_format'] )
 638                  ? strval( $row['content_format'] ) : null;
 639  
 640              // Enforce spacing trimming on supplied text
 641              $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
 642              $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
 643              $this->mTextRow = null;
 644  
 645              $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
 646  
 647              // if we have a Content object, override mText and mContentModel
 648              if ( !empty( $row['content'] ) ) {
 649                  if ( !( $row['content'] instanceof Content ) ) {
 650                      throw new MWException( '`content` field must contain a Content object.' );
 651                  }
 652  
 653                  $handler = $this->getContentHandler();
 654                  $this->mContent = $row['content'];
 655  
 656                  $this->mContentModel = $this->mContent->getModel();
 657                  $this->mContentHandler = null;
 658  
 659                  $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
 660              } elseif ( $this->mText !== null ) {
 661                  $handler = $this->getContentHandler();
 662                  $this->mContent = $handler->unserializeContent( $this->mText );
 663              }
 664  
 665              // If we have a Title object, make sure it is consistent with mPage.
 666              if ( $this->mTitle && $this->mTitle->exists() ) {
 667                  if ( $this->mPage === null ) {
 668                      // if the page ID wasn't known, set it now
 669                      $this->mPage = $this->mTitle->getArticleID();
 670                  } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
 671                      // Got different page IDs. This may be legit (e.g. during undeletion),
 672                      // but it seems worth mentioning it in the log.
 673                      wfDebug( "Page ID " . $this->mPage . " mismatches the ID " .
 674                          $this->mTitle->getArticleID() . " provided by the Title object." );
 675                  }
 676              }
 677  
 678              $this->mCurrent = false;
 679  
 680              // If we still have no length, see it we have the text to figure it out
 681              if ( !$this->mSize ) {
 682                  if ( $this->mContent !== null ) {
 683                      $this->mSize = $this->mContent->getSize();
 684                  } else {
 685                      #NOTE: this should never happen if we have either text or content object!
 686                      $this->mSize = null;
 687                  }
 688              }
 689  
 690              // Same for sha1
 691              if ( $this->mSha1 === null ) {
 692                  $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
 693              }
 694  
 695              // force lazy init
 696              $this->getContentModel();
 697              $this->getContentFormat();
 698          } else {
 699              throw new MWException( 'Revision constructor passed invalid row format.' );
 700          }
 701          $this->mUnpatrolled = null;
 702      }
 703  
 704      /**
 705       * Get revision ID
 706       *
 707       * @return int|null
 708       */
 709  	public function getId() {
 710          return $this->mId;
 711      }
 712  
 713      /**
 714       * Set the revision ID
 715       *
 716       * @since 1.19
 717       * @param int $id
 718       */
 719  	public function setId( $id ) {
 720          $this->mId = $id;
 721      }
 722  
 723      /**
 724       * Get text row ID
 725       *
 726       * @return int|null
 727       */
 728  	public function getTextId() {
 729          return $this->mTextId;
 730      }
 731  
 732      /**
 733       * Get parent revision ID (the original previous page revision)
 734       *
 735       * @return int|null
 736       */
 737  	public function getParentId() {
 738          return $this->mParentId;
 739      }
 740  
 741      /**
 742       * Returns the length of the text in this revision, or null if unknown.
 743       *
 744       * @return int|null
 745       */
 746  	public function getSize() {
 747          return $this->mSize;
 748      }
 749  
 750      /**
 751       * Returns the base36 sha1 of the text in this revision, or null if unknown.
 752       *
 753       * @return string|null
 754       */
 755  	public function getSha1() {
 756          return $this->mSha1;
 757      }
 758  
 759      /**
 760       * Returns the title of the page associated with this entry or null.
 761       *
 762       * Will do a query, when title is not set and id is given.
 763       *
 764       * @return Title|null
 765       */
 766  	public function getTitle() {
 767          if ( $this->mTitle !== null ) {
 768              return $this->mTitle;
 769          }
 770          //rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
 771          if ( $this->mId !== null ) {
 772              $dbr = wfGetDB( DB_SLAVE );
 773              $row = $dbr->selectRow(
 774                  array( 'page', 'revision' ),
 775                  self::selectPageFields(),
 776                  array( 'page_id=rev_page',
 777                      'rev_id' => $this->mId ),
 778                  __METHOD__ );
 779              if ( $row ) {
 780                  $this->mTitle = Title::newFromRow( $row );
 781              }
 782          }
 783  
 784          if ( !$this->mTitle && $this->mPage !== null && $this->mPage > 0 ) {
 785              $this->mTitle = Title::newFromID( $this->mPage );
 786          }
 787  
 788          return $this->mTitle;
 789      }
 790  
 791      /**
 792       * Set the title of the revision
 793       *
 794       * @param Title $title
 795       */
 796  	public function setTitle( $title ) {
 797          $this->mTitle = $title;
 798      }
 799  
 800      /**
 801       * Get the page ID
 802       *
 803       * @return int|null
 804       */
 805  	public function getPage() {
 806          return $this->mPage;
 807      }
 808  
 809      /**
 810       * Fetch revision's user id if it's available to the specified audience.
 811       * If the specified audience does not have access to it, zero will be
 812       * returned.
 813       *
 814       * @param int $audience One of:
 815       *   Revision::FOR_PUBLIC       to be displayed to all users
 816       *   Revision::FOR_THIS_USER    to be displayed to the given user
 817       *   Revision::RAW              get the ID regardless of permissions
 818       * @param User $user User object to check for, only if FOR_THIS_USER is passed
 819       *   to the $audience parameter
 820       * @return int
 821       */
 822  	public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
 823          if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
 824              return 0;
 825          } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
 826              return 0;
 827          } else {
 828              return $this->mUser;
 829          }
 830      }
 831  
 832      /**
 833       * Fetch revision's user id without regard for the current user's permissions
 834       *
 835       * @return string
 836       */
 837  	public function getRawUser() {
 838          return $this->mUser;
 839      }
 840  
 841      /**
 842       * Fetch revision's username if it's available to the specified audience.
 843       * If the specified audience does not have access to the username, an
 844       * empty string will be returned.
 845       *
 846       * @param int $audience One of:
 847       *   Revision::FOR_PUBLIC       to be displayed to all users
 848       *   Revision::FOR_THIS_USER    to be displayed to the given user
 849       *   Revision::RAW              get the text regardless of permissions
 850       * @param User $user User object to check for, only if FOR_THIS_USER is passed
 851       *   to the $audience parameter
 852       * @return string
 853       */
 854  	public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
 855          if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
 856              return '';
 857          } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
 858              return '';
 859          } else {
 860              return $this->getRawUserText();
 861          }
 862      }
 863  
 864      /**
 865       * Fetch revision's username without regard for view restrictions
 866       *
 867       * @return string
 868       */
 869  	public function getRawUserText() {
 870          if ( $this->mUserText === null ) {
 871              $this->mUserText = User::whoIs( $this->mUser ); // load on demand
 872              if ( $this->mUserText === false ) {
 873                  # This shouldn't happen, but it can if the wiki was recovered
 874                  # via importing revs and there is no user table entry yet.
 875                  $this->mUserText = $this->mOrigUserText;
 876              }
 877          }
 878          return $this->mUserText;
 879      }
 880  
 881      /**
 882       * Fetch revision comment if it's available to the specified audience.
 883       * If the specified audience does not have access to the comment, an
 884       * empty string will be returned.
 885       *
 886       * @param int $audience One of:
 887       *   Revision::FOR_PUBLIC       to be displayed to all users
 888       *   Revision::FOR_THIS_USER    to be displayed to the given user
 889       *   Revision::RAW              get the text regardless of permissions
 890       * @param User $user User object to check for, only if FOR_THIS_USER is passed
 891       *   to the $audience parameter
 892       * @return string
 893       */
 894  	function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
 895          if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
 896              return '';
 897          } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $user ) ) {
 898              return '';
 899          } else {
 900              return $this->mComment;
 901          }
 902      }
 903  
 904      /**
 905       * Fetch revision comment without regard for the current user's permissions
 906       *
 907       * @return string
 908       */
 909  	public function getRawComment() {
 910          return $this->mComment;
 911      }
 912  
 913      /**
 914       * @return bool
 915       */
 916  	public function isMinor() {
 917          return (bool)$this->mMinorEdit;
 918      }
 919  
 920      /**
 921       * @return int Rcid of the unpatrolled row, zero if there isn't one
 922       */
 923  	public function isUnpatrolled() {
 924          if ( $this->mUnpatrolled !== null ) {
 925              return $this->mUnpatrolled;
 926          }
 927          $rc = $this->getRecentChange();
 928          if ( $rc && $rc->getAttribute( 'rc_patrolled' ) == 0 ) {
 929              $this->mUnpatrolled = $rc->getAttribute( 'rc_id' );
 930          } else {
 931              $this->mUnpatrolled = 0;
 932          }
 933          return $this->mUnpatrolled;
 934      }
 935  
 936      /**
 937       * Get the RC object belonging to the current revision, if there's one
 938       *
 939       * @since 1.22
 940       * @return RecentChange|null
 941       */
 942  	public function getRecentChange() {
 943          $dbr = wfGetDB( DB_SLAVE );
 944          return RecentChange::newFromConds(
 945              array(
 946                  'rc_user_text' => $this->getRawUserText(),
 947                  'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
 948                  'rc_this_oldid' => $this->getId()
 949              ),
 950              __METHOD__
 951          );
 952      }
 953  
 954      /**
 955       * @param int $field One of DELETED_* bitfield constants
 956       *
 957       * @return bool
 958       */
 959  	public function isDeleted( $field ) {
 960          return ( $this->mDeleted & $field ) == $field;
 961      }
 962  
 963      /**
 964       * Get the deletion bitfield of the revision
 965       *
 966       * @return int
 967       */
 968  	public function getVisibility() {
 969          return (int)$this->mDeleted;
 970      }
 971  
 972      /**
 973       * Fetch revision text if it's available to the specified audience.
 974       * If the specified audience does not have the ability to view this
 975       * revision, an empty string will be returned.
 976       *
 977       * @param int $audience One of:
 978       *   Revision::FOR_PUBLIC       to be displayed to all users
 979       *   Revision::FOR_THIS_USER    to be displayed to the given user
 980       *   Revision::RAW              get the text regardless of permissions
 981       * @param User $user User object to check for, only if FOR_THIS_USER is passed
 982       *   to the $audience parameter
 983       *
 984       * @deprecated since 1.21, use getContent() instead
 985       * @todo Replace usage in core
 986       * @return string
 987       */
 988  	public function getText( $audience = self::FOR_PUBLIC, User $user = null ) {
 989          ContentHandler::deprecated( __METHOD__, '1.21' );
 990  
 991          $content = $this->getContent( $audience, $user );
 992          return ContentHandler::getContentText( $content ); # returns the raw content text, if applicable
 993      }
 994  
 995      /**
 996       * Fetch revision content if it's available to the specified audience.
 997       * If the specified audience does not have the ability to view this
 998       * revision, null will be returned.
 999       *
1000       * @param int $audience One of:
1001       *   Revision::FOR_PUBLIC       to be displayed to all users
1002       *   Revision::FOR_THIS_USER    to be displayed to $wgUser
1003       *   Revision::RAW              get the text regardless of permissions
1004       * @param User $user User object to check for, only if FOR_THIS_USER is passed
1005       *   to the $audience parameter
1006       * @since 1.21
1007       * @return Content|null
1008       */
1009  	public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
1010          if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
1011              return null;
1012          } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
1013              return null;
1014          } else {
1015              return $this->getContentInternal();
1016          }
1017      }
1018  
1019      /**
1020       * Fetch revision text without regard for view restrictions
1021       *
1022       * @return string
1023       *
1024       * @deprecated since 1.21. Instead, use Revision::getContent( Revision::RAW )
1025       *                         or Revision::getSerializedData() as appropriate.
1026       */
1027  	public function getRawText() {
1028          ContentHandler::deprecated( __METHOD__, "1.21" );
1029          return $this->getText( self::RAW );
1030      }
1031  
1032      /**
1033       * Fetch original serialized data without regard for view restrictions
1034       *
1035       * @since 1.21
1036       * @return string
1037       */
1038  	public function getSerializedData() {
1039          if ( $this->mText === null ) {
1040              $this->mText = $this->loadText();
1041          }
1042  
1043          return $this->mText;
1044      }
1045  
1046      /**
1047       * Gets the content object for the revision (or null on failure).
1048       *
1049       * Note that for mutable Content objects, each call to this method will return a
1050       * fresh clone.
1051       *
1052       * @since 1.21
1053       * @return Content|null The Revision's content, or null on failure.
1054       */
1055  	protected function getContentInternal() {
1056          if ( $this->mContent === null ) {
1057              // Revision is immutable. Load on demand:
1058              if ( $this->mText === null ) {
1059                  $this->mText = $this->loadText();
1060              }
1061  
1062              if ( $this->mText !== null && $this->mText !== false ) {
1063                  // Unserialize content
1064                  $handler = $this->getContentHandler();
1065                  $format = $this->getContentFormat();
1066  
1067                  $this->mContent = $handler->unserializeContent( $this->mText, $format );
1068              } else {
1069                  $this->mContent = false; // negative caching!
1070              }
1071          }
1072  
1073          // NOTE: copy() will return $this for immutable content objects
1074          return $this->mContent ? $this->mContent->copy() : null;
1075      }
1076  
1077      /**
1078       * Returns the content model for this revision.
1079       *
1080       * If no content model was stored in the database, $this->getTitle()->getContentModel() is
1081       * used to determine the content model to use. If no title is know, CONTENT_MODEL_WIKITEXT
1082       * is used as a last resort.
1083       *
1084       * @return string The content model id associated with this revision,
1085       *     see the CONTENT_MODEL_XXX constants.
1086       **/
1087  	public function getContentModel() {
1088          if ( !$this->mContentModel ) {
1089              $title = $this->getTitle();
1090              $this->mContentModel = ( $title ? $title->getContentModel() : CONTENT_MODEL_WIKITEXT );
1091  
1092              assert( !empty( $this->mContentModel ) );
1093          }
1094  
1095          return $this->mContentModel;
1096      }
1097  
1098      /**
1099       * Returns the content format for this revision.
1100       *
1101       * If no content format was stored in the database, the default format for this
1102       * revision's content model is returned.
1103       *
1104       * @return string The content format id associated with this revision,
1105       *     see the CONTENT_FORMAT_XXX constants.
1106       **/
1107  	public function getContentFormat() {
1108          if ( !$this->mContentFormat ) {
1109              $handler = $this->getContentHandler();
1110              $this->mContentFormat = $handler->getDefaultFormat();
1111  
1112              assert( !empty( $this->mContentFormat ) );
1113          }
1114  
1115          return $this->mContentFormat;
1116      }
1117  
1118      /**
1119       * Returns the content handler appropriate for this revision's content model.
1120       *
1121       * @throws MWException
1122       * @return ContentHandler
1123       */
1124  	public function getContentHandler() {
1125          if ( !$this->mContentHandler ) {
1126              $model = $this->getContentModel();
1127              $this->mContentHandler = ContentHandler::getForModelID( $model );
1128  
1129              $format = $this->getContentFormat();
1130  
1131              if ( !$this->mContentHandler->isSupportedFormat( $format ) ) {
1132                  throw new MWException( "Oops, the content format $format is not supported for "
1133                      . "this content model, $model" );
1134              }
1135          }
1136  
1137          return $this->mContentHandler;
1138      }
1139  
1140      /**
1141       * @return string
1142       */
1143  	public function getTimestamp() {
1144          return wfTimestamp( TS_MW, $this->mTimestamp );
1145      }
1146  
1147      /**
1148       * @return bool
1149       */
1150  	public function isCurrent() {
1151          return $this->mCurrent;
1152      }
1153  
1154      /**
1155       * Get previous revision for this title
1156       *
1157       * @return Revision|null
1158       */
1159  	public function getPrevious() {
1160          if ( $this->getTitle() ) {
1161              $prev = $this->getTitle()->getPreviousRevisionID( $this->getId() );
1162              if ( $prev ) {
1163                  return self::newFromTitle( $this->getTitle(), $prev );
1164              }
1165          }
1166          return null;
1167      }
1168  
1169      /**
1170       * Get next revision for this title
1171       *
1172       * @return Revision|null
1173       */
1174  	public function getNext() {
1175          if ( $this->getTitle() ) {
1176              $next = $this->getTitle()->getNextRevisionID( $this->getId() );
1177              if ( $next ) {
1178                  return self::newFromTitle( $this->getTitle(), $next );
1179              }
1180          }
1181          return null;
1182      }
1183  
1184      /**
1185       * Get previous revision Id for this page_id
1186       * This is used to populate rev_parent_id on save
1187       *
1188       * @param DatabaseBase $db
1189       * @return int
1190       */
1191  	private function getPreviousRevisionId( $db ) {
1192          if ( $this->mPage === null ) {
1193              return 0;
1194          }
1195          # Use page_latest if ID is not given
1196          if ( !$this->mId ) {
1197              $prevId = $db->selectField( 'page', 'page_latest',
1198                  array( 'page_id' => $this->mPage ),
1199                  __METHOD__ );
1200          } else {
1201              $prevId = $db->selectField( 'revision', 'rev_id',
1202                  array( 'rev_page' => $this->mPage, 'rev_id < ' . $this->mId ),
1203                  __METHOD__,
1204                  array( 'ORDER BY' => 'rev_id DESC' ) );
1205          }
1206          return intval( $prevId );
1207      }
1208  
1209      /**
1210       * Get revision text associated with an old or archive row
1211       * $row is usually an object from wfFetchRow(), both the flags and the text
1212       * field must be included.
1213       *
1214       * @param stdClass $row The text data
1215       * @param string $prefix Table prefix (default 'old_')
1216       * @param string|bool $wiki The name of the wiki to load the revision text from
1217       *   (same as the the wiki $row was loaded from) or false to indicate the local
1218       *   wiki (this is the default). Otherwise, it must be a symbolic wiki database
1219       *   identifier as understood by the LoadBalancer class.
1220       * @return string Text the text requested or false on failure
1221       */
1222  	public static function getRevisionText( $row, $prefix = 'old_', $wiki = false ) {
1223          wfProfileIn( __METHOD__ );
1224  
1225          # Get data
1226          $textField = $prefix . 'text';
1227          $flagsField = $prefix . 'flags';
1228  
1229          if ( isset( $row->$flagsField ) ) {
1230              $flags = explode( ',', $row->$flagsField );
1231          } else {
1232              $flags = array();
1233          }
1234  
1235          if ( isset( $row->$textField ) ) {
1236              $text = $row->$textField;
1237          } else {
1238              wfProfileOut( __METHOD__ );
1239              return false;
1240          }
1241  
1242          # Use external methods for external objects, text in table is URL-only then
1243          if ( in_array( 'external', $flags ) ) {
1244              $url = $text;
1245              $parts = explode( '://', $url, 2 );
1246              if ( count( $parts ) == 1 || $parts[1] == '' ) {
1247                  wfProfileOut( __METHOD__ );
1248                  return false;
1249              }
1250              $text = ExternalStore::fetchFromURL( $url, array( 'wiki' => $wiki ) );
1251          }
1252  
1253          // If the text was fetched without an error, convert it
1254          if ( $text !== false ) {
1255              $text = self::decompressRevisionText( $text, $flags );
1256          }
1257          wfProfileOut( __METHOD__ );
1258          return $text;
1259      }
1260  
1261      /**
1262       * If $wgCompressRevisions is enabled, we will compress data.
1263       * The input string is modified in place.
1264       * Return value is the flags field: contains 'gzip' if the
1265       * data is compressed, and 'utf-8' if we're saving in UTF-8
1266       * mode.
1267       *
1268       * @param mixed $text Reference to a text
1269       * @return string
1270       */
1271  	public static function compressRevisionText( &$text ) {
1272          global $wgCompressRevisions;
1273          $flags = array();
1274  
1275          # Revisions not marked this way will be converted
1276          # on load if $wgLegacyCharset is set in the future.
1277          $flags[] = 'utf-8';
1278  
1279          if ( $wgCompressRevisions ) {
1280              if ( function_exists( 'gzdeflate' ) ) {
1281                  $text = gzdeflate( $text );
1282                  $flags[] = 'gzip';
1283              } else {
1284                  wfDebug( __METHOD__ . " -- no zlib support, not compressing\n" );
1285              }
1286          }
1287          return implode( ',', $flags );
1288      }
1289  
1290      /**
1291       * Re-converts revision text according to it's flags.
1292       *
1293       * @param mixed $text Reference to a text
1294       * @param array $flags Compression flags
1295       * @return string|bool Decompressed text, or false on failure
1296       */
1297  	public static function decompressRevisionText( $text, $flags ) {
1298          if ( in_array( 'gzip', $flags ) ) {
1299              # Deal with optional compression of archived pages.
1300              # This can be done periodically via maintenance/compressOld.php, and
1301              # as pages are saved if $wgCompressRevisions is set.
1302              $text = gzinflate( $text );
1303          }
1304  
1305          if ( in_array( 'object', $flags ) ) {
1306              # Generic compressed storage
1307              $obj = unserialize( $text );
1308              if ( !is_object( $obj ) ) {
1309                  // Invalid object
1310                  return false;
1311              }
1312              $text = $obj->getText();
1313          }
1314  
1315          global $wgLegacyEncoding;
1316          if ( $text !== false && $wgLegacyEncoding
1317              && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags )
1318          ) {
1319              # Old revisions kept around in a legacy encoding?
1320              # Upconvert on demand.
1321              # ("utf8" checked for compatibility with some broken
1322              #  conversion scripts 2008-12-30)
1323              global $wgContLang;
1324              $text = $wgContLang->iconv( $wgLegacyEncoding, 'UTF-8', $text );
1325          }
1326  
1327          return $text;
1328      }
1329  
1330      /**
1331       * Insert a new revision into the database, returning the new revision ID
1332       * number on success and dies horribly on failure.
1333       *
1334       * @param DatabaseBase $dbw (master connection)
1335       * @throws MWException
1336       * @return int
1337       */
1338  	public function insertOn( $dbw ) {
1339          global $wgDefaultExternalStore, $wgContentHandlerUseDB;
1340  
1341          wfProfileIn( __METHOD__ );
1342  
1343          $this->checkContentModel();
1344  
1345          $data = $this->mText;
1346          $flags = self::compressRevisionText( $data );
1347  
1348          # Write to external storage if required
1349          if ( $wgDefaultExternalStore ) {
1350              // Store and get the URL
1351              $data = ExternalStore::insertToDefault( $data );
1352              if ( !$data ) {
1353                  wfProfileOut( __METHOD__ );
1354                  throw new MWException( "Unable to store text to external storage" );
1355              }
1356              if ( $flags ) {
1357                  $flags .= ',';
1358              }
1359              $flags .= 'external';
1360          }
1361  
1362          # Record the text (or external storage URL) to the text table
1363          if ( $this->mTextId === null ) {
1364              $old_id = $dbw->nextSequenceValue( 'text_old_id_seq' );
1365              $dbw->insert( 'text',
1366                  array(
1367                      'old_id' => $old_id,
1368                      'old_text' => $data,
1369                      'old_flags' => $flags,
1370                  ), __METHOD__
1371              );
1372              $this->mTextId = $dbw->insertId();
1373          }
1374  
1375          if ( $this->mComment === null ) {
1376              $this->mComment = "";
1377          }
1378  
1379          # Record the edit in revisions
1380          $rev_id = $this->mId !== null
1381              ? $this->mId
1382              : $dbw->nextSequenceValue( 'revision_rev_id_seq' );
1383          $row = array(
1384              'rev_id'         => $rev_id,
1385              'rev_page'       => $this->mPage,
1386              'rev_text_id'    => $this->mTextId,
1387              'rev_comment'    => $this->mComment,
1388              'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
1389              'rev_user'       => $this->mUser,
1390              'rev_user_text'  => $this->mUserText,
1391              'rev_timestamp'  => $dbw->timestamp( $this->mTimestamp ),
1392              'rev_deleted'    => $this->mDeleted,
1393              'rev_len'        => $this->mSize,
1394              'rev_parent_id'  => $this->mParentId === null
1395                  ? $this->getPreviousRevisionId( $dbw )
1396                  : $this->mParentId,
1397              'rev_sha1'       => $this->mSha1 === null
1398                  ? Revision::base36Sha1( $this->mText )
1399                  : $this->mSha1,
1400          );
1401  
1402          if ( $wgContentHandlerUseDB ) {
1403              //NOTE: Store null for the default model and format, to save space.
1404              //XXX: Makes the DB sensitive to changed defaults.
1405              // Make this behavior optional? Only in miser mode?
1406  
1407              $model = $this->getContentModel();
1408              $format = $this->getContentFormat();
1409  
1410              $title = $this->getTitle();
1411  
1412              if ( $title === null ) {
1413                  wfProfileOut( __METHOD__ );
1414                  throw new MWException( "Insufficient information to determine the title of the "
1415                      . "revision's page!" );
1416              }
1417  
1418              $defaultModel = ContentHandler::getDefaultModelFor( $title );
1419              $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
1420  
1421              $row['rev_content_model'] = ( $model === $defaultModel ) ? null : $model;
1422              $row['rev_content_format'] = ( $format === $defaultFormat ) ? null : $format;
1423          }
1424  
1425          $dbw->insert( 'revision', $row, __METHOD__ );
1426  
1427          $this->mId = $rev_id !== null ? $rev_id : $dbw->insertId();
1428  
1429          wfRunHooks( 'RevisionInsertComplete', array( &$this, $data, $flags ) );
1430  
1431          wfProfileOut( __METHOD__ );
1432          return $this->mId;
1433      }
1434  
1435  	protected function checkContentModel() {
1436          global $wgContentHandlerUseDB;
1437  
1438          $title = $this->getTitle(); //note: may return null for revisions that have not yet been inserted.
1439  
1440          $model = $this->getContentModel();
1441          $format = $this->getContentFormat();
1442          $handler = $this->getContentHandler();
1443  
1444          if ( !$handler->isSupportedFormat( $format ) ) {
1445              $t = $title->getPrefixedDBkey();
1446  
1447              throw new MWException( "Can't use format $format with content model $model on $t" );
1448          }
1449  
1450          if ( !$wgContentHandlerUseDB && $title ) {
1451              // if $wgContentHandlerUseDB is not set,
1452              // all revisions must use the default content model and format.
1453  
1454              $defaultModel = ContentHandler::getDefaultModelFor( $title );
1455              $defaultHandler = ContentHandler::getForModelID( $defaultModel );
1456              $defaultFormat = $defaultHandler->getDefaultFormat();
1457  
1458              if ( $this->getContentModel() != $defaultModel ) {
1459                  $t = $title->getPrefixedDBkey();
1460  
1461                  throw new MWException( "Can't save non-default content model with "
1462                      . "\$wgContentHandlerUseDB disabled: model is $model, "
1463                      . "default for $t is $defaultModel" );
1464              }
1465  
1466              if ( $this->getContentFormat() != $defaultFormat ) {
1467                  $t = $title->getPrefixedDBkey();
1468  
1469                  throw new MWException( "Can't use non-default content format with "
1470                      . "\$wgContentHandlerUseDB disabled: format is $format, "
1471                      . "default for $t is $defaultFormat" );
1472              }
1473          }
1474  
1475          $content = $this->getContent( Revision::RAW );
1476  
1477          if ( !$content || !$content->isValid() ) {
1478              $t = $title->getPrefixedDBkey();
1479  
1480              throw new MWException( "Content of $t is not valid! Content model is $model" );
1481          }
1482      }
1483  
1484      /**
1485       * Get the base 36 SHA-1 value for a string of text
1486       * @param string $text
1487       * @return string
1488       */
1489  	public static function base36Sha1( $text ) {
1490          return wfBaseConvert( sha1( $text ), 16, 36, 31 );
1491      }
1492  
1493      /**
1494       * Lazy-load the revision's text.
1495       * Currently hardcoded to the 'text' table storage engine.
1496       *
1497       * @return string|bool The revision's text, or false on failure
1498       */
1499  	protected function loadText() {
1500          wfProfileIn( __METHOD__ );
1501  
1502          // Caching may be beneficial for massive use of external storage
1503          global $wgRevisionCacheExpiry, $wgMemc;
1504          $textId = $this->getTextId();
1505          $key = wfMemcKey( 'revisiontext', 'textid', $textId );
1506          if ( $wgRevisionCacheExpiry ) {
1507              $text = $wgMemc->get( $key );
1508              if ( is_string( $text ) ) {
1509                  wfDebug( __METHOD__ . ": got id $textId from cache\n" );
1510                  wfProfileOut( __METHOD__ );
1511                  return $text;
1512              }
1513          }
1514  
1515          // If we kept data for lazy extraction, use it now...
1516          if ( $this->mTextRow !== null ) {
1517              $row = $this->mTextRow;
1518              $this->mTextRow = null;
1519          } else {
1520              $row = null;
1521          }
1522  
1523          if ( !$row ) {
1524              // Text data is immutable; check slaves first.
1525              $dbr = wfGetDB( DB_SLAVE );
1526              $row = $dbr->selectRow( 'text',
1527                  array( 'old_text', 'old_flags' ),
1528                  array( 'old_id' => $textId ),
1529                  __METHOD__ );
1530          }
1531  
1532          // Fallback to the master in case of slave lag. Also use FOR UPDATE if it was
1533          // used to fetch this revision to avoid missing the row due to REPEATABLE-READ.
1534          $forUpdate = ( $this->mQueryFlags & self::READ_LOCKING == self::READ_LOCKING );
1535          if ( !$row && ( $forUpdate || wfGetLB()->getServerCount() > 1 ) ) {
1536              $dbw = wfGetDB( DB_MASTER );
1537              $row = $dbw->selectRow( 'text',
1538                  array( 'old_text', 'old_flags' ),
1539                  array( 'old_id' => $textId ),
1540                  __METHOD__,
1541                  $forUpdate ? array( 'FOR UPDATE' ) : array() );
1542          }
1543  
1544          if ( !$row ) {
1545              wfDebugLog( 'Revision', "No text row with ID '$textId' (revision {$this->getId()})." );
1546          }
1547  
1548          $text = self::getRevisionText( $row );
1549          if ( $row && $text === false ) {
1550              wfDebugLog( 'Revision', "No blob for text row '$textId' (revision {$this->getId()})." );
1551          }
1552  
1553          # No negative caching -- negative hits on text rows may be due to corrupted slave servers
1554          if ( $wgRevisionCacheExpiry && $text !== false ) {
1555              $wgMemc->set( $key, $text, $wgRevisionCacheExpiry );
1556          }
1557  
1558          wfProfileOut( __METHOD__ );
1559  
1560          return $text;
1561      }
1562  
1563      /**
1564       * Create a new null-revision for insertion into a page's
1565       * history. This will not re-save the text, but simply refer
1566       * to the text from the previous version.
1567       *
1568       * Such revisions can for instance identify page rename
1569       * operations and other such meta-modifications.
1570       *
1571       * @param DatabaseBase $dbw
1572       * @param int $pageId ID number of the page to read from
1573       * @param string $summary Revision's summary
1574       * @param bool $minor Whether the revision should be considered as minor
1575       * @param User|null $user User object to use or null for $wgUser
1576       * @return Revision|null Revision or null on error
1577       */
1578  	public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) {
1579          global $wgContentHandlerUseDB;
1580  
1581          wfProfileIn( __METHOD__ );
1582  
1583          $fields = array( 'page_latest', 'page_namespace', 'page_title',
1584                          'rev_text_id', 'rev_len', 'rev_sha1' );
1585  
1586          if ( $wgContentHandlerUseDB ) {
1587              $fields[] = 'rev_content_model';
1588              $fields[] = 'rev_content_format';
1589          }
1590  
1591          $current = $dbw->selectRow(
1592              array( 'page', 'revision' ),
1593              $fields,
1594              array(
1595                  'page_id' => $pageId,
1596                  'page_latest=rev_id',
1597                  ),
1598              __METHOD__ );
1599  
1600          if ( $current ) {
1601              if ( !$user ) {
1602                  global $wgUser;
1603                  $user = $wgUser;
1604              }
1605  
1606              $row = array(
1607                  'page'       => $pageId,
1608                  'user_text'  => $user->getName(),
1609                  'user'       => $user->getId(),
1610                  'comment'    => $summary,
1611                  'minor_edit' => $minor,
1612                  'text_id'    => $current->rev_text_id,
1613                  'parent_id'  => $current->page_latest,
1614                  'len'        => $current->rev_len,
1615                  'sha1'       => $current->rev_sha1
1616              );
1617  
1618              if ( $wgContentHandlerUseDB ) {
1619                  $row['content_model'] = $current->rev_content_model;
1620                  $row['content_format'] = $current->rev_content_format;
1621              }
1622  
1623              $revision = new Revision( $row );
1624              $revision->setTitle( Title::makeTitle( $current->page_namespace, $current->page_title ) );
1625          } else {
1626              $revision = null;
1627          }
1628  
1629          wfProfileOut( __METHOD__ );
1630          return $revision;
1631      }
1632  
1633      /**
1634       * Determine if the current user is allowed to view a particular
1635       * field of this revision, if it's marked as deleted.
1636       *
1637       * @param int $field One of self::DELETED_TEXT,
1638       *                              self::DELETED_COMMENT,
1639       *                              self::DELETED_USER
1640       * @param User|null $user User object to check, or null to use $wgUser
1641       * @return bool
1642       */
1643  	public function userCan( $field, User $user = null ) {
1644          return self::userCanBitfield( $this->mDeleted, $field, $user );
1645      }
1646  
1647      /**
1648       * Determine if the current user is allowed to view a particular
1649       * field of this revision, if it's marked as deleted. This is used
1650       * by various classes to avoid duplication.
1651       *
1652       * @param int $bitfield Current field
1653       * @param int $field One of self::DELETED_TEXT = File::DELETED_FILE,
1654       *                               self::DELETED_COMMENT = File::DELETED_COMMENT,
1655       *                               self::DELETED_USER = File::DELETED_USER
1656       * @param User|null $user User object to check, or null to use $wgUser
1657       * @param Title|null $title A Title object to check for per-page restrictions on,
1658       *                          instead of just plain userrights
1659       * @return bool
1660       */
1661  	public static function userCanBitfield( $bitfield, $field, User $user = null,
1662          Title $title = null
1663      ) {
1664          if ( $bitfield & $field ) { // aspect is deleted
1665              if ( $user === null ) {
1666                  global $wgUser;
1667                  $user = $wgUser;
1668              }
1669              if ( $bitfield & self::DELETED_RESTRICTED ) {
1670                  $permissions = array( 'suppressrevision', 'viewsuppressed' );
1671              } elseif ( $field & self::DELETED_TEXT ) {
1672                  $permissions = array( 'deletedtext' );
1673              } else {
1674                  $permissions = array( 'deletedhistory' );
1675              }
1676              $permissionlist = implode( ', ', $permissions );
1677              if ( $title === null ) {
1678                  wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
1679                  return call_user_func_array( array( $user, 'isAllowedAny' ), $permissions );
1680              } else {
1681                  $text = $title->getPrefixedText();
1682                  wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" );
1683                  foreach ( $permissions as $perm ) {
1684                      if ( $title->userCan( $perm, $user ) ) {
1685                          return true;
1686                      }
1687                  }
1688                  return false;
1689              }
1690          } else {
1691              return true;
1692          }
1693      }
1694  
1695      /**
1696       * Get rev_timestamp from rev_id, without loading the rest of the row
1697       *
1698       * @param Title $title
1699       * @param int $id
1700       * @return string
1701       */
1702  	static function getTimestampFromId( $title, $id ) {
1703          $dbr = wfGetDB( DB_SLAVE );
1704          // Casting fix for databases that can't take '' for rev_id
1705          if ( $id == '' ) {
1706              $id = 0;
1707          }
1708          $conds = array( 'rev_id' => $id );
1709          $conds['rev_page'] = $title->getArticleID();
1710          $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
1711          if ( $timestamp === false && wfGetLB()->getServerCount() > 1 ) {
1712              # Not in slave, try master
1713              $dbw = wfGetDB( DB_MASTER );
1714              $timestamp = $dbw->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
1715          }
1716          return wfTimestamp( TS_MW, $timestamp );
1717      }
1718  
1719      /**
1720       * Get count of revisions per page...not very efficient
1721       *
1722       * @param DatabaseBase $db
1723       * @param int $id Page id
1724       * @return int
1725       */
1726  	static function countByPageId( $db, $id ) {
1727          $row = $db->selectRow( 'revision', array( 'revCount' => 'COUNT(*)' ),
1728              array( 'rev_page' => $id ), __METHOD__ );
1729          if ( $row ) {
1730              return $row->revCount;
1731          }
1732          return 0;
1733      }
1734  
1735      /**
1736       * Get count of revisions per page...not very efficient
1737       *
1738       * @param DatabaseBase $db
1739       * @param Title $title
1740       * @return int
1741       */
1742  	static function countByTitle( $db, $title ) {
1743          $id = $title->getArticleID();
1744          if ( $id ) {
1745              return self::countByPageId( $db, $id );
1746          }
1747          return 0;
1748      }
1749  
1750      /**
1751       * Check if no edits were made by other users since
1752       * the time a user started editing the page. Limit to
1753       * 50 revisions for the sake of performance.
1754       *
1755       * @since 1.20
1756       * @deprecated since 1.24
1757       *
1758       * @param DatabaseBase|int $db The Database to perform the check on. May be given as a
1759       *        Database object or a database identifier usable with wfGetDB.
1760       * @param int $pageId The ID of the page in question
1761       * @param int $userId The ID of the user in question
1762       * @param string $since Look at edits since this time
1763       *
1764       * @return bool True if the given user was the only one to edit since the given timestamp
1765       */
1766  	public static function userWasLastToEdit( $db, $pageId, $userId, $since ) {
1767          if ( !$userId ) {
1768              return false;
1769          }
1770  
1771          if ( is_int( $db ) ) {
1772              $db = wfGetDB( $db );
1773          }
1774  
1775          $res = $db->select( 'revision',
1776              'rev_user',
1777              array(
1778                  'rev_page' => $pageId,
1779                  'rev_timestamp > ' . $db->addQuotes( $db->timestamp( $since ) )
1780              ),
1781              __METHOD__,
1782              array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ) );
1783          foreach ( $res as $row ) {
1784              if ( $row->rev_user != $userId ) {
1785                  return false;
1786              }
1787          }
1788          return true;
1789      }
1790  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1