MediaWiki REL1_29
Revision.php
Go to the documentation of this file.
1<?php
29
33class Revision implements IDBAccessObject {
35 protected $mId;
37 protected $mPage;
39 protected $mUserText;
41 protected $mOrigUserText;
43 protected $mUser;
45 protected $mMinorEdit;
47 protected $mTimestamp;
49 protected $mDeleted;
51 protected $mSize;
53 protected $mSha1;
55 protected $mParentId;
57 protected $mComment;
59 protected $mText;
61 protected $mTextId;
63 protected $mUnpatrolled;
64
66 protected $mTextRow;
67
69 protected $mTitle;
71 protected $mCurrent;
73 protected $mContentModel;
75 protected $mContentFormat;
76
78 protected $mContent;
81
83 protected $mQueryFlags = 0;
85 protected $mRefreshMutableFields = false;
87 protected $mWiki = false;
88
89 // Revision deletion constants
90 const DELETED_TEXT = 1;
91 const DELETED_COMMENT = 2;
92 const DELETED_USER = 4;
94 const SUPPRESSED_USER = 12; // convenience
95 const SUPPRESSED_ALL = 15; // convenience
96
97 // Audience options for accessors
98 const FOR_PUBLIC = 1;
99 const FOR_THIS_USER = 2;
100 const RAW = 3;
101
102 const TEXT_CACHE_GROUP = 'revisiontext:10'; // process cache name and max key count
103
116 public static function newFromId( $id, $flags = 0 ) {
117 return self::newFromConds( [ 'rev_id' => intval( $id ) ], $flags );
118 }
119
134 public static function newFromTitle( LinkTarget $linkTarget, $id = 0, $flags = 0 ) {
135 $conds = [
136 'page_namespace' => $linkTarget->getNamespace(),
137 'page_title' => $linkTarget->getDBkey()
138 ];
139 if ( $id ) {
140 // Use the specified ID
141 $conds['rev_id'] = $id;
142 return self::newFromConds( $conds, $flags );
143 } else {
144 // Use a join to get the latest revision
145 $conds[] = 'rev_id=page_latest';
146 $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
147 return self::loadFromConds( $db, $conds, $flags );
148 }
149 }
150
165 public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) {
166 $conds = [ 'page_id' => $pageId ];
167 if ( $revId ) {
168 $conds['rev_id'] = $revId;
169 return self::newFromConds( $conds, $flags );
170 } else {
171 // Use a join to get the latest revision
172 $conds[] = 'rev_id = page_latest';
173 $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
174 return self::loadFromConds( $db, $conds, $flags );
175 }
176 }
177
189 public static function newFromArchiveRow( $row, $overrides = [] ) {
191
192 $attribs = $overrides + [
193 'page' => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
194 'id' => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
195 'comment' => $row->ar_comment,
196 'user' => $row->ar_user,
197 'user_text' => $row->ar_user_text,
198 'timestamp' => $row->ar_timestamp,
199 'minor_edit' => $row->ar_minor_edit,
200 'text_id' => isset( $row->ar_text_id ) ? $row->ar_text_id : null,
201 'deleted' => $row->ar_deleted,
202 'len' => $row->ar_len,
203 'sha1' => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
204 'content_model' => isset( $row->ar_content_model ) ? $row->ar_content_model : null,
205 'content_format' => isset( $row->ar_content_format ) ? $row->ar_content_format : null,
206 ];
207
208 if ( !$wgContentHandlerUseDB ) {
209 unset( $attribs['content_model'] );
210 unset( $attribs['content_format'] );
211 }
212
213 if ( !isset( $attribs['title'] )
214 && isset( $row->ar_namespace )
215 && isset( $row->ar_title )
216 ) {
217 $attribs['title'] = Title::makeTitle( $row->ar_namespace, $row->ar_title );
218 }
219
220 if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
221 // Pre-1.5 ar_text row
222 $attribs['text'] = self::getRevisionText( $row, 'ar_' );
223 if ( $attribs['text'] === false ) {
224 throw new MWException( 'Unable to load text from archive row (possibly T24624)' );
225 }
226 }
227 return new self( $attribs );
228 }
229
236 public static function newFromRow( $row ) {
237 return new self( $row );
238 }
239
248 public static function loadFromId( $db, $id ) {
249 return self::loadFromConds( $db, [ 'rev_id' => intval( $id ) ] );
250 }
251
262 public static function loadFromPageId( $db, $pageid, $id = 0 ) {
263 $conds = [ 'rev_page' => intval( $pageid ), 'page_id' => intval( $pageid ) ];
264 if ( $id ) {
265 $conds['rev_id'] = intval( $id );
266 } else {
267 $conds[] = 'rev_id=page_latest';
268 }
269 return self::loadFromConds( $db, $conds );
270 }
271
282 public static function loadFromTitle( $db, $title, $id = 0 ) {
283 if ( $id ) {
284 $matchId = intval( $id );
285 } else {
286 $matchId = 'page_latest';
287 }
288 return self::loadFromConds( $db,
289 [
290 "rev_id=$matchId",
291 'page_namespace' => $title->getNamespace(),
292 'page_title' => $title->getDBkey()
293 ]
294 );
295 }
296
307 public static function loadFromTimestamp( $db, $title, $timestamp ) {
308 return self::loadFromConds( $db,
309 [
310 'rev_timestamp' => $db->timestamp( $timestamp ),
311 'page_namespace' => $title->getNamespace(),
312 'page_title' => $title->getDBkey()
313 ]
314 );
315 }
316
327 private static function newFromConds( $conditions, $flags = 0 ) {
328 $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
329
330 $rev = self::loadFromConds( $db, $conditions, $flags );
331 // Make sure new pending/committed revision are visibile later on
332 // within web requests to certain avoid bugs like T93866 and T94407.
333 if ( !$rev
334 && !( $flags & self::READ_LATEST )
335 && wfGetLB()->getServerCount() > 1
336 && wfGetLB()->hasOrMadeRecentMasterChanges()
337 ) {
338 $flags = self::READ_LATEST;
339 $db = wfGetDB( DB_MASTER );
340 $rev = self::loadFromConds( $db, $conditions, $flags );
341 }
342
343 if ( $rev ) {
344 $rev->mQueryFlags = $flags;
345 }
346
347 return $rev;
348 }
349
359 private static function loadFromConds( $db, $conditions, $flags = 0 ) {
360 $row = self::fetchFromConds( $db, $conditions, $flags );
361 if ( $row ) {
362 $rev = new Revision( $row );
363 $rev->mWiki = $db->getWikiID();
364
365 return $rev;
366 }
367
368 return null;
369 }
370
380 public static function fetchRevision( LinkTarget $title ) {
383 [
384 'rev_id=page_latest',
385 'page_namespace' => $title->getNamespace(),
386 'page_title' => $title->getDBkey()
387 ]
388 );
389
390 return new FakeResultWrapper( $row ? [ $row ] : [] );
391 }
392
403 private static function fetchFromConds( $db, $conditions, $flags = 0 ) {
404 $fields = array_merge(
405 self::selectFields(),
406 self::selectPageFields(),
407 self::selectUserFields()
408 );
409 $options = [];
410 if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
411 $options[] = 'FOR UPDATE';
412 }
413 return $db->selectRow(
414 [ 'revision', 'page', 'user' ],
415 $fields,
416 $conditions,
417 __METHOD__,
418 $options,
419 [ 'page' => self::pageJoinCond(), 'user' => self::userJoinCond() ]
420 );
421 }
422
429 public static function userJoinCond() {
430 return [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ];
431 }
432
439 public static function pageJoinCond() {
440 return [ 'INNER JOIN', [ 'page_id = rev_page' ] ];
441 }
442
448 public static function selectFields() {
450
451 $fields = [
452 'rev_id',
453 'rev_page',
454 'rev_text_id',
455 'rev_timestamp',
456 'rev_comment',
457 'rev_user_text',
458 'rev_user',
459 'rev_minor_edit',
460 'rev_deleted',
461 'rev_len',
462 'rev_parent_id',
463 'rev_sha1',
464 ];
465
467 $fields[] = 'rev_content_format';
468 $fields[] = 'rev_content_model';
469 }
470
471 return $fields;
472 }
473
479 public static function selectArchiveFields() {
481 $fields = [
482 'ar_id',
483 'ar_page_id',
484 'ar_rev_id',
485 'ar_text',
486 'ar_text_id',
487 'ar_timestamp',
488 'ar_comment',
489 'ar_user_text',
490 'ar_user',
491 'ar_minor_edit',
492 'ar_deleted',
493 'ar_len',
494 'ar_parent_id',
495 'ar_sha1',
496 ];
497
499 $fields[] = 'ar_content_format';
500 $fields[] = 'ar_content_model';
501 }
502 return $fields;
503 }
504
510 public static function selectTextFields() {
511 return [
512 'old_text',
513 'old_flags'
514 ];
515 }
516
521 public static function selectPageFields() {
522 return [
523 'page_namespace',
524 'page_title',
525 'page_id',
526 'page_latest',
527 'page_is_redirect',
528 'page_len',
529 ];
530 }
531
536 public static function selectUserFields() {
537 return [ 'user_name' ];
538 }
539
546 public static function getParentLengths( $db, array $revIds ) {
547 $revLens = [];
548 if ( !$revIds ) {
549 return $revLens; // empty
550 }
551 $res = $db->select( 'revision',
552 [ 'rev_id', 'rev_len' ],
553 [ 'rev_id' => $revIds ],
554 __METHOD__ );
555 foreach ( $res as $row ) {
556 $revLens[$row->rev_id] = $row->rev_len;
557 }
558 return $revLens;
559 }
560
568 function __construct( $row ) {
569 if ( is_object( $row ) ) {
570 $this->mId = intval( $row->rev_id );
571 $this->mPage = intval( $row->rev_page );
572 $this->mTextId = intval( $row->rev_text_id );
573 $this->mComment = $row->rev_comment;
574 $this->mUser = intval( $row->rev_user );
575 $this->mMinorEdit = intval( $row->rev_minor_edit );
576 $this->mTimestamp = $row->rev_timestamp;
577 $this->mDeleted = intval( $row->rev_deleted );
578
579 if ( !isset( $row->rev_parent_id ) ) {
580 $this->mParentId = null;
581 } else {
582 $this->mParentId = intval( $row->rev_parent_id );
583 }
584
585 if ( !isset( $row->rev_len ) ) {
586 $this->mSize = null;
587 } else {
588 $this->mSize = intval( $row->rev_len );
589 }
590
591 if ( !isset( $row->rev_sha1 ) ) {
592 $this->mSha1 = null;
593 } else {
594 $this->mSha1 = $row->rev_sha1;
595 }
596
597 if ( isset( $row->page_latest ) ) {
598 $this->mCurrent = ( $row->rev_id == $row->page_latest );
599 $this->mTitle = Title::newFromRow( $row );
600 } else {
601 $this->mCurrent = false;
602 $this->mTitle = null;
603 }
604
605 if ( !isset( $row->rev_content_model ) ) {
606 $this->mContentModel = null; # determine on demand if needed
607 } else {
608 $this->mContentModel = strval( $row->rev_content_model );
609 }
610
611 if ( !isset( $row->rev_content_format ) ) {
612 $this->mContentFormat = null; # determine on demand if needed
613 } else {
614 $this->mContentFormat = strval( $row->rev_content_format );
615 }
616
617 // Lazy extraction...
618 $this->mText = null;
619 if ( isset( $row->old_text ) ) {
620 $this->mTextRow = $row;
621 } else {
622 // 'text' table row entry will be lazy-loaded
623 $this->mTextRow = null;
624 }
625
626 // Use user_name for users and rev_user_text for IPs...
627 $this->mUserText = null; // lazy load if left null
628 if ( $this->mUser == 0 ) {
629 $this->mUserText = $row->rev_user_text; // IP user
630 } elseif ( isset( $row->user_name ) ) {
631 $this->mUserText = $row->user_name; // logged-in user
632 }
633 $this->mOrigUserText = $row->rev_user_text;
634 } elseif ( is_array( $row ) ) {
635 // Build a new revision to be saved...
636 global $wgUser; // ugh
637
638 # if we have a content object, use it to set the model and type
639 if ( !empty( $row['content'] ) ) {
640 // @todo when is that set? test with external store setup! check out insertOn() [dk]
641 if ( !empty( $row['text_id'] ) ) {
642 throw new MWException( "Text already stored in external store (id {$row['text_id']}), " .
643 "can't serialize content object" );
644 }
645
646 $row['content_model'] = $row['content']->getModel();
647 # note: mContentFormat is initializes later accordingly
648 # note: content is serialized later in this method!
649 # also set text to null?
650 }
651
652 $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
653 $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
654 $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
655 $this->mUserText = isset( $row['user_text'] )
656 ? strval( $row['user_text'] ) : $wgUser->getName();
657 $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
658 $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
659 $this->mTimestamp = isset( $row['timestamp'] )
660 ? strval( $row['timestamp'] ) : wfTimestampNow();
661 $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
662 $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
663 $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
664 $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
665
666 $this->mContentModel = isset( $row['content_model'] )
667 ? strval( $row['content_model'] ) : null;
668 $this->mContentFormat = isset( $row['content_format'] )
669 ? strval( $row['content_format'] ) : null;
670
671 // Enforce spacing trimming on supplied text
672 $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
673 $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
674 $this->mTextRow = null;
675
676 $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
677
678 // if we have a Content object, override mText and mContentModel
679 if ( !empty( $row['content'] ) ) {
680 if ( !( $row['content'] instanceof Content ) ) {
681 throw new MWException( '`content` field must contain a Content object.' );
682 }
683
684 $handler = $this->getContentHandler();
685 $this->mContent = $row['content'];
686
687 $this->mContentModel = $this->mContent->getModel();
688 $this->mContentHandler = null;
689
690 $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
691 } elseif ( $this->mText !== null ) {
692 $handler = $this->getContentHandler();
693 $this->mContent = $handler->unserializeContent( $this->mText );
694 }
695
696 // If we have a Title object, make sure it is consistent with mPage.
697 if ( $this->mTitle && $this->mTitle->exists() ) {
698 if ( $this->mPage === null ) {
699 // if the page ID wasn't known, set it now
700 $this->mPage = $this->mTitle->getArticleID();
701 } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
702 // Got different page IDs. This may be legit (e.g. during undeletion),
703 // but it seems worth mentioning it in the log.
704 wfDebug( "Page ID " . $this->mPage . " mismatches the ID " .
705 $this->mTitle->getArticleID() . " provided by the Title object." );
706 }
707 }
708
709 $this->mCurrent = false;
710
711 // If we still have no length, see it we have the text to figure it out
712 if ( !$this->mSize && $this->mContent !== null ) {
713 $this->mSize = $this->mContent->getSize();
714 }
715
716 // Same for sha1
717 if ( $this->mSha1 === null ) {
718 $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
719 }
720
721 // force lazy init
722 $this->getContentModel();
723 $this->getContentFormat();
724 } else {
725 throw new MWException( 'Revision constructor passed invalid row format.' );
726 }
727 $this->mUnpatrolled = null;
728 }
729
735 public function getId() {
736 return $this->mId;
737 }
738
747 public function setId( $id ) {
748 $this->mId = (int)$id;
749 }
750
760 public function setUserIdAndName( $id, $name ) {
761 $this->mUser = (int)$id;
762 $this->mUserText = $name;
763 $this->mOrigUserText = $name;
764 }
765
771 public function getTextId() {
772 return $this->mTextId;
773 }
774
780 public function getParentId() {
781 return $this->mParentId;
782 }
783
789 public function getSize() {
790 return $this->mSize;
791 }
792
798 public function getSha1() {
799 return $this->mSha1;
800 }
801
809 public function getTitle() {
810 if ( $this->mTitle !== null ) {
811 return $this->mTitle;
812 }
813 // rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
814 if ( $this->mId !== null ) {
815 $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki );
816 $row = $dbr->selectRow(
817 [ 'page', 'revision' ],
818 self::selectPageFields(),
819 [ 'page_id=rev_page', 'rev_id' => $this->mId ],
820 __METHOD__
821 );
822 if ( $row ) {
823 // @TODO: better foreign title handling
824 $this->mTitle = Title::newFromRow( $row );
825 }
826 }
827
828 if ( $this->mWiki === false || $this->mWiki === wfWikiID() ) {
829 // Loading by ID is best, though not possible for foreign titles
830 if ( !$this->mTitle && $this->mPage !== null && $this->mPage > 0 ) {
831 $this->mTitle = Title::newFromID( $this->mPage );
832 }
833 }
834
835 return $this->mTitle;
836 }
837
843 public function setTitle( $title ) {
844 $this->mTitle = $title;
845 }
846
852 public function getPage() {
853 return $this->mPage;
854 }
855
869 public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
870 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
871 return 0;
872 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
873 return 0;
874 } else {
875 return $this->mUser;
876 }
877 }
878
885 public function getRawUser() {
886 wfDeprecated( __METHOD__, '1.25' );
887 return $this->getUser( self::RAW );
888 }
889
903 public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
904 $this->loadMutableFields();
905
906 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
907 return '';
908 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
909 return '';
910 } else {
911 if ( $this->mUserText === null ) {
912 $this->mUserText = User::whoIs( $this->mUser ); // load on demand
913 if ( $this->mUserText === false ) {
914 # This shouldn't happen, but it can if the wiki was recovered
915 # via importing revs and there is no user table entry yet.
916 $this->mUserText = $this->mOrigUserText;
917 }
918 }
919 return $this->mUserText;
920 }
921 }
922
929 public function getRawUserText() {
930 wfDeprecated( __METHOD__, '1.25' );
931 return $this->getUserText( self::RAW );
932 }
933
947 function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
948 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
949 return '';
950 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $user ) ) {
951 return '';
952 } else {
953 return $this->mComment;
954 }
955 }
956
963 public function getRawComment() {
964 wfDeprecated( __METHOD__, '1.25' );
965 return $this->getComment( self::RAW );
966 }
967
971 public function isMinor() {
972 return (bool)$this->mMinorEdit;
973 }
974
978 public function isUnpatrolled() {
979 if ( $this->mUnpatrolled !== null ) {
980 return $this->mUnpatrolled;
981 }
982 $rc = $this->getRecentChange();
983 if ( $rc && $rc->getAttribute( 'rc_patrolled' ) == 0 ) {
984 $this->mUnpatrolled = $rc->getAttribute( 'rc_id' );
985 } else {
986 $this->mUnpatrolled = 0;
987 }
988 return $this->mUnpatrolled;
989 }
990
1000 public function getRecentChange( $flags = 0 ) {
1001 $dbr = wfGetDB( DB_REPLICA );
1002
1004
1006 [
1007 'rc_user_text' => $this->getUserText( Revision::RAW ),
1008 'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
1009 'rc_this_oldid' => $this->getId()
1010 ],
1011 __METHOD__,
1012 $dbType
1013 );
1014 }
1015
1021 public function isDeleted( $field ) {
1022 if ( $this->isCurrent() && $field === self::DELETED_TEXT ) {
1023 // Current revisions of pages cannot have the content hidden. Skipping this
1024 // check is very useful for Parser as it fetches templates using newKnownCurrent().
1025 // Calling getVisibility() in that case triggers a verification database query.
1026 return false; // no need to check
1027 }
1028
1029 return ( $this->getVisibility() & $field ) == $field;
1030 }
1031
1037 public function getVisibility() {
1038 $this->loadMutableFields();
1039
1040 return (int)$this->mDeleted;
1041 }
1042
1057 public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
1058 if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
1059 return null;
1060 } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
1061 return null;
1062 } else {
1063 return $this->getContentInternal();
1064 }
1065 }
1066
1073 public function getSerializedData() {
1074 if ( $this->mText === null ) {
1075 // Revision is immutable. Load on demand.
1076 $this->mText = $this->loadText();
1077 }
1078
1079 return $this->mText;
1080 }
1081
1091 protected function getContentInternal() {
1092 if ( $this->mContent === null ) {
1093 $text = $this->getSerializedData();
1094
1095 if ( $text !== null && $text !== false ) {
1096 // Unserialize content
1097 $handler = $this->getContentHandler();
1098 $format = $this->getContentFormat();
1099
1100 $this->mContent = $handler->unserializeContent( $text, $format );
1101 }
1102 }
1103
1104 // NOTE: copy() will return $this for immutable content objects
1105 return $this->mContent ? $this->mContent->copy() : null;
1106 }
1107
1118 public function getContentModel() {
1119 if ( !$this->mContentModel ) {
1120 $title = $this->getTitle();
1121 if ( $title ) {
1122 $this->mContentModel = ContentHandler::getDefaultModelFor( $title );
1123 } else {
1124 $this->mContentModel = CONTENT_MODEL_WIKITEXT;
1125 }
1126
1127 assert( !empty( $this->mContentModel ) );
1128 }
1129
1130 return $this->mContentModel;
1131 }
1132
1142 public function getContentFormat() {
1143 if ( !$this->mContentFormat ) {
1144 $handler = $this->getContentHandler();
1145 $this->mContentFormat = $handler->getDefaultFormat();
1146
1147 assert( !empty( $this->mContentFormat ) );
1148 }
1149
1150 return $this->mContentFormat;
1151 }
1152
1159 public function getContentHandler() {
1160 if ( !$this->mContentHandler ) {
1161 $model = $this->getContentModel();
1162 $this->mContentHandler = ContentHandler::getForModelID( $model );
1163
1164 $format = $this->getContentFormat();
1165
1166 if ( !$this->mContentHandler->isSupportedFormat( $format ) ) {
1167 throw new MWException( "Oops, the content format $format is not supported for "
1168 . "this content model, $model" );
1169 }
1170 }
1171
1173 }
1174
1178 public function getTimestamp() {
1179 return wfTimestamp( TS_MW, $this->mTimestamp );
1180 }
1181
1185 public function isCurrent() {
1186 return $this->mCurrent;
1187 }
1188
1194 public function getPrevious() {
1195 if ( $this->getTitle() ) {
1196 $prev = $this->getTitle()->getPreviousRevisionID( $this->getId() );
1197 if ( $prev ) {
1198 return self::newFromTitle( $this->getTitle(), $prev );
1199 }
1200 }
1201 return null;
1202 }
1203
1209 public function getNext() {
1210 if ( $this->getTitle() ) {
1211 $next = $this->getTitle()->getNextRevisionID( $this->getId() );
1212 if ( $next ) {
1213 return self::newFromTitle( $this->getTitle(), $next );
1214 }
1215 }
1216 return null;
1217 }
1218
1226 private function getPreviousRevisionId( $db ) {
1227 if ( $this->mPage === null ) {
1228 return 0;
1229 }
1230 # Use page_latest if ID is not given
1231 if ( !$this->mId ) {
1232 $prevId = $db->selectField( 'page', 'page_latest',
1233 [ 'page_id' => $this->mPage ],
1234 __METHOD__ );
1235 } else {
1236 $prevId = $db->selectField( 'revision', 'rev_id',
1237 [ 'rev_page' => $this->mPage, 'rev_id < ' . $this->mId ],
1238 __METHOD__,
1239 [ 'ORDER BY' => 'rev_id DESC' ] );
1240 }
1241 return intval( $prevId );
1242 }
1243
1258 public static function getRevisionText( $row, $prefix = 'old_', $wiki = false ) {
1259 $textField = $prefix . 'text';
1260 $flagsField = $prefix . 'flags';
1261
1262 if ( isset( $row->$flagsField ) ) {
1263 $flags = explode( ',', $row->$flagsField );
1264 } else {
1265 $flags = [];
1266 }
1267
1268 if ( isset( $row->$textField ) ) {
1269 $text = $row->$textField;
1270 } else {
1271 return false;
1272 }
1273
1274 // Use external methods for external objects, text in table is URL-only then
1275 if ( in_array( 'external', $flags ) ) {
1276 $url = $text;
1277 $parts = explode( '://', $url, 2 );
1278 if ( count( $parts ) == 1 || $parts[1] == '' ) {
1279 return false;
1280 }
1281
1282 if ( isset( $row->old_id ) && $wiki === false ) {
1283 // Make use of the wiki-local revision text cache
1284 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1285 // The cached value should be decompressed, so handle that and return here
1286 return $cache->getWithSetCallback(
1287 $cache->makeKey( 'revisiontext', 'textid', $row->old_id ),
1288 self::getCacheTTL( $cache ),
1289 function () use ( $url, $wiki, $flags ) {
1290 // No negative caching per Revision::loadText()
1291 $text = ExternalStore::fetchFromURL( $url, [ 'wiki' => $wiki ] );
1292
1293 return self::decompressRevisionText( $text, $flags );
1294 },
1295 [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => $cache::TTL_PROC_LONG ]
1296 );
1297 } else {
1298 $text = ExternalStore::fetchFromURL( $url, [ 'wiki' => $wiki ] );
1299 }
1300 }
1301
1302 return self::decompressRevisionText( $text, $flags );
1303 }
1304
1315 public static function compressRevisionText( &$text ) {
1317 $flags = [];
1318
1319 # Revisions not marked this way will be converted
1320 # on load if $wgLegacyCharset is set in the future.
1321 $flags[] = 'utf-8';
1322
1323 if ( $wgCompressRevisions ) {
1324 if ( function_exists( 'gzdeflate' ) ) {
1325 $deflated = gzdeflate( $text );
1326
1327 if ( $deflated === false ) {
1328 wfLogWarning( __METHOD__ . ': gzdeflate() failed' );
1329 } else {
1330 $text = $deflated;
1331 $flags[] = 'gzip';
1332 }
1333 } else {
1334 wfDebug( __METHOD__ . " -- no zlib support, not compressing\n" );
1335 }
1336 }
1337 return implode( ',', $flags );
1338 }
1339
1347 public static function decompressRevisionText( $text, $flags ) {
1349
1350 if ( $text === false ) {
1351 // Text failed to be fetched; nothing to do
1352 return false;
1353 }
1354
1355 if ( in_array( 'gzip', $flags ) ) {
1356 # Deal with optional compression of archived pages.
1357 # This can be done periodically via maintenance/compressOld.php, and
1358 # as pages are saved if $wgCompressRevisions is set.
1359 $text = gzinflate( $text );
1360
1361 if ( $text === false ) {
1362 wfLogWarning( __METHOD__ . ': gzinflate() failed' );
1363 return false;
1364 }
1365 }
1366
1367 if ( in_array( 'object', $flags ) ) {
1368 # Generic compressed storage
1369 $obj = unserialize( $text );
1370 if ( !is_object( $obj ) ) {
1371 // Invalid object
1372 return false;
1373 }
1374 $text = $obj->getText();
1375 }
1376
1377 if ( $text !== false && $wgLegacyEncoding
1378 && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags )
1379 ) {
1380 # Old revisions kept around in a legacy encoding?
1381 # Upconvert on demand.
1382 # ("utf8" checked for compatibility with some broken
1383 # conversion scripts 2008-12-30)
1384 $text = $wgContLang->iconv( $wgLegacyEncoding, 'UTF-8', $text );
1385 }
1386
1387 return $text;
1388 }
1389
1398 public function insertOn( $dbw ) {
1400
1401 // We're inserting a new revision, so we have to use master anyway.
1402 // If it's a null revision, it may have references to rows that
1403 // are not in the replica yet (the text row).
1404 $this->mQueryFlags |= self::READ_LATEST;
1405
1406 // Not allowed to have rev_page equal to 0, false, etc.
1407 if ( !$this->mPage ) {
1408 $title = $this->getTitle();
1409 if ( $title instanceof Title ) {
1410 $titleText = ' for page ' . $title->getPrefixedText();
1411 } else {
1412 $titleText = '';
1413 }
1414 throw new MWException( "Cannot insert revision$titleText: page ID must be nonzero" );
1415 }
1416
1417 $this->checkContentModel();
1418
1419 $data = $this->mText;
1421
1422 # Write to external storage if required
1424 // Store and get the URL
1425 $data = ExternalStore::insertToDefault( $data );
1426 if ( !$data ) {
1427 throw new MWException( "Unable to store text to external storage" );
1428 }
1429 if ( $flags ) {
1430 $flags .= ',';
1431 }
1432 $flags .= 'external';
1433 }
1434
1435 # Record the text (or external storage URL) to the text table
1436 if ( $this->mTextId === null ) {
1437 $old_id = $dbw->nextSequenceValue( 'text_old_id_seq' );
1438 $dbw->insert( 'text',
1439 [
1440 'old_id' => $old_id,
1441 'old_text' => $data,
1442 'old_flags' => $flags,
1443 ], __METHOD__
1444 );
1445 $this->mTextId = $dbw->insertId();
1446 }
1447
1448 if ( $this->mComment === null ) {
1449 $this->mComment = "";
1450 }
1451
1452 # Record the edit in revisions
1453 $rev_id = $this->mId !== null
1454 ? $this->mId
1455 : $dbw->nextSequenceValue( 'revision_rev_id_seq' );
1456 $row = [
1457 'rev_id' => $rev_id,
1458 'rev_page' => $this->mPage,
1459 'rev_text_id' => $this->mTextId,
1460 'rev_comment' => $this->mComment,
1461 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
1462 'rev_user' => $this->mUser,
1463 'rev_user_text' => $this->mUserText,
1464 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
1465 'rev_deleted' => $this->mDeleted,
1466 'rev_len' => $this->mSize,
1467 'rev_parent_id' => $this->mParentId === null
1468 ? $this->getPreviousRevisionId( $dbw )
1470 'rev_sha1' => $this->mSha1 === null
1471 ? Revision::base36Sha1( $this->mText )
1472 : $this->mSha1,
1473 ];
1474
1475 if ( $wgContentHandlerUseDB ) {
1476 // NOTE: Store null for the default model and format, to save space.
1477 // XXX: Makes the DB sensitive to changed defaults.
1478 // Make this behavior optional? Only in miser mode?
1479
1480 $model = $this->getContentModel();
1481 $format = $this->getContentFormat();
1482
1483 $title = $this->getTitle();
1484
1485 if ( $title === null ) {
1486 throw new MWException( "Insufficient information to determine the title of the "
1487 . "revision's page!" );
1488 }
1489
1490 $defaultModel = ContentHandler::getDefaultModelFor( $title );
1491 $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
1492
1493 $row['rev_content_model'] = ( $model === $defaultModel ) ? null : $model;
1494 $row['rev_content_format'] = ( $format === $defaultFormat ) ? null : $format;
1495 }
1496
1497 $dbw->insert( 'revision', $row, __METHOD__ );
1498
1499 $this->mId = $rev_id !== null ? $rev_id : $dbw->insertId();
1500
1501 // Assertion to try to catch T92046
1502 if ( (int)$this->mId === 0 ) {
1503 throw new UnexpectedValueException(
1504 'After insert, Revision mId is ' . var_export( $this->mId, 1 ) . ': ' .
1505 var_export( $row, 1 )
1506 );
1507 }
1508
1509 // Avoid PHP 7.1 warning of passing $this by reference
1510 $revision = $this;
1511 Hooks::run( 'RevisionInsertComplete', [ &$revision, $data, $flags ] );
1512
1513 return $this->mId;
1514 }
1515
1516 protected function checkContentModel() {
1518
1519 // Note: may return null for revisions that have not yet been inserted
1520 $title = $this->getTitle();
1521
1522 $model = $this->getContentModel();
1523 $format = $this->getContentFormat();
1524 $handler = $this->getContentHandler();
1525
1526 if ( !$handler->isSupportedFormat( $format ) ) {
1527 $t = $title->getPrefixedDBkey();
1528
1529 throw new MWException( "Can't use format $format with content model $model on $t" );
1530 }
1531
1532 if ( !$wgContentHandlerUseDB && $title ) {
1533 // if $wgContentHandlerUseDB is not set,
1534 // all revisions must use the default content model and format.
1535
1536 $defaultModel = ContentHandler::getDefaultModelFor( $title );
1537 $defaultHandler = ContentHandler::getForModelID( $defaultModel );
1538 $defaultFormat = $defaultHandler->getDefaultFormat();
1539
1540 if ( $this->getContentModel() != $defaultModel ) {
1541 $t = $title->getPrefixedDBkey();
1542
1543 throw new MWException( "Can't save non-default content model with "
1544 . "\$wgContentHandlerUseDB disabled: model is $model, "
1545 . "default for $t is $defaultModel" );
1546 }
1547
1548 if ( $this->getContentFormat() != $defaultFormat ) {
1549 $t = $title->getPrefixedDBkey();
1550
1551 throw new MWException( "Can't use non-default content format with "
1552 . "\$wgContentHandlerUseDB disabled: format is $format, "
1553 . "default for $t is $defaultFormat" );
1554 }
1555 }
1556
1557 $content = $this->getContent( Revision::RAW );
1558 $prefixedDBkey = $title->getPrefixedDBkey();
1560
1561 if ( !$content ) {
1562 throw new MWException(
1563 "Content of revision $revId ($prefixedDBkey) could not be loaded for validation!"
1564 );
1565 }
1566 if ( !$content->isValid() ) {
1567 throw new MWException(
1568 "Content of revision $revId ($prefixedDBkey) is not valid! Content model is $model"
1569 );
1570 }
1571 }
1572
1578 public static function base36Sha1( $text ) {
1579 return Wikimedia\base_convert( sha1( $text ), 16, 36, 31 );
1580 }
1581
1588 private static function getCacheTTL( WANObjectCache $cache ) {
1590
1591 if ( $cache->getQoS( $cache::ATTR_EMULATION ) <= $cache::QOS_EMULATION_SQL ) {
1592 // Do not cache RDBMs blobs in...the RDBMs store
1593 $ttl = $cache::TTL_UNCACHEABLE;
1594 } else {
1595 $ttl = $wgRevisionCacheExpiry ?: $cache::TTL_UNCACHEABLE;
1596 }
1597
1598 return $ttl;
1599 }
1600
1607 private function loadText() {
1608 $cache = ObjectCache::getMainWANInstance();
1609
1610 // No negative caching; negative hits on text rows may be due to corrupted replica DBs
1611 return $cache->getWithSetCallback(
1612 $cache->makeKey( 'revisiontext', 'textid', $this->getTextId() ),
1613 self::getCacheTTL( $cache ),
1614 function () {
1615 return $this->fetchText();
1616 },
1617 [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => $cache::TTL_PROC_LONG ]
1618 );
1619 }
1620
1621 private function fetchText() {
1622 $textId = $this->getTextId();
1623
1624 // If we kept data for lazy extraction, use it now...
1625 if ( $this->mTextRow !== null ) {
1626 $row = $this->mTextRow;
1627 $this->mTextRow = null;
1628 } else {
1629 $row = null;
1630 }
1631
1632 // Callers doing updates will pass in READ_LATEST as usual. Since the text/blob tables
1633 // do not normally get rows changed around, set READ_LATEST_IMMUTABLE in those cases.
1635 $flags |= DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST )
1636 ? self::READ_LATEST_IMMUTABLE
1637 : 0;
1638
1639 list( $index, $options, $fallbackIndex, $fallbackOptions ) =
1641
1642 if ( !$row ) {
1643 // Text data is immutable; check replica DBs first.
1644 $row = wfGetDB( $index )->selectRow(
1645 'text',
1646 [ 'old_text', 'old_flags' ],
1647 [ 'old_id' => $textId ],
1648 __METHOD__,
1649 $options
1650 );
1651 }
1652
1653 // Fallback to DB_MASTER in some cases if the row was not found
1654 if ( !$row && $fallbackIndex !== null ) {
1655 // Use FOR UPDATE if it was used to fetch this revision. This avoids missing the row
1656 // due to REPEATABLE-READ. Also fallback to the master if READ_LATEST is provided.
1657 $row = wfGetDB( $fallbackIndex )->selectRow(
1658 'text',
1659 [ 'old_text', 'old_flags' ],
1660 [ 'old_id' => $textId ],
1661 __METHOD__,
1662 $fallbackOptions
1663 );
1664 }
1665
1666 if ( !$row ) {
1667 wfDebugLog( 'Revision', "No text row with ID '$textId' (revision {$this->getId()})." );
1668 }
1669
1670 $text = self::getRevisionText( $row );
1671 if ( $row && $text === false ) {
1672 wfDebugLog( 'Revision', "No blob for text row '$textId' (revision {$this->getId()})." );
1673 }
1674
1675 return is_string( $text ) ? $text : false;
1676 }
1677
1693 public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) {
1695
1696 $fields = [ 'page_latest', 'page_namespace', 'page_title',
1697 'rev_text_id', 'rev_len', 'rev_sha1' ];
1698
1699 if ( $wgContentHandlerUseDB ) {
1700 $fields[] = 'rev_content_model';
1701 $fields[] = 'rev_content_format';
1702 }
1703
1704 $current = $dbw->selectRow(
1705 [ 'page', 'revision' ],
1706 $fields,
1707 [
1708 'page_id' => $pageId,
1709 'page_latest=rev_id',
1710 ],
1711 __METHOD__,
1712 [ 'FOR UPDATE' ] // T51581
1713 );
1714
1715 if ( $current ) {
1716 if ( !$user ) {
1718 $user = $wgUser;
1719 }
1720
1721 // Truncate for whole multibyte characters
1722 $summary = $wgContLang->truncate( $summary, 255 );
1723
1724 $row = [
1725 'page' => $pageId,
1726 'user_text' => $user->getName(),
1727 'user' => $user->getId(),
1728 'comment' => $summary,
1729 'minor_edit' => $minor,
1730 'text_id' => $current->rev_text_id,
1731 'parent_id' => $current->page_latest,
1732 'len' => $current->rev_len,
1733 'sha1' => $current->rev_sha1
1734 ];
1735
1736 if ( $wgContentHandlerUseDB ) {
1737 $row['content_model'] = $current->rev_content_model;
1738 $row['content_format'] = $current->rev_content_format;
1739 }
1740
1741 $row['title'] = Title::makeTitle( $current->page_namespace, $current->page_title );
1742
1743 $revision = new Revision( $row );
1744 } else {
1745 $revision = null;
1746 }
1747
1748 return $revision;
1749 }
1750
1761 public function userCan( $field, User $user = null ) {
1762 return self::userCanBitfield( $this->getVisibility(), $field, $user );
1763 }
1764
1779 public static function userCanBitfield( $bitfield, $field, User $user = null,
1780 Title $title = null
1781 ) {
1782 if ( $bitfield & $field ) { // aspect is deleted
1783 if ( $user === null ) {
1785 $user = $wgUser;
1786 }
1787 if ( $bitfield & self::DELETED_RESTRICTED ) {
1788 $permissions = [ 'suppressrevision', 'viewsuppressed' ];
1789 } elseif ( $field & self::DELETED_TEXT ) {
1790 $permissions = [ 'deletedtext' ];
1791 } else {
1792 $permissions = [ 'deletedhistory' ];
1793 }
1794 $permissionlist = implode( ', ', $permissions );
1795 if ( $title === null ) {
1796 wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
1797 return call_user_func_array( [ $user, 'isAllowedAny' ], $permissions );
1798 } else {
1799 $text = $title->getPrefixedText();
1800 wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" );
1801 foreach ( $permissions as $perm ) {
1802 if ( $title->userCan( $perm, $user ) ) {
1803 return true;
1804 }
1805 }
1806 return false;
1807 }
1808 } else {
1809 return true;
1810 }
1811 }
1812
1821 static function getTimestampFromId( $title, $id, $flags = 0 ) {
1822 $db = ( $flags & self::READ_LATEST )
1823 ? wfGetDB( DB_MASTER )
1824 : wfGetDB( DB_REPLICA );
1825 // Casting fix for databases that can't take '' for rev_id
1826 if ( $id == '' ) {
1827 $id = 0;
1828 }
1829 $conds = [ 'rev_id' => $id ];
1830 $conds['rev_page'] = $title->getArticleID();
1831 $timestamp = $db->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
1832
1833 return ( $timestamp !== false ) ? wfTimestamp( TS_MW, $timestamp ) : false;
1834 }
1835
1843 static function countByPageId( $db, $id ) {
1844 $row = $db->selectRow( 'revision', [ 'revCount' => 'COUNT(*)' ],
1845 [ 'rev_page' => $id ], __METHOD__ );
1846 if ( $row ) {
1847 return $row->revCount;
1848 }
1849 return 0;
1850 }
1851
1859 static function countByTitle( $db, $title ) {
1860 $id = $title->getArticleID();
1861 if ( $id ) {
1862 return self::countByPageId( $db, $id );
1863 }
1864 return 0;
1865 }
1866
1883 public static function userWasLastToEdit( $db, $pageId, $userId, $since ) {
1884 if ( !$userId ) {
1885 return false;
1886 }
1887
1888 if ( is_int( $db ) ) {
1889 $db = wfGetDB( $db );
1890 }
1891
1892 $res = $db->select( 'revision',
1893 'rev_user',
1894 [
1895 'rev_page' => $pageId,
1896 'rev_timestamp > ' . $db->addQuotes( $db->timestamp( $since ) )
1897 ],
1898 __METHOD__,
1899 [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ] );
1900 foreach ( $res as $row ) {
1901 if ( $row->rev_user != $userId ) {
1902 return false;
1903 }
1904 }
1905 return true;
1906 }
1907
1921 public static function newKnownCurrent( IDatabase $db, $pageId, $revId ) {
1922 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1923 return $cache->getWithSetCallback(
1924 // Page/rev IDs passed in from DB to reflect history merges
1925 $cache->makeGlobalKey( 'revision', $db->getWikiID(), $pageId, $revId ),
1926 $cache::TTL_WEEK,
1927 function ( $curValue, &$ttl, array &$setOpts ) use ( $db, $pageId, $revId ) {
1928 $setOpts += Database::getCacheSetOptions( $db );
1929
1930 $rev = Revision::loadFromPageId( $db, $pageId, $revId );
1931 // Reflect revision deletion and user renames
1932 if ( $rev ) {
1933 $rev->mTitle = null; // mutable; lazy-load
1934 $rev->mRefreshMutableFields = true;
1935 }
1936
1937 return $rev ?: false; // don't cache negatives
1938 }
1939 );
1940 }
1941
1945 private function loadMutableFields() {
1946 if ( !$this->mRefreshMutableFields ) {
1947 return; // not needed
1948 }
1949
1950 $this->mRefreshMutableFields = false;
1951 $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki );
1952 $row = $dbr->selectRow(
1953 [ 'revision', 'user' ],
1954 [ 'rev_deleted', 'user_name' ],
1955 [ 'rev_id' => $this->mId, 'user_id = rev_user' ],
1956 __METHOD__
1957 );
1958 if ( $row ) { // update values
1959 $this->mDeleted = (int)$row->rev_deleted;
1960 $this->mUserText = $row->user_name;
1961 }
1962 }
1963}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
unserialize( $serialized)
$wgLegacyEncoding
Set this to eg 'ISO-8859-1' to perform character set conversion when loading old revisions not marked...
$wgRevisionCacheExpiry
Revision text may be cached in $wgMemc to reduce load on external storage servers and object extracti...
array $wgDefaultExternalStore
The place to put new revisions, false to put them in the local text table.
$wgCompressRevisions
We can also compress text stored in the 'text' table.
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfGetLB( $wiki=false)
Get a load balancer object.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
$wgUser
Definition Setup.php:781
A content handler knows how do deal with a specific type of content on a wiki page.
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title.
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
static hasFlags( $bitfield, $flags)
static fetchFromURL( $url, array $params=[])
Fetch data from given URL.
static insertToDefault( $data, array $params=[])
Like insert() above, but does more of the work for us.
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
stdClass null $mTextRow
Definition Revision.php:66
bool $mCurrent
Definition Revision.php:71
static newKnownCurrent(IDatabase $db, $pageId, $revId)
Load a revision based on a known page ID and current revision ID from the DB.
getRecentChange( $flags=0)
Get the RC object belonging to the current revision, if there's one.
getUserText( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's username if it's available to the specified audience.
Definition Revision.php:903
getTitle()
Returns the title of the page associated with this entry or null.
Definition Revision.php:809
static selectArchiveFields()
Return the list of revision fields that should be selected to create a new revision from an archive r...
Definition Revision.php:479
getPrevious()
Get previous revision for this title.
null ContentHandler $mContentHandler
Definition Revision.php:80
getSize()
Returns the length of the text in this revision, or null if unknown.
Definition Revision.php:789
loadMutableFields()
For cached revisions, make sure the user name and rev_deleted is up-to-date.
static getCacheTTL(WANObjectCache $cache)
Get the text cache TTL.
null Title $mTitle
Definition Revision.php:69
getContentInternal()
Gets the content object for the revision (or null on failure).
getSerializedData()
Get original serialized data (without checking view restrictions)
getId()
Get revision ID.
Definition Revision.php:735
int $mUnpatrolled
Definition Revision.php:63
static compressRevisionText(&$text)
If $wgCompressRevisions is enabled, we will compress data.
getRawUser()
Fetch revision's user id without regard for the current user's permissions.
Definition Revision.php:885
static userJoinCond()
Return the value of a select() JOIN conds array for the user table.
Definition Revision.php:429
static decompressRevisionText( $text, $flags)
Re-converts revision text according to it's flags.
bool $mRefreshMutableFields
Used for cached values to reload user text and rev_deleted.
Definition Revision.php:85
setUserIdAndName( $id, $name)
Set the user ID/name.
Definition Revision.php:760
getContentHandler()
Returns the content handler appropriate for this revision's content model.
string $mComment
Definition Revision.php:57
static getRevisionText( $row, $prefix='old_', $wiki=false)
Get revision text associated with an old or archive row.
getComment( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision comment if it's available to the specified audience.
Definition Revision.php:947
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:403
string $mContentFormat
Definition Revision.php:75
static loadFromTitle( $db, $title, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition Revision.php:282
getNext()
Get next revision for this title.
getTextId()
Get text row ID.
Definition Revision.php:771
static selectTextFields()
Return the list of text fields that should be selected to read the revision text.
Definition Revision.php:510
static newFromPageId( $pageId, $revId=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given page ID.
Definition Revision.php:165
static selectPageFields()
Return the list of page fields that should be selected from page table.
Definition Revision.php:521
getContentFormat()
Returns the content format for this revision.
static selectFields()
Return the list of revision fields that should be selected to create a new revision.
Definition Revision.php:448
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition Revision.php:307
int $mUser
Definition Revision.php:43
string $mOrigUserText
Definition Revision.php:41
static loadFromConds( $db, $conditions, $flags=0)
Given a set of conditions, fetch a revision from the given database connection.
Definition Revision.php:359
getPage()
Get the page ID.
Definition Revision.php:852
static newFromRow( $row)
Definition Revision.php:236
int $mSize
Definition Revision.php:51
static newNullRevision( $dbw, $pageId, $summary, $minor, $user=null)
Create a new null-revision for insertion into a page's history.
static loadFromId( $db, $id)
Load a page revision from a given revision ID number.
Definition Revision.php:248
static getTimestampFromId( $title, $id, $flags=0)
Get rev_timestamp from rev_id, without loading the rest of the row.
static selectUserFields()
Return the list of user fields that should be selected from user table.
Definition Revision.php:536
getContentModel()
Returns the content model for this revision.
int null $mId
Definition Revision.php:35
static countByPageId( $db, $id)
Get count of revisions per page...not very efficient.
getUser( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's user id if it's available to the specified audience.
Definition Revision.php:869
static newFromArchiveRow( $row, $overrides=[])
Make a fake revision object from an archive table row.
Definition Revision.php:189
getContent( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision content if it's available to the specified audience.
static countByTitle( $db, $title)
Get count of revisions per page...not very efficient.
string $mContentModel
Definition Revision.php:73
static pageJoinCond()
Return the value of a select() page conds array for the page table.
Definition Revision.php:439
checkContentModel()
const DELETED_USER
Definition Revision.php:92
string $mUserText
Definition Revision.php:39
static newFromConds( $conditions, $flags=0)
Given a set of conditions, fetch a revision.
Definition Revision.php:327
const TEXT_CACHE_GROUP
Definition Revision.php:102
const DELETED_TEXT
Definition Revision.php:90
static base36Sha1( $text)
Get the base 36 SHA-1 value for a string of text.
int $mQueryFlags
Definition Revision.php:83
__construct( $row)
Constructor.
Definition Revision.php:568
getSha1()
Returns the base36 sha1 of the text in this revision, or null if unknown.
Definition Revision.php:798
const DELETED_RESTRICTED
Definition Revision.php:93
bool $mMinorEdit
Definition Revision.php:45
static loadFromPageId( $db, $pageid, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition Revision.php:262
getRawComment()
Fetch revision comment without regard for the current user's permissions.
Definition Revision.php:963
string $mTimestamp
Definition Revision.php:47
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,...
getVisibility()
Get the deletion bitfield of the revision.
setTitle( $title)
Set the title of the revision.
Definition Revision.php:843
int null $mPage
Definition Revision.php:37
string $mSha1
Definition Revision.php:53
insertOn( $dbw)
Insert a new revision into the database, returning the new revision ID number on success and dies hor...
Content null bool $mContent
Definition Revision.php:78
string $mWiki
Wiki ID; false means the current wiki.
Definition Revision.php:87
static userWasLastToEdit( $db, $pageId, $userId, $since)
Check if no edits were made by other users since the time a user started editing the page.
getRawUserText()
Fetch revision's username without regard for view restrictions.
Definition Revision.php:929
static getParentLengths( $db, array $revIds)
Do a batched query to get the parent revision lengths.
Definition Revision.php:546
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:380
isUnpatrolled()
Definition Revision.php:978
userCan( $field, User $user=null)
Determine if the current user is allowed to view a particular field of this revision,...
const FOR_PUBLIC
Definition Revision.php:98
int $mParentId
Definition Revision.php:55
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition Revision.php:134
getParentId()
Get parent revision ID (the original previous page revision)
Definition Revision.php:780
int $mDeleted
Definition Revision.php:49
const SUPPRESSED_ALL
Definition Revision.php:95
string $mText
Definition Revision.php:59
getPreviousRevisionId( $db)
Get previous revision Id for this page_id This is used to populate rev_parent_id on save.
loadText()
Lazy-load the revision's text.
setId( $id)
Set the revision ID.
Definition Revision.php:747
const RAW
Definition Revision.php:100
isDeleted( $field)
const DELETED_COMMENT
Definition Revision.php:91
const FOR_THIS_USER
Definition Revision.php:99
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition Revision.php:116
const SUPPRESSED_USER
Definition Revision.php:94
int $mTextId
Definition Revision.php:61
Represents a title within MediaWiki.
Definition Title.php:39
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:50
Multi-datacenter aware caching interface.
Relational database abstraction object.
Definition Database.php:45
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
Result wrapper for grabbing data queried from an IDatabase object.
$res
Definition database.txt:21
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
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
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:57
when a variable name is used in a function
Definition design.txt:94
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
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
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:233
the array() calling protocol came about after MediaWiki 1.4rc1.
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:249
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1102
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1100
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:964
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition hooks.txt:2753
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1101
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:1975
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:88
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:903
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition hooks.txt:1751
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:37
Base interface for content objects.
Definition Content.php:34
Interface for database access objects.
getNamespace()
Get the namespace index.
getDBkey()
Get the main part with underscores.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:40
getWikiID()
Alias for getDomainID()
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:26