MediaWiki  1.28.1
Revision.php
Go to the documentation of this file.
1 <?php
24 
28 class Revision implements IDBAccessObject {
30  protected $mId;
32  protected $mPage;
34  protected $mUserText;
36  protected $mOrigUserText;
38  protected $mUser;
40  protected $mMinorEdit;
42  protected $mTimestamp;
44  protected $mDeleted;
46  protected $mSize;
48  protected $mSha1;
50  protected $mParentId;
52  protected $mComment;
54  protected $mText;
56  protected $mTextId;
58  protected $mUnpatrolled;
59 
61  protected $mTextRow;
62 
64  protected $mTitle;
66  protected $mCurrent;
68  protected $mContentModel;
70  protected $mContentFormat;
71 
73  protected $mContent;
75  protected $mContentHandler;
76 
78  protected $mQueryFlags = 0;
80  protected $mRefreshMutableFields = false;
82  protected $mWiki = false;
83 
84  // Revision deletion constants
85  const DELETED_TEXT = 1;
86  const DELETED_COMMENT = 2;
87  const DELETED_USER = 4;
88  const DELETED_RESTRICTED = 8;
89  const SUPPRESSED_USER = 12; // convenience
90 
91  // Audience options for accessors
92  const FOR_PUBLIC = 1;
93  const FOR_THIS_USER = 2;
94  const RAW = 3;
95 
96  const TEXT_CACHE_GROUP = 'revisiontext:10'; // process cache name and max key count
97 
110  public static function newFromId( $id, $flags = 0 ) {
111  return self::newFromConds( [ 'rev_id' => intval( $id ) ], $flags );
112  }
113 
128  public static function newFromTitle( LinkTarget $linkTarget, $id = 0, $flags = 0 ) {
129  $conds = [
130  'page_namespace' => $linkTarget->getNamespace(),
131  'page_title' => $linkTarget->getDBkey()
132  ];
133  if ( $id ) {
134  // Use the specified ID
135  $conds['rev_id'] = $id;
136  return self::newFromConds( $conds, $flags );
137  } else {
138  // Use a join to get the latest revision
139  $conds[] = 'rev_id=page_latest';
140  $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
141  return self::loadFromConds( $db, $conds, $flags );
142  }
143  }
144 
159  public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) {
160  $conds = [ 'page_id' => $pageId ];
161  if ( $revId ) {
162  $conds['rev_id'] = $revId;
163  return self::newFromConds( $conds, $flags );
164  } else {
165  // Use a join to get the latest revision
166  $conds[] = 'rev_id = page_latest';
167  $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
168  return self::loadFromConds( $db, $conds, $flags );
169  }
170  }
171 
183  public static function newFromArchiveRow( $row, $overrides = [] ) {
184  global $wgContentHandlerUseDB;
185 
186  $attribs = $overrides + [
187  'page' => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
188  'id' => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
189  'comment' => $row->ar_comment,
190  'user' => $row->ar_user,
191  'user_text' => $row->ar_user_text,
192  'timestamp' => $row->ar_timestamp,
193  'minor_edit' => $row->ar_minor_edit,
194  'text_id' => isset( $row->ar_text_id ) ? $row->ar_text_id : null,
195  'deleted' => $row->ar_deleted,
196  'len' => $row->ar_len,
197  'sha1' => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
198  'content_model' => isset( $row->ar_content_model ) ? $row->ar_content_model : null,
199  'content_format' => isset( $row->ar_content_format ) ? $row->ar_content_format : null,
200  ];
201 
202  if ( !$wgContentHandlerUseDB ) {
203  unset( $attribs['content_model'] );
204  unset( $attribs['content_format'] );
205  }
206 
207  if ( !isset( $attribs['title'] )
208  && isset( $row->ar_namespace )
209  && isset( $row->ar_title )
210  ) {
211  $attribs['title'] = Title::makeTitle( $row->ar_namespace, $row->ar_title );
212  }
213 
214  if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
215  // Pre-1.5 ar_text row
216  $attribs['text'] = self::getRevisionText( $row, 'ar_' );
217  if ( $attribs['text'] === false ) {
218  throw new MWException( 'Unable to load text from archive row (possibly bug 22624)' );
219  }
220  }
221  return new self( $attribs );
222  }
223 
230  public static function newFromRow( $row ) {
231  return new self( $row );
232  }
233 
242  public static function loadFromId( $db, $id ) {
243  return self::loadFromConds( $db, [ 'rev_id' => intval( $id ) ] );
244  }
245 
256  public static function loadFromPageId( $db, $pageid, $id = 0 ) {
257  $conds = [ 'rev_page' => intval( $pageid ), 'page_id' => intval( $pageid ) ];
258  if ( $id ) {
259  $conds['rev_id'] = intval( $id );
260  } else {
261  $conds[] = 'rev_id=page_latest';
262  }
263  return self::loadFromConds( $db, $conds );
264  }
265 
276  public static function loadFromTitle( $db, $title, $id = 0 ) {
277  if ( $id ) {
278  $matchId = intval( $id );
279  } else {
280  $matchId = 'page_latest';
281  }
282  return self::loadFromConds( $db,
283  [
284  "rev_id=$matchId",
285  'page_namespace' => $title->getNamespace(),
286  'page_title' => $title->getDBkey()
287  ]
288  );
289  }
290 
301  public static function loadFromTimestamp( $db, $title, $timestamp ) {
302  return self::loadFromConds( $db,
303  [
304  'rev_timestamp' => $db->timestamp( $timestamp ),
305  'page_namespace' => $title->getNamespace(),
306  'page_title' => $title->getDBkey()
307  ]
308  );
309  }
310 
321  private static function newFromConds( $conditions, $flags = 0 ) {
322  $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
323 
324  $rev = self::loadFromConds( $db, $conditions, $flags );
325  // Make sure new pending/committed revision are visibile later on
326  // within web requests to certain avoid bugs like T93866 and T94407.
327  if ( !$rev
328  && !( $flags & self::READ_LATEST )
329  && wfGetLB()->getServerCount() > 1
330  && wfGetLB()->hasOrMadeRecentMasterChanges()
331  ) {
332  $flags = self::READ_LATEST;
333  $db = wfGetDB( DB_MASTER );
334  $rev = self::loadFromConds( $db, $conditions, $flags );
335  }
336 
337  if ( $rev ) {
338  $rev->mQueryFlags = $flags;
339  }
340 
341  return $rev;
342  }
343 
353  private static function loadFromConds( $db, $conditions, $flags = 0 ) {
354  $row = self::fetchFromConds( $db, $conditions, $flags );
355  if ( $row ) {
356  $rev = new Revision( $row );
357  $rev->mWiki = $db->getWikiID();
358 
359  return $rev;
360  }
361 
362  return null;
363  }
364 
374  public static function fetchRevision( LinkTarget $title ) {
375  $row = self::fetchFromConds(
376  wfGetDB( DB_REPLICA ),
377  [
378  'rev_id=page_latest',
379  'page_namespace' => $title->getNamespace(),
380  'page_title' => $title->getDBkey()
381  ]
382  );
383 
384  return new FakeResultWrapper( $row ? [ $row ] : [] );
385  }
386 
397  private static function fetchFromConds( $db, $conditions, $flags = 0 ) {
398  $fields = array_merge(
399  self::selectFields(),
400  self::selectPageFields(),
401  self::selectUserFields()
402  );
403  $options = [];
404  if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
405  $options[] = 'FOR UPDATE';
406  }
407  return $db->selectRow(
408  [ 'revision', 'page', 'user' ],
409  $fields,
410  $conditions,
411  __METHOD__,
412  $options,
413  [ 'page' => self::pageJoinCond(), 'user' => self::userJoinCond() ]
414  );
415  }
416 
423  public static function userJoinCond() {
424  return [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ];
425  }
426 
433  public static function pageJoinCond() {
434  return [ 'INNER JOIN', [ 'page_id = rev_page' ] ];
435  }
436 
442  public static function selectFields() {
443  global $wgContentHandlerUseDB;
444 
445  $fields = [
446  'rev_id',
447  'rev_page',
448  'rev_text_id',
449  'rev_timestamp',
450  'rev_comment',
451  'rev_user_text',
452  'rev_user',
453  'rev_minor_edit',
454  'rev_deleted',
455  'rev_len',
456  'rev_parent_id',
457  'rev_sha1',
458  ];
459 
460  if ( $wgContentHandlerUseDB ) {
461  $fields[] = 'rev_content_format';
462  $fields[] = 'rev_content_model';
463  }
464 
465  return $fields;
466  }
467 
473  public static function selectArchiveFields() {
474  global $wgContentHandlerUseDB;
475  $fields = [
476  'ar_id',
477  'ar_page_id',
478  'ar_rev_id',
479  'ar_text',
480  'ar_text_id',
481  'ar_timestamp',
482  'ar_comment',
483  'ar_user_text',
484  'ar_user',
485  'ar_minor_edit',
486  'ar_deleted',
487  'ar_len',
488  'ar_parent_id',
489  'ar_sha1',
490  ];
491 
492  if ( $wgContentHandlerUseDB ) {
493  $fields[] = 'ar_content_format';
494  $fields[] = 'ar_content_model';
495  }
496  return $fields;
497  }
498 
504  public static function selectTextFields() {
505  return [
506  'old_text',
507  'old_flags'
508  ];
509  }
510 
515  public static function selectPageFields() {
516  return [
517  'page_namespace',
518  'page_title',
519  'page_id',
520  'page_latest',
521  'page_is_redirect',
522  'page_len',
523  ];
524  }
525 
530  public static function selectUserFields() {
531  return [ 'user_name' ];
532  }
533 
540  public static function getParentLengths( $db, array $revIds ) {
541  $revLens = [];
542  if ( !$revIds ) {
543  return $revLens; // empty
544  }
545  $res = $db->select( 'revision',
546  [ 'rev_id', 'rev_len' ],
547  [ 'rev_id' => $revIds ],
548  __METHOD__ );
549  foreach ( $res as $row ) {
550  $revLens[$row->rev_id] = $row->rev_len;
551  }
552  return $revLens;
553  }
554 
562  function __construct( $row ) {
563  if ( is_object( $row ) ) {
564  $this->mId = intval( $row->rev_id );
565  $this->mPage = intval( $row->rev_page );
566  $this->mTextId = intval( $row->rev_text_id );
567  $this->mComment = $row->rev_comment;
568  $this->mUser = intval( $row->rev_user );
569  $this->mMinorEdit = intval( $row->rev_minor_edit );
570  $this->mTimestamp = $row->rev_timestamp;
571  $this->mDeleted = intval( $row->rev_deleted );
572 
573  if ( !isset( $row->rev_parent_id ) ) {
574  $this->mParentId = null;
575  } else {
576  $this->mParentId = intval( $row->rev_parent_id );
577  }
578 
579  if ( !isset( $row->rev_len ) ) {
580  $this->mSize = null;
581  } else {
582  $this->mSize = intval( $row->rev_len );
583  }
584 
585  if ( !isset( $row->rev_sha1 ) ) {
586  $this->mSha1 = null;
587  } else {
588  $this->mSha1 = $row->rev_sha1;
589  }
590 
591  if ( isset( $row->page_latest ) ) {
592  $this->mCurrent = ( $row->rev_id == $row->page_latest );
593  $this->mTitle = Title::newFromRow( $row );
594  } else {
595  $this->mCurrent = false;
596  $this->mTitle = null;
597  }
598 
599  if ( !isset( $row->rev_content_model ) ) {
600  $this->mContentModel = null; # determine on demand if needed
601  } else {
602  $this->mContentModel = strval( $row->rev_content_model );
603  }
604 
605  if ( !isset( $row->rev_content_format ) ) {
606  $this->mContentFormat = null; # determine on demand if needed
607  } else {
608  $this->mContentFormat = strval( $row->rev_content_format );
609  }
610 
611  // Lazy extraction...
612  $this->mText = null;
613  if ( isset( $row->old_text ) ) {
614  $this->mTextRow = $row;
615  } else {
616  // 'text' table row entry will be lazy-loaded
617  $this->mTextRow = null;
618  }
619 
620  // Use user_name for users and rev_user_text for IPs...
621  $this->mUserText = null; // lazy load if left null
622  if ( $this->mUser == 0 ) {
623  $this->mUserText = $row->rev_user_text; // IP user
624  } elseif ( isset( $row->user_name ) ) {
625  $this->mUserText = $row->user_name; // logged-in user
626  }
627  $this->mOrigUserText = $row->rev_user_text;
628  } elseif ( is_array( $row ) ) {
629  // Build a new revision to be saved...
630  global $wgUser; // ugh
631 
632  # if we have a content object, use it to set the model and type
633  if ( !empty( $row['content'] ) ) {
634  // @todo when is that set? test with external store setup! check out insertOn() [dk]
635  if ( !empty( $row['text_id'] ) ) {
636  throw new MWException( "Text already stored in external store (id {$row['text_id']}), " .
637  "can't serialize content object" );
638  }
639 
640  $row['content_model'] = $row['content']->getModel();
641  # note: mContentFormat is initializes later accordingly
642  # note: content is serialized later in this method!
643  # also set text to null?
644  }
645 
646  $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
647  $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
648  $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
649  $this->mUserText = isset( $row['user_text'] )
650  ? strval( $row['user_text'] ) : $wgUser->getName();
651  $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
652  $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
653  $this->mTimestamp = isset( $row['timestamp'] )
654  ? strval( $row['timestamp'] ) : wfTimestampNow();
655  $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
656  $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
657  $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
658  $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
659 
660  $this->mContentModel = isset( $row['content_model'] )
661  ? strval( $row['content_model'] ) : null;
662  $this->mContentFormat = isset( $row['content_format'] )
663  ? strval( $row['content_format'] ) : null;
664 
665  // Enforce spacing trimming on supplied text
666  $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
667  $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
668  $this->mTextRow = null;
669 
670  $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
671 
672  // if we have a Content object, override mText and mContentModel
673  if ( !empty( $row['content'] ) ) {
674  if ( !( $row['content'] instanceof Content ) ) {
675  throw new MWException( '`content` field must contain a Content object.' );
676  }
677 
678  $handler = $this->getContentHandler();
679  $this->mContent = $row['content'];
680 
681  $this->mContentModel = $this->mContent->getModel();
682  $this->mContentHandler = null;
683 
684  $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
685  } elseif ( $this->mText !== null ) {
686  $handler = $this->getContentHandler();
687  $this->mContent = $handler->unserializeContent( $this->mText );
688  }
689 
690  // If we have a Title object, make sure it is consistent with mPage.
691  if ( $this->mTitle && $this->mTitle->exists() ) {
692  if ( $this->mPage === null ) {
693  // if the page ID wasn't known, set it now
694  $this->mPage = $this->mTitle->getArticleID();
695  } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
696  // Got different page IDs. This may be legit (e.g. during undeletion),
697  // but it seems worth mentioning it in the log.
698  wfDebug( "Page ID " . $this->mPage . " mismatches the ID " .
699  $this->mTitle->getArticleID() . " provided by the Title object." );
700  }
701  }
702 
703  $this->mCurrent = false;
704 
705  // If we still have no length, see it we have the text to figure it out
706  if ( !$this->mSize && $this->mContent !== null ) {
707  $this->mSize = $this->mContent->getSize();
708  }
709 
710  // Same for sha1
711  if ( $this->mSha1 === null ) {
712  $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
713  }
714 
715  // force lazy init
716  $this->getContentModel();
717  $this->getContentFormat();
718  } else {
719  throw new MWException( 'Revision constructor passed invalid row format.' );
720  }
721  $this->mUnpatrolled = null;
722  }
723 
729  public function getId() {
730  return $this->mId;
731  }
732 
741  public function setId( $id ) {
742  $this->mId = (int)$id;
743  }
744 
754  public function setUserIdAndName( $id, $name ) {
755  $this->mUser = (int)$id;
756  $this->mUserText = $name;
757  $this->mOrigUserText = $name;
758  }
759 
765  public function getTextId() {
766  return $this->mTextId;
767  }
768 
774  public function getParentId() {
775  return $this->mParentId;
776  }
777 
783  public function getSize() {
784  return $this->mSize;
785  }
786 
792  public function getSha1() {
793  return $this->mSha1;
794  }
795 
803  public function getTitle() {
804  if ( $this->mTitle !== null ) {
805  return $this->mTitle;
806  }
807  // rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
808  if ( $this->mId !== null ) {
809  $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki );
810  $row = $dbr->selectRow(
811  [ 'page', 'revision' ],
812  self::selectPageFields(),
813  [ 'page_id=rev_page', 'rev_id' => $this->mId ],
814  __METHOD__
815  );
816  if ( $row ) {
817  // @TODO: better foreign title handling
818  $this->mTitle = Title::newFromRow( $row );
819  }
820  }
821 
822  if ( $this->mWiki === false || $this->mWiki === wfWikiID() ) {
823  // Loading by ID is best, though not possible for foreign titles
824  if ( !$this->mTitle && $this->mPage !== null && $this->mPage > 0 ) {
825  $this->mTitle = Title::newFromID( $this->mPage );
826  }
827  }
828 
829  return $this->mTitle;
830  }
831 
837  public function setTitle( $title ) {
838  $this->mTitle = $title;
839  }
840 
846  public function getPage() {
847  return $this->mPage;
848  }
849 
863  public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
864  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
865  return 0;
866  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
867  return 0;
868  } else {
869  return $this->mUser;
870  }
871  }
872 
879  public function getRawUser() {
880  wfDeprecated( __METHOD__, '1.25' );
881  return $this->getUser( self::RAW );
882  }
883 
897  public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
898  $this->loadMutableFields();
899 
900  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
901  return '';
902  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
903  return '';
904  } else {
905  if ( $this->mUserText === null ) {
906  $this->mUserText = User::whoIs( $this->mUser ); // load on demand
907  if ( $this->mUserText === false ) {
908  # This shouldn't happen, but it can if the wiki was recovered
909  # via importing revs and there is no user table entry yet.
910  $this->mUserText = $this->mOrigUserText;
911  }
912  }
913  return $this->mUserText;
914  }
915  }
916 
923  public function getRawUserText() {
924  wfDeprecated( __METHOD__, '1.25' );
925  return $this->getUserText( self::RAW );
926  }
927 
941  function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
942  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
943  return '';
944  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $user ) ) {
945  return '';
946  } else {
947  return $this->mComment;
948  }
949  }
950 
957  public function getRawComment() {
958  wfDeprecated( __METHOD__, '1.25' );
959  return $this->getComment( self::RAW );
960  }
961 
965  public function isMinor() {
966  return (bool)$this->mMinorEdit;
967  }
968 
972  public function isUnpatrolled() {
973  if ( $this->mUnpatrolled !== null ) {
974  return $this->mUnpatrolled;
975  }
976  $rc = $this->getRecentChange();
977  if ( $rc && $rc->getAttribute( 'rc_patrolled' ) == 0 ) {
978  $this->mUnpatrolled = $rc->getAttribute( 'rc_id' );
979  } else {
980  $this->mUnpatrolled = 0;
981  }
982  return $this->mUnpatrolled;
983  }
984 
994  public function getRecentChange( $flags = 0 ) {
995  $dbr = wfGetDB( DB_REPLICA );
996 
998 
1000  [
1001  'rc_user_text' => $this->getUserText( Revision::RAW ),
1002  'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
1003  'rc_this_oldid' => $this->getId()
1004  ],
1005  __METHOD__,
1006  $dbType
1007  );
1008  }
1009 
1015  public function isDeleted( $field ) {
1016  if ( $this->isCurrent() && $field === self::DELETED_TEXT ) {
1017  // Current revisions of pages cannot have the content hidden. Skipping this
1018  // check is very useful for Parser as it fetches templates using newKnownCurrent().
1019  // Calling getVisibility() in that case triggers a verification database query.
1020  return false; // no need to check
1021  }
1022 
1023  return ( $this->getVisibility() & $field ) == $field;
1024  }
1025 
1031  public function getVisibility() {
1032  $this->loadMutableFields();
1033 
1034  return (int)$this->mDeleted;
1035  }
1036 
1052  public function getText( $audience = self::FOR_PUBLIC, User $user = null ) {
1053  wfDeprecated( __METHOD__, '1.21' );
1054 
1055  $content = $this->getContent( $audience, $user );
1056  return ContentHandler::getContentText( $content ); # returns the raw content text, if applicable
1057  }
1058 
1073  public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
1074  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
1075  return null;
1076  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
1077  return null;
1078  } else {
1079  return $this->getContentInternal();
1080  }
1081  }
1082 
1089  public function getSerializedData() {
1090  if ( $this->mText === null ) {
1091  // Revision is immutable. Load on demand.
1092  $this->mText = $this->loadText();
1093  }
1094 
1095  return $this->mText;
1096  }
1097 
1107  protected function getContentInternal() {
1108  if ( $this->mContent === null ) {
1109  $text = $this->getSerializedData();
1110 
1111  if ( $text !== null && $text !== false ) {
1112  // Unserialize content
1113  $handler = $this->getContentHandler();
1114  $format = $this->getContentFormat();
1115 
1116  $this->mContent = $handler->unserializeContent( $text, $format );
1117  }
1118  }
1119 
1120  // NOTE: copy() will return $this for immutable content objects
1121  return $this->mContent ? $this->mContent->copy() : null;
1122  }
1123 
1134  public function getContentModel() {
1135  if ( !$this->mContentModel ) {
1136  $title = $this->getTitle();
1137  if ( $title ) {
1138  $this->mContentModel = ContentHandler::getDefaultModelFor( $title );
1139  } else {
1140  $this->mContentModel = CONTENT_MODEL_WIKITEXT;
1141  }
1142 
1143  assert( !empty( $this->mContentModel ) );
1144  }
1145 
1146  return $this->mContentModel;
1147  }
1148 
1158  public function getContentFormat() {
1159  if ( !$this->mContentFormat ) {
1160  $handler = $this->getContentHandler();
1161  $this->mContentFormat = $handler->getDefaultFormat();
1162 
1163  assert( !empty( $this->mContentFormat ) );
1164  }
1165 
1166  return $this->mContentFormat;
1167  }
1168 
1175  public function getContentHandler() {
1176  if ( !$this->mContentHandler ) {
1177  $model = $this->getContentModel();
1178  $this->mContentHandler = ContentHandler::getForModelID( $model );
1179 
1180  $format = $this->getContentFormat();
1181 
1182  if ( !$this->mContentHandler->isSupportedFormat( $format ) ) {
1183  throw new MWException( "Oops, the content format $format is not supported for "
1184  . "this content model, $model" );
1185  }
1186  }
1187 
1188  return $this->mContentHandler;
1189  }
1190 
1194  public function getTimestamp() {
1195  return wfTimestamp( TS_MW, $this->mTimestamp );
1196  }
1197 
1201  public function isCurrent() {
1202  return $this->mCurrent;
1203  }
1204 
1210  public function getPrevious() {
1211  if ( $this->getTitle() ) {
1212  $prev = $this->getTitle()->getPreviousRevisionID( $this->getId() );
1213  if ( $prev ) {
1214  return self::newFromTitle( $this->getTitle(), $prev );
1215  }
1216  }
1217  return null;
1218  }
1219 
1225  public function getNext() {
1226  if ( $this->getTitle() ) {
1227  $next = $this->getTitle()->getNextRevisionID( $this->getId() );
1228  if ( $next ) {
1229  return self::newFromTitle( $this->getTitle(), $next );
1230  }
1231  }
1232  return null;
1233  }
1234 
1242  private function getPreviousRevisionId( $db ) {
1243  if ( $this->mPage === null ) {
1244  return 0;
1245  }
1246  # Use page_latest if ID is not given
1247  if ( !$this->mId ) {
1248  $prevId = $db->selectField( 'page', 'page_latest',
1249  [ 'page_id' => $this->mPage ],
1250  __METHOD__ );
1251  } else {
1252  $prevId = $db->selectField( 'revision', 'rev_id',
1253  [ 'rev_page' => $this->mPage, 'rev_id < ' . $this->mId ],
1254  __METHOD__,
1255  [ 'ORDER BY' => 'rev_id DESC' ] );
1256  }
1257  return intval( $prevId );
1258  }
1259 
1273  public static function getRevisionText( $row, $prefix = 'old_', $wiki = false ) {
1274 
1275  # Get data
1276  $textField = $prefix . 'text';
1277  $flagsField = $prefix . 'flags';
1278 
1279  if ( isset( $row->$flagsField ) ) {
1280  $flags = explode( ',', $row->$flagsField );
1281  } else {
1282  $flags = [];
1283  }
1284 
1285  if ( isset( $row->$textField ) ) {
1286  $text = $row->$textField;
1287  } else {
1288  return false;
1289  }
1290 
1291  # Use external methods for external objects, text in table is URL-only then
1292  if ( in_array( 'external', $flags ) ) {
1293  $url = $text;
1294  $parts = explode( '://', $url, 2 );
1295  if ( count( $parts ) == 1 || $parts[1] == '' ) {
1296  return false;
1297  }
1298  $text = ExternalStore::fetchFromURL( $url, [ 'wiki' => $wiki ] );
1299  }
1300 
1301  // If the text was fetched without an error, convert it
1302  if ( $text !== false ) {
1303  $text = self::decompressRevisionText( $text, $flags );
1304  }
1305  return $text;
1306  }
1307 
1318  public static function compressRevisionText( &$text ) {
1320  $flags = [];
1321 
1322  # Revisions not marked this way will be converted
1323  # on load if $wgLegacyCharset is set in the future.
1324  $flags[] = 'utf-8';
1325 
1326  if ( $wgCompressRevisions ) {
1327  if ( function_exists( 'gzdeflate' ) ) {
1328  $deflated = gzdeflate( $text );
1329 
1330  if ( $deflated === false ) {
1331  wfLogWarning( __METHOD__ . ': gzdeflate() failed' );
1332  } else {
1333  $text = $deflated;
1334  $flags[] = 'gzip';
1335  }
1336  } else {
1337  wfDebug( __METHOD__ . " -- no zlib support, not compressing\n" );
1338  }
1339  }
1340  return implode( ',', $flags );
1341  }
1342 
1350  public static function decompressRevisionText( $text, $flags ) {
1351  if ( in_array( 'gzip', $flags ) ) {
1352  # Deal with optional compression of archived pages.
1353  # This can be done periodically via maintenance/compressOld.php, and
1354  # as pages are saved if $wgCompressRevisions is set.
1355  $text = gzinflate( $text );
1356 
1357  if ( $text === false ) {
1358  wfLogWarning( __METHOD__ . ': gzinflate() failed' );
1359  return false;
1360  }
1361  }
1362 
1363  if ( in_array( 'object', $flags ) ) {
1364  # Generic compressed storage
1365  $obj = unserialize( $text );
1366  if ( !is_object( $obj ) ) {
1367  // Invalid object
1368  return false;
1369  }
1370  $text = $obj->getText();
1371  }
1372 
1374  if ( $text !== false && $wgLegacyEncoding
1375  && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags )
1376  ) {
1377  # Old revisions kept around in a legacy encoding?
1378  # Upconvert on demand.
1379  # ("utf8" checked for compatibility with some broken
1380  # conversion scripts 2008-12-30)
1382  $text = $wgContLang->iconv( $wgLegacyEncoding, 'UTF-8', $text );
1383  }
1384 
1385  return $text;
1386  }
1387 
1396  public function insertOn( $dbw ) {
1397  global $wgDefaultExternalStore, $wgContentHandlerUseDB;
1398 
1399  // We're inserting a new revision, so we have to use master anyway.
1400  // If it's a null revision, it may have references to rows that
1401  // are not in the replica yet (the text row).
1402  $this->mQueryFlags |= self::READ_LATEST;
1403 
1404  // Not allowed to have rev_page equal to 0, false, etc.
1405  if ( !$this->mPage ) {
1406  $title = $this->getTitle();
1407  if ( $title instanceof Title ) {
1408  $titleText = ' for page ' . $title->getPrefixedText();
1409  } else {
1410  $titleText = '';
1411  }
1412  throw new MWException( "Cannot insert revision$titleText: page ID must be nonzero" );
1413  }
1414 
1415  $this->checkContentModel();
1416 
1417  $data = $this->mText;
1418  $flags = self::compressRevisionText( $data );
1419 
1420  # Write to external storage if required
1421  if ( $wgDefaultExternalStore ) {
1422  // Store and get the URL
1423  $data = ExternalStore::insertToDefault( $data );
1424  if ( !$data ) {
1425  throw new MWException( "Unable to store text to external storage" );
1426  }
1427  if ( $flags ) {
1428  $flags .= ',';
1429  }
1430  $flags .= 'external';
1431  }
1432 
1433  # Record the text (or external storage URL) to the text table
1434  if ( $this->mTextId === null ) {
1435  $old_id = $dbw->nextSequenceValue( 'text_old_id_seq' );
1436  $dbw->insert( 'text',
1437  [
1438  'old_id' => $old_id,
1439  'old_text' => $data,
1440  'old_flags' => $flags,
1441  ], __METHOD__
1442  );
1443  $this->mTextId = $dbw->insertId();
1444  }
1445 
1446  if ( $this->mComment === null ) {
1447  $this->mComment = "";
1448  }
1449 
1450  # Record the edit in revisions
1451  $rev_id = $this->mId !== null
1452  ? $this->mId
1453  : $dbw->nextSequenceValue( 'revision_rev_id_seq' );
1454  $row = [
1455  'rev_id' => $rev_id,
1456  'rev_page' => $this->mPage,
1457  'rev_text_id' => $this->mTextId,
1458  'rev_comment' => $this->mComment,
1459  'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
1460  'rev_user' => $this->mUser,
1461  'rev_user_text' => $this->mUserText,
1462  'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
1463  'rev_deleted' => $this->mDeleted,
1464  'rev_len' => $this->mSize,
1465  'rev_parent_id' => $this->mParentId === null
1466  ? $this->getPreviousRevisionId( $dbw )
1467  : $this->mParentId,
1468  'rev_sha1' => $this->mSha1 === null
1469  ? Revision::base36Sha1( $this->mText )
1470  : $this->mSha1,
1471  ];
1472 
1473  if ( $wgContentHandlerUseDB ) {
1474  // NOTE: Store null for the default model and format, to save space.
1475  // XXX: Makes the DB sensitive to changed defaults.
1476  // Make this behavior optional? Only in miser mode?
1477 
1478  $model = $this->getContentModel();
1479  $format = $this->getContentFormat();
1480 
1481  $title = $this->getTitle();
1482 
1483  if ( $title === null ) {
1484  throw new MWException( "Insufficient information to determine the title of the "
1485  . "revision's page!" );
1486  }
1487 
1488  $defaultModel = ContentHandler::getDefaultModelFor( $title );
1489  $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
1490 
1491  $row['rev_content_model'] = ( $model === $defaultModel ) ? null : $model;
1492  $row['rev_content_format'] = ( $format === $defaultFormat ) ? null : $format;
1493  }
1494 
1495  $dbw->insert( 'revision', $row, __METHOD__ );
1496 
1497  $this->mId = $rev_id !== null ? $rev_id : $dbw->insertId();
1498 
1499  // Assertion to try to catch T92046
1500  if ( (int)$this->mId === 0 ) {
1501  throw new UnexpectedValueException(
1502  'After insert, Revision mId is ' . var_export( $this->mId, 1 ) . ': ' .
1503  var_export( $row, 1 )
1504  );
1505  }
1506 
1507  Hooks::run( 'RevisionInsertComplete', [ &$this, $data, $flags ] );
1508 
1509  return $this->mId;
1510  }
1511 
1512  protected function checkContentModel() {
1513  global $wgContentHandlerUseDB;
1514 
1515  // Note: may return null for revisions that have not yet been inserted
1516  $title = $this->getTitle();
1517 
1518  $model = $this->getContentModel();
1519  $format = $this->getContentFormat();
1520  $handler = $this->getContentHandler();
1521 
1522  if ( !$handler->isSupportedFormat( $format ) ) {
1523  $t = $title->getPrefixedDBkey();
1524 
1525  throw new MWException( "Can't use format $format with content model $model on $t" );
1526  }
1527 
1528  if ( !$wgContentHandlerUseDB && $title ) {
1529  // if $wgContentHandlerUseDB is not set,
1530  // all revisions must use the default content model and format.
1531 
1532  $defaultModel = ContentHandler::getDefaultModelFor( $title );
1533  $defaultHandler = ContentHandler::getForModelID( $defaultModel );
1534  $defaultFormat = $defaultHandler->getDefaultFormat();
1535 
1536  if ( $this->getContentModel() != $defaultModel ) {
1537  $t = $title->getPrefixedDBkey();
1538 
1539  throw new MWException( "Can't save non-default content model with "
1540  . "\$wgContentHandlerUseDB disabled: model is $model, "
1541  . "default for $t is $defaultModel" );
1542  }
1543 
1544  if ( $this->getContentFormat() != $defaultFormat ) {
1545  $t = $title->getPrefixedDBkey();
1546 
1547  throw new MWException( "Can't use non-default content format with "
1548  . "\$wgContentHandlerUseDB disabled: format is $format, "
1549  . "default for $t is $defaultFormat" );
1550  }
1551  }
1552 
1553  $content = $this->getContent( Revision::RAW );
1554  $prefixedDBkey = $title->getPrefixedDBkey();
1555  $revId = $this->mId;
1556 
1557  if ( !$content ) {
1558  throw new MWException(
1559  "Content of revision $revId ($prefixedDBkey) could not be loaded for validation!"
1560  );
1561  }
1562  if ( !$content->isValid() ) {
1563  throw new MWException(
1564  "Content of revision $revId ($prefixedDBkey) is not valid! Content model is $model"
1565  );
1566  }
1567  }
1568 
1574  public static function base36Sha1( $text ) {
1575  return Wikimedia\base_convert( sha1( $text ), 16, 36, 31 );
1576  }
1577 
1584  private function loadText() {
1586 
1588  if ( $cache->getQoS( $cache::ATTR_EMULATION ) <= $cache::QOS_EMULATION_SQL ) {
1589  // Do not cache RDBMs blobs in...the RDBMs store
1590  $ttl = $cache::TTL_UNCACHEABLE;
1591  } else {
1592  $ttl = $wgRevisionCacheExpiry ?: $cache::TTL_UNCACHEABLE;
1593  }
1594 
1595  // No negative caching; negative hits on text rows may be due to corrupted replica DBs
1596  return $cache->getWithSetCallback(
1597  $cache->makeKey( 'revisiontext', 'textid', $this->getTextId() ),
1598  $ttl,
1599  function () {
1600  return $this->fetchText();
1601  },
1602  [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => $cache::TTL_PROC_LONG ]
1603  );
1604  }
1605 
1606  private function fetchText() {
1607  $textId = $this->getTextId();
1608 
1609  // If we kept data for lazy extraction, use it now...
1610  if ( $this->mTextRow !== null ) {
1611  $row = $this->mTextRow;
1612  $this->mTextRow = null;
1613  } else {
1614  $row = null;
1615  }
1616 
1617  // Callers doing updates will pass in READ_LATEST as usual. Since the text/blob tables
1618  // do not normally get rows changed around, set READ_LATEST_IMMUTABLE in those cases.
1620  $flags |= DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST )
1621  ? self::READ_LATEST_IMMUTABLE
1622  : 0;
1623 
1624  list( $index, $options, $fallbackIndex, $fallbackOptions ) =
1626 
1627  if ( !$row ) {
1628  // Text data is immutable; check replica DBs first.
1629  $row = wfGetDB( $index )->selectRow(
1630  'text',
1631  [ 'old_text', 'old_flags' ],
1632  [ 'old_id' => $textId ],
1633  __METHOD__,
1634  $options
1635  );
1636  }
1637 
1638  // Fallback to DB_MASTER in some cases if the row was not found
1639  if ( !$row && $fallbackIndex !== null ) {
1640  // Use FOR UPDATE if it was used to fetch this revision. This avoids missing the row
1641  // due to REPEATABLE-READ. Also fallback to the master if READ_LATEST is provided.
1642  $row = wfGetDB( $fallbackIndex )->selectRow(
1643  'text',
1644  [ 'old_text', 'old_flags' ],
1645  [ 'old_id' => $textId ],
1646  __METHOD__,
1647  $fallbackOptions
1648  );
1649  }
1650 
1651  if ( !$row ) {
1652  wfDebugLog( 'Revision', "No text row with ID '$textId' (revision {$this->getId()})." );
1653  }
1654 
1655  $text = self::getRevisionText( $row );
1656  if ( $row && $text === false ) {
1657  wfDebugLog( 'Revision', "No blob for text row '$textId' (revision {$this->getId()})." );
1658  }
1659 
1660  return is_string( $text ) ? $text : false;
1661  }
1662 
1678  public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) {
1679  global $wgContentHandlerUseDB, $wgContLang;
1680 
1681  $fields = [ 'page_latest', 'page_namespace', 'page_title',
1682  'rev_text_id', 'rev_len', 'rev_sha1' ];
1683 
1684  if ( $wgContentHandlerUseDB ) {
1685  $fields[] = 'rev_content_model';
1686  $fields[] = 'rev_content_format';
1687  }
1688 
1689  $current = $dbw->selectRow(
1690  [ 'page', 'revision' ],
1691  $fields,
1692  [
1693  'page_id' => $pageId,
1694  'page_latest=rev_id',
1695  ],
1696  __METHOD__,
1697  [ 'FOR UPDATE' ] // T51581
1698  );
1699 
1700  if ( $current ) {
1701  if ( !$user ) {
1702  global $wgUser;
1703  $user = $wgUser;
1704  }
1705 
1706  // Truncate for whole multibyte characters
1707  $summary = $wgContLang->truncate( $summary, 255 );
1708 
1709  $row = [
1710  'page' => $pageId,
1711  'user_text' => $user->getName(),
1712  'user' => $user->getId(),
1713  'comment' => $summary,
1714  'minor_edit' => $minor,
1715  'text_id' => $current->rev_text_id,
1716  'parent_id' => $current->page_latest,
1717  'len' => $current->rev_len,
1718  'sha1' => $current->rev_sha1
1719  ];
1720 
1721  if ( $wgContentHandlerUseDB ) {
1722  $row['content_model'] = $current->rev_content_model;
1723  $row['content_format'] = $current->rev_content_format;
1724  }
1725 
1726  $row['title'] = Title::makeTitle( $current->page_namespace, $current->page_title );
1727 
1728  $revision = new Revision( $row );
1729  } else {
1730  $revision = null;
1731  }
1732 
1733  return $revision;
1734  }
1735 
1746  public function userCan( $field, User $user = null ) {
1747  return self::userCanBitfield( $this->getVisibility(), $field, $user );
1748  }
1749 
1764  public static function userCanBitfield( $bitfield, $field, User $user = null,
1765  Title $title = null
1766  ) {
1767  if ( $bitfield & $field ) { // aspect is deleted
1768  if ( $user === null ) {
1769  global $wgUser;
1770  $user = $wgUser;
1771  }
1772  if ( $bitfield & self::DELETED_RESTRICTED ) {
1773  $permissions = [ 'suppressrevision', 'viewsuppressed' ];
1774  } elseif ( $field & self::DELETED_TEXT ) {
1775  $permissions = [ 'deletedtext' ];
1776  } else {
1777  $permissions = [ 'deletedhistory' ];
1778  }
1779  $permissionlist = implode( ', ', $permissions );
1780  if ( $title === null ) {
1781  wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
1782  return call_user_func_array( [ $user, 'isAllowedAny' ], $permissions );
1783  } else {
1784  $text = $title->getPrefixedText();
1785  wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" );
1786  foreach ( $permissions as $perm ) {
1787  if ( $title->userCan( $perm, $user ) ) {
1788  return true;
1789  }
1790  }
1791  return false;
1792  }
1793  } else {
1794  return true;
1795  }
1796  }
1797 
1805  static function getTimestampFromId( $title, $id, $flags = 0 ) {
1806  $db = ( $flags & self::READ_LATEST )
1807  ? wfGetDB( DB_MASTER )
1808  : wfGetDB( DB_REPLICA );
1809  // Casting fix for databases that can't take '' for rev_id
1810  if ( $id == '' ) {
1811  $id = 0;
1812  }
1813  $conds = [ 'rev_id' => $id ];
1814  $conds['rev_page'] = $title->getArticleID();
1815  $timestamp = $db->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
1816 
1817  return ( $timestamp !== false ) ? wfTimestamp( TS_MW, $timestamp ) : false;
1818  }
1819 
1827  static function countByPageId( $db, $id ) {
1828  $row = $db->selectRow( 'revision', [ 'revCount' => 'COUNT(*)' ],
1829  [ 'rev_page' => $id ], __METHOD__ );
1830  if ( $row ) {
1831  return $row->revCount;
1832  }
1833  return 0;
1834  }
1835 
1843  static function countByTitle( $db, $title ) {
1844  $id = $title->getArticleID();
1845  if ( $id ) {
1846  return self::countByPageId( $db, $id );
1847  }
1848  return 0;
1849  }
1850 
1867  public static function userWasLastToEdit( $db, $pageId, $userId, $since ) {
1868  if ( !$userId ) {
1869  return false;
1870  }
1871 
1872  if ( is_int( $db ) ) {
1873  $db = wfGetDB( $db );
1874  }
1875 
1876  $res = $db->select( 'revision',
1877  'rev_user',
1878  [
1879  'rev_page' => $pageId,
1880  'rev_timestamp > ' . $db->addQuotes( $db->timestamp( $since ) )
1881  ],
1882  __METHOD__,
1883  [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ] );
1884  foreach ( $res as $row ) {
1885  if ( $row->rev_user != $userId ) {
1886  return false;
1887  }
1888  }
1889  return true;
1890  }
1891 
1905  public static function newKnownCurrent( IDatabase $db, $pageId, $revId ) {
1906  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1907  return $cache->getWithSetCallback(
1908  // Page/rev IDs passed in from DB to reflect history merges
1909  $cache->makeGlobalKey( 'revision', $db->getWikiID(), $pageId, $revId ),
1910  $cache::TTL_WEEK,
1911  function ( $curValue, &$ttl, array &$setOpts ) use ( $db, $pageId, $revId ) {
1912  $setOpts += Database::getCacheSetOptions( $db );
1913 
1914  $rev = Revision::loadFromPageId( $db, $pageId, $revId );
1915  // Reflect revision deletion and user renames
1916  if ( $rev ) {
1917  $rev->mTitle = null; // mutable; lazy-load
1918  $rev->mRefreshMutableFields = true;
1919  }
1920 
1921  return $rev ?: false; // don't cache negatives
1922  }
1923  );
1924  }
1925 
1929  private function loadMutableFields() {
1930  if ( !$this->mRefreshMutableFields ) {
1931  return; // not needed
1932  }
1933 
1934  $this->mRefreshMutableFields = false;
1935  $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki );
1936  $row = $dbr->selectRow(
1937  [ 'revision', 'user' ],
1938  [ 'rev_deleted', 'user_name' ],
1939  [ 'rev_id' => $this->mId, 'user_id = rev_user' ],
1940  __METHOD__
1941  );
1942  if ( $row ) { // update values
1943  $this->mDeleted = (int)$row->rev_deleted;
1944  $this->mUserText = $row->user_name;
1945  }
1946  }
1947 }
static newFromRow($row)
Make a Title object from a DB row.
Definition: Title.php:450
const FOR_THIS_USER
Definition: Revision.php:93
#define the
table suitable for use with IDatabase::select()
static newFromID($id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:402
static whoIs($id)
Get the username corresponding to a given user ID.
Definition: User.php:708
string $mTimestamp
Definition: Revision.php:42
static getMainWANInstance()
Get the main WAN cache object.
string $mContentFormat
Definition: Revision.php:70
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 $row is usually an object from wfFetchRow()...
Definition: Revision.php:1273
const TEXT_CACHE_GROUP
Definition: Revision.php:96
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:239
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:374
setTitle($title)
Set the title of the revision.
Definition: Revision.php:837
getPage()
Get the page ID.
Definition: Revision.php:846
int null $mPage
Definition: Revision.php:32
per default it will return the text for text based content
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
getTimestamp()
Definition: Revision.php:1194
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.
static getCacheSetOptions(IDatabase $db1)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:3039
setUserIdAndName($id, $name)
Set the user ID/name.
Definition: Revision.php:754
static getTimestampFromId($title, $id, $flags=0)
Get rev_timestamp from rev_id, without loading the rest of the row.
Definition: Revision.php:1805
static newFromConds($conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
int $mParentId
Definition: Revision.php:50
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:1584
int $mUnpatrolled
Definition: Revision.php:58
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context $revId
Definition: hooks.txt:1046
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:2703
isUnpatrolled()
Definition: Revision.php:972
static selectArchiveFields()
Return the list of revision fields that should be selected to create a new revision from an archive r...
Definition: Revision.php:473
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:159
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:128
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:397
$wgCompressRevisions
We can also compress text stored in the 'text' table.
const DB_MASTER
Definition: defines.php:23
loadMutableFields()
For cached revisions, make sure the user name and rev_deleted is up-to-date.
Definition: Revision.php:1929
getNamespace()
Get the namespace index.
getSerializedData()
Get original serialized data (without checking view restrictions)
Definition: Revision.php:1089
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:957
string $mUserText
Definition: Revision.php:34
bool $mRefreshMutableFields
Used for cached values to reload user text and rev_deleted.
Definition: Revision.php:80
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
int null $mId
Definition: Revision.php:30
static loadFromConds($db, $conditions, $flags=0)
Given a set of conditions, fetch a revision from the given database connection.
Definition: Revision.php:353
checkContentModel()
Definition: Revision.php:1512
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:1015
null ContentHandler $mContentHandler
Definition: Revision.php:75
wfGetLB($wiki=false)
Get a load balancer object.
getTitle()
Returns the title of the page associated with this entry or null.
Definition: Revision.php:803
$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:774
unserialize($serialized)
Definition: ApiMessage.php:102
const FOR_PUBLIC
Definition: Revision.php:92
getUserText($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's username if it's available to the specified audience.
Definition: Revision.php:897
if($limit) $timestamp
getRawUserText()
Fetch revision's username without regard for view restrictions.
Definition: Revision.php:923
getId()
Get revision ID.
Definition: Revision.php:729
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1046
$res
Definition: database.txt:21
static loadFromTimestamp($db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:301
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
getContentHandler()
Returns the content handler appropriate for this revision's content model.
Definition: Revision.php:1175
__construct($row)
Constructor.
Definition: Revision.php:562
$summary
static newNullRevision($dbw, $pageId, $summary, $minor, $user=null)
Create a new null-revision for insertion into a page's history.
Definition: Revision.php:1678
static compressRevisionText(&$text)
If $wgCompressRevisions is enabled, we will compress data.
Definition: Revision.php:1318
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Base interface for content objects.
Definition: Content.php:34
getDBkey()
Get the main part with underscores.
$cache
Definition: mcc.php:33
static selectFields()
Return the list of revision fields that should be selected to create a new revision.
Definition: Revision.php:442
static selectTextFields()
Return the list of text fields that should be selected to read the revision text. ...
Definition: Revision.php:504
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:1746
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: defines.php:11
getComment($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision comment if it's available to the specified audience.
Definition: Revision.php:941
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:1936
const DELETED_RESTRICTED
Definition: Revision.php:88
getContentInternal()
Gets the content object for the revision (or null on failure).
Definition: Revision.php:1107
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:953
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:1905
bool $mCurrent
Definition: Revision.php:66
int $mQueryFlags
Definition: Revision.php:78
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
static getDBOptions($bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
static loadFromTitle($db, $title, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition: Revision.php:276
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:1721
getPrevious()
Get previous revision for this title.
Definition: Revision.php:1210
setId($id)
Set the revision ID.
Definition: Revision.php:741
const RAW
Definition: Revision.php:94
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:46
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:242
getRecentChange($flags=0)
Get the RC object belonging to the current revision, if there's one.
Definition: Revision.php:994
getContentModel()
Returns the content model for this revision.
Definition: Revision.php:1134
const DELETED_TEXT
Definition: Revision.php:85
const SUPPRESSED_USER
Definition: Revision.php:89
static countByPageId($db, $id)
Get count of revisions per page...not very efficient.
Definition: Revision.php:1827
string $mComment
Definition: Revision.php:52
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:110
static pageJoinCond()
Return the value of a select() page conds array for the page table.
Definition: Revision.php:433
getVisibility()
Get the deletion bitfield of the revision.
Definition: Revision.php:1031
int $mTextId
Definition: Revision.php:56
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:87
getWikiID()
Alias for getDomainID()
int $mDeleted
Definition: Revision.php:44
static hasFlags($bitfield, $flags)
getTextId()
Get text row ID.
Definition: Revision.php:765
insertOn($dbw)
Insert a new revision into the database, returning the new revision ID number on success and dies hor...
Definition: Revision.php:1396
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
string $mSha1
Definition: Revision.php:48
string $mText
Definition: Revision.php:54
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition: hooks.txt:1046
static fetchFromURL($url, array $params=[])
Fetch data from given URL.
getText($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision text if it's available to the specified audience.
Definition: Revision.php:1052
int $mUser
Definition: Revision.php:38
static countByTitle($db, $title)
Get count of revisions per page...not very efficient.
Definition: Revision.php:1843
string $mContentModel
Definition: Revision.php:68
getContent($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision content if it's available to the specified audience.
Definition: Revision.php:1073
getPreviousRevisionId($db)
Get previous revision Id for this page_id This is used to populate rev_parent_id on save...
Definition: Revision.php:1242
static newFromRow($row)
Definition: Revision.php:230
static loadFromPageId($db, $pageid, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition: Revision.php:256
stdClass null $mTextRow
Definition: Revision.php:61
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.
getNext()
Get next revision for this title.
Definition: Revision.php:1225
static getParentLengths($db, array $revIds)
Do a batched query to get the parent revision lengths.
Definition: Revision.php:540
null Title $mTitle
Definition: Revision.php:64
string $mOrigUserText
Definition: Revision.php:36
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:86
static base36Sha1($text)
Get the base 36 SHA-1 value for a string of text.
Definition: Revision.php:1574
getUser($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's user id if it's available to the specified audience.
Definition: Revision.php:863
static selectPageFields()
Return the list of page fields that should be selected from page table.
Definition: Revision.php:515
const DB_REPLICA
Definition: defines.php:22
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:1764
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:86
bool $mMinorEdit
Definition: Revision.php:40
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:802
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:1867
static userJoinCond()
Return the value of a select() JOIN conds array for the user table.
Definition: Revision.php:423
getContentFormat()
Returns the content format for this revision.
Definition: Revision.php:1158
static loadFromId($db, $id)
Load a page revision from a given revision ID number.
Definition: Revision.php:242
Content null bool $mContent
Definition: Revision.php:73
static newFromArchiveRow($row, $overrides=[])
Make a fake revision object from an archive table row.
Definition: Revision.php:183
getRawUser()
Fetch revision's user id without regard for the current user's permissions.
Definition: Revision.php:879
getSize()
Returns the length of the text in this revision, or null if unknown.
Definition: Revision.php:783
string $mWiki
Wiki ID; false means the current wiki.
Definition: Revision.php:82
static decompressRevisionText($text, $flags)
Re-converts revision text according to it's flags.
Definition: Revision.php:1350
static selectUserFields()
Return the list of user fields that should be selected from user table.
Definition: Revision.php:530
static makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:511
static newFromConds($conditions, $flags=0)
Given a set of conditions, fetch a revision.
Definition: Revision.php:321
getSha1()
Returns the base36 sha1 of the text in this revision, or null if unknown.
Definition: Revision.php:792
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:34
$wgUser
Definition: Setup.php:806
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:300