MediaWiki  1.29.2
Revision.php
Go to the documentation of this file.
1 <?php
29 
33 class Revision implements IDBAccessObject {
35  protected $mId;
37  protected $mPage;
39  protected $mUserText;
41  protected $mOrigUserText;
43  protected $mUser;
45  protected $mMinorEdit;
47  protected $mTimestamp;
49  protected $mDeleted;
51  protected $mSize;
53  protected $mSha1;
55  protected $mParentId;
57  protected $mComment;
59  protected $mText;
61  protected $mTextId;
63  protected $mUnpatrolled;
64 
66  protected $mTextRow;
67 
69  protected $mTitle;
71  protected $mCurrent;
73  protected $mContentModel;
75  protected $mContentFormat;
76 
78  protected $mContent;
80  protected $mContentHandler;
81 
83  protected $mQueryFlags = 0;
85  protected $mRefreshMutableFields = false;
87  protected $mWiki = false;
88 
89  // Revision deletion constants
90  const DELETED_TEXT = 1;
91  const DELETED_COMMENT = 2;
92  const DELETED_USER = 4;
93  const DELETED_RESTRICTED = 8;
94  const SUPPRESSED_USER = 12; // convenience
95  const SUPPRESSED_ALL = 15; // convenience
96 
97  // Audience options for accessors
98  const FOR_PUBLIC = 1;
99  const FOR_THIS_USER = 2;
100  const RAW = 3;
101 
102  const TEXT_CACHE_GROUP = 'revisiontext:10'; // process cache name and max key count
103 
116  public static function newFromId( $id, $flags = 0 ) {
117  return self::newFromConds( [ 'rev_id' => intval( $id ) ], $flags );
118  }
119 
134  public static function newFromTitle( LinkTarget $linkTarget, $id = 0, $flags = 0 ) {
135  $conds = [
136  'page_namespace' => $linkTarget->getNamespace(),
137  'page_title' => $linkTarget->getDBkey()
138  ];
139  if ( $id ) {
140  // Use the specified ID
141  $conds['rev_id'] = $id;
142  return self::newFromConds( $conds, $flags );
143  } else {
144  // Use a join to get the latest revision
145  $conds[] = 'rev_id=page_latest';
146  $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
147  return self::loadFromConds( $db, $conds, $flags );
148  }
149  }
150 
165  public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) {
166  $conds = [ 'page_id' => $pageId ];
167  if ( $revId ) {
168  $conds['rev_id'] = $revId;
169  return self::newFromConds( $conds, $flags );
170  } else {
171  // Use a join to get the latest revision
172  $conds[] = 'rev_id = page_latest';
173  $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
174  return self::loadFromConds( $db, $conds, $flags );
175  }
176  }
177 
189  public static function newFromArchiveRow( $row, $overrides = [] ) {
190  global $wgContentHandlerUseDB;
191 
192  $attribs = $overrides + [
193  'page' => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
194  'id' => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
195  'comment' => $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 ) {
381  $row = self::fetchFromConds(
382  wfGetDB( DB_REPLICA ),
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() {
449  global $wgContentHandlerUseDB;
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 
466  if ( $wgContentHandlerUseDB ) {
467  $fields[] = 'rev_content_format';
468  $fields[] = 'rev_content_model';
469  }
470 
471  return $fields;
472  }
473 
479  public static function selectArchiveFields() {
480  global $wgContentHandlerUseDB;
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 
498  if ( $wgContentHandlerUseDB ) {
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 
1172  return $this->mContentHandler;
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 ),
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 ) {
1399  global $wgDefaultExternalStore, $wgContentHandlerUseDB;
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
1423  if ( $wgDefaultExternalStore ) {
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 )
1469  : $this->mParentId,
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() {
1517  global $wgContentHandlerUseDB;
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();
1559  $revId = $this->mId;
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() {
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 ) {
1694  global $wgContentHandlerUseDB, $wgContLang;
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 ) {
1717  global $wgUser;
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 ) {
1784  global $wgUser;
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 }
Revision\FOR_PUBLIC
const FOR_PUBLIC
Definition: Revision.php:98
Revision\newFromArchiveRow
static newFromArchiveRow( $row, $overrides=[])
Make a fake revision object from an archive table row.
Definition: Revision.php:189
ExternalStore\insertToDefault
static insertToDefault( $data, array $params=[])
Like insert() above, but does more of the work for us.
Definition: ExternalStore.php:169
Revision\DELETED_USER
const DELETED_USER
Definition: Revision.php:92
ContentHandler
A content handler knows how do deal with a specific type of content on a wiki page.
Definition: ContentHandler.php:49
Revision\getTimestamp
getTimestamp()
Definition: Revision.php:1178
ContentHandler\getForModelID
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
Definition: ContentHandler.php:293
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:45
Revision\$mPage
int null $mPage
Definition: Revision.php:37
Revision\DELETED_RESTRICTED
const DELETED_RESTRICTED
Definition: Revision.php:93
$wgUser
$wgUser
Definition: Setup.php:781
Revision\getUserText
getUserText( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's username if it's available to the specified audience.
Definition: Revision.php:903
Revision\SUPPRESSED_ALL
const SUPPRESSED_ALL
Definition: Revision.php:95
Revision\DELETED_COMMENT
const DELETED_COMMENT
Definition: Revision.php:91
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:116
Revision\getPreviousRevisionId
getPreviousRevisionId( $db)
Get previous revision Id for this page_id This is used to populate rev_parent_id on save.
Definition: Revision.php:1226
Revision\getRawUser
getRawUser()
Fetch revision's user id without regard for the current user's permissions.
Definition: Revision.php:885
Revision\pageJoinCond
static pageJoinCond()
Return the value of a select() page conds array for the page table.
Definition: Revision.php:439
Revision\getUser
getUser( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's user id if it's available to the specified audience.
Definition: Revision.php:869
Revision\userCanBitfield
static userCanBitfield( $bitfield, $field, User $user=null, Title $title=null)
Determine if the current user is allowed to view a particular field of this revision,...
Definition: Revision.php:1779
Revision\fetchText
fetchText()
Definition: Revision.php:1621
RecentChange\newFromConds
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
Definition: RecentChange.php:185
$wgLegacyEncoding
$wgLegacyEncoding
Set this to eg 'ISO-8859-1' to perform character set conversion when loading old revisions not marked...
Definition: DefaultSettings.php:2958
Revision\loadText
loadText()
Lazy-load the revision's text.
Definition: Revision.php:1607
Revision\$mContentHandler
null ContentHandler $mContentHandler
Definition: Revision.php:80
Revision\getSize
getSize()
Returns the length of the text in this revision, or null if unknown.
Definition: Revision.php:789
captcha-old.count
count
Definition: captcha-old.py:225
wfGetLB
wfGetLB( $wiki=false)
Get a load balancer object.
Definition: GlobalFunctions.php:3073
Revision\$mOrigUserText
string $mOrigUserText
Definition: Revision.php:41
Revision\setId
setId( $id)
Set the revision ID.
Definition: Revision.php:747
Wikimedia\Rdbms\IDatabase\getWikiID
getWikiID()
Alias for getDomainID()
Revision\getContent
getContent( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision content if it's available to the specified audience.
Definition: Revision.php:1057
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1994
Revision\getPage
getPage()
Get the page ID.
Definition: Revision.php:852
Revision\newKnownCurrent
static newKnownCurrent(IDatabase $db, $pageId, $revId)
Load a revision based on a known page ID and current revision ID from the DB.
Definition: Revision.php:1921
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:246
ExternalStore\fetchFromURL
static fetchFromURL( $url, array $params=[])
Fetch data from given URL.
Definition: ExternalStore.php:75
unserialize
unserialize( $serialized)
Definition: ApiMessage.php:185
Revision\$mSha1
string $mSha1
Definition: Revision.php:53
Revision\getRevisionText
static getRevisionText( $row, $prefix='old_', $wiki=false)
Get revision text associated with an old or archive row.
Definition: Revision.php:1258
Revision\getParentId
getParentId()
Get parent revision ID (the original previous page revision)
Definition: Revision.php:780
Revision\$mTitle
null Title $mTitle
Definition: Revision.php:69
Revision\setTitle
setTitle( $title)
Set the title of the revision.
Definition: Revision.php:843
Revision\getContentHandler
getContentHandler()
Returns the content handler appropriate for this revision's content model.
Definition: Revision.php:1159
Revision\fetchFromConds
static fetchFromConds( $db, $conditions, $flags=0)
Given a set of conditions, return a ResultWrapper which will return matching database rows with the f...
Definition: Revision.php:403
Revision\getSerializedData
getSerializedData()
Get original serialized data (without checking view restrictions)
Definition: Revision.php:1073
Revision\$mMinorEdit
bool $mMinorEdit
Definition: Revision.php:45
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1155
Revision\getRecentChange
getRecentChange( $flags=0)
Get the RC object belonging to the current revision, if there's one.
Definition: Revision.php:1000
Revision\TEXT_CACHE_GROUP
const TEXT_CACHE_GROUP
Definition: Revision.php:102
$res
$res
Definition: database.txt:21
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
Revision\isCurrent
isCurrent()
Definition: Revision.php:1185
Wikimedia\Rdbms\ResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: ResultWrapper.php:24
CONTENT_MODEL_WIKITEXT
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:233
Revision\newFromPageId
static newFromPageId( $pageId, $revId=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given page ID.
Definition: Revision.php:165
Revision\$mRefreshMutableFields
bool $mRefreshMutableFields
Used for cached values to reload user text and rev_deleted.
Definition: Revision.php:85
Revision\getSha1
getSha1()
Returns the base36 sha1 of the text in this revision, or null if unknown.
Definition: Revision.php:798
Revision\loadFromId
static loadFromId( $db, $id)
Load a page revision from a given revision ID number.
Definition: Revision.php:248
$wgDefaultExternalStore
array $wgDefaultExternalStore
The place to put new revisions, false to put them in the local text table.
Definition: DefaultSettings.php:2124
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:55
Revision\getId
getId()
Get revision ID.
Definition: Revision.php:735
Revision\getContentModel
getContentModel()
Returns the content model for this revision.
Definition: Revision.php:1118
Wikimedia\Rdbms\FakeResultWrapper
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
Definition: FakeResultWrapper.php:11
Revision\insertOn
insertOn( $dbw)
Insert a new revision into the database, returning the new revision ID number on success and dies hor...
Definition: Revision.php:1398
Revision\$mComment
string $mComment
Definition: Revision.php:57
Revision\base36Sha1
static base36Sha1( $text)
Get the base 36 SHA-1 value for a string of text.
Definition: Revision.php:1578
Revision\$mDeleted
int $mDeleted
Definition: Revision.php:49
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1092
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:40
Revision\$mQueryFlags
int $mQueryFlags
Definition: Revision.php:83
Revision\selectTextFields
static selectTextFields()
Return the list of text fields that should be selected to read the revision text.
Definition: Revision.php:510
Revision\FOR_THIS_USER
const FOR_THIS_USER
Definition: Revision.php:99
Revision
Definition: Revision.php:33
Revision\newFromTitle
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition: Revision.php:134
Revision\getCacheTTL
static getCacheTTL(WANObjectCache $cache)
Get the text cache TTL.
Definition: Revision.php:1588
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
Revision\SUPPRESSED_USER
const SUPPRESSED_USER
Definition: Revision.php:94
ContentHandler\getDefaultModelFor
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title.
Definition: ContentHandler.php:178
Revision\$mParentId
int $mParentId
Definition: Revision.php:55
MWException
MediaWiki exception.
Definition: MWException.php:26
Revision\getNext
getNext()
Get next revision for this title.
Definition: Revision.php:1209
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:934
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1128
Revision\setUserIdAndName
setUserIdAndName( $id, $name)
Set the user ID/name.
Definition: Revision.php:760
Revision\getTimestampFromId
static getTimestampFromId( $title, $id, $flags=0)
Get rev_timestamp from rev_id, without loading the rest of the row.
Definition: Revision.php:1821
Revision\$mTextRow
stdClass null $mTextRow
Definition: Revision.php:66
Title\newFromRow
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:453
$content
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:1049
Revision\isUnpatrolled
isUnpatrolled()
Definition: Revision.php:978
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3060
Revision\loadFromPageId
static loadFromPageId( $db, $pageid, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition: Revision.php:262
Revision\$mUser
int $mUser
Definition: Revision.php:43
Revision\$mId
int null $mId
Definition: Revision.php:35
$attribs
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition: hooks.txt:1956
Revision\compressRevisionText
static compressRevisionText(&$text)
If $wgCompressRevisions is enabled, we will compress data.
Definition: Revision.php:1315
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:514
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2023
DB_MASTER
const DB_MASTER
Definition: defines.php:26
Revision\$mSize
int $mSize
Definition: Revision.php:51
$wgCompressRevisions
$wgCompressRevisions
We can also compress text stored in the 'text' table.
Definition: DefaultSettings.php:2079
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:999
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
Revision\countByTitle
static countByTitle( $db, $title)
Get count of revisions per page...not very efficient.
Definition: Revision.php:1859
Revision\getPrevious
getPrevious()
Get previous revision for this title.
Definition: Revision.php:1194
Revision\countByPageId
static countByPageId( $db, $id)
Get count of revisions per page...not very efficient.
Definition: Revision.php:1843
Revision\newFromConds
static newFromConds( $conditions, $flags=0)
Given a set of conditions, fetch a revision.
Definition: Revision.php:327
Revision\$mText
string $mText
Definition: Revision.php:59
Revision\$mTimestamp
string $mTimestamp
Definition: Revision.php:47
Revision\selectPageFields
static selectPageFields()
Return the list of page fields that should be selected from page table.
Definition: Revision.php:521
Revision\getTitle
getTitle()
Returns the title of the page associated with this entry or null.
Definition: Revision.php:809
Revision\$mContentModel
string $mContentModel
Definition: Revision.php:73
Revision\checkContentModel
checkContentModel()
Definition: Revision.php:1516
Revision\userWasLastToEdit
static userWasLastToEdit( $db, $pageId, $userId, $since)
Check if no edits were made by other users since the time a user started editing the page.
Definition: Revision.php:1883
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:3011
Revision\__construct
__construct( $row)
Constructor.
Definition: Revision.php:568
Revision\$mTextId
int $mTextId
Definition: Revision.php:61
Revision\selectArchiveFields
static selectArchiveFields()
Return the list of revision fields that should be selected to create a new revision from an archive r...
Definition: Revision.php:479
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:739
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
on
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going on
Definition: hooks.txt:86
Revision\getParentLengths
static getParentLengths( $db, array $revIds)
Do a batched query to get the parent revision lengths.
Definition: Revision.php:546
Revision\getVisibility
getVisibility()
Get the deletion bitfield of the revision.
Definition: Revision.php:1037
Revision\getTextId
getTextId()
Get text row ID.
Definition: Revision.php:771
Revision\$mCurrent
bool $mCurrent
Definition: Revision.php:71
$wgRevisionCacheExpiry
$wgRevisionCacheExpiry
Revision text may be cached in $wgMemc to reduce load on external storage servers and object extracti...
Definition: DefaultSettings.php:2132
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:81
Revision\$mUserText
string $mUserText
Definition: Revision.php:39
MediaWiki\Linker\LinkTarget\getDBkey
getDBkey()
Get the main part with underscores.
Revision\isDeleted
isDeleted( $field)
Definition: Revision.php:1021
Revision\newFromRow
static newFromRow( $row)
Definition: Revision.php:236
Revision\$mContent
Content null bool $mContent
Definition: Revision.php:78
Revision\RAW
const RAW
Definition: Revision.php:100
Revision\getRawUserText
getRawUserText()
Fetch revision's username without regard for view restrictions.
Definition: Revision.php:929
$handler
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:783
Revision\getContentFormat
getContentFormat()
Returns the content format for this revision.
Definition: Revision.php:1142
Content
Base interface for content objects.
Definition: Content.php:34
Revision\fetchRevision
static fetchRevision(LinkTarget $title)
Return a wrapper for a series of database rows to fetch all of a given page's revisions in turn.
Definition: Revision.php:380
Title
Represents a title within MediaWiki.
Definition: Title.php:39
$dbr
if(! $regexes) $dbr
Definition: cleanup.php:94
$cache
$cache
Definition: mcc.php:33
ObjectCache\getMainWANInstance
static getMainWANInstance()
Get the main WAN cache object.
Definition: ObjectCache.php:370
$rev
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1741
needed
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when needed(most notably, OutputPage::addWikiText()). The StandardSkin object is a complete implementation
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
Revision\userJoinCond
static userJoinCond()
Return the value of a select() JOIN conds array for the user table.
Definition: Revision.php:429
Revision\$mUnpatrolled
int $mUnpatrolled
Definition: Revision.php:63
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:307
Revision\getContentInternal
getContentInternal()
Gets the content object for the revision (or null on failure).
Definition: Revision.php:1091
Revision\getRawComment
getRawComment()
Fetch revision comment without regard for the current user's permissions.
Definition: Revision.php:963
Revision\getComment
getComment( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision comment if it's available to the specified audience.
Definition: Revision.php:947
Revision\loadFromConds
static loadFromConds( $db, $conditions, $flags=0)
Given a set of conditions, fetch a revision from the given database connection.
Definition: Revision.php:359
$revId
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:1049
Revision\newNullRevision
static newNullRevision( $dbw, $pageId, $summary, $minor, $user=null)
Create a new null-revision for insertion into a page's history.
Definition: Revision.php:1693
Revision\$mContentFormat
string $mContentFormat
Definition: Revision.php:75
Revision\isMinor
isMinor()
Definition: Revision.php:971
Revision\selectUserFields
static selectUserFields()
Return the list of user fields that should be selected from user table.
Definition: Revision.php:536
$t
$t
Definition: testCompression.php:67
MediaWikiServices
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:27
Revision\selectFields
static selectFields()
Return the list of revision fields that should be selected to create a new revision.
Definition: Revision.php:448
Revision\decompressRevisionText
static decompressRevisionText( $text, $flags)
Re-converts revision text according to it's flags.
Definition: Revision.php:1347
Revision\loadFromTitle
static loadFromTitle( $db, $title, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition: Revision.php:282
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:50
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:405
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
Revision\loadMutableFields
loadMutableFields()
For cached revisions, make sure the user name and rev_deleted is up-to-date.
Definition: Revision.php:1945
$options
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:1049
Revision\DELETED_TEXT
const DELETED_TEXT
Definition: Revision.php:90
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2749
Revision\userCan
userCan( $field, User $user=null)
Determine if the current user is allowed to view a particular field of this revision,...
Definition: Revision.php:1761
array
the array() calling protocol came about after MediaWiki 1.4rc1.
Revision\$mWiki
string $mWiki
Wiki ID; false means the current wiki.
Definition: Revision.php:87
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:56