MediaWiki  1.30.1
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 = [] ) {
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() {
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() {
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  function __construct( $row ) {
575  if ( is_object( $row ) ) {
576  $this->mId = intval( $row->rev_id );
577  $this->mPage = intval( $row->rev_page );
578  $this->mTextId = intval( $row->rev_text_id );
579  $this->mComment = CommentStore::newKey( 'rev_comment' )
580  // Legacy because $row probably came from self::selectFields()
581  ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text;
582  $this->mUser = intval( $row->rev_user );
583  $this->mMinorEdit = intval( $row->rev_minor_edit );
584  $this->mTimestamp = $row->rev_timestamp;
585  $this->mDeleted = intval( $row->rev_deleted );
586 
587  if ( !isset( $row->rev_parent_id ) ) {
588  $this->mParentId = null;
589  } else {
590  $this->mParentId = intval( $row->rev_parent_id );
591  }
592 
593  if ( !isset( $row->rev_len ) ) {
594  $this->mSize = null;
595  } else {
596  $this->mSize = intval( $row->rev_len );
597  }
598 
599  if ( !isset( $row->rev_sha1 ) ) {
600  $this->mSha1 = null;
601  } else {
602  $this->mSha1 = $row->rev_sha1;
603  }
604 
605  if ( isset( $row->page_latest ) ) {
606  $this->mCurrent = ( $row->rev_id == $row->page_latest );
607  $this->mTitle = Title::newFromRow( $row );
608  } else {
609  $this->mCurrent = false;
610  $this->mTitle = null;
611  }
612 
613  if ( !isset( $row->rev_content_model ) ) {
614  $this->mContentModel = null; # determine on demand if needed
615  } else {
616  $this->mContentModel = strval( $row->rev_content_model );
617  }
618 
619  if ( !isset( $row->rev_content_format ) ) {
620  $this->mContentFormat = null; # determine on demand if needed
621  } else {
622  $this->mContentFormat = strval( $row->rev_content_format );
623  }
624 
625  // Lazy extraction...
626  $this->mText = null;
627  if ( isset( $row->old_text ) ) {
628  $this->mTextRow = $row;
629  } else {
630  // 'text' table row entry will be lazy-loaded
631  $this->mTextRow = null;
632  }
633 
634  // Use user_name for users and rev_user_text for IPs...
635  $this->mUserText = null; // lazy load if left null
636  if ( $this->mUser == 0 ) {
637  $this->mUserText = $row->rev_user_text; // IP user
638  } elseif ( isset( $row->user_name ) ) {
639  $this->mUserText = $row->user_name; // logged-in user
640  }
641  $this->mOrigUserText = $row->rev_user_text;
642  } elseif ( is_array( $row ) ) {
643  // Build a new revision to be saved...
644  global $wgUser; // ugh
645 
646  # if we have a content object, use it to set the model and type
647  if ( !empty( $row['content'] ) ) {
648  // @todo when is that set? test with external store setup! check out insertOn() [dk]
649  if ( !empty( $row['text_id'] ) ) {
650  throw new MWException( "Text already stored in external store (id {$row['text_id']}), " .
651  "can't serialize content object" );
652  }
653 
654  $row['content_model'] = $row['content']->getModel();
655  # note: mContentFormat is initializes later accordingly
656  # note: content is serialized later in this method!
657  # also set text to null?
658  }
659 
660  $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
661  $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
662  $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
663  $this->mUserText = isset( $row['user_text'] )
664  ? strval( $row['user_text'] ) : $wgUser->getName();
665  $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
666  $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
667  $this->mTimestamp = isset( $row['timestamp'] )
668  ? strval( $row['timestamp'] ) : wfTimestampNow();
669  $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
670  $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
671  $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
672  $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
673 
674  $this->mContentModel = isset( $row['content_model'] )
675  ? strval( $row['content_model'] ) : null;
676  $this->mContentFormat = isset( $row['content_format'] )
677  ? strval( $row['content_format'] ) : null;
678 
679  // Enforce spacing trimming on supplied text
680  $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
681  $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
682  $this->mTextRow = null;
683 
684  $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
685 
686  // if we have a Content object, override mText and mContentModel
687  if ( !empty( $row['content'] ) ) {
688  if ( !( $row['content'] instanceof Content ) ) {
689  throw new MWException( '`content` field must contain a Content object.' );
690  }
691 
692  $handler = $this->getContentHandler();
693  $this->mContent = $row['content'];
694 
695  $this->mContentModel = $this->mContent->getModel();
696  $this->mContentHandler = null;
697 
698  $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
699  } elseif ( $this->mText !== null ) {
700  $handler = $this->getContentHandler();
701  $this->mContent = $handler->unserializeContent( $this->mText );
702  }
703 
704  // If we have a Title object, make sure it is consistent with mPage.
705  if ( $this->mTitle && $this->mTitle->exists() ) {
706  if ( $this->mPage === null ) {
707  // if the page ID wasn't known, set it now
708  $this->mPage = $this->mTitle->getArticleID();
709  } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
710  // Got different page IDs. This may be legit (e.g. during undeletion),
711  // but it seems worth mentioning it in the log.
712  wfDebug( "Page ID " . $this->mPage . " mismatches the ID " .
713  $this->mTitle->getArticleID() . " provided by the Title object." );
714  }
715  }
716 
717  $this->mCurrent = false;
718 
719  // If we still have no length, see it we have the text to figure it out
720  if ( !$this->mSize && $this->mContent !== null ) {
721  $this->mSize = $this->mContent->getSize();
722  }
723 
724  // Same for sha1
725  if ( $this->mSha1 === null ) {
726  $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
727  }
728 
729  // force lazy init
730  $this->getContentModel();
731  $this->getContentFormat();
732  } else {
733  throw new MWException( 'Revision constructor passed invalid row format.' );
734  }
735  $this->mUnpatrolled = null;
736  }
737 
743  public function getId() {
744  return $this->mId;
745  }
746 
755  public function setId( $id ) {
756  $this->mId = (int)$id;
757  }
758 
768  public function setUserIdAndName( $id, $name ) {
769  $this->mUser = (int)$id;
770  $this->mUserText = $name;
771  $this->mOrigUserText = $name;
772  }
773 
779  public function getTextId() {
780  return $this->mTextId;
781  }
782 
788  public function getParentId() {
789  return $this->mParentId;
790  }
791 
797  public function getSize() {
798  return $this->mSize;
799  }
800 
806  public function getSha1() {
807  return $this->mSha1;
808  }
809 
817  public function getTitle() {
818  if ( $this->mTitle !== null ) {
819  return $this->mTitle;
820  }
821  // rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
822  if ( $this->mId !== null ) {
823  $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki );
824  $row = $dbr->selectRow(
825  [ 'page', 'revision' ],
826  self::selectPageFields(),
827  [ 'page_id=rev_page', 'rev_id' => $this->mId ],
828  __METHOD__
829  );
830  if ( $row ) {
831  // @TODO: better foreign title handling
832  $this->mTitle = Title::newFromRow( $row );
833  }
834  }
835 
836  if ( $this->mWiki === false || $this->mWiki === wfWikiID() ) {
837  // Loading by ID is best, though not possible for foreign titles
838  if ( !$this->mTitle && $this->mPage !== null && $this->mPage > 0 ) {
839  $this->mTitle = Title::newFromID( $this->mPage );
840  }
841  }
842 
843  return $this->mTitle;
844  }
845 
851  public function setTitle( $title ) {
852  $this->mTitle = $title;
853  }
854 
860  public function getPage() {
861  return $this->mPage;
862  }
863 
877  public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
878  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
879  return 0;
880  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
881  return 0;
882  } else {
883  return $this->mUser;
884  }
885  }
886 
893  public function getRawUser() {
894  wfDeprecated( __METHOD__, '1.25' );
895  return $this->getUser( self::RAW );
896  }
897 
911  public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
912  $this->loadMutableFields();
913 
914  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
915  return '';
916  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
917  return '';
918  } else {
919  if ( $this->mUserText === null ) {
920  $this->mUserText = User::whoIs( $this->mUser ); // load on demand
921  if ( $this->mUserText === false ) {
922  # This shouldn't happen, but it can if the wiki was recovered
923  # via importing revs and there is no user table entry yet.
924  $this->mUserText = $this->mOrigUserText;
925  }
926  }
927  return $this->mUserText;
928  }
929  }
930 
937  public function getRawUserText() {
938  wfDeprecated( __METHOD__, '1.25' );
939  return $this->getUserText( self::RAW );
940  }
941 
955  function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
956  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
957  return '';
958  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $user ) ) {
959  return '';
960  } else {
961  return $this->mComment;
962  }
963  }
964 
971  public function getRawComment() {
972  wfDeprecated( __METHOD__, '1.25' );
973  return $this->getComment( self::RAW );
974  }
975 
979  public function isMinor() {
980  return (bool)$this->mMinorEdit;
981  }
982 
986  public function isUnpatrolled() {
987  if ( $this->mUnpatrolled !== null ) {
988  return $this->mUnpatrolled;
989  }
990  $rc = $this->getRecentChange();
991  if ( $rc && $rc->getAttribute( 'rc_patrolled' ) == 0 ) {
992  $this->mUnpatrolled = $rc->getAttribute( 'rc_id' );
993  } else {
994  $this->mUnpatrolled = 0;
995  }
996  return $this->mUnpatrolled;
997  }
998 
1008  public function getRecentChange( $flags = 0 ) {
1009  $dbr = wfGetDB( DB_REPLICA );
1010 
1012 
1014  [
1015  'rc_user_text' => $this->getUserText( self::RAW ),
1016  'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
1017  'rc_this_oldid' => $this->getId()
1018  ],
1019  __METHOD__,
1020  $dbType
1021  );
1022  }
1023 
1029  public function isDeleted( $field ) {
1030  if ( $this->isCurrent() && $field === self::DELETED_TEXT ) {
1031  // Current revisions of pages cannot have the content hidden. Skipping this
1032  // check is very useful for Parser as it fetches templates using newKnownCurrent().
1033  // Calling getVisibility() in that case triggers a verification database query.
1034  return false; // no need to check
1035  }
1036 
1037  return ( $this->getVisibility() & $field ) == $field;
1038  }
1039 
1045  public function getVisibility() {
1046  $this->loadMutableFields();
1047 
1048  return (int)$this->mDeleted;
1049  }
1050 
1065  public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
1066  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
1067  return null;
1068  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
1069  return null;
1070  } else {
1071  return $this->getContentInternal();
1072  }
1073  }
1074 
1081  public function getSerializedData() {
1082  if ( $this->mText === null ) {
1083  // Revision is immutable. Load on demand.
1084  $this->mText = $this->loadText();
1085  }
1086 
1087  return $this->mText;
1088  }
1089 
1099  protected function getContentInternal() {
1100  if ( $this->mContent === null ) {
1101  $text = $this->getSerializedData();
1102 
1103  if ( $text !== null && $text !== false ) {
1104  // Unserialize content
1105  $handler = $this->getContentHandler();
1106  $format = $this->getContentFormat();
1107 
1108  $this->mContent = $handler->unserializeContent( $text, $format );
1109  }
1110  }
1111 
1112  // NOTE: copy() will return $this for immutable content objects
1113  return $this->mContent ? $this->mContent->copy() : null;
1114  }
1115 
1126  public function getContentModel() {
1127  if ( !$this->mContentModel ) {
1128  $title = $this->getTitle();
1129  if ( $title ) {
1130  $this->mContentModel = ContentHandler::getDefaultModelFor( $title );
1131  } else {
1132  $this->mContentModel = CONTENT_MODEL_WIKITEXT;
1133  }
1134 
1135  assert( !empty( $this->mContentModel ) );
1136  }
1137 
1138  return $this->mContentModel;
1139  }
1140 
1150  public function getContentFormat() {
1151  if ( !$this->mContentFormat ) {
1152  $handler = $this->getContentHandler();
1153  $this->mContentFormat = $handler->getDefaultFormat();
1154 
1155  assert( !empty( $this->mContentFormat ) );
1156  }
1157 
1158  return $this->mContentFormat;
1159  }
1160 
1167  public function getContentHandler() {
1168  if ( !$this->mContentHandler ) {
1169  $model = $this->getContentModel();
1170  $this->mContentHandler = ContentHandler::getForModelID( $model );
1171 
1172  $format = $this->getContentFormat();
1173 
1174  if ( !$this->mContentHandler->isSupportedFormat( $format ) ) {
1175  throw new MWException( "Oops, the content format $format is not supported for "
1176  . "this content model, $model" );
1177  }
1178  }
1179 
1180  return $this->mContentHandler;
1181  }
1182 
1186  public function getTimestamp() {
1187  return wfTimestamp( TS_MW, $this->mTimestamp );
1188  }
1189 
1193  public function isCurrent() {
1194  return $this->mCurrent;
1195  }
1196 
1202  public function getPrevious() {
1203  if ( $this->getTitle() ) {
1204  $prev = $this->getTitle()->getPreviousRevisionID( $this->getId() );
1205  if ( $prev ) {
1206  return self::newFromTitle( $this->getTitle(), $prev );
1207  }
1208  }
1209  return null;
1210  }
1211 
1217  public function getNext() {
1218  if ( $this->getTitle() ) {
1219  $next = $this->getTitle()->getNextRevisionID( $this->getId() );
1220  if ( $next ) {
1221  return self::newFromTitle( $this->getTitle(), $next );
1222  }
1223  }
1224  return null;
1225  }
1226 
1234  private function getPreviousRevisionId( $db ) {
1235  if ( $this->mPage === null ) {
1236  return 0;
1237  }
1238  # Use page_latest if ID is not given
1239  if ( !$this->mId ) {
1240  $prevId = $db->selectField( 'page', 'page_latest',
1241  [ 'page_id' => $this->mPage ],
1242  __METHOD__ );
1243  } else {
1244  $prevId = $db->selectField( 'revision', 'rev_id',
1245  [ 'rev_page' => $this->mPage, 'rev_id < ' . $this->mId ],
1246  __METHOD__,
1247  [ 'ORDER BY' => 'rev_id DESC' ] );
1248  }
1249  return intval( $prevId );
1250  }
1251 
1266  public static function getRevisionText( $row, $prefix = 'old_', $wiki = false ) {
1267  $textField = $prefix . 'text';
1268  $flagsField = $prefix . 'flags';
1269 
1270  if ( isset( $row->$flagsField ) ) {
1271  $flags = explode( ',', $row->$flagsField );
1272  } else {
1273  $flags = [];
1274  }
1275 
1276  if ( isset( $row->$textField ) ) {
1277  $text = $row->$textField;
1278  } else {
1279  return false;
1280  }
1281 
1282  // Use external methods for external objects, text in table is URL-only then
1283  if ( in_array( 'external', $flags ) ) {
1284  $url = $text;
1285  $parts = explode( '://', $url, 2 );
1286  if ( count( $parts ) == 1 || $parts[1] == '' ) {
1287  return false;
1288  }
1289 
1290  if ( isset( $row->old_id ) && $wiki === false ) {
1291  // Make use of the wiki-local revision text cache
1292  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1293  // The cached value should be decompressed, so handle that and return here
1294  return $cache->getWithSetCallback(
1295  $cache->makeKey( 'revisiontext', 'textid', $row->old_id ),
1297  function () use ( $url, $wiki, $flags ) {
1298  // No negative caching per Revision::loadText()
1299  $text = ExternalStore::fetchFromURL( $url, [ 'wiki' => $wiki ] );
1300 
1301  return self::decompressRevisionText( $text, $flags );
1302  },
1303  [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => $cache::TTL_PROC_LONG ]
1304  );
1305  } else {
1306  $text = ExternalStore::fetchFromURL( $url, [ 'wiki' => $wiki ] );
1307  }
1308  }
1309 
1310  return self::decompressRevisionText( $text, $flags );
1311  }
1312 
1323  public static function compressRevisionText( &$text ) {
1325  $flags = [];
1326 
1327  # Revisions not marked this way will be converted
1328  # on load if $wgLegacyCharset is set in the future.
1329  $flags[] = 'utf-8';
1330 
1331  if ( $wgCompressRevisions ) {
1332  if ( function_exists( 'gzdeflate' ) ) {
1333  $deflated = gzdeflate( $text );
1334 
1335  if ( $deflated === false ) {
1336  wfLogWarning( __METHOD__ . ': gzdeflate() failed' );
1337  } else {
1338  $text = $deflated;
1339  $flags[] = 'gzip';
1340  }
1341  } else {
1342  wfDebug( __METHOD__ . " -- no zlib support, not compressing\n" );
1343  }
1344  }
1345  return implode( ',', $flags );
1346  }
1347 
1355  public static function decompressRevisionText( $text, $flags ) {
1357 
1358  if ( $text === false ) {
1359  // Text failed to be fetched; nothing to do
1360  return false;
1361  }
1362 
1363  if ( in_array( 'gzip', $flags ) ) {
1364  # Deal with optional compression of archived pages.
1365  # This can be done periodically via maintenance/compressOld.php, and
1366  # as pages are saved if $wgCompressRevisions is set.
1367  $text = gzinflate( $text );
1368 
1369  if ( $text === false ) {
1370  wfLogWarning( __METHOD__ . ': gzinflate() failed' );
1371  return false;
1372  }
1373  }
1374 
1375  if ( in_array( 'object', $flags ) ) {
1376  # Generic compressed storage
1377  $obj = unserialize( $text );
1378  if ( !is_object( $obj ) ) {
1379  // Invalid object
1380  return false;
1381  }
1382  $text = $obj->getText();
1383  }
1384 
1385  if ( $text !== false && $wgLegacyEncoding
1386  && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags )
1387  ) {
1388  # Old revisions kept around in a legacy encoding?
1389  # Upconvert on demand.
1390  # ("utf8" checked for compatibility with some broken
1391  # conversion scripts 2008-12-30)
1392  $text = $wgContLang->iconv( $wgLegacyEncoding, 'UTF-8', $text );
1393  }
1394 
1395  return $text;
1396  }
1397 
1406  public function insertOn( $dbw ) {
1408 
1409  // We're inserting a new revision, so we have to use master anyway.
1410  // If it's a null revision, it may have references to rows that
1411  // are not in the replica yet (the text row).
1412  $this->mQueryFlags |= self::READ_LATEST;
1413 
1414  // Not allowed to have rev_page equal to 0, false, etc.
1415  if ( !$this->mPage ) {
1416  $title = $this->getTitle();
1417  if ( $title instanceof Title ) {
1418  $titleText = ' for page ' . $title->getPrefixedText();
1419  } else {
1420  $titleText = '';
1421  }
1422  throw new MWException( "Cannot insert revision$titleText: page ID must be nonzero" );
1423  }
1424 
1425  $this->checkContentModel();
1426 
1427  $data = $this->mText;
1429 
1430  # Write to external storage if required
1431  if ( $wgDefaultExternalStore ) {
1432  // Store and get the URL
1433  $data = ExternalStore::insertToDefault( $data );
1434  if ( !$data ) {
1435  throw new MWException( "Unable to store text to external storage" );
1436  }
1437  if ( $flags ) {
1438  $flags .= ',';
1439  }
1440  $flags .= 'external';
1441  }
1442 
1443  # Record the text (or external storage URL) to the text table
1444  if ( $this->mTextId === null ) {
1445  $dbw->insert( 'text',
1446  [
1447  'old_text' => $data,
1448  'old_flags' => $flags,
1449  ], __METHOD__
1450  );
1451  $this->mTextId = $dbw->insertId();
1452  }
1453 
1454  if ( $this->mComment === null ) {
1455  $this->mComment = "";
1456  }
1457 
1458  # Record the edit in revisions
1459  $row = [
1460  'rev_page' => $this->mPage,
1461  'rev_text_id' => $this->mTextId,
1462  'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
1463  'rev_user' => $this->mUser,
1464  'rev_user_text' => $this->mUserText,
1465  'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
1466  'rev_deleted' => $this->mDeleted,
1467  'rev_len' => $this->mSize,
1468  'rev_parent_id' => $this->mParentId === null
1469  ? $this->getPreviousRevisionId( $dbw )
1470  : $this->mParentId,
1471  'rev_sha1' => $this->mSha1 === null
1472  ? self::base36Sha1( $this->mText )
1473  : $this->mSha1,
1474  ];
1475  if ( $this->mId !== null ) {
1476  $row['rev_id'] = $this->mId;
1477  }
1478 
1479  list( $commentFields, $commentCallback ) =
1480  CommentStore::newKey( 'rev_comment' )->insertWithTempTable( $dbw, $this->mComment );
1481  $row += $commentFields;
1482 
1483  if ( $wgContentHandlerUseDB ) {
1484  // NOTE: Store null for the default model and format, to save space.
1485  // XXX: Makes the DB sensitive to changed defaults.
1486  // Make this behavior optional? Only in miser mode?
1487 
1488  $model = $this->getContentModel();
1489  $format = $this->getContentFormat();
1490 
1491  $title = $this->getTitle();
1492 
1493  if ( $title === null ) {
1494  throw new MWException( "Insufficient information to determine the title of the "
1495  . "revision's page!" );
1496  }
1497 
1498  $defaultModel = ContentHandler::getDefaultModelFor( $title );
1499  $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
1500 
1501  $row['rev_content_model'] = ( $model === $defaultModel ) ? null : $model;
1502  $row['rev_content_format'] = ( $format === $defaultFormat ) ? null : $format;
1503  }
1504 
1505  $dbw->insert( 'revision', $row, __METHOD__ );
1506 
1507  if ( $this->mId === null ) {
1508  // Only if auto-increment was used
1509  $this->mId = $dbw->insertId();
1510  }
1511  $commentCallback( $this->mId );
1512 
1513  // Assertion to try to catch T92046
1514  if ( (int)$this->mId === 0 ) {
1515  throw new UnexpectedValueException(
1516  'After insert, Revision mId is ' . var_export( $this->mId, 1 ) . ': ' .
1517  var_export( $row, 1 )
1518  );
1519  }
1520 
1521  // Insert IP revision into ip_changes for use when querying for a range.
1522  if ( $this->mUser === 0 && IP::isValid( $this->mUserText ) ) {
1523  $ipcRow = [
1524  'ipc_rev_id' => $this->mId,
1525  'ipc_rev_timestamp' => $row['rev_timestamp'],
1526  'ipc_hex' => IP::toHex( $row['rev_user_text'] ),
1527  ];
1528  $dbw->insert( 'ip_changes', $ipcRow, __METHOD__ );
1529  }
1530 
1531  // Avoid PHP 7.1 warning of passing $this by reference
1532  $revision = $this;
1533  Hooks::run( 'RevisionInsertComplete', [ &$revision, $data, $flags ] );
1534 
1535  return $this->mId;
1536  }
1537 
1538  protected function checkContentModel() {
1540 
1541  // Note: may return null for revisions that have not yet been inserted
1542  $title = $this->getTitle();
1543 
1544  $model = $this->getContentModel();
1545  $format = $this->getContentFormat();
1546  $handler = $this->getContentHandler();
1547 
1548  if ( !$handler->isSupportedFormat( $format ) ) {
1549  $t = $title->getPrefixedDBkey();
1550 
1551  throw new MWException( "Can't use format $format with content model $model on $t" );
1552  }
1553 
1554  if ( !$wgContentHandlerUseDB && $title ) {
1555  // if $wgContentHandlerUseDB is not set,
1556  // all revisions must use the default content model and format.
1557 
1558  $defaultModel = ContentHandler::getDefaultModelFor( $title );
1559  $defaultHandler = ContentHandler::getForModelID( $defaultModel );
1560  $defaultFormat = $defaultHandler->getDefaultFormat();
1561 
1562  if ( $this->getContentModel() != $defaultModel ) {
1563  $t = $title->getPrefixedDBkey();
1564 
1565  throw new MWException( "Can't save non-default content model with "
1566  . "\$wgContentHandlerUseDB disabled: model is $model, "
1567  . "default for $t is $defaultModel" );
1568  }
1569 
1570  if ( $this->getContentFormat() != $defaultFormat ) {
1571  $t = $title->getPrefixedDBkey();
1572 
1573  throw new MWException( "Can't use non-default content format with "
1574  . "\$wgContentHandlerUseDB disabled: format is $format, "
1575  . "default for $t is $defaultFormat" );
1576  }
1577  }
1578 
1579  $content = $this->getContent( self::RAW );
1580  $prefixedDBkey = $title->getPrefixedDBkey();
1581  $revId = $this->mId;
1582 
1583  if ( !$content ) {
1584  throw new MWException(
1585  "Content of revision $revId ($prefixedDBkey) could not be loaded for validation!"
1586  );
1587  }
1588  if ( !$content->isValid() ) {
1589  throw new MWException(
1590  "Content of revision $revId ($prefixedDBkey) is not valid! Content model is $model"
1591  );
1592  }
1593  }
1594 
1600  public static function base36Sha1( $text ) {
1601  return Wikimedia\base_convert( sha1( $text ), 16, 36, 31 );
1602  }
1603 
1610  private static function getCacheTTL( WANObjectCache $cache ) {
1612 
1613  if ( $cache->getQoS( $cache::ATTR_EMULATION ) <= $cache::QOS_EMULATION_SQL ) {
1614  // Do not cache RDBMs blobs in...the RDBMs store
1615  $ttl = $cache::TTL_UNCACHEABLE;
1616  } else {
1617  $ttl = $wgRevisionCacheExpiry ?: $cache::TTL_UNCACHEABLE;
1618  }
1619 
1620  return $ttl;
1621  }
1622 
1629  private function loadText() {
1631 
1632  // No negative caching; negative hits on text rows may be due to corrupted replica DBs
1633  return $cache->getWithSetCallback(
1634  $cache->makeKey( 'revisiontext', 'textid', $this->getTextId() ),
1635  self::getCacheTTL( $cache ),
1636  function () {
1637  return $this->fetchText();
1638  },
1639  [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => $cache::TTL_PROC_LONG ]
1640  );
1641  }
1642 
1643  private function fetchText() {
1644  $textId = $this->getTextId();
1645 
1646  // If we kept data for lazy extraction, use it now...
1647  if ( $this->mTextRow !== null ) {
1648  $row = $this->mTextRow;
1649  $this->mTextRow = null;
1650  } else {
1651  $row = null;
1652  }
1653 
1654  // Callers doing updates will pass in READ_LATEST as usual. Since the text/blob tables
1655  // do not normally get rows changed around, set READ_LATEST_IMMUTABLE in those cases.
1657  $flags |= DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST )
1658  ? self::READ_LATEST_IMMUTABLE
1659  : 0;
1660 
1661  list( $index, $options, $fallbackIndex, $fallbackOptions ) =
1663 
1664  if ( !$row ) {
1665  // Text data is immutable; check replica DBs first.
1666  $row = wfGetDB( $index )->selectRow(
1667  'text',
1668  [ 'old_text', 'old_flags' ],
1669  [ 'old_id' => $textId ],
1670  __METHOD__,
1671  $options
1672  );
1673  }
1674 
1675  // Fallback to DB_MASTER in some cases if the row was not found
1676  if ( !$row && $fallbackIndex !== null ) {
1677  // Use FOR UPDATE if it was used to fetch this revision. This avoids missing the row
1678  // due to REPEATABLE-READ. Also fallback to the master if READ_LATEST is provided.
1679  $row = wfGetDB( $fallbackIndex )->selectRow(
1680  'text',
1681  [ 'old_text', 'old_flags' ],
1682  [ 'old_id' => $textId ],
1683  __METHOD__,
1684  $fallbackOptions
1685  );
1686  }
1687 
1688  if ( !$row ) {
1689  wfDebugLog( 'Revision', "No text row with ID '$textId' (revision {$this->getId()})." );
1690  }
1691 
1692  $text = self::getRevisionText( $row );
1693  if ( $row && $text === false ) {
1694  wfDebugLog( 'Revision', "No blob for text row '$textId' (revision {$this->getId()})." );
1695  }
1696 
1697  return is_string( $text ) ? $text : false;
1698  }
1699 
1715  public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) {
1717 
1718  $fields = [ 'page_latest', 'page_namespace', 'page_title',
1719  'rev_text_id', 'rev_len', 'rev_sha1' ];
1720 
1721  if ( $wgContentHandlerUseDB ) {
1722  $fields[] = 'rev_content_model';
1723  $fields[] = 'rev_content_format';
1724  }
1725 
1726  $current = $dbw->selectRow(
1727  [ 'page', 'revision' ],
1728  $fields,
1729  [
1730  'page_id' => $pageId,
1731  'page_latest=rev_id',
1732  ],
1733  __METHOD__,
1734  [ 'FOR UPDATE' ] // T51581
1735  );
1736 
1737  if ( $current ) {
1738  if ( !$user ) {
1739  global $wgUser;
1740  $user = $wgUser;
1741  }
1742 
1743  $row = [
1744  'page' => $pageId,
1745  'user_text' => $user->getName(),
1746  'user' => $user->getId(),
1747  'comment' => $summary,
1748  'minor_edit' => $minor,
1749  'text_id' => $current->rev_text_id,
1750  'parent_id' => $current->page_latest,
1751  'len' => $current->rev_len,
1752  'sha1' => $current->rev_sha1
1753  ];
1754 
1755  if ( $wgContentHandlerUseDB ) {
1756  $row['content_model'] = $current->rev_content_model;
1757  $row['content_format'] = $current->rev_content_format;
1758  }
1759 
1760  $row['title'] = Title::makeTitle( $current->page_namespace, $current->page_title );
1761 
1762  $revision = new Revision( $row );
1763  } else {
1764  $revision = null;
1765  }
1766 
1767  return $revision;
1768  }
1769 
1780  public function userCan( $field, User $user = null ) {
1781  return self::userCanBitfield( $this->getVisibility(), $field, $user );
1782  }
1783 
1798  public static function userCanBitfield( $bitfield, $field, User $user = null,
1799  Title $title = null
1800  ) {
1801  if ( $bitfield & $field ) { // aspect is deleted
1802  if ( $user === null ) {
1803  global $wgUser;
1804  $user = $wgUser;
1805  }
1806  if ( $bitfield & self::DELETED_RESTRICTED ) {
1807  $permissions = [ 'suppressrevision', 'viewsuppressed' ];
1808  } elseif ( $field & self::DELETED_TEXT ) {
1809  $permissions = [ 'deletedtext' ];
1810  } else {
1811  $permissions = [ 'deletedhistory' ];
1812  }
1813  $permissionlist = implode( ', ', $permissions );
1814  if ( $title === null ) {
1815  wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
1816  return call_user_func_array( [ $user, 'isAllowedAny' ], $permissions );
1817  } else {
1818  $text = $title->getPrefixedText();
1819  wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" );
1820  foreach ( $permissions as $perm ) {
1821  if ( $title->userCan( $perm, $user ) ) {
1822  return true;
1823  }
1824  }
1825  return false;
1826  }
1827  } else {
1828  return true;
1829  }
1830  }
1831 
1840  static function getTimestampFromId( $title, $id, $flags = 0 ) {
1841  $db = ( $flags & self::READ_LATEST )
1842  ? wfGetDB( DB_MASTER )
1843  : wfGetDB( DB_REPLICA );
1844  // Casting fix for databases that can't take '' for rev_id
1845  if ( $id == '' ) {
1846  $id = 0;
1847  }
1848  $conds = [ 'rev_id' => $id ];
1849  $conds['rev_page'] = $title->getArticleID();
1850  $timestamp = $db->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
1851 
1852  return ( $timestamp !== false ) ? wfTimestamp( TS_MW, $timestamp ) : false;
1853  }
1854 
1862  static function countByPageId( $db, $id ) {
1863  $row = $db->selectRow( 'revision', [ 'revCount' => 'COUNT(*)' ],
1864  [ 'rev_page' => $id ], __METHOD__ );
1865  if ( $row ) {
1866  return $row->revCount;
1867  }
1868  return 0;
1869  }
1870 
1878  static function countByTitle( $db, $title ) {
1879  $id = $title->getArticleID();
1880  if ( $id ) {
1881  return self::countByPageId( $db, $id );
1882  }
1883  return 0;
1884  }
1885 
1902  public static function userWasLastToEdit( $db, $pageId, $userId, $since ) {
1903  if ( !$userId ) {
1904  return false;
1905  }
1906 
1907  if ( is_int( $db ) ) {
1908  $db = wfGetDB( $db );
1909  }
1910 
1911  $res = $db->select( 'revision',
1912  'rev_user',
1913  [
1914  'rev_page' => $pageId,
1915  'rev_timestamp > ' . $db->addQuotes( $db->timestamp( $since ) )
1916  ],
1917  __METHOD__,
1918  [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ] );
1919  foreach ( $res as $row ) {
1920  if ( $row->rev_user != $userId ) {
1921  return false;
1922  }
1923  }
1924  return true;
1925  }
1926 
1940  public static function newKnownCurrent( IDatabase $db, $pageId, $revId ) {
1941  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1942  return $cache->getWithSetCallback(
1943  // Page/rev IDs passed in from DB to reflect history merges
1944  $cache->makeGlobalKey( 'revision', $db->getDomainID(), $pageId, $revId ),
1945  $cache::TTL_WEEK,
1946  function ( $curValue, &$ttl, array &$setOpts ) use ( $db, $pageId, $revId ) {
1947  $setOpts += Database::getCacheSetOptions( $db );
1948 
1949  $rev = Revision::loadFromPageId( $db, $pageId, $revId );
1950  // Reflect revision deletion and user renames
1951  if ( $rev ) {
1952  $rev->mTitle = null; // mutable; lazy-load
1953  $rev->mRefreshMutableFields = true;
1954  }
1955 
1956  return $rev ?: false; // don't cache negatives
1957  }
1958  );
1959  }
1960 
1964  private function loadMutableFields() {
1965  if ( !$this->mRefreshMutableFields ) {
1966  return; // not needed
1967  }
1968 
1969  $this->mRefreshMutableFields = false;
1970  $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki );
1971  $row = $dbr->selectRow(
1972  [ 'revision', 'user' ],
1973  [ 'rev_deleted', 'user_name' ],
1974  [ 'rev_id' => $this->mId, 'user_id = rev_user' ],
1975  __METHOD__
1976  );
1977  if ( $row ) { // update values
1978  $this->mDeleted = (int)$row->rev_deleted;
1979  $this->mUserText = $row->user_name;
1980  }
1981  }
1982 }
Revision\FOR_PUBLIC
const FOR_PUBLIC
Definition: Revision.php:98
Revision\newFromArchiveRow
static newFromArchiveRow( $row, $overrides=[])
Make a fake revision object from an archive table row.
Definition: Revision.php:189
ExternalStore\insertToDefault
static insertToDefault( $data, array $params=[])
Like insert() above, but does more of the work for us.
Definition: ExternalStore.php:169
Revision\DELETED_USER
const DELETED_USER
Definition: Revision.php:92
IP\toHex
static toHex( $ip)
Return a zero-padded upper case hexadecimal representation of an IP address.
Definition: IP.php:417
ContentHandler
A content handler knows how do deal with a specific type of content on a wiki page.
Definition: ContentHandler.php:49
Revision\getTimestamp
getTimestamp()
Definition: Revision.php:1186
ContentHandler\getForModelID
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
Definition: ContentHandler.php:293
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:45
Revision\$mPage
int null $mPage
Definition: Revision.php:37
$user
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 account $user
Definition: hooks.txt:244
Revision\DELETED_RESTRICTED
const DELETED_RESTRICTED
Definition: Revision.php:93
$wgUser
$wgUser
Definition: Setup.php:809
Revision\getUserText
getUserText( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's username if it's available to the specified audience.
Definition: Revision.php:911
Revision\SUPPRESSED_ALL
const SUPPRESSED_ALL
Definition: Revision.php:95
Revision\DELETED_COMMENT
const DELETED_COMMENT
Definition: Revision.php:91
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:116
Revision\getPreviousRevisionId
getPreviousRevisionId( $db)
Get previous revision Id for this page_id This is used to populate rev_parent_id on save.
Definition: Revision.php:1234
Revision\getRawUser
getRawUser()
Fetch revision's user id without regard for the current user's permissions.
Definition: Revision.php:893
Revision\pageJoinCond
static pageJoinCond()
Return the value of a select() page conds array for the page table.
Definition: Revision.php:441
Revision\getUser
getUser( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's user id if it's available to the specified audience.
Definition: Revision.php:877
Revision\userCanBitfield
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,...
Definition: Revision.php:1798
Revision\fetchText
fetchText()
Definition: Revision.php:1643
RecentChange\newFromConds
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
Definition: RecentChange.php:189
$wgLegacyEncoding
$wgLegacyEncoding
Set this to eg 'ISO-8859-1' to perform character set conversion when loading old revisions not marked...
Definition: DefaultSettings.php:2980
Revision\loadText
loadText()
Lazy-load the revision's text.
Definition: Revision.php:1629
Revision\$mContentHandler
null ContentHandler $mContentHandler
Definition: Revision.php:80
Revision\getSize
getSize()
Returns the length of the text in this revision, or null if unknown.
Definition: Revision.php:797
captcha-old.count
count
Definition: captcha-old.py:249
wfGetLB
wfGetLB( $wiki=false)
Get a load balancer object.
Definition: GlobalFunctions.php:2869
Revision\$mOrigUserText
string $mOrigUserText
Definition: Revision.php:41
Revision\setId
setId( $id)
Set the revision ID.
Definition: Revision.php:755
Revision\getContent
getContent( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision content if it's available to the specified audience.
Definition: Revision.php:1065
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:2040
Revision\getPage
getPage()
Get the page ID.
Definition: Revision.php:860
Revision\newKnownCurrent
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:1940
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
ExternalStore\fetchFromURL
static fetchFromURL( $url, array $params=[])
Fetch data from given URL.
Definition: ExternalStore.php:75
unserialize
unserialize( $serialized)
Definition: ApiMessage.php:185
Revision\$mSha1
string $mSha1
Definition: Revision.php:53
Revision\getRevisionText
static getRevisionText( $row, $prefix='old_', $wiki=false)
Get revision text associated with an old or archive row.
Definition: Revision.php:1266
Revision\getParentId
getParentId()
Get parent revision ID (the original previous page revision)
Definition: Revision.php:788
Revision\$mTitle
null Title $mTitle
Definition: Revision.php:69
Revision\setTitle
setTitle( $title)
Set the title of the revision.
Definition: Revision.php:851
Revision\getContentHandler
getContentHandler()
Returns the content handler appropriate for this revision's content model.
Definition: Revision.php:1167
Revision\fetchFromConds
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
Revision\getSerializedData
getSerializedData()
Get original serialized data (without checking view restrictions)
Definition: Revision.php:1081
Revision\$mMinorEdit
bool $mMinorEdit
Definition: Revision.php:45
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1203
Revision\getRecentChange
getRecentChange( $flags=0)
Get the RC object belonging to the current revision, if there's one.
Definition: Revision.php:1008
Revision\TEXT_CACHE_GROUP
const TEXT_CACHE_GROUP
Definition: Revision.php:102
$res
$res
Definition: database.txt:21
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
Revision\isCurrent
isCurrent()
Definition: Revision.php:1193
Wikimedia\Rdbms\ResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: ResultWrapper.php:24
CONTENT_MODEL_WIKITEXT
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:236
Revision\newFromPageId
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
Revision\$mRefreshMutableFields
bool $mRefreshMutableFields
Used for cached values to reload user text and rev_deleted.
Definition: Revision.php:85
Revision\getSha1
getSha1()
Returns the base36 sha1 of the text in this revision, or null if unknown.
Definition: Revision.php:806
Revision\loadFromId
static loadFromId( $db, $id)
Load a page revision from a given revision ID number.
Definition: Revision.php:250
$wgDefaultExternalStore
array $wgDefaultExternalStore
The place to put new revisions, false to put them in the local text table.
Definition: DefaultSettings.php:2146
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:55
Revision\getId
getId()
Get revision ID.
Definition: Revision.php:743
Revision\getContentModel
getContentModel()
Returns the content model for this revision.
Definition: Revision.php:1126
Wikimedia\Rdbms\FakeResultWrapper
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
Definition: FakeResultWrapper.php:11
$wgContentHandlerUseDB
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility.
Definition: DefaultSettings.php:8476
CommentStore\newKey
static newKey( $key)
Static constructor for easier chaining.
Definition: CommentStore.php:114
Revision\insertOn
insertOn( $dbw)
Insert a new revision into the database, returning the new revision ID number on success and dies hor...
Definition: Revision.php:1406
Revision\$mComment
string $mComment
Definition: Revision.php:57
Revision\base36Sha1
static base36Sha1( $text)
Get the base 36 SHA-1 value for a string of text.
Definition: Revision.php:1600
Revision\$mDeleted
int $mDeleted
Definition: Revision.php:49
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1140
php
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
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:40
Revision\$mQueryFlags
int $mQueryFlags
Definition: Revision.php:83
Revision\selectTextFields
static selectTextFields()
Return the list of text fields that should be selected to read the revision text.
Definition: Revision.php:518
Revision\FOR_THIS_USER
const FOR_THIS_USER
Definition: Revision.php:99
Revision
Definition: Revision.php:33
Revision\newFromTitle
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
Revision\getCacheTTL
static getCacheTTL(WANObjectCache $cache)
Get the text cache TTL.
Definition: Revision.php:1610
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
Revision\SUPPRESSED_USER
const SUPPRESSED_USER
Definition: Revision.php:94
ContentHandler\getDefaultModelFor
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title.
Definition: ContentHandler.php:178
Revision\$mParentId
int $mParentId
Definition: Revision.php:55
MWException
MediaWiki exception.
Definition: MWException.php:26
Revision\getNext
getNext()
Get next revision for this title.
Definition: Revision.php:1217
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:932
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1176
Revision\setUserIdAndName
setUserIdAndName( $id, $name)
Set the user ID/name.
Definition: Revision.php:768
Revision\getTimestampFromId
static getTimestampFromId( $title, $id, $flags=0)
Get rev_timestamp from rev_id, without loading the rest of the row.
Definition: Revision.php:1840
Revision\$mTextRow
stdClass null $mTextRow
Definition: Revision.php:66
Title\newFromRow
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:459
Revision\isUnpatrolled
isUnpatrolled()
Definition: Revision.php:986
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2856
Revision\loadFromPageId
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
Revision\$mUser
int $mUser
Definition: Revision.php:43
Revision\$mId
int null $mId
Definition: Revision.php:35
$attribs
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:1965
Revision\compressRevisionText
static compressRevisionText(&$text)
If $wgCompressRevisions is enabled, we will compress data.
Definition: Revision.php:1323
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:529
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2069
DB_MASTER
const DB_MASTER
Definition: defines.php:26
Revision\$mSize
int $mSize
Definition: Revision.php:51
$wgCompressRevisions
$wgCompressRevisions
We can also compress text stored in the 'text' table.
Definition: DefaultSettings.php:2101
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:1047
list
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
Revision\countByTitle
static countByTitle( $db, $title)
Get count of revisions per page...not very efficient.
Definition: Revision.php:1878
Revision\getPrevious
getPrevious()
Get previous revision for this title.
Definition: Revision.php:1202
Revision\countByPageId
static countByPageId( $db, $id)
Get count of revisions per page...not very efficient.
Definition: Revision.php:1862
Revision\newFromConds
static newFromConds( $conditions, $flags=0)
Given a set of conditions, fetch a revision.
Definition: Revision.php:329
Revision\$mText
string $mText
Definition: Revision.php:59
Revision\$mTimestamp
string $mTimestamp
Definition: Revision.php:47
Revision\selectPageFields
static selectPageFields()
Return the list of page fields that should be selected from page table.
Definition: Revision.php:529
Revision\getTitle
getTitle()
Returns the title of the page associated with this entry or null.
Definition: Revision.php:817
Revision\$mContentModel
string $mContentModel
Definition: Revision.php:73
Revision\checkContentModel
checkContentModel()
Definition: Revision.php:1538
Revision\userWasLastToEdit
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:1902
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:2807
Revision\__construct
__construct( $row)
Definition: Revision.php:574
Revision\$mTextId
int $mTextId
Definition: Revision.php:61
Revision\selectArchiveFields
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
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:745
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
on
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
Revision\getParentLengths
static getParentLengths( $db, array $revIds)
Do a batched query to get the parent revision lengths.
Definition: Revision.php:554
Revision\getVisibility
getVisibility()
Get the deletion bitfield of the revision.
Definition: Revision.php:1045
Revision\getTextId
getTextId()
Get text row ID.
Definition: Revision.php:779
Revision\$mCurrent
bool $mCurrent
Definition: Revision.php:71
Wikimedia\Rdbms\IDatabase\getDomainID
getDomainID()
$wgRevisionCacheExpiry
$wgRevisionCacheExpiry
Revision text may be cached in $wgMemc to reduce load on external storage servers and object extracti...
Definition: DefaultSettings.php:2154
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:80
Revision\$mUserText
string $mUserText
Definition: Revision.php:39
MediaWiki\Linker\LinkTarget\getDBkey
getDBkey()
Get the main part with underscores.
Revision\isDeleted
isDeleted( $field)
Definition: Revision.php:1029
Revision\newFromRow
static newFromRow( $row)
Definition: Revision.php:238
Revision\$mContent
Content null bool $mContent
Definition: Revision.php:78
Revision\RAW
const RAW
Definition: Revision.php:100
Revision\getRawUserText
getRawUserText()
Fetch revision's username without regard for view restrictions.
Definition: Revision.php:937
$handler
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:781
IP\isValid
static isValid( $ip)
Validate an IP address.
Definition: IP.php:111
Revision\getContentFormat
getContentFormat()
Returns the content format for this revision.
Definition: Revision.php:1150
Content
Base interface for content objects.
Definition: Content.php:34
Revision\fetchRevision
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
Title
Represents a title within MediaWiki.
Definition: Title.php:39
$dbr
if(! $regexes) $dbr
Definition: cleanup.php:94
$cache
$cache
Definition: mcc.php:33
$options
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:1965
ObjectCache\getMainWANInstance
static getMainWANInstance()
Get the main WAN cache object.
Definition: ObjectCache.php:370
$rev
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:1750
needed
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
as
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
Revision\userJoinCond
static userJoinCond()
Return the value of a select() JOIN conds array for the user table.
Definition: Revision.php:431
Revision\$mUnpatrolled
int $mUnpatrolled
Definition: Revision.php:63
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:309
Revision\getContentInternal
getContentInternal()
Gets the content object for the revision (or null on failure).
Definition: Revision.php:1099
Revision\getRawComment
getRawComment()
Fetch revision comment without regard for the current user's permissions.
Definition: Revision.php:971
Revision\getComment
getComment( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision comment if it's available to the specified audience.
Definition: Revision.php:955
Revision\loadFromConds
static loadFromConds( $db, $conditions, $flags=0)
Given a set of conditions, fetch a revision from the given database connection.
Definition: Revision.php:361
Revision\newNullRevision
static newNullRevision( $dbw, $pageId, $summary, $minor, $user=null)
Create a new null-revision for insertion into a page's history.
Definition: Revision.php:1715
Revision\$mContentFormat
string $mContentFormat
Definition: Revision.php:75
Revision\isMinor
isMinor()
Definition: Revision.php:979
Revision\selectUserFields
static selectUserFields()
Return the list of user fields that should be selected from user table.
Definition: Revision.php:544
$t
$t
Definition: testCompression.php:67
MediaWikiServices
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
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:27
Revision\selectFields
static selectFields()
Return the list of revision fields that should be selected to create a new revision.
Definition: Revision.php:452
Revision\decompressRevisionText
static decompressRevisionText( $text, $flags)
Re-converts revision text according to it's flags.
Definition: Revision.php:1355
Revision\loadFromTitle
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
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:51
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:411
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
Revision\loadMutableFields
loadMutableFields()
For cached revisions, make sure the user name and rev_deleted is up-to-date.
Definition: Revision.php:1964
Revision\DELETED_TEXT
const DELETED_TEXT
Definition: Revision.php:90
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2801
Revision\userCan
userCan( $field, User $user=null)
Determine if the current user is allowed to view a particular field of this revision,...
Definition: Revision.php:1780
array
the array() calling protocol came about after MediaWiki 1.4rc1.
Revision\$mWiki
string $mWiki
Wiki ID; false means the current wiki.
Definition: Revision.php:87
$wgContLang
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 content language as $wgContLang
Definition: design.txt:56