build: Upgrade mediawiki-codesniffer from 26.0.0 to 28.0.0
[lhc/web/wiklou.git] / includes / Revision / RevisionSlots.php
1 <?php
2 /**
3 * Value object representing the set of slots belonging to a revision.
4 *
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.
9 *
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.
14 *
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
19 *
20 * @file
21 */
22
23 namespace MediaWiki\Revision;
24
25 use Content;
26 use LogicException;
27 use Wikimedia\Assert\Assert;
28
29 /**
30 * Value object representing the set of slots belonging to a revision.
31 *
32 * @since 1.31
33 * @since 1.32 Renamed from MediaWiki\Storage\RevisionSlots
34 */
35 class RevisionSlots {
36
37 /** @var SlotRecord[]|callable */
38 protected $slots;
39
40 /**
41 * @param SlotRecord[]|callable $slots SlotRecords,
42 * or a callback that returns such a structure.
43 */
44 public function __construct( $slots ) {
45 Assert::parameterType( 'array|callable', $slots, '$slots' );
46
47 if ( is_callable( $slots ) ) {
48 $this->slots = $slots;
49 } else {
50 $this->setSlotsInternal( $slots );
51 }
52 }
53
54 /**
55 * @param SlotRecord[] $slots
56 */
57 private function setSlotsInternal( array $slots ) {
58 Assert::parameterElementType( SlotRecord::class, $slots, '$slots' );
59
60 $this->slots = [];
61
62 // re-key the slot array
63 foreach ( $slots as $slot ) {
64 $role = $slot->getRole();
65 $this->slots[$role] = $slot;
66 }
67 }
68
69 /**
70 * Implemented to defy serialization.
71 *
72 * @throws LogicException always
73 */
74 public function __sleep() {
75 throw new LogicException( __CLASS__ . ' is not serializable.' );
76 }
77
78 /**
79 * Returns the Content of the given slot.
80 * Call getSlotNames() to get a list of available slots.
81 *
82 * Note that for mutable Content objects, each call to this method will return a
83 * fresh clone.
84 *
85 * @param string $role The role name of the desired slot
86 *
87 * @throws RevisionAccessException if the slot does not exist or slot data
88 * could not be lazy-loaded.
89 * @return Content
90 */
91 public function getContent( $role ) {
92 // Return a copy to be safe. Immutable content objects return $this from copy().
93 return $this->getSlot( $role )->getContent()->copy();
94 }
95
96 /**
97 * Returns the SlotRecord of the given slot.
98 * Call getSlotNames() to get a list of available slots.
99 *
100 * @param string $role The role name of the desired slot
101 *
102 * @throws RevisionAccessException if the slot does not exist or slot data
103 * could not be lazy-loaded.
104 * @return SlotRecord
105 */
106 public function getSlot( $role ) {
107 $slots = $this->getSlots();
108
109 if ( isset( $slots[$role] ) ) {
110 return $slots[$role];
111 } else {
112 throw new RevisionAccessException( 'No such slot: ' . $role );
113 }
114 }
115
116 /**
117 * Returns whether the given slot is set.
118 *
119 * @param string $role The role name of the desired slot
120 *
121 * @return bool
122 */
123 public function hasSlot( $role ) {
124 $slots = $this->getSlots();
125
126 return isset( $slots[$role] );
127 }
128
129 /**
130 * Returns the slot names (roles) of all slots present in this revision.
131 * getContent() will succeed only for the names returned by this method.
132 *
133 * @return string[]
134 */
135 public function getSlotRoles() {
136 $slots = $this->getSlots();
137 return array_keys( $slots );
138 }
139
140 /**
141 * Computes the total nominal size of the revision's slots, in bogo-bytes.
142 *
143 * @warning This is potentially expensive! It may cause all slot's content to be loaded
144 * and deserialized.
145 *
146 * @return int
147 */
148 public function computeSize() {
149 return array_reduce( $this->getSlots(), function ( $accu, SlotRecord $slot ) {
150 return $accu + $slot->getSize();
151 }, 0 );
152 }
153
154 /**
155 * Returns an associative array that maps role names to SlotRecords. Each SlotRecord
156 * represents the content meta-data of a slot, together they define the content of
157 * a revision.
158 *
159 * @note This may cause the content meta-data for the revision to be lazy-loaded.
160 *
161 * @return SlotRecord[] revision slot/content rows, keyed by slot role name.
162 */
163 public function getSlots() {
164 if ( is_callable( $this->slots ) ) {
165 $slots = call_user_func( $this->slots );
166
167 Assert::postcondition(
168 is_array( $slots ),
169 'Slots info callback should return an array of objects'
170 );
171
172 $this->setSlotsInternal( $slots );
173 }
174
175 return $this->slots;
176 }
177
178 /**
179 * Computes the combined hash of the revisions's slots.
180 *
181 * @note For backwards compatibility, the combined hash of a single slot
182 * is that slot's hash. For consistency, the combined hash of an empty set of slots
183 * is the hash of the empty string.
184 *
185 * @warning This is potentially expensive! It may cause all slot's content to be loaded
186 * and deserialized, then re-serialized and hashed.
187 *
188 * @return string
189 */
190 public function computeSha1() {
191 $slots = $this->getSlots();
192 ksort( $slots );
193
194 if ( empty( $slots ) ) {
195 return SlotRecord::base36Sha1( '' );
196 }
197
198 return array_reduce( $slots, function ( $accu, SlotRecord $slot ) {
199 return $accu === null
200 ? $slot->getSha1()
201 : SlotRecord::base36Sha1( $accu . $slot->getSha1() );
202 }, null );
203 }
204
205 /**
206 * Return all slots that belong to the revision they originate from (that is,
207 * they are not inherited from some other revision).
208 *
209 * @note This may cause the slot meta-data for the revision to be lazy-loaded.
210 *
211 * @return SlotRecord[]
212 */
213 public function getOriginalSlots() {
214 return array_filter(
215 $this->getSlots(),
216 function ( SlotRecord $slot ) {
217 return !$slot->isInherited();
218 }
219 );
220 }
221
222 /**
223 * Return all slots that are not not originate in the revision they belong to (that is,
224 * they are inherited from some other revision).
225 *
226 * @note This may cause the slot meta-data for the revision to be lazy-loaded.
227 *
228 * @return SlotRecord[]
229 */
230 public function getInheritedSlots() {
231 return array_filter(
232 $this->getSlots(),
233 function ( SlotRecord $slot ) {
234 return $slot->isInherited();
235 }
236 );
237 }
238
239 /**
240 * Checks whether the other RevisionSlots instance has the same content
241 * as this instance. Note that this does not mean that the slots have to be the same:
242 * they could for instance belong to different revisions.
243 *
244 * @param RevisionSlots $other
245 *
246 * @return bool
247 */
248 public function hasSameContent( RevisionSlots $other ) {
249 if ( $other === $this ) {
250 return true;
251 }
252
253 $aSlots = $this->getSlots();
254 $bSlots = $other->getSlots();
255
256 ksort( $aSlots );
257 ksort( $bSlots );
258
259 if ( array_keys( $aSlots ) !== array_keys( $bSlots ) ) {
260 return false;
261 }
262
263 foreach ( $aSlots as $role => $s ) {
264 $t = $bSlots[$role];
265
266 if ( !$s->hasSameContent( $t ) ) {
267 return false;
268 }
269 }
270
271 return true;
272 }
273
274 /**
275 * Find roles for which the $other RevisionSlots object has different content
276 * as this RevisionSlots object, including any roles that are present in one
277 * but not the other.
278 *
279 * @param RevisionSlots $other
280 *
281 * @return string[] a list of slot roles that are different.
282 */
283 public function getRolesWithDifferentContent( RevisionSlots $other ) {
284 if ( $other === $this ) {
285 return [];
286 }
287
288 $aSlots = $this->getSlots();
289 $bSlots = $other->getSlots();
290
291 ksort( $aSlots );
292 ksort( $bSlots );
293
294 $different = array_keys( array_merge(
295 array_diff_key( $aSlots, $bSlots ),
296 array_diff_key( $bSlots, $aSlots )
297 ) );
298
299 /** @var SlotRecord[] $common */
300 $common = array_intersect_key( $aSlots, $bSlots );
301
302 foreach ( $common as $role => $s ) {
303 $t = $bSlots[$role];
304
305 if ( !$s->hasSameContent( $t ) ) {
306 $different[] = $role;
307 }
308 }
309
310 return $different;
311 }
312
313 }
314
315 /**
316 * Retain the old class name for backwards compatibility.
317 * @deprecated since 1.32
318 */
319 class_alias( RevisionSlots::class, 'MediaWiki\Storage\RevisionSlots' );