MediaWiki  master
RevisionRecord.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\Revision;
24 
26 use Content;
33 use Title;
34 use User;
36 
46 abstract class RevisionRecord {
47 
48  // RevisionRecord deletion constants
49  const DELETED_TEXT = 1;
50  const DELETED_COMMENT = 2;
51  const DELETED_USER = 4;
52  const DELETED_RESTRICTED = 8;
53  const SUPPRESSED_USER = self::DELETED_USER | self::DELETED_RESTRICTED; // convenience
54  const SUPPRESSED_ALL = self::DELETED_TEXT | self::DELETED_COMMENT | self::DELETED_USER |
55  self::DELETED_RESTRICTED; // convenience
56 
57  // Audience options for accessors
58  const FOR_PUBLIC = 1;
59  const FOR_THIS_USER = 2;
60  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  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 
227  public function getSlots() {
228  return $this->mSlots;
229  }
230 
245  public function getOriginalSlots() {
246  return new RevisionSlots( $this->mSlots->getOriginalSlots() );
247  }
248 
260  public function getInheritedSlots() {
261  return new RevisionSlots( $this->mSlots->getInheritedSlots() );
262  }
263 
273  public function getId() {
274  return $this->mId;
275  }
276 
289  public function getParentId() {
290  return $this->mParentId;
291  }
292 
303  abstract public function getSize();
304 
316  abstract public function getSha1();
317 
325  public function getPageId() {
326  return $this->mPageId;
327  }
328 
334  public function getWikiId() {
335  return $this->mWiki;
336  }
337 
345  public function getPageAsLinkTarget() {
346  return $this->mTitle;
347  }
348 
365  public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
366  if ( !$this->audienceCan( self::DELETED_USER, $audience, $user ) ) {
367  return null;
368  } else {
369  return $this->mUser;
370  }
371  }
372 
390  public function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
391  if ( !$this->audienceCan( self::DELETED_COMMENT, $audience, $user ) ) {
392  return null;
393  } else {
394  return $this->mComment;
395  }
396  }
397 
403  public function isMinor() {
404  return (bool)$this->mMinorEdit;
405  }
406 
414  public function isDeleted( $field ) {
415  return ( $this->getVisibility() & $field ) == $field;
416  }
417 
425  public function getVisibility() {
426  return (int)$this->mDeleted;
427  }
428 
436  public function getTimestamp() {
437  return $this->mTimestamp;
438  }
439 
457  public function audienceCan( $field, $audience, User $user = null ) {
458  if ( $audience == self::FOR_PUBLIC && $this->isDeleted( $field ) ) {
459  return false;
460  } elseif ( $audience == self::FOR_THIS_USER ) {
461  if ( !$user ) {
462  throw new InvalidArgumentException(
463  'A User object must be given when checking FOR_THIS_USER audience.'
464  );
465  }
466 
467  if ( !$this->userCan( $field, $user ) ) {
468  return false;
469  }
470  }
471 
472  return true;
473  }
474 
487  protected function userCan( $field, User $user ) {
488  // TODO: use callback for permission checks, so we don't need to know a Title object!
489  return self::userCanBitfield( $this->getVisibility(), $field, $user, $this->mTitle );
490  }
491 
508  public static function userCanBitfield( $bitfield, $field, User $user, Title $title = null ) {
509  if ( $bitfield & $field ) { // aspect is deleted
510  if ( $bitfield & self::DELETED_RESTRICTED ) {
511  $permissions = [ 'suppressrevision', 'viewsuppressed' ];
512  } elseif ( $field & self::DELETED_TEXT ) {
513  $permissions = [ 'deletedtext' ];
514  } else {
515  $permissions = [ 'deletedhistory' ];
516  }
517 
518  // XXX: How can we avoid global scope here?
519  // Perhaps the audience check should be done in a callback.
520  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
521  $permissionlist = implode( ', ', $permissions );
522  if ( $title === null ) {
523  wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
524  foreach ( $permissions as $perm ) {
525  if ( $permissionManager->userHasRight( $user, $perm ) ) {
526  return true;
527  }
528  }
529  return false;
530  } else {
531  $text = $title->getPrefixedText();
532  wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" );
533 
534  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
535 
536  foreach ( $permissions as $perm ) {
537  if ( $permissionManager->userCan( $perm, $user, $title ) ) {
538  return true;
539  }
540  }
541  return false;
542  }
543  } else {
544  return true;
545  }
546  }
547 
559  public function isReadyForInsertion() {
560  // NOTE: don't check getSize() and getSha1(), since that may cause the full content to
561  // be loaded in order to calculate the values. Just assume these methods will not return
562  // null if mSlots is not empty.
563 
564  // NOTE: getId() and getPageId() may return null before a revision is saved, so don't
565  // check them.
566 
567  return $this->getTimestamp() !== null
568  && $this->getComment( self::RAW ) !== null
569  && $this->getUser( self::RAW ) !== null
570  && $this->mSlots->getSlotRoles() !== [];
571  }
572 
573 }
574 
579 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:3028
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
getSlots()
Returns the slots defined for this revision.
hasSameContent(RevisionRecord $rec)
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
isDeleted( $field)
MCR migration note: this replaces Revision::isDeleted.
hasSlot( $role)
Returns whether the given slot is defined in this revision.
static getInstance()
Returns the global default instance of the top level service locator.
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.
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:773
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:918
isReadyForInsertion()
Returns whether this RevisionRecord is ready for insertion, that is, whether it contains all informat...
CommentStoreComment null $mComment
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
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.
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
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
getSha1()
Returns the base36 sha1 of this revision.
getTimestamp()
MCR migration note: this replaces Revision::getTimestamp.
string $mWiki
Wiki ID; false means the current wiki.
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.
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.
$content
Definition: pageupdater.txt:72
getParentId()
Get parent revision ID (the original previous page revision).
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1460
getPageId()
Get the page ID.