MediaWiki  master
Revision.php
Go to the documentation of this file.
1 <?php
23 
27 class Revision implements IDBAccessObject {
28  protected $mId;
29 
33  protected $mPage;
34  protected $mUserText;
35  protected $mOrigUserText;
36  protected $mUser;
37  protected $mMinorEdit;
38  protected $mTimestamp;
39  protected $mDeleted;
40  protected $mSize;
41  protected $mSha1;
42  protected $mParentId;
43  protected $mComment;
44  protected $mText;
45  protected $mTextId;
46 
50  protected $mTextRow;
51 
55  protected $mTitle;
56  protected $mCurrent;
57  protected $mContentModel;
58  protected $mContentFormat;
59 
63  protected $mContent;
64 
68  protected $mContentHandler;
69 
73  protected $mQueryFlags = 0;
74 
75  // Revision deletion constants
76  const DELETED_TEXT = 1;
77  const DELETED_COMMENT = 2;
78  const DELETED_USER = 4;
79  const DELETED_RESTRICTED = 8;
80  const SUPPRESSED_USER = 12; // convenience
81 
82  // Audience options for accessors
83  const FOR_PUBLIC = 1;
84  const FOR_THIS_USER = 2;
85  const RAW = 3;
86 
99  public static function newFromId( $id, $flags = 0 ) {
100  return self::newFromConds( [ 'rev_id' => intval( $id ) ], $flags );
101  }
102 
117  public static function newFromTitle( LinkTarget $linkTarget, $id = 0, $flags = 0 ) {
118  $conds = [
119  'page_namespace' => $linkTarget->getNamespace(),
120  'page_title' => $linkTarget->getDBkey()
121  ];
122  if ( $id ) {
123  // Use the specified ID
124  $conds['rev_id'] = $id;
125  return self::newFromConds( $conds, $flags );
126  } else {
127  // Use a join to get the latest revision
128  $conds[] = 'rev_id=page_latest';
129  $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
130  return self::loadFromConds( $db, $conds, $flags );
131  }
132  }
133 
148  public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) {
149  $conds = [ 'page_id' => $pageId ];
150  if ( $revId ) {
151  $conds['rev_id'] = $revId;
152  return self::newFromConds( $conds, $flags );
153  } else {
154  // Use a join to get the latest revision
155  $conds[] = 'rev_id = page_latest';
156  $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
157  return self::loadFromConds( $db, $conds, $flags );
158  }
159  }
160 
172  public static function newFromArchiveRow( $row, $overrides = [] ) {
174 
175  $attribs = $overrides + [
176  'page' => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
177  'id' => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
178  'comment' => $row->ar_comment,
179  'user' => $row->ar_user,
180  'user_text' => $row->ar_user_text,
181  'timestamp' => $row->ar_timestamp,
182  'minor_edit' => $row->ar_minor_edit,
183  'text_id' => isset( $row->ar_text_id ) ? $row->ar_text_id : null,
184  'deleted' => $row->ar_deleted,
185  'len' => $row->ar_len,
186  'sha1' => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
187  'content_model' => isset( $row->ar_content_model ) ? $row->ar_content_model : null,
188  'content_format' => isset( $row->ar_content_format ) ? $row->ar_content_format : null,
189  ];
190 
191  if ( !$wgContentHandlerUseDB ) {
192  unset( $attribs['content_model'] );
193  unset( $attribs['content_format'] );
194  }
195 
196  if ( !isset( $attribs['title'] )
197  && isset( $row->ar_namespace )
198  && isset( $row->ar_title )
199  ) {
200  $attribs['title'] = Title::makeTitle( $row->ar_namespace, $row->ar_title );
201  }
202 
203  if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
204  // Pre-1.5 ar_text row
205  $attribs['text'] = self::getRevisionText( $row, 'ar_' );
206  if ( $attribs['text'] === false ) {
207  throw new MWException( 'Unable to load text from archive row (possibly bug 22624)' );
208  }
209  }
210  return new self( $attribs );
211  }
212 
219  public static function newFromRow( $row ) {
220  return new self( $row );
221  }
222 
231  public static function loadFromId( $db, $id ) {
232  return self::loadFromConds( $db, [ 'rev_id' => intval( $id ) ] );
233  }
234 
245  public static function loadFromPageId( $db, $pageid, $id = 0 ) {
246  $conds = [ 'rev_page' => intval( $pageid ), 'page_id' => intval( $pageid ) ];
247  if ( $id ) {
248  $conds['rev_id'] = intval( $id );
249  } else {
250  $conds[] = 'rev_id=page_latest';
251  }
252  return self::loadFromConds( $db, $conds );
253  }
254 
265  public static function loadFromTitle( $db, $title, $id = 0 ) {
266  if ( $id ) {
267  $matchId = intval( $id );
268  } else {
269  $matchId = 'page_latest';
270  }
271  return self::loadFromConds( $db,
272  [
273  "rev_id=$matchId",
274  'page_namespace' => $title->getNamespace(),
275  'page_title' => $title->getDBkey()
276  ]
277  );
278  }
279 
290  public static function loadFromTimestamp( $db, $title, $timestamp ) {
291  return self::loadFromConds( $db,
292  [
293  'rev_timestamp' => $db->timestamp( $timestamp ),
294  'page_namespace' => $title->getNamespace(),
295  'page_title' => $title->getDBkey()
296  ]
297  );
298  }
299 
310  private static function newFromConds( $conditions, $flags = 0 ) {
311  $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
312 
313  $rev = self::loadFromConds( $db, $conditions, $flags );
314  // Make sure new pending/committed revision are visibile later on
315  // within web requests to certain avoid bugs like T93866 and T94407.
316  if ( !$rev
317  && !( $flags & self::READ_LATEST )
318  && wfGetLB()->getServerCount() > 1
319  && wfGetLB()->hasOrMadeRecentMasterChanges()
320  ) {
321  $flags = self::READ_LATEST;
322  $db = wfGetDB( DB_MASTER );
323  $rev = self::loadFromConds( $db, $conditions, $flags );
324  }
325 
326  if ( $rev ) {
327  $rev->mQueryFlags = $flags;
328  }
329 
330  return $rev;
331  }
332 
342  private static function loadFromConds( $db, $conditions, $flags = 0 ) {
343  $res = self::fetchFromConds( $db, $conditions, $flags );
344  if ( $res ) {
345  $row = $res->fetchObject();
346  if ( $row ) {
347  $ret = new Revision( $row );
348  return $ret;
349  }
350  }
351  $ret = null;
352  return $ret;
353  }
354 
363  public static function fetchRevision( $title ) {
364  return self::fetchFromConds(
365  wfGetDB( DB_SLAVE ),
366  [
367  'rev_id=page_latest',
368  'page_namespace' => $title->getNamespace(),
369  'page_title' => $title->getDBkey()
370  ]
371  );
372  }
373 
384  private static function fetchFromConds( $db, $conditions, $flags = 0 ) {
385  $fields = array_merge(
386  self::selectFields(),
387  self::selectPageFields(),
388  self::selectUserFields()
389  );
390  $options = [ 'LIMIT' => 1 ];
391  if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
392  $options[] = 'FOR UPDATE';
393  }
394  return $db->select(
395  [ 'revision', 'page', 'user' ],
396  $fields,
397  $conditions,
398  __METHOD__,
399  $options,
400  [ 'page' => self::pageJoinCond(), 'user' => self::userJoinCond() ]
401  );
402  }
403 
410  public static function userJoinCond() {
411  return [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ];
412  }
413 
420  public static function pageJoinCond() {
421  return [ 'INNER JOIN', [ 'page_id = rev_page' ] ];
422  }
423 
429  public static function selectFields() {
431 
432  $fields = [
433  'rev_id',
434  'rev_page',
435  'rev_text_id',
436  'rev_timestamp',
437  'rev_comment',
438  'rev_user_text',
439  'rev_user',
440  'rev_minor_edit',
441  'rev_deleted',
442  'rev_len',
443  'rev_parent_id',
444  'rev_sha1',
445  ];
446 
447  if ( $wgContentHandlerUseDB ) {
448  $fields[] = 'rev_content_format';
449  $fields[] = 'rev_content_model';
450  }
451 
452  return $fields;
453  }
454 
460  public static function selectArchiveFields() {
462  $fields = [
463  'ar_id',
464  'ar_page_id',
465  'ar_rev_id',
466  'ar_text',
467  'ar_text_id',
468  'ar_timestamp',
469  'ar_comment',
470  'ar_user_text',
471  'ar_user',
472  'ar_minor_edit',
473  'ar_deleted',
474  'ar_len',
475  'ar_parent_id',
476  'ar_sha1',
477  ];
478 
479  if ( $wgContentHandlerUseDB ) {
480  $fields[] = 'ar_content_format';
481  $fields[] = 'ar_content_model';
482  }
483  return $fields;
484  }
485 
491  public static function selectTextFields() {
492  return [
493  'old_text',
494  'old_flags'
495  ];
496  }
497 
502  public static function selectPageFields() {
503  return [
504  'page_namespace',
505  'page_title',
506  'page_id',
507  'page_latest',
508  'page_is_redirect',
509  'page_len',
510  ];
511  }
512 
517  public static function selectUserFields() {
518  return [ 'user_name' ];
519  }
520 
527  public static function getParentLengths( $db, array $revIds ) {
528  $revLens = [];
529  if ( !$revIds ) {
530  return $revLens; // empty
531  }
532  $res = $db->select( 'revision',
533  [ 'rev_id', 'rev_len' ],
534  [ 'rev_id' => $revIds ],
535  __METHOD__ );
536  foreach ( $res as $row ) {
537  $revLens[$row->rev_id] = $row->rev_len;
538  }
539  return $revLens;
540  }
541 
549  function __construct( $row ) {
550  if ( is_object( $row ) ) {
551  $this->mId = intval( $row->rev_id );
552  $this->mPage = intval( $row->rev_page );
553  $this->mTextId = intval( $row->rev_text_id );
554  $this->mComment = $row->rev_comment;
555  $this->mUser = intval( $row->rev_user );
556  $this->mMinorEdit = intval( $row->rev_minor_edit );
557  $this->mTimestamp = $row->rev_timestamp;
558  $this->mDeleted = intval( $row->rev_deleted );
559 
560  if ( !isset( $row->rev_parent_id ) ) {
561  $this->mParentId = null;
562  } else {
563  $this->mParentId = intval( $row->rev_parent_id );
564  }
565 
566  if ( !isset( $row->rev_len ) ) {
567  $this->mSize = null;
568  } else {
569  $this->mSize = intval( $row->rev_len );
570  }
571 
572  if ( !isset( $row->rev_sha1 ) ) {
573  $this->mSha1 = null;
574  } else {
575  $this->mSha1 = $row->rev_sha1;
576  }
577 
578  if ( isset( $row->page_latest ) ) {
579  $this->mCurrent = ( $row->rev_id == $row->page_latest );
580  $this->mTitle = Title::newFromRow( $row );
581  } else {
582  $this->mCurrent = false;
583  $this->mTitle = null;
584  }
585 
586  if ( !isset( $row->rev_content_model ) ) {
587  $this->mContentModel = null; # determine on demand if needed
588  } else {
589  $this->mContentModel = strval( $row->rev_content_model );
590  }
591 
592  if ( !isset( $row->rev_content_format ) ) {
593  $this->mContentFormat = null; # determine on demand if needed
594  } else {
595  $this->mContentFormat = strval( $row->rev_content_format );
596  }
597 
598  // Lazy extraction...
599  $this->mText = null;
600  if ( isset( $row->old_text ) ) {
601  $this->mTextRow = $row;
602  } else {
603  // 'text' table row entry will be lazy-loaded
604  $this->mTextRow = null;
605  }
606 
607  // Use user_name for users and rev_user_text for IPs...
608  $this->mUserText = null; // lazy load if left null
609  if ( $this->mUser == 0 ) {
610  $this->mUserText = $row->rev_user_text; // IP user
611  } elseif ( isset( $row->user_name ) ) {
612  $this->mUserText = $row->user_name; // logged-in user
613  }
614  $this->mOrigUserText = $row->rev_user_text;
615  } elseif ( is_array( $row ) ) {
616  // Build a new revision to be saved...
617  global $wgUser; // ugh
618 
619  # if we have a content object, use it to set the model and type
620  if ( !empty( $row['content'] ) ) {
621  // @todo when is that set? test with external store setup! check out insertOn() [dk]
622  if ( !empty( $row['text_id'] ) ) {
623  throw new MWException( "Text already stored in external store (id {$row['text_id']}), " .
624  "can't serialize content object" );
625  }
626 
627  $row['content_model'] = $row['content']->getModel();
628  # note: mContentFormat is initializes later accordingly
629  # note: content is serialized later in this method!
630  # also set text to null?
631  }
632 
633  $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
634  $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
635  $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
636  $this->mUserText = isset( $row['user_text'] )
637  ? strval( $row['user_text'] ) : $wgUser->getName();
638  $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
639  $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
640  $this->mTimestamp = isset( $row['timestamp'] )
641  ? strval( $row['timestamp'] ) : wfTimestampNow();
642  $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
643  $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
644  $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
645  $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
646 
647  $this->mContentModel = isset( $row['content_model'] )
648  ? strval( $row['content_model'] ) : null;
649  $this->mContentFormat = isset( $row['content_format'] )
650  ? strval( $row['content_format'] ) : null;
651 
652  // Enforce spacing trimming on supplied text
653  $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
654  $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
655  $this->mTextRow = null;
656 
657  $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
658 
659  // if we have a Content object, override mText and mContentModel
660  if ( !empty( $row['content'] ) ) {
661  if ( !( $row['content'] instanceof Content ) ) {
662  throw new MWException( '`content` field must contain a Content object.' );
663  }
664 
665  $handler = $this->getContentHandler();
666  $this->mContent = $row['content'];
667 
668  $this->mContentModel = $this->mContent->getModel();
669  $this->mContentHandler = null;
670 
671  $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
672  } elseif ( $this->mText !== null ) {
673  $handler = $this->getContentHandler();
674  $this->mContent = $handler->unserializeContent( $this->mText );
675  }
676 
677  // If we have a Title object, make sure it is consistent with mPage.
678  if ( $this->mTitle && $this->mTitle->exists() ) {
679  if ( $this->mPage === null ) {
680  // if the page ID wasn't known, set it now
681  $this->mPage = $this->mTitle->getArticleID();
682  } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
683  // Got different page IDs. This may be legit (e.g. during undeletion),
684  // but it seems worth mentioning it in the log.
685  wfDebug( "Page ID " . $this->mPage . " mismatches the ID " .
686  $this->mTitle->getArticleID() . " provided by the Title object." );
687  }
688  }
689 
690  $this->mCurrent = false;
691 
692  // If we still have no length, see it we have the text to figure it out
693  if ( !$this->mSize && $this->mContent !== null ) {
694  $this->mSize = $this->mContent->getSize();
695  }
696 
697  // Same for sha1
698  if ( $this->mSha1 === null ) {
699  $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
700  }
701 
702  // force lazy init
703  $this->getContentModel();
704  $this->getContentFormat();
705  } else {
706  throw new MWException( 'Revision constructor passed invalid row format.' );
707  }
708  $this->mUnpatrolled = null;
709  }
710 
716  public function getId() {
717  return $this->mId;
718  }
719 
728  public function setId( $id ) {
729  $this->mId = (int)$id;
730  }
731 
741  public function setUserIdAndName( $id, $name ) {
742  $this->mUser = (int)$id;
743  $this->mUserText = $name;
744  $this->mOrigUserText = $name;
745  }
746 
752  public function getTextId() {
753  return $this->mTextId;
754  }
755 
761  public function getParentId() {
762  return $this->mParentId;
763  }
764 
770  public function getSize() {
771  return $this->mSize;
772  }
773 
779  public function getSha1() {
780  return $this->mSha1;
781  }
782 
790  public function getTitle() {
791  if ( $this->mTitle !== null ) {
792  return $this->mTitle;
793  }
794  // rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
795  if ( $this->mId !== null ) {
796  $dbr = wfGetDB( DB_SLAVE );
797  $row = $dbr->selectRow(
798  [ 'page', 'revision' ],
799  self::selectPageFields(),
800  [ 'page_id=rev_page',
801  'rev_id' => $this->mId ],
802  __METHOD__ );
803  if ( $row ) {
804  $this->mTitle = Title::newFromRow( $row );
805  }
806  }
807 
808  if ( !$this->mTitle && $this->mPage !== null && $this->mPage > 0 ) {
809  $this->mTitle = Title::newFromID( $this->mPage );
810  }
811 
812  return $this->mTitle;
813  }
814 
820  public function setTitle( $title ) {
821  $this->mTitle = $title;
822  }
823 
829  public function getPage() {
830  return $this->mPage;
831  }
832 
846  public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
847  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
848  return 0;
849  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
850  return 0;
851  } else {
852  return $this->mUser;
853  }
854  }
855 
862  public function getRawUser() {
863  wfDeprecated( __METHOD__, '1.25' );
864  return $this->getUser( self::RAW );
865  }
866 
880  public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
881  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
882  return '';
883  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
884  return '';
885  } else {
886  if ( $this->mUserText === null ) {
887  $this->mUserText = User::whoIs( $this->mUser ); // load on demand
888  if ( $this->mUserText === false ) {
889  # This shouldn't happen, but it can if the wiki was recovered
890  # via importing revs and there is no user table entry yet.
891  $this->mUserText = $this->mOrigUserText;
892  }
893  }
894  return $this->mUserText;
895  }
896  }
897 
904  public function getRawUserText() {
905  wfDeprecated( __METHOD__, '1.25' );
906  return $this->getUserText( self::RAW );
907  }
908 
922  function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
923  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
924  return '';
925  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $user ) ) {
926  return '';
927  } else {
928  return $this->mComment;
929  }
930  }
931 
938  public function getRawComment() {
939  wfDeprecated( __METHOD__, '1.25' );
940  return $this->getComment( self::RAW );
941  }
942 
946  public function isMinor() {
947  return (bool)$this->mMinorEdit;
948  }
949 
953  public function isUnpatrolled() {
954  if ( $this->mUnpatrolled !== null ) {
955  return $this->mUnpatrolled;
956  }
957  $rc = $this->getRecentChange();
958  if ( $rc && $rc->getAttribute( 'rc_patrolled' ) == 0 ) {
959  $this->mUnpatrolled = $rc->getAttribute( 'rc_id' );
960  } else {
961  $this->mUnpatrolled = 0;
962  }
963  return $this->mUnpatrolled;
964  }
965 
975  public function getRecentChange( $flags = 0 ) {
976  $dbr = wfGetDB( DB_SLAVE );
977 
979 
981  [
982  'rc_user_text' => $this->getUserText( Revision::RAW ),
983  'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
984  'rc_this_oldid' => $this->getId()
985  ],
986  __METHOD__,
987  $dbType
988  );
989  }
990 
996  public function isDeleted( $field ) {
997  return ( $this->mDeleted & $field ) == $field;
998  }
999 
1005  public function getVisibility() {
1006  return (int)$this->mDeleted;
1007  }
1008 
1025  public function getText( $audience = self::FOR_PUBLIC, User $user = null ) {
1026  ContentHandler::deprecated( __METHOD__, '1.21' );
1027 
1028  $content = $this->getContent( $audience, $user );
1029  return ContentHandler::getContentText( $content ); # returns the raw content text, if applicable
1030  }
1031 
1046  public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
1047  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
1048  return null;
1049  } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
1050  return null;
1051  } else {
1052  return $this->getContentInternal();
1053  }
1054  }
1055 
1062  public function getSerializedData() {
1063  if ( $this->mText === null ) {
1064  $this->mText = $this->loadText();
1065  }
1066 
1067  return $this->mText;
1068  }
1069 
1079  protected function getContentInternal() {
1080  if ( $this->mContent === null ) {
1081  // Revision is immutable. Load on demand:
1082  if ( $this->mText === null ) {
1083  $this->mText = $this->loadText();
1084  }
1085 
1086  if ( $this->mText !== null && $this->mText !== false ) {
1087  // Unserialize content
1088  $handler = $this->getContentHandler();
1089  $format = $this->getContentFormat();
1090 
1091  $this->mContent = $handler->unserializeContent( $this->mText, $format );
1092  }
1093  }
1094 
1095  // NOTE: copy() will return $this for immutable content objects
1096  return $this->mContent ? $this->mContent->copy() : null;
1097  }
1098 
1109  public function getContentModel() {
1110  if ( !$this->mContentModel ) {
1111  $title = $this->getTitle();
1112  if ( $title ) {
1113  $this->mContentModel = ContentHandler::getDefaultModelFor( $title );
1114  } else {
1115  $this->mContentModel = CONTENT_MODEL_WIKITEXT;
1116  }
1117 
1118  assert( !empty( $this->mContentModel ) );
1119  }
1120 
1121  return $this->mContentModel;
1122  }
1123 
1133  public function getContentFormat() {
1134  if ( !$this->mContentFormat ) {
1135  $handler = $this->getContentHandler();
1136  $this->mContentFormat = $handler->getDefaultFormat();
1137 
1138  assert( !empty( $this->mContentFormat ) );
1139  }
1140 
1141  return $this->mContentFormat;
1142  }
1143 
1150  public function getContentHandler() {
1151  if ( !$this->mContentHandler ) {
1152  $model = $this->getContentModel();
1153  $this->mContentHandler = ContentHandler::getForModelID( $model );
1154 
1155  $format = $this->getContentFormat();
1156 
1157  if ( !$this->mContentHandler->isSupportedFormat( $format ) ) {
1158  throw new MWException( "Oops, the content format $format is not supported for "
1159  . "this content model, $model" );
1160  }
1161  }
1162 
1163  return $this->mContentHandler;
1164  }
1165 
1169  public function getTimestamp() {
1170  return wfTimestamp( TS_MW, $this->mTimestamp );
1171  }
1172 
1176  public function isCurrent() {
1177  return $this->mCurrent;
1178  }
1179 
1185  public function getPrevious() {
1186  if ( $this->getTitle() ) {
1187  $prev = $this->getTitle()->getPreviousRevisionID( $this->getId() );
1188  if ( $prev ) {
1189  return self::newFromTitle( $this->getTitle(), $prev );
1190  }
1191  }
1192  return null;
1193  }
1194 
1200  public function getNext() {
1201  if ( $this->getTitle() ) {
1202  $next = $this->getTitle()->getNextRevisionID( $this->getId() );
1203  if ( $next ) {
1204  return self::newFromTitle( $this->getTitle(), $next );
1205  }
1206  }
1207  return null;
1208  }
1209 
1217  private function getPreviousRevisionId( $db ) {
1218  if ( $this->mPage === null ) {
1219  return 0;
1220  }
1221  # Use page_latest if ID is not given
1222  if ( !$this->mId ) {
1223  $prevId = $db->selectField( 'page', 'page_latest',
1224  [ 'page_id' => $this->mPage ],
1225  __METHOD__ );
1226  } else {
1227  $prevId = $db->selectField( 'revision', 'rev_id',
1228  [ 'rev_page' => $this->mPage, 'rev_id < ' . $this->mId ],
1229  __METHOD__,
1230  [ 'ORDER BY' => 'rev_id DESC' ] );
1231  }
1232  return intval( $prevId );
1233  }
1234 
1248  public static function getRevisionText( $row, $prefix = 'old_', $wiki = false ) {
1249 
1250  # Get data
1251  $textField = $prefix . 'text';
1252  $flagsField = $prefix . 'flags';
1253 
1254  if ( isset( $row->$flagsField ) ) {
1255  $flags = explode( ',', $row->$flagsField );
1256  } else {
1257  $flags = [];
1258  }
1259 
1260  if ( isset( $row->$textField ) ) {
1261  $text = $row->$textField;
1262  } else {
1263  return false;
1264  }
1265 
1266  # Use external methods for external objects, text in table is URL-only then
1267  if ( in_array( 'external', $flags ) ) {
1268  $url = $text;
1269  $parts = explode( '://', $url, 2 );
1270  if ( count( $parts ) == 1 || $parts[1] == '' ) {
1271  return false;
1272  }
1273  $text = ExternalStore::fetchFromURL( $url, [ 'wiki' => $wiki ] );
1274  }
1275 
1276  // If the text was fetched without an error, convert it
1277  if ( $text !== false ) {
1278  $text = self::decompressRevisionText( $text, $flags );
1279  }
1280  return $text;
1281  }
1282 
1293  public static function compressRevisionText( &$text ) {
1295  $flags = [];
1296 
1297  # Revisions not marked this way will be converted
1298  # on load if $wgLegacyCharset is set in the future.
1299  $flags[] = 'utf-8';
1300 
1301  if ( $wgCompressRevisions ) {
1302  if ( function_exists( 'gzdeflate' ) ) {
1303  $deflated = gzdeflate( $text );
1304 
1305  if ( $deflated === false ) {
1306  wfLogWarning( __METHOD__ . ': gzdeflate() failed' );
1307  } else {
1308  $text = $deflated;
1309  $flags[] = 'gzip';
1310  }
1311  } else {
1312  wfDebug( __METHOD__ . " -- no zlib support, not compressing\n" );
1313  }
1314  }
1315  return implode( ',', $flags );
1316  }
1317 
1325  public static function decompressRevisionText( $text, $flags ) {
1326  if ( in_array( 'gzip', $flags ) ) {
1327  # Deal with optional compression of archived pages.
1328  # This can be done periodically via maintenance/compressOld.php, and
1329  # as pages are saved if $wgCompressRevisions is set.
1330  $text = gzinflate( $text );
1331 
1332  if ( $text === false ) {
1333  wfLogWarning( __METHOD__ . ': gzinflate() failed' );
1334  return false;
1335  }
1336  }
1337 
1338  if ( in_array( 'object', $flags ) ) {
1339  # Generic compressed storage
1340  $obj = unserialize( $text );
1341  if ( !is_object( $obj ) ) {
1342  // Invalid object
1343  return false;
1344  }
1345  $text = $obj->getText();
1346  }
1347 
1349  if ( $text !== false && $wgLegacyEncoding
1350  && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags )
1351  ) {
1352  # Old revisions kept around in a legacy encoding?
1353  # Upconvert on demand.
1354  # ("utf8" checked for compatibility with some broken
1355  # conversion scripts 2008-12-30)
1357  $text = $wgContLang->iconv( $wgLegacyEncoding, 'UTF-8', $text );
1358  }
1359 
1360  return $text;
1361  }
1362 
1371  public function insertOn( $dbw ) {
1373 
1374  // Not allowed to have rev_page equal to 0, false, etc.
1375  if ( !$this->mPage ) {
1376  $title = $this->getTitle();
1377  if ( $title instanceof Title ) {
1378  $titleText = ' for page ' . $title->getPrefixedText();
1379  } else {
1380  $titleText = '';
1381  }
1382  throw new MWException( "Cannot insert revision$titleText: page ID must be nonzero" );
1383  }
1384 
1385  $this->checkContentModel();
1386 
1387  $data = $this->mText;
1388  $flags = self::compressRevisionText( $data );
1389 
1390  # Write to external storage if required
1391  if ( $wgDefaultExternalStore ) {
1392  // Store and get the URL
1393  $data = ExternalStore::insertToDefault( $data );
1394  if ( !$data ) {
1395  throw new MWException( "Unable to store text to external storage" );
1396  }
1397  if ( $flags ) {
1398  $flags .= ',';
1399  }
1400  $flags .= 'external';
1401  }
1402 
1403  # Record the text (or external storage URL) to the text table
1404  if ( $this->mTextId === null ) {
1405  $old_id = $dbw->nextSequenceValue( 'text_old_id_seq' );
1406  $dbw->insert( 'text',
1407  [
1408  'old_id' => $old_id,
1409  'old_text' => $data,
1410  'old_flags' => $flags,
1411  ], __METHOD__
1412  );
1413  $this->mTextId = $dbw->insertId();
1414  }
1415 
1416  if ( $this->mComment === null ) {
1417  $this->mComment = "";
1418  }
1419 
1420  # Record the edit in revisions
1421  $rev_id = $this->mId !== null
1422  ? $this->mId
1423  : $dbw->nextSequenceValue( 'revision_rev_id_seq' );
1424  $row = [
1425  'rev_id' => $rev_id,
1426  'rev_page' => $this->mPage,
1427  'rev_text_id' => $this->mTextId,
1428  'rev_comment' => $this->mComment,
1429  'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
1430  'rev_user' => $this->mUser,
1431  'rev_user_text' => $this->mUserText,
1432  'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
1433  'rev_deleted' => $this->mDeleted,
1434  'rev_len' => $this->mSize,
1435  'rev_parent_id' => $this->mParentId === null
1436  ? $this->getPreviousRevisionId( $dbw )
1437  : $this->mParentId,
1438  'rev_sha1' => $this->mSha1 === null
1439  ? Revision::base36Sha1( $this->mText )
1440  : $this->mSha1,
1441  ];
1442 
1443  if ( $wgContentHandlerUseDB ) {
1444  // NOTE: Store null for the default model and format, to save space.
1445  // XXX: Makes the DB sensitive to changed defaults.
1446  // Make this behavior optional? Only in miser mode?
1447 
1448  $model = $this->getContentModel();
1449  $format = $this->getContentFormat();
1450 
1451  $title = $this->getTitle();
1452 
1453  if ( $title === null ) {
1454  throw new MWException( "Insufficient information to determine the title of the "
1455  . "revision's page!" );
1456  }
1457 
1458  $defaultModel = ContentHandler::getDefaultModelFor( $title );
1459  $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
1460 
1461  $row['rev_content_model'] = ( $model === $defaultModel ) ? null : $model;
1462  $row['rev_content_format'] = ( $format === $defaultFormat ) ? null : $format;
1463  }
1464 
1465  $dbw->insert( 'revision', $row, __METHOD__ );
1466 
1467  $this->mId = $rev_id !== null ? $rev_id : $dbw->insertId();
1468 
1469  // Assertion to try to catch T92046
1470  if ( (int)$this->mId === 0 ) {
1471  throw new UnexpectedValueException(
1472  'After insert, Revision mId is ' . var_export( $this->mId, 1 ) . ': ' .
1473  var_export( $row, 1 )
1474  );
1475  }
1476 
1477  Hooks::run( 'RevisionInsertComplete', [ &$this, $data, $flags ] );
1478 
1479  return $this->mId;
1480  }
1481 
1482  protected function checkContentModel() {
1484 
1485  // Note: may return null for revisions that have not yet been inserted
1486  $title = $this->getTitle();
1487 
1488  $model = $this->getContentModel();
1489  $format = $this->getContentFormat();
1490  $handler = $this->getContentHandler();
1491 
1492  if ( !$handler->isSupportedFormat( $format ) ) {
1493  $t = $title->getPrefixedDBkey();
1494 
1495  throw new MWException( "Can't use format $format with content model $model on $t" );
1496  }
1497 
1498  if ( !$wgContentHandlerUseDB && $title ) {
1499  // if $wgContentHandlerUseDB is not set,
1500  // all revisions must use the default content model and format.
1501 
1502  $defaultModel = ContentHandler::getDefaultModelFor( $title );
1503  $defaultHandler = ContentHandler::getForModelID( $defaultModel );
1504  $defaultFormat = $defaultHandler->getDefaultFormat();
1505 
1506  if ( $this->getContentModel() != $defaultModel ) {
1507  $t = $title->getPrefixedDBkey();
1508 
1509  throw new MWException( "Can't save non-default content model with "
1510  . "\$wgContentHandlerUseDB disabled: model is $model, "
1511  . "default for $t is $defaultModel" );
1512  }
1513 
1514  if ( $this->getContentFormat() != $defaultFormat ) {
1515  $t = $title->getPrefixedDBkey();
1516 
1517  throw new MWException( "Can't use non-default content format with "
1518  . "\$wgContentHandlerUseDB disabled: format is $format, "
1519  . "default for $t is $defaultFormat" );
1520  }
1521  }
1522 
1523  $content = $this->getContent( Revision::RAW );
1524  $prefixedDBkey = $title->getPrefixedDBkey();
1525  $revId = $this->mId;
1526 
1527  if ( !$content ) {
1528  throw new MWException(
1529  "Content of revision $revId ($prefixedDBkey) could not be loaded for validation!"
1530  );
1531  }
1532  if ( !$content->isValid() ) {
1533  throw new MWException(
1534  "Content of revision $revId ($prefixedDBkey) is not valid! Content model is $model"
1535  );
1536  }
1537  }
1538 
1544  public static function base36Sha1( $text ) {
1545  return Wikimedia\base_convert( sha1( $text ), 16, 36, 31 );
1546  }
1547 
1554  protected function loadText() {
1555  // Caching may be beneficial for massive use of external storage
1557  static $processCache = null;
1558 
1559  if ( !$processCache ) {
1560  $processCache = new MapCacheLRU( 10 );
1561  }
1562 
1564  $textId = $this->getTextId();
1565  $key = wfMemcKey( 'revisiontext', 'textid', $textId );
1566 
1567  if ( $wgRevisionCacheExpiry ) {
1568  if ( $processCache->has( $key ) ) {
1569  return $processCache->get( $key );
1570  }
1571  $text = $cache->get( $key );
1572  if ( is_string( $text ) ) {
1573  $processCache->set( $key, $text );
1574  return $text;
1575  }
1576  }
1577 
1578  // If we kept data for lazy extraction, use it now...
1579  if ( $this->mTextRow !== null ) {
1580  $row = $this->mTextRow;
1581  $this->mTextRow = null;
1582  } else {
1583  $row = null;
1584  }
1585 
1586  if ( !$row ) {
1587  // Text data is immutable; check slaves first.
1588  $dbr = wfGetDB( DB_SLAVE );
1589  $row = $dbr->selectRow( 'text',
1590  [ 'old_text', 'old_flags' ],
1591  [ 'old_id' => $textId ],
1592  __METHOD__ );
1593  }
1594 
1595  // Fallback to the master in case of slave lag. Also use FOR UPDATE if it was
1596  // used to fetch this revision to avoid missing the row due to REPEATABLE-READ.
1597  $forUpdate = ( $this->mQueryFlags & self::READ_LOCKING == self::READ_LOCKING );
1598  if ( !$row && ( $forUpdate || wfGetLB()->getServerCount() > 1 ) ) {
1599  $dbw = wfGetDB( DB_MASTER );
1600  $row = $dbw->selectRow( 'text',
1601  [ 'old_text', 'old_flags' ],
1602  [ 'old_id' => $textId ],
1603  __METHOD__,
1604  $forUpdate ? [ 'FOR UPDATE' ] : [] );
1605  }
1606 
1607  if ( !$row ) {
1608  wfDebugLog( 'Revision', "No text row with ID '$textId' (revision {$this->getId()})." );
1609  }
1610 
1611  $text = self::getRevisionText( $row );
1612  if ( $row && $text === false ) {
1613  wfDebugLog( 'Revision', "No blob for text row '$textId' (revision {$this->getId()})." );
1614  }
1615 
1616  # No negative caching -- negative hits on text rows may be due to corrupted slave servers
1617  if ( $wgRevisionCacheExpiry && $text !== false ) {
1618  $processCache->set( $key, $text );
1619  $cache->set( $key, $text, $wgRevisionCacheExpiry );
1620  }
1621 
1622  return $text;
1623  }
1624 
1640  public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) {
1642 
1643  $fields = [ 'page_latest', 'page_namespace', 'page_title',
1644  'rev_text_id', 'rev_len', 'rev_sha1' ];
1645 
1646  if ( $wgContentHandlerUseDB ) {
1647  $fields[] = 'rev_content_model';
1648  $fields[] = 'rev_content_format';
1649  }
1650 
1651  $current = $dbw->selectRow(
1652  [ 'page', 'revision' ],
1653  $fields,
1654  [
1655  'page_id' => $pageId,
1656  'page_latest=rev_id',
1657  ],
1658  __METHOD__,
1659  [ 'FOR UPDATE' ] // T51581
1660  );
1661 
1662  if ( $current ) {
1663  if ( !$user ) {
1664  global $wgUser;
1665  $user = $wgUser;
1666  }
1667 
1668  // Truncate for whole multibyte characters
1669  $summary = $wgContLang->truncate( $summary, 255 );
1670 
1671  $row = [
1672  'page' => $pageId,
1673  'user_text' => $user->getName(),
1674  'user' => $user->getId(),
1675  'comment' => $summary,
1676  'minor_edit' => $minor,
1677  'text_id' => $current->rev_text_id,
1678  'parent_id' => $current->page_latest,
1679  'len' => $current->rev_len,
1680  'sha1' => $current->rev_sha1
1681  ];
1682 
1683  if ( $wgContentHandlerUseDB ) {
1684  $row['content_model'] = $current->rev_content_model;
1685  $row['content_format'] = $current->rev_content_format;
1686  }
1687 
1688  $row['title'] = Title::makeTitle( $current->page_namespace, $current->page_title );
1689 
1690  $revision = new Revision( $row );
1691  } else {
1692  $revision = null;
1693  }
1694 
1695  return $revision;
1696  }
1697 
1708  public function userCan( $field, User $user = null ) {
1709  return self::userCanBitfield( $this->mDeleted, $field, $user );
1710  }
1711 
1726  public static function userCanBitfield( $bitfield, $field, User $user = null,
1727  Title $title = null
1728  ) {
1729  if ( $bitfield & $field ) { // aspect is deleted
1730  if ( $user === null ) {
1731  global $wgUser;
1732  $user = $wgUser;
1733  }
1734  if ( $bitfield & self::DELETED_RESTRICTED ) {
1735  $permissions = [ 'suppressrevision', 'viewsuppressed' ];
1736  } elseif ( $field & self::DELETED_TEXT ) {
1737  $permissions = [ 'deletedtext' ];
1738  } else {
1739  $permissions = [ 'deletedhistory' ];
1740  }
1741  $permissionlist = implode( ', ', $permissions );
1742  if ( $title === null ) {
1743  wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
1744  return call_user_func_array( [ $user, 'isAllowedAny' ], $permissions );
1745  } else {
1746  $text = $title->getPrefixedText();
1747  wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" );
1748  foreach ( $permissions as $perm ) {
1749  if ( $title->userCan( $perm, $user ) ) {
1750  return true;
1751  }
1752  }
1753  return false;
1754  }
1755  } else {
1756  return true;
1757  }
1758  }
1759 
1767  static function getTimestampFromId( $title, $id, $flags = 0 ) {
1768  $db = ( $flags & self::READ_LATEST )
1769  ? wfGetDB( DB_MASTER )
1770  : wfGetDB( DB_SLAVE );
1771  // Casting fix for databases that can't take '' for rev_id
1772  if ( $id == '' ) {
1773  $id = 0;
1774  }
1775  $conds = [ 'rev_id' => $id ];
1776  $conds['rev_page'] = $title->getArticleID();
1777  $timestamp = $db->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
1778 
1779  return ( $timestamp !== false ) ? wfTimestamp( TS_MW, $timestamp ) : false;
1780  }
1781 
1789  static function countByPageId( $db, $id ) {
1790  $row = $db->selectRow( 'revision', [ 'revCount' => 'COUNT(*)' ],
1791  [ 'rev_page' => $id ], __METHOD__ );
1792  if ( $row ) {
1793  return $row->revCount;
1794  }
1795  return 0;
1796  }
1797 
1805  static function countByTitle( $db, $title ) {
1806  $id = $title->getArticleID();
1807  if ( $id ) {
1808  return self::countByPageId( $db, $id );
1809  }
1810  return 0;
1811  }
1812 
1829  public static function userWasLastToEdit( $db, $pageId, $userId, $since ) {
1830  if ( !$userId ) {
1831  return false;
1832  }
1833 
1834  if ( is_int( $db ) ) {
1835  $db = wfGetDB( $db );
1836  }
1837 
1838  $res = $db->select( 'revision',
1839  'rev_user',
1840  [
1841  'rev_page' => $pageId,
1842  'rev_timestamp > ' . $db->addQuotes( $db->timestamp( $since ) )
1843  ],
1844  __METHOD__,
1845  [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ] );
1846  foreach ( $res as $row ) {
1847  if ( $row->rev_user != $userId ) {
1848  return false;
1849  }
1850  }
1851  return true;
1852  }
1853 }
static newFromRow($row)
Make a Title object from a DB row.
Definition: Title.php:444
const FOR_THIS_USER
Definition: Revision.php:84
#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:396
static whoIs($id)
Get the username corresponding to a given user ID.
Definition: User.php:744
static getMainWANInstance()
Get the main WAN cache object.
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:1248
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:278
array $wgDefaultExternalStore
The place to put new revisions, false to put them in the local text table.
setTitle($title)
Set the title of the revision.
Definition: Revision.php:820
getPage()
Get the page ID.
Definition: Revision.php:829
int null $mPage
Definition: Revision.php:33
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:1169
static insertToDefault($data, array $params=[])
Like insert() above, but does more of the work for us.
setUserIdAndName($id, $name)
Set the user ID/name.
Definition: Revision.php:741
static getTimestampFromId($title, $id, $flags=0)
Get rev_timestamp from rev_id, without loading the rest of the row.
Definition: Revision.php:1767
$mContentModel
Definition: Revision.php:57
loadText()
Lazy-load the revision's text.
Definition: Revision.php:1554
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:1020
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2588
isUnpatrolled()
Definition: Revision.php:953
static selectArchiveFields()
Return the list of revision fields that should be selected to create a new revision from an archive r...
Definition: Revision.php:460
Represents a title within MediaWiki.
Definition: Title.php:36
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:148
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:117
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:384
$wgCompressRevisions
We can also compress text stored in the 'text' table.
getNamespace()
Get the namespace index.
getSerializedData()
Fetch original serialized data without regard for view restrictions.
Definition: Revision.php:1062
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:938
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:47
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility...
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static loadFromConds($db, $conditions, $flags=0)
Given a set of conditions, fetch a revision from the given database connection.
Definition: Revision.php:342
checkContentModel()
Definition: Revision.php:1482
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:996
null ContentHandler $mContentHandler
Definition: Revision.php:68
wfGetLB($wiki=false)
Get a load balancer object.
getTitle()
Returns the title of the page associated with this entry or null.
Definition: Revision.php:790
$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:761
static fetchRevision($title)
Return a wrapper for a series of database rows to fetch all of a given page's revisions in turn...
Definition: Revision.php:363
unserialize($serialized)
Definition: ApiMessage.php:102
const FOR_PUBLIC
Definition: Revision.php:83
getUserText($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's username if it's available to the specified audience.
Definition: Revision.php:880
if($limit) $timestamp
getRawUserText()
Fetch revision's username without regard for view restrictions.
Definition: Revision.php:904
getId()
Get revision ID.
Definition: Revision.php:716
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:1020
$res
Definition: database.txt:21
static loadFromTimestamp($db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:290
getContentHandler()
Returns the content handler appropriate for this revision's content model.
Definition: Revision.php:1150
__construct($row)
Constructor.
Definition: Revision.php:549
$summary
static newNullRevision($dbw, $pageId, $summary, $minor, $user=null)
Create a new null-revision for insertion into a page's history.
Definition: Revision.php:1640
MediaWiki exception.
Definition: MWException.php:26
static compressRevisionText(&$text)
If $wgCompressRevisions is enabled, we will compress data.
Definition: Revision.php:1293
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:429
static selectTextFields()
Return the list of text fields that should be selected to read the revision text. ...
Definition: Revision.php:491
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:1708
getComment($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision comment if it's available to the specified audience.
Definition: Revision.php:922
const DELETED_RESTRICTED
Definition: Revision.php:79
const DB_SLAVE
Definition: Defines.php:46
getContentInternal()
Gets the content object for the revision (or null on failure).
Definition: Revision.php:1079
Allows to change the fields on the form that will be generated are created Can be used to omit specific feeds from being outputted You must not use this hook to add use OutputPage::addFeedLink() instead.&$feedLinks conditions will AND in the final query as a Content object as a Content object $title
Definition: hooks.txt:312
int $mQueryFlags
Definition: Revision.php:73
$mContentFormat
Definition: Revision.php:58
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 and options 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:265
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:34
$mOrigUserText
Definition: Revision.php:35
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:1601
getPrevious()
Get previous revision for this title.
Definition: Revision.php:1185
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 & $ret
Definition: hooks.txt:1816
setId($id)
Set the revision ID.
Definition: Revision.php:728
const RAW
Definition: Revision.php:85
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
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:975
getContentModel()
Returns the content model for this revision.
Definition: Revision.php:1109
const DELETED_TEXT
Definition: Revision.php:76
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
const SUPPRESSED_USER
Definition: Revision.php:80
static countByPageId($db, $id)
Get count of revisions per page...not very efficient.
Definition: Revision.php:1789
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:99
static pageJoinCond()
Return the value of a select() page conds array for the page table.
Definition: Revision.php:420
getVisibility()
Get the deletion bitfield of the revision.
Definition: Revision.php:1005
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:78
getTextId()
Get text row ID.
Definition: Revision.php:752
insertOn($dbw)
Insert a new revision into the database, returning the new revision ID number on success and dies hor...
Definition: Revision.php:1371
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:1020
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:1025
Some quick notes on the file repository architecture Functionality as driven by data model *The repository object stores configuration information about a file storage method *The file object is a process local cache of information about a particular file Thus the file object is the primary public entry point for obtaining information about since access via the file object can be whereas access via the repository should not be cached Functions which can act on any file specified in their parameters typically find their place either in the repository where reference to repository specific configuration is needed
Definition: README:3
static countByTitle($db, $title)
Get count of revisions per page...not very efficient.
Definition: Revision.php:1805
getContent($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision content if it's available to the specified audience.
Definition: Revision.php:1046
getPreviousRevisionId($db)
Get previous revision Id for this page_id This is used to populate rev_parent_id on save...
Definition: Revision.php:1217
static newFromRow($row)
Definition: Revision.php:219
static loadFromPageId($db, $pageid, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition: Revision.php:245
stdClass null $mTextRow
Definition: Revision.php:50
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:1200
static getParentLengths($db, array $revIds)
Do a batched query to get the parent revision lengths.
Definition: Revision.php:527
null Title $mTitle
Definition: Revision.php:55
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
wfMemcKey()
Make a cache key for the local wiki.
const DB_MASTER
Definition: Defines.php:47
static base36Sha1($text)
Get the base 36 SHA-1 value for a string of text.
Definition: Revision.php:1544
getUser($audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's user id if it's available to the specified audience.
Definition: Revision.php:846
static selectPageFields()
Return the list of page fields that should be selected from page table.
Definition: Revision.php:502
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:1726
wfLogWarning($msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
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:1816
const DELETED_COMMENT
Definition: Revision.php:77
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:776
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:1829
static userJoinCond()
Return the value of a select() JOIN conds array for the user table.
Definition: Revision.php:410
getContentFormat()
Returns the content format for this revision.
Definition: Revision.php:1133
static loadFromId($db, $id)
Load a page revision from a given revision ID number.
Definition: Revision.php:231
Content null bool $mContent
Definition: Revision.php:63
static newFromArchiveRow($row, $overrides=[])
Make a fake revision object from an archive table row.
Definition: Revision.php:172
getRawUser()
Fetch revision's user id without regard for the current user's permissions.
Definition: Revision.php:862
getSize()
Returns the length of the text in this revision, or null if unknown.
Definition: Revision.php:770
static decompressRevisionText($text, $flags)
Re-converts revision text according to it's flags.
Definition: Revision.php:1325
static selectUserFields()
Return the list of user fields that should be selected from user table.
Definition: Revision.php:517
static makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:503
static newFromConds($conditions, $flags=0)
Given a set of conditions, fetch a revision.
Definition: Revision.php:310
static newFromConds($conds, $fname=__METHOD__, $dbType=DB_SLAVE)
Find the first recent change matching some specific conditions.
getSha1()
Returns the base36 sha1 of the text in this revision, or null if unknown.
Definition: Revision.php:779
$wgUser
Definition: Setup.php:801
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310