MediaWiki REL1_35
RevisionRecord.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Revision;
24
26use Content;
27use InvalidArgumentException;
28use LogicException;
32use MWException;
33use Title;
34use User;
35use Wikimedia\Assert\Assert;
36
46abstract 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 ) ) {
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" );
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" );
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
582 public function isCurrent() {
583 return false;
584 }
585
586}
587
592class_alias( RevisionRecord::class, 'MediaWiki\Storage\RevisionRecord' );
getUser()
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
CommentStoreComment represents a comment stored by CommentStore.
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static getInstance()
Returns the global default instance of the top level service locator.
Page revision base class.
getParentId()
Get parent revision ID (the original previous page revision).
getWikiId()
Get the ID of the wiki this revision belongs to.
getSize()
Returns the nominal size of this revision, in bogo-bytes.
isReadyForInsertion()
Returns whether this RevisionRecord is ready for insertion, that is, whether it contains all informat...
userCan( $field, User $user)
Determine if the current user is allowed to view a particular field of this revision,...
getOriginalSlots()
Returns the slots that originate in this revision.
getComment( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision comment, if it's available to the specified audience.
int $mDeleted
using the DELETED_XXX and SUPPRESSED_XXX flags
CommentStoreComment null $mComment
getSlotRoles()
Returns the slot names (roles) of all slots present in 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,...
isCurrent()
Checks whether the revision record is a stored current revision.
getVisibility()
Get the deletion bitfield of the revision.
getSlots()
Returns the slots defined for this revision.
getContent( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns the Content of the given slot of this revision.
getInheritedSlots()
Returns slots inherited from some previous revision.
getTimestamp()
MCR migration note: this replaces Revision::getTimestamp.
__construct(Title $title, RevisionSlots $slots, $dbDomain=false)
audienceCan( $field, $audience, User $user=null)
Check that the given audience has access to the given field.
string false $mWiki
Wiki ID; false means the current wiki.
hasSlot( $role)
Returns whether the given slot is defined in this revision.
getSlot( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns meta-data for the given slot.
getSha1()
Returns the base36 sha1 of this revision.
isMinor()
MCR migration note: this replaces Revision::isMinor.
getPageAsLinkTarget()
Returns the title of the page this revision is associated with as a LinkTarget object.
getUser( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's author's user identity, if it's available to the specified audience.
isDeleted( $field)
MCR migration note: this replaces Revision::isDeleted.
Value object representing the set of slots belonging to a revision.
static newWithSuppressedContent(SlotRecord $slot)
Returns a new SlotRecord just like the given $slot, except that calling getContent() will fail with a...
Represents a title within MediaWiki.
Definition Title.php:42
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
Base interface for content objects.
Definition Content.php:35
Interface for objects representing user identity.
$content
Definition router.php:76