ParsoidParser: Record ParserOptions watcher on ParserOutput object
[mediawiki.git] / includes / Revision / RevisionSlots.php
blobf3b1a3af97e3eb52f7507ed3fd8af25e9567cd1d
1 <?php
2 /**
3 * Value object representing the set of slots belonging to a revision.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
20 * @file
23 namespace MediaWiki\Revision;
25 use Content;
26 use Wikimedia\Assert\Assert;
27 use Wikimedia\NonSerializable\NonSerializableTrait;
29 /**
30 * Value object representing the set of slots belonging to a revision.
32 * @note RevisionSlots provides "raw" access to the slots and does not apply audience checks.
33 * If audience checks are desired, use RevisionRecord::getSlot() or RevisionRecord::getContent()
34 * instead.
36 * @newable
38 * @since 1.31
39 * @since 1.32 Renamed from MediaWiki\Storage\RevisionSlots
41 class RevisionSlots {
42 use NonSerializableTrait;
44 /** @var SlotRecord[]|callable */
45 protected $slots;
47 /**
48 * @stable to call.
50 * @param SlotRecord[]|callable $slots SlotRecords,
51 * or a callback that returns such a structure.
53 public function __construct( $slots ) {
54 Assert::parameterType( [ 'array', 'callable' ], $slots, '$slots' );
56 if ( is_callable( $slots ) ) {
57 $this->slots = $slots;
58 } else {
59 $this->setSlotsInternal( $slots );
63 /**
64 * @param SlotRecord[] $slots
66 private function setSlotsInternal( array $slots ): void {
67 Assert::parameterElementType( SlotRecord::class, $slots, '$slots' );
69 $this->slots = [];
71 // re-key the slot array
72 foreach ( $slots as $slot ) {
73 $role = $slot->getRole();
74 $this->slots[$role] = $slot;
78 /**
79 * Returns the Content of the given slot.
80 * Call getSlotNames() to get a list of available slots.
82 * Note that for mutable Content objects, each call to this method will return a
83 * fresh clone.
85 * @see SlotRecord::getContent()
87 * @param string $role The role name of the desired slot
89 * @throws RevisionAccessException if the slot does not exist or slot data
90 * could not be lazy-loaded. See SlotRecord::getContent() for details.
91 * @return Content
93 public function getContent( $role ): Content {
94 // Return a copy to be safe. Immutable content objects return $this from copy().
95 return $this->getSlot( $role )->getContent()->copy();
98 /**
99 * Returns the SlotRecord of the given slot.
100 * Call getSlotNames() to get a list of available slots.
102 * @param string $role The role name of the desired slot
104 * @throws RevisionAccessException if the slot does not exist or slot data
105 * could not be lazy-loaded.
106 * @return SlotRecord
108 public function getSlot( $role ): SlotRecord {
109 $slots = $this->getSlots();
111 if ( isset( $slots[$role] ) ) {
112 return $slots[$role];
113 } else {
114 throw new RevisionAccessException(
115 'No such slot: {role}',
116 [ 'role' => $role ]
122 * Returns whether the given slot is set.
124 * @param string $role The role name of the desired slot
126 * @return bool
128 public function hasSlot( $role ): bool {
129 $slots = $this->getSlots();
131 return isset( $slots[$role] );
135 * Returns the slot names (roles) of all slots present in this revision.
136 * getContent() will succeed only for the names returned by this method.
138 * @return string[]
140 public function getSlotRoles(): array {
141 $slots = $this->getSlots();
142 return array_keys( $slots );
146 * Computes the total nominal size of the revision's slots, in bogo-bytes.
148 * @warning This is potentially expensive! It may cause some slots' content to be loaded
149 * and deserialized.
151 * @return int
153 public function computeSize(): int {
154 return array_reduce( $this->getPrimarySlots(), static function ( $accu, SlotRecord $slot ) {
155 return $accu + $slot->getSize();
156 }, 0 );
160 * Returns an associative array that maps role names to SlotRecords. Each SlotRecord
161 * represents the content meta-data of a slot, together they define the content of
162 * a revision.
164 * @note This may cause the content meta-data for the revision to be lazy-loaded.
166 * @return SlotRecord[] revision slot/content rows, keyed by slot role name.
168 public function getSlots(): array {
169 if ( is_callable( $this->slots ) ) {
170 $slots = call_user_func( $this->slots );
172 Assert::postcondition(
173 is_array( $slots ),
174 'Slots info callback should return an array of objects'
177 $this->setSlotsInternal( $slots );
180 return $this->slots;
184 * Computes the combined hash of the revisions's slots.
186 * @note For backwards compatibility, the combined hash of a single slot
187 * is that slot's hash. For consistency, the combined hash of an empty set of slots
188 * is the hash of the empty string.
190 * @warning This is potentially expensive! It may cause some slots' content to be loaded
191 * and deserialized, then re-serialized and hashed.
193 * @return string
195 public function computeSha1(): string {
196 $slots = $this->getPrimarySlots();
197 ksort( $slots );
199 if ( empty( $slots ) ) {
200 return SlotRecord::base36Sha1( '' );
203 return array_reduce( $slots, static function ( $accu, SlotRecord $slot ) {
204 return $accu === null
205 ? $slot->getSha1()
206 : SlotRecord::base36Sha1( $accu . $slot->getSha1() );
207 }, null );
211 * Return all slots that belong to the revision they originate from (that is,
212 * they are not inherited from some other revision).
214 * @note This may cause the slot meta-data for the revision to be lazy-loaded.
216 * @return SlotRecord[]
218 public function getOriginalSlots(): array {
219 return array_filter(
220 $this->getSlots(),
221 static function ( SlotRecord $slot ) {
222 return !$slot->isInherited();
228 * Return all slots that are not originate in the revision they belong to (that is,
229 * they are inherited from some other revision).
231 * @note This may cause the slot meta-data for the revision to be lazy-loaded.
233 * @return SlotRecord[]
235 public function getInheritedSlots(): array {
236 return array_filter(
237 $this->getSlots(),
238 static function ( SlotRecord $slot ) {
239 return $slot->isInherited();
245 * Return all primary slots (those that are not derived).
247 * @return SlotRecord[]
248 * @since 1.36
250 public function getPrimarySlots(): array {
251 return array_filter(
252 $this->getSlots(),
253 static function ( SlotRecord $slot ) {
254 return !$slot->isDerived();
260 * Checks whether the other RevisionSlots instance has the same content
261 * as this instance. Note that this does not mean that the slots have to be the same:
262 * they could for instance belong to different revisions.
264 * @param RevisionSlots $other
266 * @return bool
268 public function hasSameContent( RevisionSlots $other ): bool {
269 if ( $other === $this ) {
270 return true;
273 $aSlots = $this->getSlots();
274 $bSlots = $other->getSlots();
276 ksort( $aSlots );
277 ksort( $bSlots );
279 if ( array_keys( $aSlots ) !== array_keys( $bSlots ) ) {
280 return false;
283 foreach ( $aSlots as $role => $s ) {
284 $t = $bSlots[$role];
286 if ( !$s->hasSameContent( $t ) ) {
287 return false;
291 return true;
295 * Find roles for which the $other RevisionSlots object has different content
296 * as this RevisionSlots object, including any roles that are present in one
297 * but not the other.
299 * @param RevisionSlots $other
301 * @return string[] a list of slot roles that are different.
303 public function getRolesWithDifferentContent( RevisionSlots $other ): array {
304 if ( $other === $this ) {
305 return [];
308 $aSlots = $this->getSlots();
309 $bSlots = $other->getSlots();
311 ksort( $aSlots );
312 ksort( $bSlots );
314 $different = array_keys( array_merge(
315 array_diff_key( $aSlots, $bSlots ),
316 array_diff_key( $bSlots, $aSlots )
317 ) );
319 /** @var SlotRecord[] $common */
320 $common = array_intersect_key( $aSlots, $bSlots );
322 foreach ( $common as $role => $s ) {
323 $t = $bSlots[$role];
325 if ( !$s->hasSameContent( $t ) ) {
326 $different[] = $role;
330 return $different;