MediaWiki  master
Revision.php
Go to the documentation of this file.
1 <?php
29 
33 class Revision implements IDBAccessObject {
35  protected $mId;
37  protected $mPage;
39  protected $mUserText;
41  protected $mOrigUserText;
43  protected $mUser;
45  protected $mMinorEdit;
47  protected $mTimestamp;
49  protected $mDeleted;
51  protected $mSize;
53  protected $mSha1;
55  protected $mParentId;
57  protected $mComment;
59  protected $mText;
61  protected $mTextId;
63  protected $mUnpatrolled;
64 
66  protected $mTextRow;
67 
69  protected $mTitle;
71  protected $mCurrent;
73  protected $mContentModel;
75  protected $mContentFormat;
76 
78  protected $mContent;
80  protected $mContentHandler;
81 
83  protected $mQueryFlags = 0;
85  protected $mRefreshMutableFields = false;
87  protected $mWiki = false;
88 
89  // Revision deletion constants
90  const DELETED_TEXT = 1;
91  const DELETED_COMMENT = 2;
92  const DELETED_USER = 4;
93  const DELETED_RESTRICTED = 8;
94  const SUPPRESSED_USER = 12; // convenience
95  const SUPPRESSED_ALL = 15; // convenience
96 
97  // Audience options for accessors
98  const FOR_PUBLIC = 1;
99  const FOR_THIS_USER = 2;
100  const RAW = 3;
101 
102  const TEXT_CACHE_GROUP = 'revisiontext:10'; // process cache name and max key count
103 
116  public static function newFromId( $id, $flags = 0 ) {
117  return self::newFromConds( [ 'rev_id' => intval( $id ) ], $flags );
118  }
119 
134  public static function newFromTitle( LinkTarget $linkTarget, $id = 0, $flags = 0 ) {
135  $conds = [
136  'page_namespace' => $linkTarget->getNamespace(),
137  'page_title' => $linkTarget->getDBkey()
138  ];
139  if ( $id ) {
140  // Use the specified ID
141  $conds['rev_id'] = $id;
142  return self::newFromConds( $conds, $flags );
143  } else {
144  // Use a join to get the latest revision
145  $conds[] = 'rev_id=page_latest';
146  $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
147  return self::loadFromConds( $db, $conds, $flags );
148  }
149  }
150 
165  public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) {
166  $conds = [ 'page_id' => $pageId ];
167  if ( $revId ) {
168  $conds['rev_id'] = $revId;
169  return self::newFromConds( $conds, $flags );
170  } else {
171  // Use a join to get the latest revision
172  $conds[] = 'rev_id = page_latest';
173  $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
174  return self::loadFromConds( $db, $conds, $flags );
175  }
176  }
177 
189  public static function newFromArchiveRow( $row, $overrides = [] ) {
190  global $wgContentHandlerUseDB;
191 
192  $attribs = $overrides + [
193  'page' => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
194  'id' => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
195  'comment' => CommentStore::newKey( 'ar_comment' )
196  // Legacy because $row probably came from self::selectArchiveFields()
197  ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text,
198  'user' => $row->ar_user,
199  'user_text' => $row->ar_user_text,
200  'timestamp' => $row->ar_timestamp,
201  'minor_edit' => $row->ar_minor_edit,
202  'text_id' => isset( $row->ar_text_id ) ? $row->ar_text_id : null,
203  'deleted' => $row->ar_deleted,
204  'len' => $row->ar_len,
205  'sha1' => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
206  'content_model' => isset( $row->ar_content_model ) ? $row->ar_content_model : null,
207  'content_format' => isset( $row->ar_content_format ) ? $row->ar_content_format : null,
208  ];
209 
210  if ( !$wgContentHandlerUseDB ) {
211  unset( $attribs['content_model'] );
212  unset( $attribs['content_format'] );
213  }
214 
215  if ( !isset( $attribs['title'] )
216  && isset( $row->ar_namespace )
217  && isset( $row->ar_title )
218  ) {
219  $attribs['title'] = Title::makeTitle( $row->ar_namespace, $row->ar_title );
220  }
221 
222  if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
223  // Pre-1.5 ar_text row
224  $attribs['text'] = self::getRevisionText( $row, 'ar_' );
225  if ( $attribs['text'] === false ) {
226  throw new MWException( 'Unable to load text from archive row (possibly T24624)' );
227  }
228  }
229  return new self( $attribs );
230  }
231 
238  public static function newFromRow( $row ) {
239  return new self( $row );
240  }
241 
250  public static function loadFromId( $db, $id ) {
251  return self::loadFromConds( $db, [ 'rev_id' => intval( $id ) ] );
252  }
253 
264  public static function loadFromPageId( $db, $pageid, $id = 0 ) {
265  $conds = [ 'rev_page' => intval( $pageid ), 'page_id' => intval( $pageid ) ];
266  if ( $id ) {
267  $conds['rev_id'] = intval( $id );
268  } else {
269  $conds[] = 'rev_id=page_latest';
270  }
271  return self::loadFromConds( $db, $conds );
272  }
273 
284  public static function loadFromTitle( $db, $title, $id = 0 ) {
285  if ( $id ) {
286  $matchId = intval( $id );
287  } else {
288  $matchId = 'page_latest';
289  }
290  return self::loadFromConds( $db,
291  [
292  "rev_id=$matchId",
293  'page_namespace' => $title->getNamespace(),
294  'page_title' => $title->getDBkey()
295  ]
296  );
297  }
298 
309  public static function loadFromTimestamp( $db, $title, $timestamp ) {
310  return self::loadFromConds( $db,
311  [
312  'rev_timestamp' => $db->timestamp( $timestamp ),
313  'page_namespace' => $title->getNamespace(),
314  'page_title' => $title->getDBkey()
315  ]
316  );
317  }
318 
329  private static function newFromConds( $conditions, $flags = 0 ) {
330  $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
331 
332  $rev = self::loadFromConds( $db, $conditions, $flags );
333  // Make sure new pending/committed revision are visibile later on
334  // within web requests to certain avoid bugs like T93866 and T94407.
335  if ( !$rev
336  && !( $flags & self::READ_LATEST )
337  && wfGetLB()->getServerCount() > 1
338  && wfGetLB()->hasOrMadeRecentMasterChanges()
339  ) {
340  $flags = self::READ_LATEST;
341  $db = wfGetDB( DB_MASTER );
342  $rev = self::loadFromConds( $db, $conditions, $flags );
343  }
344 
345  if ( $rev ) {
346  $rev->mQueryFlags = $flags;
347  }
348 
349  return $rev;
350  }
351 
361  private static function loadFromConds( $db, $conditions, $flags = 0 ) {
362  $row = self::fetchFromConds( $db, $conditions, $flags );
363  if ( $row ) {
364  $rev = new Revision( $row );
365  $rev->mWiki = $db->getDomainID();
366 
367  return $rev;
368  }
369 
370  return null;
371  }
372 
382  public static function fetchRevision( LinkTarget $title ) {
383  $row = self::fetchFromConds(
384  wfGetDB( DB_REPLICA ),
385  [
386  'rev_id=page_latest',
387  'page_namespace' => $title->getNamespace(),
388  'page_title' => $title->getDBkey()
389  ]
390  );
391 
392  return new FakeResultWrapper( $row ? [ $row ] : [] );
393  }
394 
405  private static function fetchFromConds( $db, $conditions, $flags = 0 ) {
406  $fields = array_merge(
407  self::selectFields(),
408  self::selectPageFields(),
409  self::selectUserFields()
410  );
411  $options = [];
412  if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
413  $options[] = 'FOR UPDATE';
414  }
415  return $db->selectRow(
416  [ 'revision', 'page', 'user' ],
417  $fields,
418  $conditions,
419  __METHOD__,
420  $options,
421  [ 'page' => self::pageJoinCond(), 'user' => self::userJoinCond() ]
422  );
423  }
424 
431  public static function userJoinCond() {
432  return [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ];
433  }
434 
441  public static function pageJoinCond() {
442  return [ 'INNER JOIN', [ 'page_id = rev_page' ] ];
443  }
444 
452  public static function selectFields() {
453  global $wgContentHandlerUseDB;
454 
455  $fields = [
456  'rev_id',
457  'rev_page',
458  'rev_text_id',
459  'rev_timestamp',
460  'rev_user_text',
461  'rev_user',
462  'rev_minor_edit',
463  'rev_deleted',
464  'rev_len',
465  'rev_parent_id',
466  'rev_sha1',
467  ];
468 
469  $fields += CommentStore::newKey( 'rev_comment' )->getFields();
470 
471  if ( $wgContentHandlerUseDB ) {
472  $fields[] = 'rev_content_format';
473  $fields[] = 'rev_content_model';
474  }
475 
476  return $fields;
477  }
478 
486  public static function selectArchiveFields() {
487  global $wgContentHandlerUseDB;
488  $fields = [
489  'ar_id',
490  'ar_page_id',
491  'ar_rev_id',
492  'ar_text',
493  'ar_text_id',
494  'ar_timestamp',
495  'ar_user_text',
496  'ar_user',
497  'ar_minor_edit',
498  'ar_deleted',
499  'ar_len',
500  'ar_parent_id',
501  'ar_sha1',
502  ];
503 
504  $fields += CommentStore::newKey( 'ar_comment' )->getFields();
505 
506  if ( $wgContentHandlerUseDB ) {
507  $fields[] = 'ar_content_format';
508  $fields[] = 'ar_content_model';
509  }
510  return $fields;
511  }
512 
518  public static function selectTextFields() {
519  return [
520  'old_text',
521  'old_flags'
522  ];
523  }
524 
529  public static function selectPageFields() {
530  return [
531  'page_namespace',
532  'page_title',
533  'page_id',
534  'page_latest',
535  'page_is_redirect',
536  'page_len',
537  ];
538  }
539 
544  public static function selectUserFields() {
545  return [ 'user_name' ];
546  }
547 
554  public static function getParentLengths( $db, array $revIds ) {
555  $revLens = [];
556  if ( !$revIds ) {
557  return $revLens; // empty
558  }
559  $res = $db->select( 'revision',
560  [ 'rev_id', 'rev_len' ],
561  [ 'rev_id' => $revIds ],
562  __METHOD__ );
563  foreach ( $res as $row ) {
564  $revLens[$row->rev_id] = $row->rev_len;
565  }
566  return $revLens;
567  }
568 
574  public function __construct( $row ) {
575  if ( is_object( $row ) ) {
576  $this->constructFromDbRowObject( $row );
577  } elseif ( is_array( $row ) ) {
578  $this->constructFromRowArray( $row );
579  } else {
580  throw new MWException( 'Revision constructor passed invalid row format.' );
581  }
582  $this->mUnpatrolled = null;
583  }
584 
588  private function constructFromDbRowObject( $row ) {
589  $this->mId = intval( $row->rev_id );
590  $this->mPage = intval( $row->rev_page );
591  $this->mTextId = intval( $row->rev_text_id );
592  $this->mComment = CommentStore::newKey( 'rev_comment' )
593  // Legacy because $row probably came from self::selectFields()
594  ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text;
595  $this->mUser = intval( $row->rev_user );
596  $this->mMinorEdit = intval( $row->rev_minor_edit );
597  $this->mTimestamp = $row->rev_timestamp;
598  $this->mDeleted = intval( $row->rev_deleted );
599 
600  if ( !isset( $row->rev_parent_id ) ) {
601  $this->mParentId = null;
602  } else {
603  $this->mParentId = intval( $row->rev_parent_id );
604  }
605 
606  if ( !isset( $row->rev_len ) ) {
607  $this->mSize = null;
608  } else {
609  $this->mSize = intval( $row->rev_len );
610  }
611 
612  if ( !isset( $row->rev_sha1 ) ) {
613  $this->mSha1 = null;
614  } else {
615  $this->mSha1 = $row->rev_sha1;
616  }
617 
618  if ( isset( $row->page_latest ) ) {
619  $this->mCurrent = ( $row->rev_id == $row->page_latest );
620  $this->mTitle = Title::newFromRow( $row );
621  } else {
622  $this->mCurrent = false;
623  $this->mTitle = null;
624  }
625 
626  if ( !isset( $row->rev_content_model ) ) {
627  $this->mContentModel = null; # determine on demand if needed
628  } else {
629  $this->mContentModel = strval( $row->rev_content_model );
630  }
631 
632  if ( !isset( $row->rev_content_format ) ) {
633  $this->mContentFormat = null; # determine on demand if needed
634  } else {
635  $this->mContentFormat = strval( $row->rev_content_format );
636  }
637 
638  // Lazy extraction...
639  $this->mText = null;
640  if ( isset( $row->old_text ) ) {
641  $this->mTextRow = $row;
642  } else {
643  // 'text' table row entry will be lazy-loaded
644  $this->mTextRow = null;
645  }
646 
647  // Use user_name for users and rev_user_text for IPs...
648  $this->mUserText = null; // lazy load if left null
649  if ( $this->mUser == 0 ) {
650  $this->mUserText = $row->rev_user_text; // IP user
651  } elseif ( isset( $row->user_name ) ) {
652  $this->mUserText = $row->user_name; // logged-in user
653  }
654  $this->mOrigUserText = $row->rev_user_text;
655  }
656 
662  private function constructFromRowArray( array $row ) {
663  // Build a new revision to be saved...
664  global $wgUser; // ugh
665 
666  # if we have a content object, use it to set the model and type
667  if ( !empty( $row['content'] ) ) {
668  if ( !( $row['content'] instanceof Content ) ) {
669  throw new MWException( '`content` field must contain a Content object.' );
670  }
671 
672  // @todo when is that set? test with external store setup! check out insertOn() [dk]
673  if ( !empty( $row['text_id'] ) ) {
674  throw new MWException( "Text already stored in external store (id {$row['text_id']}), " .
675  "can't serialize content object" );
676  }
677 
678  $row['content_model'] = $row['content']->getModel();
679  # note: mContentFormat is initializes later accordingly
680  # note: content is serialized later in this method!
681  # also set text to null?
682  }
683 
684  $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
685  $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
686  $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
687  $this->mUserText = isset( $row['user_text'] )
688  ? strval( $row['user_text'] ) : $wgUser->getName();
689  $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
690  $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
691  $this->mTimestamp = isset( $row['timestamp'] )
692  ? strval( $row['timestamp'] ) : wfTimestampNow();
693  $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
694  $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
695  $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
696  $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
697 
698  $this->mContentModel = isset( $row['content_model'] )
699  ? strval( $row['content_model'] ) : null;
700  $this->mContentFormat = isset( $row['content_format'] )
701  ? strval( $row['content_format'] ) : null;
702 
703  // Enforce spacing trimming on supplied text
704  $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
705  $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
706  $this->mTextRow = null;
707 
708  $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
709 
710  // if we have a Content object, override mText and mContentModel
711  if ( !empty( $row['content'] ) ) {
712  $handler = $this->getContentHandler();
713  $this->mContent = $row['content'];
714 
715  $this->mContentModel = $this->mContent->getModel();
716  $this->mContentHandler = null;
717 
718  $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
719  } elseif ( $this->mText !== null ) {
720  $handler = $this->getContentHandler();
721  $this->mContent = $handler->unserializeContent( $this->mText );
722  }
723 
724  // If we have a Title object, make sure it is consistent with mPage.
725  if ( $this->mTitle && $this->mTitle->exists() ) {
726  if ( $this->mPage === null ) {
727  // if the page ID wasn't known, set it now
728  $this->mPage = $this->mTitle->getArticleID();
729  } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
730  // Got different page IDs. This may be legit (e.g. during undeletion),
731  // but it seems worth mentioning it in the log.
732  wfDebug( "Page ID " . $this->mPage . " mismatches the ID " .
733  $this->mTitle->getArticleID() . " provided by the Title object." );
734  }
735  }
736 
737  $this->mCurrent = false;
738 
739  // If we still have no length, see it we have the text to figure it out
740  if ( !$this->mSize && $this->mContent !== null ) {
741  $this->mSize = $this->mContent->getSize();
742  }
743 
744  // Same for sha1
745  if ( $this->mSha1 === null ) {
746  $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
747  }
748 
749  // force lazy init
750  $this->getContentModel();
751  $this->getContentFormat();
752  }
753 
759  public function getId() {
760  return $this->mId;
761  }
762 
771  public function setId( $id ) {
772  $this->mId = (int)$id;
773  }
774 
784  public function setUserIdAndName( $id, $name ) {
785  $this->mUser = (int)$id;
786  $this->mUserText = $name;
787  $this->mOrigUserText = $name;
788  }
789 
795  public function getTextId() {
796  return $this->mTextId;
797  }
798 
804  public function getParentId() {
805  return $this->mParentId;
806  }
807 
813  public function getSize() {
814  return $this->mSize;
815  }
816 
822  public function getSha1() {
823  return $this->mSha1;
824  }
825 
833  public function getTitle() {
834  if ( $this->mTitle !== null ) {
835  return $this->mTitle;
836  }
837  // rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
838  if ( $this->mId !== null ) {
839  $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki );
840  $row = $dbr->selectRow(
841  [ 'page', 'revision' ],
842  self::selectPageFields(),
843  [ 'page_id=rev_page', 'rev_id' => $this->mId ],
844  __METHOD__
845  );
846  if ( $row ) {
847  // @TODO: better foreign title handling
848  $this->mTitle = Title::newFromRow( $row );
849  }
850  }
851 
852  if ( $this->mWiki === false || $this->mWiki === wfWikiID() ) {
853  // Loading by ID is best, though not possible for foreign titles
854  if ( !$this->mTitle && $this->mPage !== null && $this->mPage > 0 ) {
855  $this->mTitle = Title::newFromID( $this->mPage );
856  }
857  }
858 
859  return $this->mTitle;
860  }
861 
867  public function setTitle( $title ) {
868  $this->mTitle = $title;
869  }
870 
876  public function getPage() {
877  return $this->mPage;
878  }
879 
893  public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
894  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
895  return 0;
896  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
897  return 0;
898  } else {
899  return $this->mUser;
900  }
901  }
902 
909  public function getRawUser() {
910  wfDeprecated( __METHOD__, '1.25' );
911  return $this->getUser( self::RAW );
912  }
913 
927  public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
928  $this->loadMutableFields();
929 
930  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
931  return '';
932  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
933  return '';
934  } else {
935  if ( $this->mUserText === null ) {
936  $this->mUserText = User::whoIs( $this->mUser ); // load on demand
937  if ( $this->mUserText === false ) {
938  # This shouldn't happen, but it can if the wiki was recovered
939  # via importing revs and there is no user table entry yet.
940  $this->mUserText = $this->mOrigUserText;
941  }
942  }
943  return $this->mUserText;
944  }
945  }
946 
953  public function getRawUserText() {
954  wfDeprecated( __METHOD__, '1.25' );
955  return $this->getUserText( self::RAW );
956  }
957 
971  function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
972  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
973  return '';
974  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $user ) ) {
975  return '';
976  } else {
977  return $this->mComment;
978  }
979  }
980 
987  public function getRawComment() {
988  wfDeprecated( __METHOD__, '1.25' );
989  return $this->getComment( self::RAW );
990  }
991 
995  public function isMinor() {
996  return (bool)$this->mMinorEdit;
997  }
998 
1002  public function isUnpatrolled() {
1003  if ( $this->mUnpatrolled !== null ) {
1004  return $this->mUnpatrolled;
1005  }
1006  $rc = $this->getRecentChange();
1007  if ( $rc && $rc->getAttribute( 'rc_patrolled' ) == 0 ) {
1008  $this->mUnpatrolled = $rc->getAttribute( 'rc_id' );
1009  } else {
1010  $this->mUnpatrolled = 0;
1011  }
1012  return $this->mUnpatrolled;
1013  }
1014 
1024  public function getRecentChange( $flags = 0 ) {
1025  $dbr = wfGetDB( DB_REPLICA );
1026 
1028 
1030  [
1031  'rc_user_text' => $this->getUserText( self::RAW ),
1032  'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
1033  'rc_this_oldid' => $this->getId()
1034  ],
1035  __METHOD__,
1036  $dbType
1037  );
1038  }
1039 
1045  public function isDeleted( $field ) {
1046  if ( $this->isCurrent() && $field === self::DELETED_TEXT ) {
1047  // Current revisions of pages cannot have the content hidden. Skipping this
1048  // check is very useful for Parser as it fetches templates using newKnownCurrent().
1049  // Calling getVisibility() in that case triggers a verification database query.
1050  return false; // no need to check
1051  }
1052 
1053  return ( $this->getVisibility() & $field ) == $field;
1054  }
1055 
1061  public function getVisibility() {
1062  $this->loadMutableFields();
1063 
1064  return (int)$this->mDeleted;
1065  }
1066 
1081  public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
1082  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
1083  return null;
1084  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
1085  return null;
1086  } else {
1087  return $this->getContentInternal();
1088  }
1089  }
1090 
1097  public function getSerializedData() {
1098  if ( $this->mText === null ) {
1099  // Revision is immutable. Load on demand.
1100  $this->mText = $this->loadText();
1101  }
1102 
1103  return $this->mText;
1104  }
1105 
1115  protected function getContentInternal() {
1116  if ( $this->mContent === null ) {
1117  $text = $this->getSerializedData();
1118 
1119  if ( $text !== null && $text !== false ) {
1120  // Unserialize content
1121  $handler = $this->getContentHandler();
1122  $format = $this->getContentFormat();
1123 
1124  $this->mContent = $handler->unserializeContent( $text, $format );
1125  }
1126  }
1127 
1128  // NOTE: copy() will return $this for immutable content objects
1129  return $this->mContent ? $this->mContent->copy() : null;
1130  }
1131 
1142  public function getContentModel() {
1143  if ( !$this->mContentModel ) {
1144  $title = $this->getTitle();
1145  if ( $title ) {
1146  $this->mContentModel = ContentHandler::getDefaultModelFor( $title );
1147  } else {
1148  $this->mContentModel = CONTENT_MODEL_WIKITEXT;
1149  }
1150 
1151  assert( !empty( $this->mContentModel ) );
1152  }
1153 
1154  return $this->mContentModel;
1155  }
1156 
1166  public function getContentFormat() {
1167  if ( !$this->mContentFormat ) {
1168  $handler = $this->getContentHandler();
1169  $this->mContentFormat = $handler->getDefaultFormat();
1170 
1171  assert( !empty( $this->mContentFormat ) );
1172  }
1173 
1174  return $this->mContentFormat;
1175  }
1176 
1183  public function getContentHandler() {
1184  if ( !$this->mContentHandler ) {
1185  $model = $this->getContentModel();
1186  $this->mContentHandler = ContentHandler::getForModelID( $model );
1187 
1188  $format = $this->getContentFormat();
1189 
1190  if ( !$this->mContentHandler->isSupportedFormat( $format ) ) {
1191  throw new MWException( "Oops, the content format $format is not supported for "
1192  . "this content model, $model" );
1193  }
1194  }
1195 
1196  return $this->mContentHandler;
1197  }
1198 
1202  public function getTimestamp() {
1203  return wfTimestamp( TS_MW, $this->mTimestamp );
1204  }
1205 
1209  public function isCurrent() {
1210  return $this->mCurrent;
1211  }
1212 
1218  public function getPrevious() {
1219  if ( $this->getTitle() ) {
1220  $prev = $this->getTitle()->getPreviousRevisionID( $this->getId() );
1221  if ( $prev ) {
1222  return self::newFromTitle( $this->getTitle(), $prev );
1223  }
1224  }
1225  return null;
1226  }
1227 
1233  public function getNext() {
1234  if ( $this->getTitle() ) {
1235  $next = $this->getTitle()->getNextRevisionID( $this->getId() );
1236  if ( $next ) {
1237  return self::newFromTitle( $this->getTitle(), $next );
1238  }
1239  }
1240  return null;
1241  }
1242 
1250  private function getPreviousRevisionId( $db ) {
1251  if ( $this->mPage === null ) {
1252  return 0;
1253  }
1254  # Use page_latest if ID is not given
1255  if ( !$this->mId ) {
1256  $prevId = $db->selectField( 'page', 'page_latest',
1257  [ 'page_id' => $this->mPage ],
1258  __METHOD__ );
1259  } else {
1260  $prevId = $db->selectField( 'revision', 'rev_id',
1261  [ 'rev_page' => $this->mPage, 'rev_id < ' . $this->mId ],
1262  __METHOD__,
1263  [ 'ORDER BY' => 'rev_id DESC' ] );
1264  }
1265  return intval( $prevId );
1266  }
1267 
1282  public static function getRevisionText( $row, $prefix = 'old_', $wiki = false ) {
1283  $textField = $prefix . 'text';
1284  $flagsField = $prefix . 'flags';
1285 
1286  if ( isset( $row->$flagsField ) ) {
1287  $flags = explode( ',', $row->$flagsField );
1288  } else {
1289  $flags = [];
1290  }
1291 
1292  if ( isset( $row->$textField ) ) {
1293  $text = $row->$textField;
1294  } else {
1295  return false;
1296  }
1297 
1298  // Use external methods for external objects, text in table is URL-only then
1299  if ( in_array( 'external', $flags ) ) {
1300  $url = $text;
1301  $parts = explode( '://', $url, 2 );
1302  if ( count( $parts ) == 1 || $parts[1] == '' ) {
1303  return false;
1304  }
1305 
1306  if ( isset( $row->old_id ) && $wiki === false ) {
1307  // Make use of the wiki-local revision text cache
1308  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1309  // The cached value should be decompressed, so handle that and return here
1310  return $cache->getWithSetCallback(
1311  $cache->makeKey( 'revisiontext', 'textid', $row->old_id ),
1312  self::getCacheTTL( $cache ),
1313  function () use ( $url, $wiki, $flags ) {
1314  // No negative caching per Revision::loadText()
1315  $text = ExternalStore::fetchFromURL( $url, [ 'wiki' => $wiki ] );
1316 
1317  return self::decompressRevisionText( $text, $flags );
1318  },
1319  [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => $cache::TTL_PROC_LONG ]
1320  );
1321  } else {
1322  $text = ExternalStore::fetchFromURL( $url, [ 'wiki' => $wiki ] );
1323  }
1324  }
1325 
1326  return self::decompressRevisionText( $text, $flags );
1327  }
1328 
1339  public static function compressRevisionText( &$text ) {
1341  $flags = [];
1342 
1343  # Revisions not marked this way will be converted
1344  # on load if $wgLegacyCharset is set in the future.
1345  $flags[] = 'utf-8';
1346 
1347  if ( $wgCompressRevisions ) {
1348  if ( function_exists( 'gzdeflate' ) ) {
1349  $deflated = gzdeflate( $text );
1350 
1351  if ( $deflated === false ) {
1352  wfLogWarning( __METHOD__ . ': gzdeflate() failed' );
1353  } else {
1354  $text = $deflated;
1355  $flags[] = 'gzip';
1356  }
1357  } else {
1358  wfDebug( __METHOD__ . " -- no zlib support, not compressing\n" );
1359  }
1360  }
1361  return implode( ',', $flags );
1362  }
1363 
1371  public static function decompressRevisionText( $text, $flags ) {
1373 
1374  if ( $text === false ) {
1375  // Text failed to be fetched; nothing to do
1376  return false;
1377  }
1378 
1379  if ( in_array( 'gzip', $flags ) ) {
1380  # Deal with optional compression of archived pages.
1381  # This can be done periodically via maintenance/compressOld.php, and
1382  # as pages are saved if $wgCompressRevisions is set.
1383  $text = gzinflate( $text );
1384 
1385  if ( $text === false ) {
1386  wfLogWarning( __METHOD__ . ': gzinflate() failed' );
1387  return false;
1388  }
1389  }
1390 
1391  if ( in_array( 'object', $flags ) ) {
1392  # Generic compressed storage
1393  $obj = unserialize( $text );
1394  if ( !is_object( $obj ) ) {
1395  // Invalid object
1396  return false;
1397  }
1398  $text = $obj->getText();
1399  }
1400 
1401  if ( $text !== false && $wgLegacyEncoding
1402  && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags )
1403  ) {
1404  # Old revisions kept around in a legacy encoding?
1405  # Upconvert on demand.
1406  # ("utf8" checked for compatibility with some broken
1407  # conversion scripts 2008-12-30)
1408  $text = $wgContLang->iconv( $wgLegacyEncoding, 'UTF-8', $text );
1409  }
1410 
1411  return $text;
1412  }
1413 
1422  public function insertOn( $dbw ) {
1423  global $wgDefaultExternalStore, $wgContentHandlerUseDB;
1424 
1425  // We're inserting a new revision, so we have to use master anyway.
1426  // If it's a null revision, it may have references to rows that
1427  // are not in the replica yet (the text row).
1428  $this->mQueryFlags |= self::READ_LATEST;
1429 
1430  // Not allowed to have rev_page equal to 0, false, etc.
1431  if ( !$this->mPage ) {
1432  $title = $this->getTitle();
1433  if ( $title instanceof Title ) {
1434  $titleText = ' for page ' . $title->getPrefixedText();
1435  } else {
1436  $titleText = '';
1437  }
1438  throw new MWException( "Cannot insert revision$titleText: page ID must be nonzero" );
1439  }
1440 
1441  $this->checkContentModel();
1442 
1443  $data = $this->mText;
1444  $flags = self::compressRevisionText( $data );
1445 
1446  # Write to external storage if required
1447  if ( $wgDefaultExternalStore ) {
1448  // Store and get the URL
1449  $data = ExternalStore::insertToDefault( $data );
1450  if ( !$data ) {
1451  throw new MWException( "Unable to store text to external storage" );
1452  }
1453  if ( $flags ) {
1454  $flags .= ',';
1455  }
1456  $flags .= 'external';
1457  }
1458 
1459  # Record the text (or external storage URL) to the text table
1460  if ( $this->mTextId === null ) {
1461  $dbw->insert( 'text',
1462  [
1463  'old_text' => $data,
1464  'old_flags' => $flags,
1465  ], __METHOD__
1466  );
1467  $this->mTextId = $dbw->insertId();
1468  }
1469 
1470  if ( $this->mComment === null ) {
1471  $this->mComment = "";
1472  }
1473 
1474  # Record the edit in revisions
1475  $row = [
1476  'rev_page' => $this->mPage,
1477  'rev_text_id' => $this->mTextId,
1478  'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
1479  'rev_user' => $this->mUser,
1480  'rev_user_text' => $this->mUserText,
1481  'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
1482  'rev_deleted' => $this->mDeleted,
1483  'rev_len' => $this->mSize,
1484  'rev_parent_id' => $this->mParentId === null
1485  ? $this->getPreviousRevisionId( $dbw )
1486  : $this->mParentId,
1487  'rev_sha1' => $this->mSha1 === null
1488  ? self::base36Sha1( $this->mText )
1489  : $this->mSha1,
1490  ];
1491  if ( $this->mId !== null ) {
1492  $row['rev_id'] = $this->mId;
1493  }
1494 
1495  list( $commentFields, $commentCallback ) =
1496  CommentStore::newKey( 'rev_comment' )->insertWithTempTable( $dbw, $this->mComment );
1497  $row += $commentFields;
1498 
1499  if ( $wgContentHandlerUseDB ) {
1500  // NOTE: Store null for the default model and format, to save space.
1501  // XXX: Makes the DB sensitive to changed defaults.
1502  // Make this behavior optional? Only in miser mode?
1503 
1504  $model = $this->getContentModel();
1505  $format = $this->getContentFormat();
1506 
1507  $title = $this->getTitle();
1508 
1509  if ( $title === null ) {
1510  throw new MWException( "Insufficient information to determine the title of the "
1511  . "revision's page!" );
1512  }
1513 
1514  $defaultModel = ContentHandler::getDefaultModelFor( $title );
1515  $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
1516 
1517  $row['rev_content_model'] = ( $model === $defaultModel ) ? null : $model;
1518  $row['rev_content_format'] = ( $format === $defaultFormat ) ? null : $format;
1519  }
1520 
1521  $dbw->insert( 'revision', $row, __METHOD__ );
1522 
1523  if ( $this->mId === null ) {
1524  // Only if auto-increment was used
1525  $this->mId = $dbw->insertId();
1526  }
1527  $commentCallback( $this->mId );
1528 
1529  // Assertion to try to catch T92046
1530  if ( (int)$this->mId === 0 ) {
1531  throw new UnexpectedValueException(
1532  'After insert, Revision mId is ' . var_export( $this->mId, 1 ) . ': ' .
1533  var_export( $row, 1 )
1534  );
1535  }
1536 
1537  // Insert IP revision into ip_changes for use when querying for a range.
1538  if ( $this->mUser === 0 && IP::isValid( $this->mUserText ) ) {
1539  $ipcRow = [
1540  'ipc_rev_id' => $this->mId,
1541  'ipc_rev_timestamp' => $row['rev_timestamp'],
1542  'ipc_hex' => IP::toHex( $row['rev_user_text'] ),
1543  ];
1544  $dbw->insert( 'ip_changes', $ipcRow, __METHOD__ );
1545  }
1546 
1547  // Avoid PHP 7.1 warning of passing $this by reference
1548  $revision = $this;
1549  Hooks::run( 'RevisionInsertComplete', [ &$revision, $data, $flags ] );
1550 
1551  return $this->mId;
1552  }
1553 
1554  protected function checkContentModel() {
1555  global $wgContentHandlerUseDB;
1556 
1557  // Note: may return null for revisions that have not yet been inserted
1558  $title = $this->getTitle();
1559 
1560  $model = $this->getContentModel();
1561  $format = $this->getContentFormat();
1562  $handler = $this->getContentHandler();
1563 
1564  if ( !$handler->isSupportedFormat( $format ) ) {
1565  $t = $title->getPrefixedDBkey();
1566 
1567  throw new MWException( "Can't use format $format with content model $model on $t" );
1568  }
1569 
1570  if ( !$wgContentHandlerUseDB && $title ) {
1571  // if $wgContentHandlerUseDB is not set,
1572  // all revisions must use the default content model and format.
1573 
1574  $defaultModel = ContentHandler::getDefaultModelFor( $title );
1575  $defaultHandler = ContentHandler::getForModelID( $defaultModel );
1576  $defaultFormat = $defaultHandler->getDefaultFormat();
1577 
1578  if ( $this->getContentModel() != $defaultModel ) {
1579  $t = $title->getPrefixedDBkey();
1580 
1581  throw new MWException( "Can't save non-default content model with "
1582  . "\$wgContentHandlerUseDB disabled: model is $model, "
1583  . "default for $t is $defaultModel" );
1584  }
1585 
1586  if ( $this->getContentFormat() != $defaultFormat ) {
1587  $t = $title->getPrefixedDBkey();
1588 
1589  throw new MWException( "Can't use non-default content format with "
1590  . "\$wgContentHandlerUseDB disabled: format is $format, "
1591  . "default for $t is $defaultFormat" );
1592  }
1593  }
1594 
1595  $content = $this->getContent( self::RAW );
1596  $prefixedDBkey = $title->getPrefixedDBkey();
1597  $revId = $this->mId;
1598 
1599  if ( !$content ) {
1600  throw new MWException(
1601  "Content of revision $revId ($prefixedDBkey) could not be loaded for validation!"
1602  );
1603  }
1604  if ( !$content->isValid() ) {
1605  throw new MWException(
1606  "Content of revision $revId ($prefixedDBkey) is not valid! Content model is $model"
1607  );
1608  }
1609  }
1610 
1616  public static function base36Sha1( $text ) {
1617  return Wikimedia\base_convert( sha1( $text ), 16, 36, 31 );
1618  }
1619 
1626  private static function getCacheTTL( WANObjectCache $cache ) {
1628 
1629  if ( $cache->getQoS( $cache::ATTR_EMULATION ) <= $cache::QOS_EMULATION_SQL ) {
1630  // Do not cache RDBMs blobs in...the RDBMs store
1631  $ttl = $cache::TTL_UNCACHEABLE;
1632  } else {
1633  $ttl = $wgRevisionCacheExpiry ?: $cache::TTL_UNCACHEABLE;
1634  }
1635 
1636  return $ttl;
1637  }
1638 
1645  private function loadText() {
1647 
1648  // No negative caching; negative hits on text rows may be due to corrupted replica DBs
1649  return $cache->getWithSetCallback(
1650  $cache->makeKey( 'revisiontext', 'textid', $this->getTextId() ),
1651  self::getCacheTTL( $cache ),
1652  function () {
1653  return $this->fetchText();
1654  },
1655  [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => $cache::TTL_PROC_LONG ]
1656  );
1657  }
1658 
1659  private function fetchText() {
1660  $textId = $this->getTextId();
1661 
1662  // If we kept data for lazy extraction, use it now...
1663  if ( $this->mTextRow !== null ) {
1664  $row = $this->mTextRow;
1665  $this->mTextRow = null;
1666  } else {
1667  $row = null;
1668  }
1669 
1670  // Callers doing updates will pass in READ_LATEST as usual. Since the text/blob tables
1671  // do not normally get rows changed around, set READ_LATEST_IMMUTABLE in those cases.
1673  $flags |= DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST )
1674  ? self::READ_LATEST_IMMUTABLE
1675  : 0;
1676 
1677  list( $index, $options, $fallbackIndex, $fallbackOptions ) =
1679 
1680  if ( !$row ) {
1681  // Text data is immutable; check replica DBs first.
1682  $row = wfGetDB( $index )->selectRow(
1683  'text',
1684  [ 'old_text', 'old_flags' ],
1685  [ 'old_id' => $textId ],
1686  __METHOD__,
1687  $options
1688  );
1689  }
1690 
1691  // Fallback to DB_MASTER in some cases if the row was not found
1692  if ( !$row && $fallbackIndex !== null ) {
1693  // Use FOR UPDATE if it was used to fetch this revision. This avoids missing the row
1694  // due to REPEATABLE-READ. Also fallback to the master if READ_LATEST is provided.
1695  $row = wfGetDB( $fallbackIndex )->selectRow(
1696  'text',
1697  [ 'old_text', 'old_flags' ],
1698  [ 'old_id' => $textId ],
1699  __METHOD__,
1700  $fallbackOptions
1701  );
1702  }
1703 
1704  if ( !$row ) {
1705  wfDebugLog( 'Revision', "No text row with ID '$textId' (revision {$this->getId()})." );
1706  }
1707 
1708  $text = self::getRevisionText( $row );
1709  if ( $row && $text === false ) {
1710  wfDebugLog( 'Revision', "No blob for text row '$textId' (revision {$this->getId()})." );
1711  }
1712 
1713  return is_string( $text ) ? $text : false;
1714  }
1715 
1731  public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) {
1732  global $wgContentHandlerUseDB;
1733 
1734  $fields = [ 'page_latest', 'page_namespace', 'page_title',
1735  'rev_text_id', 'rev_len', 'rev_sha1' ];
1736 
1737  if ( $wgContentHandlerUseDB ) {
1738  $fields[] = 'rev_content_model';
1739  $fields[] = 'rev_content_format';
1740  }
1741 
1742  $current = $dbw->selectRow(
1743  [ 'page', 'revision' ],
1744  $fields,
1745  [
1746  'page_id' => $pageId,
1747  'page_latest=rev_id',
1748  ],
1749  __METHOD__,
1750  [ 'FOR UPDATE' ] // T51581
1751  );
1752 
1753  if ( $current ) {
1754  if ( !$user ) {
1755  global $wgUser;
1756  $user = $wgUser;
1757  }
1758 
1759  $row = [
1760  'page' => $pageId,
1761  'user_text' => $user->getName(),
1762  'user' => $user->getId(),
1763  'comment' => $summary,
1764  'minor_edit' => $minor,
1765  'text_id' => $current->rev_text_id,
1766  'parent_id' => $current->page_latest,
1767  'len' => $current->rev_len,
1768  'sha1' => $current->rev_sha1
1769  ];
1770 
1771  if ( $wgContentHandlerUseDB ) {
1772  $row['content_model'] = $current->rev_content_model;
1773  $row['content_format'] = $current->rev_content_format;
1774  }
1775 
1776  $row['title'] = Title::makeTitle( $current->page_namespace, $current->page_title );
1777 
1778  $revision = new Revision( $row );
1779  } else {
1780  $revision = null;
1781  }
1782 
1783  return $revision;
1784  }
1785 
1796  public function userCan( $field, User $user = null ) {
1797  return self::userCanBitfield( $this->getVisibility(), $field, $user );
1798  }
1799 
1814  public static function userCanBitfield( $bitfield, $field, User $user = null,
1815  Title $title = null
1816  ) {
1817  if ( $bitfield & $field ) { // aspect is deleted
1818  if ( $user === null ) {
1819  global $wgUser;
1820  $user = $wgUser;
1821  }
1822  if ( $bitfield & self::DELETED_RESTRICTED ) {
1823  $permissions = [ 'suppressrevision', 'viewsuppressed' ];
1824  } elseif ( $field & self::DELETED_TEXT ) {
1825  $permissions = [ 'deletedtext' ];
1826  } else {
1827  $permissions = [ 'deletedhistory' ];
1828  }
1829  $permissionlist = implode( ', ', $permissions );
1830  if ( $title === null ) {
1831  wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
1832  return call_user_func_array( [ $user, 'isAllowedAny' ], $permissions );
1833  } else {
1834  $text = $title->getPrefixedText();
1835  wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" );
1836  foreach ( $permissions as $perm ) {
1837  if ( $title->userCan( $perm, $user ) ) {
1838  return true;
1839  }
1840  }
1841  return false;
1842  }
1843  } else {
1844  return true;
1845  }
1846  }
1847 
1856  static function getTimestampFromId( $title, $id, $flags = 0 ) {
1857  $db = ( $flags & self::READ_LATEST )
1858  ? wfGetDB( DB_MASTER )
1859  : wfGetDB( DB_REPLICA );
1860  // Casting fix for databases that can't take '' for rev_id
1861  if ( $id == '' ) {
1862  $id = 0;
1863  }
1864  $conds = [ 'rev_id' => $id ];
1865  $conds['rev_page'] = $title->getArticleID();
1866  $timestamp = $db->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
1867 
1868  return ( $timestamp !== false ) ? wfTimestamp( TS_MW, $timestamp ) : false;
1869  }
1870 
1878  static function countByPageId( $db, $id ) {
1879  $row = $db->selectRow( 'revision', [ 'revCount' => 'COUNT(*)' ],
1880  [ 'rev_page' => $id ], __METHOD__ );
1881  if ( $row ) {
1882  return $row->revCount;
1883  }
1884  return 0;
1885  }
1886 
1894  static function countByTitle( $db, $title ) {
1895  $id = $title->getArticleID();
1896  if ( $id ) {
1897  return self::countByPageId( $db, $id );
1898  }
1899  return 0;
1900  }
1901 
1918  public static function userWasLastToEdit( $db, $pageId, $userId, $since ) {
1919  if ( !$userId ) {
1920  return false;
1921  }
1922 
1923  if ( is_int( $db ) ) {
1924  $db = wfGetDB( $db );
1925  }
1926 
1927  $res = $db->select( 'revision',
1928  'rev_user',
1929  [
1930  'rev_page' => $pageId,
1931  'rev_timestamp > ' . $db->addQuotes( $db->timestamp( $since ) )
1932  ],
1933  __METHOD__,
1934  [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ] );
1935  foreach ( $res as $row ) {
1936  if ( $row->rev_user != $userId ) {
1937  return false;
1938  }
1939  }
1940  return true;
1941  }
1942 
1956  public static function newKnownCurrent( IDatabase $db, $pageId, $revId ) {
1957  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1958  return $cache->getWithSetCallback(
1959  // Page/rev IDs passed in from DB to reflect history merges
1960  $cache->makeGlobalKey( 'revision', $db->getDomainID(), $pageId, $revId ),
1961  $cache::TTL_WEEK,
1962  function ( $curValue, &$ttl, array &$setOpts ) use ( $db, $pageId, $revId ) {
1963  $setOpts += Database::getCacheSetOptions( $db );
1964 
1965  $rev = Revision::loadFromPageId( $db, $pageId, $revId );
1966  // Reflect revision deletion and user renames
1967  if ( $rev ) {
1968  $rev->mTitle = null; // mutable; lazy-load
1969  $rev->mRefreshMutableFields = true;
1970  }
1971 
1972  return $rev ?: false; // don't cache negatives
1973  }
1974  );
1975  }
1976 
1980  private function loadMutableFields() {
1981  if ( !$this->mRefreshMutableFields ) {
1982  return; // not needed
1983  }
1984 
1985  $this->mRefreshMutableFields = false;
1986  $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki );
1987  $row = $dbr->selectRow(
1988  [ 'revision', 'user' ],
1989  [ 'rev_deleted', 'user_name' ],
1990  [ 'rev_id' => $this->mId, 'user_id = rev_user' ],
1991  __METHOD__
1992  );
1993  if ( $row ) { // update values
1994  $this->mDeleted = (int)$row->rev_deleted;
1995  $this->mUserText = $row->user_name;
1996  }
1997  }
1998 }
static newFromRow($row)
Make a Title object from a DB row.
Definition: Title.php:459
const FOR_THIS_USER
Definition: Revision.php:99
static newFromID($id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:411
constructFromDbRowObject($row)
Definition: Revision.php:588
static whoIs($id)
Get the username corresponding to a given user ID.
Definition: User.php:745
string $mTimestamp
Definition: Revision.php:47
static getMainWANInstance()
Get the main WAN cache object.
string $mContentFormat
Definition: Revision.php:75
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
the array() calling protocol came about after MediaWiki 1.4rc1.
static getRevisionText($row, $prefix= 'old_', $wiki=false)
Get revision text associated with an old or archive row.
Definition: Revision.php:1282
const TEXT_CACHE_GROUP
Definition: Revision.php:102
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:236
array $wgDefaultExternalStore
The place to put new revisions, false to put them in the local text table.
static fetchRevision(LinkTarget $title)
Return a wrapper for a series of database rows to fetch all of a given page's revisions in turn...
Definition: Revision.php:382
setTitle($title)
Set the title of the revision.
Definition: Revision.php:867
getPage()
Get the page ID.
Definition: Revision.php:876
int null $mPage
Definition: Revision.php:37
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
getTimestamp()
Definition: Revision.php:1202
static getForModelID($modelId)
Returns the ContentHandler singleton for the given model ID.
static insertToDefault($data, array $params=[])
Like insert() above, but does more of the work for us.
setUserIdAndName($id, $name)
Set the user ID/name.
Definition: Revision.php:784
static getTimestampFromId($title, $id, $flags=0)
Get rev_timestamp from rev_id, without loading the rest of the row.
Definition: Revision.php:1856
static newFromConds($conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
int $mParentId
Definition: Revision.php:55
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title...
loadText()
Lazy-load the revision's text.
Definition: Revision.php:1645
int $mUnpatrolled
Definition: Revision.php:63
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2803
isUnpatrolled()
Definition: Revision.php:1002
static selectArchiveFields()
Return the list of revision fields that should be selected to create a new revision from an archive r...
Definition: Revision.php:486
static newFromPageId($pageId, $revId=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given page ID...
Definition: Revision.php:165
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target...
Definition: Revision.php:134
static fetchFromConds($db, $conditions, $flags=0)
Given a set of conditions, return a ResultWrapper which will return matching database rows with the f...
Definition: Revision.php:405
$wgCompressRevisions
We can also compress text stored in the 'text' table.
const DB_MASTER
Definition: defines.php:26
loadMutableFields()
For cached revisions, make sure the user name and rev_deleted is up-to-date.
Definition: Revision.php:1980
getNamespace()
Get the namespace index.
getSerializedData()
Get original serialized data (without checking view restrictions)
Definition: Revision.php:1097
wfDebug($text, $dest= 'all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getRawComment()
Fetch revision comment without regard for the current user's permissions.
Definition: Revision.php:987
string $mUserText
Definition: Revision.php:39
bool $mRefreshMutableFields
Used for cached values to reload user text and rev_deleted.
Definition: Revision.php:85
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
int null $mId
Definition: Revision.php:35
static loadFromConds($db, $conditions, $flags=0)
Given a set of conditions, fetch a revision from the given database connection.
Definition: Revision.php:361
checkContentModel()
Definition: Revision.php:1554
wfDebugLog($logGroup, $text, $dest= 'all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
$wgRevisionCacheExpiry
Revision text may be cached in $wgMemc to reduce load on external storage servers and object extracti...
isDeleted($field)
Definition: Revision.php:1045
null ContentHandler $mContentHandler
Definition: Revision.php:80
wfGetLB($wiki=false)
Get a load balancer object.
getTitle()
Returns the title of the page associated with this entry or null.
Definition: Revision.php:833
$wgLegacyEncoding
Set this to eg 'ISO-8859-1' to perform character set conversion when loading old revisions not marked...
getParentId()
Get parent revision ID (the original previous page revision)
Definition: Revision.php:804
unserialize($serialized)
Definition: ApiMessage.php:192
const FOR_PUBLIC
Definition: Revision.php:98
getUserText($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's username if it's available to the specified audience.
Definition: Revision.php:927
getRawUserText()
Fetch revision's username without regard for view restrictions.
Definition: Revision.php:953
getId()
Get revision ID.
Definition: Revision.php:759
static isValid($ip)
Validate an IP address.
Definition: IP.php:111
$res
Definition: database.txt:21
static loadFromTimestamp($db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:309
getContentHandler()
Returns the content handler appropriate for this revision's content model.
Definition: Revision.php:1183
__construct($row)
Definition: Revision.php:574
static newNullRevision($dbw, $pageId, $summary, $minor, $user=null)
Create a new null-revision for insertion into a page's history.
Definition: Revision.php:1731
static compressRevisionText(&$text)
If $wgCompressRevisions is enabled, we will compress data.
Definition: Revision.php:1339
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
getDBkey()
Get the main part with underscores.
$cache
Definition: mcc.php:33
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1966
static selectFields()
Return the list of revision fields that should be selected to create a new revision.
Definition: Revision.php:452
static selectTextFields()
Return the list of text fields that should be selected to read the revision text. ...
Definition: Revision.php:518
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
userCan($field, User $user=null)
Determine if the current user is allowed to view a particular field of this revision, if it's marked as deleted.
Definition: Revision.php:1796
getComment($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision comment if it's available to the specified audience.
Definition: Revision.php:971
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition: hooks.txt:1966
const DELETED_RESTRICTED
Definition: Revision.php:93
getContentInternal()
Gets the content object for the revision (or null on failure).
Definition: Revision.php:1115
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:933
static newKnownCurrent(IDatabase $db, $pageId, $revId)
Load a revision based on a known page ID and current revision ID from the DB.
Definition: Revision.php:1956
bool $mCurrent
Definition: Revision.php:71
int $mQueryFlags
Definition: Revision.php:83
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
static getDBOptions($bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
static toHex($ip)
Return a zero-padded upper case hexadecimal representation of an IP address.
Definition: IP.php:417
static loadFromTitle($db, $title, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition: Revision.php:284
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1751
getPrevious()
Get previous revision for this title.
Definition: Revision.php:1218
setId($id)
Set the revision ID.
Definition: Revision.php:771
const RAW
Definition: Revision.php:100
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
int $mSize
Definition: Revision.php:51
getRecentChange($flags=0)
Get the RC object belonging to the current revision, if there's one.
Definition: Revision.php:1024
getContentModel()
Returns the content model for this revision.
Definition: Revision.php:1142
const DELETED_TEXT
Definition: Revision.php:90
const SUPPRESSED_USER
Definition: Revision.php:94
static countByPageId($db, $id)
Get count of revisions per page...not very efficient.
Definition: Revision.php:1878
string $mComment
Definition: Revision.php:57
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:116
static pageJoinCond()
Return the value of a select() page conds array for the page table.
Definition: Revision.php:441
getVisibility()
Get the deletion bitfield of the revision.
Definition: Revision.php:1061
int $mTextId
Definition: Revision.php:61
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
const DELETED_USER
Definition: Revision.php:92
int $mDeleted
Definition: Revision.php:49
static hasFlags($bitfield, $flags)
getTextId()
Get text row ID.
Definition: Revision.php:795
insertOn($dbw)
Insert a new revision into the database, returning the new revision ID number on success and dies hor...
Definition: Revision.php:1422
string $mSha1
Definition: Revision.php:53
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
string $mText
Definition: Revision.php:59
static fetchFromURL($url, array $params=[])
Fetch data from given URL.
int $mUser
Definition: Revision.php:43
static countByTitle($db, $title)
Get count of revisions per page...not very efficient.
Definition: Revision.php:1894
string $mContentModel
Definition: Revision.php:73
getContent($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision content if it's available to the specified audience.
Definition: Revision.php:1081
getPreviousRevisionId($db)
Get previous revision Id for this page_id This is used to populate rev_parent_id on save...
Definition: Revision.php:1250
static newFromRow($row)
Definition: Revision.php:238
static getCacheTTL(WANObjectCache $cache)
Get the text cache TTL.
Definition: Revision.php:1626
static loadFromPageId($db, $pageid, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition: Revision.php:264
stdClass null $mTextRow
Definition: Revision.php:66
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
Definition: design.txt:56
Interface for database access objects.
static newKey($key)
Static constructor for easier chaining.
getNext()
Get next revision for this title.
Definition: Revision.php:1233
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
static getParentLengths($db, array $revIds)
Do a batched query to get the parent revision lengths.
Definition: Revision.php:554
null Title $mTitle
Definition: Revision.php:69
string $mOrigUserText
Definition: Revision.php:41
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when needed(most notably, OutputPage::addWikiText()).The StandardSkin object is a complete implementation
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going on
Definition: hooks.txt:84
static base36Sha1($text)
Get the base 36 SHA-1 value for a string of text.
Definition: Revision.php:1616
getUser($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's user id if it's available to the specified audience.
Definition: Revision.php:893
static selectPageFields()
Return the list of page fields that should be selected from page table.
Definition: Revision.php:529
const DB_REPLICA
Definition: defines.php:25
static userCanBitfield($bitfield, $field, User $user=null, Title $title=null)
Determine if the current user is allowed to view a particular field of this revision, if it's marked as deleted.
Definition: Revision.php:1814
wfLogWarning($msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
const DELETED_COMMENT
Definition: Revision.php:91
bool $mMinorEdit
Definition: Revision.php:45
constructFromRowArray(array $row)
Definition: Revision.php:662
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:782
static userWasLastToEdit($db, $pageId, $userId, $since)
Check if no edits were made by other users since the time a user started editing the page...
Definition: Revision.php:1918
static userJoinCond()
Return the value of a select() JOIN conds array for the user table.
Definition: Revision.php:431
getContentFormat()
Returns the content format for this revision.
Definition: Revision.php:1166
static loadFromId($db, $id)
Load a page revision from a given revision ID number.
Definition: Revision.php:250
Content null bool $mContent
Definition: Revision.php:78
static newFromArchiveRow($row, $overrides=[])
Make a fake revision object from an archive table row.
Definition: Revision.php:189
getRawUser()
Fetch revision's user id without regard for the current user's permissions.
Definition: Revision.php:909
getSize()
Returns the length of the text in this revision, or null if unknown.
Definition: Revision.php:813
string $mWiki
Wiki ID; false means the current wiki.
Definition: Revision.php:87
static decompressRevisionText($text, $flags)
Re-converts revision text according to it's flags.
Definition: Revision.php:1371
static selectUserFields()
Return the list of user fields that should be selected from user table.
Definition: Revision.php:544
static makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:529
static newFromConds($conditions, $flags=0)
Given a set of conditions, fetch a revision.
Definition: Revision.php:329
getSha1()
Returns the base36 sha1 of the text in this revision, or null if unknown.
Definition: Revision.php:822
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:244
const SUPPRESSED_ALL
Definition: Revision.php:95
$wgUser
Definition: Setup.php:809