MediaWiki  master
RevisionRecord.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\Revision;
24 
26 use Content;
28 use LogicException;
32 use MWException;
33 use Title;
34 use User;
36 
46 abstract class RevisionRecord {
47 
48  // RevisionRecord deletion constants
49  public const DELETED_TEXT = 1;
50  public const DELETED_COMMENT = 2;
51  public const DELETED_USER = 4;
52  public const DELETED_RESTRICTED = 8;
53  public const SUPPRESSED_USER = self::DELETED_USER | self::DELETED_RESTRICTED; // convenience
54  public const SUPPRESSED_ALL = self::DELETED_TEXT | self::DELETED_COMMENT | self::DELETED_USER |
55  self::DELETED_RESTRICTED; // convenience
56 
57  // Audience options for accessors
58  public const FOR_PUBLIC = 1;
59  public const FOR_THIS_USER = 2;
60  public const RAW = 3;
61 
63  protected $mWiki = false;
65  protected $mId;
67  protected $mPageId;
69  protected $mUser;
71  protected $mMinorEdit = false;
73  protected $mTimestamp;
75  protected $mDeleted = 0;
77  protected $mSize;
79  protected $mSha1;
81  protected $mParentId;
83  protected $mComment;
84 
86  protected $mTitle; // TODO: we only need the title for permission checks!
87 
89  protected $mSlots;
90 
101  public function __construct( Title $title, RevisionSlots $slots, $dbDomain = false ) {
102  Assert::parameterType( 'string|boolean', $dbDomain, '$dbDomain' );
103 
104  $this->mTitle = $title;
105  $this->mSlots = $slots;
106  $this->mWiki = $dbDomain;
107 
108  // XXX: this is a sensible default, but we may not have a Title object here in the future.
109  $this->mPageId = $title->getArticleID();
110  }
111 
117  public function __sleep() {
118  throw new LogicException( __CLASS__ . ' is not serializable.' );
119  }
120 
127  public function hasSameContent( RevisionRecord $rec ) {
128  if ( $rec === $this ) {
129  return true;
130  }
131 
132  if ( $this->getId() !== null && $this->getId() === $rec->getId() ) {
133  return true;
134  }
135 
136  // check size before hash, since size is quicker to compute
137  if ( $this->getSize() !== $rec->getSize() ) {
138  return false;
139  }
140 
141  // instead of checking the hash, we could also check the content addresses of all slots.
142 
143  if ( $this->getSha1() === $rec->getSha1() ) {
144  return true;
145  }
146 
147  return false;
148  }
149 
167  public function getContent( $role, $audience = self::FOR_PUBLIC, User $user = null ) {
168  // XXX: throwing an exception would be nicer, but would a further
169  // departure from the signature of Revision::getContent(), and thus
170  // more complex and error prone refactoring.
171  if ( !$this->audienceCan( self::DELETED_TEXT, $audience, $user ) ) {
172  return null;
173  }
174 
175  $content = $this->getSlot( $role, $audience, $user )->getContent();
176  return $content->copy();
177  }
178 
191  public function getSlot( $role, $audience = self::FOR_PUBLIC, User $user = null ) {
192  $slot = $this->mSlots->getSlot( $role );
193 
194  if ( !$this->audienceCan( self::DELETED_TEXT, $audience, $user ) ) {
195  return SlotRecord::newWithSuppressedContent( $slot );
196  }
197 
198  return $slot;
199  }
200 
208  public function hasSlot( $role ) {
209  return $this->mSlots->hasSlot( $role );
210  }
211 
218  public function getSlotRoles() {
219  return $this->mSlots->getSlotRoles();
220  }
221 
233  public function getSlots() {
234  return $this->mSlots;
235  }
236 
251  public function getOriginalSlots() {
252  return new RevisionSlots( $this->mSlots->getOriginalSlots() );
253  }
254 
266  public function getInheritedSlots() {
267  return new RevisionSlots( $this->mSlots->getInheritedSlots() );
268  }
269 
279  public function getId() {
280  return $this->mId;
281  }
282 
295  public function getParentId() {
296  return $this->mParentId;
297  }
298 
309  abstract public function getSize();
310 
322  abstract public function getSha1();
323 
331  public function getPageId() {
332  return $this->mPageId;
333  }
334 
340  public function getWikiId() {
341  return $this->mWiki;
342  }
343 
351  public function getPageAsLinkTarget() {
352  return $this->mTitle;
353  }
354 
371  public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
372  if ( !$this->audienceCan( self::DELETED_USER, $audience, $user ) ) {
373  return null;
374  } else {
375  return $this->mUser;
376  }
377  }
378 
396  public function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
397  if ( !$this->audienceCan( self::DELETED_COMMENT, $audience, $user ) ) {
398  return null;
399  } else {
400  return $this->mComment;
401  }
402  }
403 
409  public function isMinor() {
410  return (bool)$this->mMinorEdit;
411  }
412 
420  public function isDeleted( $field ) {
421  return ( $this->getVisibility() & $field ) == $field;
422  }
423 
431  public function getVisibility() {
432  return (int)$this->mDeleted;
433  }
434 
442  public function getTimestamp() {
443  return $this->mTimestamp;
444  }
445 
463  public function audienceCan( $field, $audience, User $user = null ) {
464  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( $field ) ) {
465  return false;
466  } elseif ( $audience == self::FOR_THIS_USER ) {
467  if ( !$user ) {
468  throw new InvalidArgumentException(
469  'A User object must be given when checking FOR_THIS_USER audience.'
470  );
471  }
472 
473  if ( !$this->userCan( $field, $user ) ) {
474  return false;
475  }
476  }
477 
478  return true;
479  }
480 
493  protected function userCan( $field, User $user ) {
494  // TODO: use callback for permission checks, so we don't need to know a Title object!
495  return self::userCanBitfield( $this->getVisibility(), $field, $user, $this->mTitle );
496  }
497 
514  public static function userCanBitfield( $bitfield, $field, User $user, Title $title = null ) {
515  if ( $bitfield & $field ) { // aspect is deleted
516  if ( $bitfield & self::DELETED_RESTRICTED ) {
517  $permissions = [ 'suppressrevision', 'viewsuppressed' ];
518  } elseif ( $field & self::DELETED_TEXT ) {
519  $permissions = [ 'deletedtext' ];
520  } else {
521  $permissions = [ 'deletedhistory' ];
522  }
523 
524  // XXX: How can we avoid global scope here?
525  // Perhaps the audience check should be done in a callback.
526  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
527  $permissionlist = implode( ', ', $permissions );
528  if ( $title === null ) {
529  wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
530  foreach ( $permissions as $perm ) {
531  if ( $permissionManager->userHasRight( $user, $perm ) ) {
532  return true;
533  }
534  }
535  return false;
536  } else {
537  $text = $title->getPrefixedText();
538  wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" );
539 
540  foreach ( $permissions as $perm ) {
541  if ( $permissionManager->userCan( $perm, $user, $title ) ) {
542  return true;
543  }
544  }
545  return false;
546  }
547  } else {
548  return true;
549  }
550  }
551 
563  public function isReadyForInsertion() {
564  // NOTE: don't check getSize() and getSha1(), since that may cause the full content to
565  // be loaded in order to calculate the values. Just assume these methods will not return
566  // null if mSlots is not empty.
567 
568  // NOTE: getId() and getPageId() may return null before a revision is saved, so don't
569  // check them.
570 
571  return $this->getTimestamp() !== null
572  && $this->getComment( self::RAW ) !== null
573  && $this->getUser( self::RAW ) !== null
574  && $this->mSlots->getSlotRoles() !== [];
575  }
576 
577 }
578 
583 class_alias( RevisionRecord::class, 'MediaWiki\Storage\RevisionRecord' );
__construct(Title $title, RevisionSlots $slots, $dbDomain=false)
isMinor()
MCR migration note: this replaces Revision::isMinor.
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3161
getSlots()
Returns the slots defined for this revision.
string false $mWiki
Wiki ID; false means the current wiki.
hasSameContent(RevisionRecord $rec)
static getInstance()
Returns the global default instance of the top level service locator.
isDeleted( $field)
MCR migration note: this replaces Revision::isDeleted.
hasSlot( $role)
Returns whether the given slot is defined in this revision.
userCan( $field, User $user)
Determine if the current user is allowed to view a particular field of this revision, if it&#39;s marked as deleted.
getOriginalSlots()
Returns the slots that originate in this revision.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
Created by PhpStorm.
int $mDeleted
using the DELETED_XXX and SUPPRESSED_XXX flags
static newWithSuppressedContent(SlotRecord $slot)
Returns a new SlotRecord just like the given $slot, except that calling getContent() will fail with a...
Definition: SlotRecord.php:63
getPageAsLinkTarget()
Returns the title of the page this revision is associated with as a LinkTarget object.
getVisibility()
Get the deletion bitfield of the revision.
getSlotRoles()
Returns the slot names (roles) of all slots present in this revision.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getContent( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns the Content of the given slot of this revision.
__sleep()
Implemented to defy serialization.
static userCanBitfield( $bitfield, $field, User $user, Title $title=null)
Determine if the current user is allowed to view a particular field of this revision, if it&#39;s marked as deleted.
isReadyForInsertion()
Returns whether this RevisionRecord is ready for insertion, that is, whether it contains all informat...
CommentStoreComment null $mComment
audienceCan( $field, $audience, User $user=null)
Check that the given audience has access to the given field.
getSlot( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns meta-data for the given slot.
getId()
Get revision ID.
getSha1()
Returns the base36 sha1 of this revision.
getTimestamp()
MCR migration note: this replaces Revision::getTimestamp.
getWikiId()
Get the ID of the wiki this revision belongs to.
getSize()
Returns the nominal size of this revision, in bogo-bytes.
Page revision base class.
getUser( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision&#39;s author&#39;s user identity, if it&#39;s available to the specified audience.
$content
Definition: router.php:78
Value object representing the set of slots belonging to a revision.
UserIdentity null $mUser
getInheritedSlots()
Returns slots inherited from some previous revision.
getComment( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision comment, if it&#39;s available to the specified audience.
getParentId()
Get parent revision ID (the original previous page revision).
getPageId()
Get the page ID.