Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
30.77% |
20 / 65 |
|
37.50% |
9 / 24 |
CRAP | |
0.00% |
0 / 1 |
SiteList | |
31.25% |
20 / 64 |
|
37.50% |
9 / 24 |
433.07 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
append | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
offsetSet | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
hasValidType | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getObjectType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getNewOffset | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
setElement | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
offsetUnset | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
getGlobalIdentifiers | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasSite | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSite | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
removeSite | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isEmpty | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasInternalId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSiteByInternalId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
removeSiteByInternalId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
hasNavigationId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSiteByNavigationId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
removeSiteByNavigationId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setSite | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGroup | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
getSerialVersionId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
__serialize | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
__unserialize | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\Site; |
22 | |
23 | use ArrayObject; |
24 | use InvalidArgumentException; |
25 | |
26 | /** |
27 | * Array-like collection of Site objects. |
28 | * |
29 | * This uses ArrayObject to intercept additions and deletions for purposes |
30 | * such as additional indexing, and to enforce that values are restricted |
31 | * to Site objects only. |
32 | * |
33 | * @since 1.21 |
34 | * @ingroup Site |
35 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
36 | */ |
37 | class SiteList extends ArrayObject { |
38 | /** |
39 | * @see SiteList::getNewOffset() |
40 | * @since 1.20 |
41 | * @var int |
42 | */ |
43 | protected $indexOffset = 0; |
44 | |
45 | /** |
46 | * Internal site identifiers pointing to their sites offset value. |
47 | * |
48 | * @since 1.21 |
49 | * |
50 | * @var array Maps int identifiers to local ArrayObject keys |
51 | */ |
52 | protected $byInternalId = []; |
53 | |
54 | /** |
55 | * Global site identifiers pointing to their sites offset value. |
56 | * |
57 | * @since 1.21 |
58 | * |
59 | * @var array Maps string identifiers to local ArrayObject keys |
60 | */ |
61 | protected $byGlobalId = []; |
62 | |
63 | /** |
64 | * Navigational site identifiers alias inter-language prefixes |
65 | * pointing to their sites offset value. |
66 | * |
67 | * @since 1.23 |
68 | * |
69 | * @var array Maps string identifiers to local ArrayObject keys |
70 | */ |
71 | protected $byNavigationId = []; |
72 | |
73 | /** |
74 | * @see Overrides ArrayObject::__construct https://www.php.net/manual/en/arrayobject.construct.php |
75 | * @since 1.20 |
76 | * @param null|array $input |
77 | * @param int $flags |
78 | * @param string $iterator_class |
79 | */ |
80 | public function __construct( $input = null, $flags = 0, $iterator_class = 'ArrayIterator' ) { |
81 | parent::__construct( [], $flags, $iterator_class ); |
82 | |
83 | if ( $input !== null ) { |
84 | foreach ( $input as $offset => $value ) { |
85 | $this->offsetSet( $offset, $value ); |
86 | } |
87 | } |
88 | } |
89 | |
90 | /** |
91 | * @see Overrides ArrayObject::append |
92 | * @since 1.20 |
93 | * @param mixed $value |
94 | */ |
95 | public function append( $value ): void { |
96 | $this->setElement( null, $value ); |
97 | } |
98 | |
99 | /** |
100 | * @since 1.20 |
101 | * @see Overrides ArrayObject::offsetSet() |
102 | * @param mixed $index |
103 | * @param mixed $value |
104 | */ |
105 | public function offsetSet( $index, $value ): void { |
106 | $this->setElement( $index, $value ); |
107 | } |
108 | |
109 | /** |
110 | * Returns if the provided value has the same type as the elements |
111 | * that can be added to this ArrayObject. |
112 | * |
113 | * @since 1.20 |
114 | * @param mixed $value |
115 | * @return bool |
116 | */ |
117 | protected function hasValidType( $value ) { |
118 | $class = $this->getObjectType(); |
119 | return $value instanceof $class; |
120 | } |
121 | |
122 | /** |
123 | * The class or interface type that array elements must match. |
124 | * |
125 | * @since 1.21 |
126 | * @return string |
127 | */ |
128 | public function getObjectType() { |
129 | return Site::class; |
130 | } |
131 | |
132 | /** |
133 | * Find a new offset for when appending an element. |
134 | * |
135 | * @since 1.20 |
136 | * @return int |
137 | */ |
138 | protected function getNewOffset() { |
139 | while ( $this->offsetExists( $this->indexOffset ) ) { |
140 | $this->indexOffset++; |
141 | } |
142 | |
143 | return $this->indexOffset; |
144 | } |
145 | |
146 | /** |
147 | * Actually set the element and enforce type checking and offset resolving. |
148 | * |
149 | * @since 1.20 |
150 | * @param int|string|null $index |
151 | * @param Site $site |
152 | */ |
153 | protected function setElement( $index, $site ) { |
154 | if ( !$this->hasValidType( $site ) ) { |
155 | throw new InvalidArgumentException( |
156 | 'Can only add ' . $this->getObjectType() . ' implementing objects to ' . static::class . '.' |
157 | ); |
158 | } |
159 | |
160 | $index ??= $this->getNewOffset(); |
161 | |
162 | if ( $this->hasSite( $site->getGlobalId() ) ) { |
163 | $this->removeSite( $site->getGlobalId() ); |
164 | } |
165 | |
166 | $this->byGlobalId[$site->getGlobalId()] = $index; |
167 | $this->byInternalId[$site->getInternalId()] = $index; |
168 | |
169 | $ids = $site->getNavigationIds(); |
170 | foreach ( $ids as $navId ) { |
171 | $this->byNavigationId[$navId] = $index; |
172 | } |
173 | |
174 | parent::offsetSet( $index, $site ); |
175 | } |
176 | |
177 | /** |
178 | * @see ArrayObject::offsetUnset() |
179 | * |
180 | * @since 1.21 |
181 | * |
182 | * @param mixed $index |
183 | */ |
184 | public function offsetUnset( $index ): void { |
185 | if ( $this->offsetExists( $index ) ) { |
186 | /** |
187 | * @var Site $site |
188 | */ |
189 | $site = $this->offsetGet( $index ); |
190 | |
191 | unset( $this->byGlobalId[$site->getGlobalId()] ); |
192 | unset( $this->byInternalId[$site->getInternalId()] ); |
193 | |
194 | $ids = $site->getNavigationIds(); |
195 | foreach ( $ids as $navId ) { |
196 | unset( $this->byNavigationId[$navId] ); |
197 | } |
198 | } |
199 | |
200 | parent::offsetUnset( $index ); |
201 | } |
202 | |
203 | /** |
204 | * Returns all the global site identifiers. |
205 | * Optionally only those belonging to the specified group. |
206 | * |
207 | * @since 1.21 |
208 | * |
209 | * @return array |
210 | */ |
211 | public function getGlobalIdentifiers() { |
212 | return array_keys( $this->byGlobalId ); |
213 | } |
214 | |
215 | /** |
216 | * Returns if the list contains the site with the provided global site identifier. |
217 | * |
218 | * @param string $globalSiteId |
219 | * |
220 | * @return bool |
221 | */ |
222 | public function hasSite( $globalSiteId ) { |
223 | return array_key_exists( $globalSiteId, $this->byGlobalId ); |
224 | } |
225 | |
226 | /** |
227 | * Returns the Site with the provided global site identifier. |
228 | * The site needs to exist, so if not sure, call hasGlobalId first. |
229 | * |
230 | * @since 1.21 |
231 | * |
232 | * @param string $globalSiteId |
233 | * |
234 | * @return Site |
235 | */ |
236 | public function getSite( $globalSiteId ) { |
237 | return $this->offsetGet( $this->byGlobalId[$globalSiteId] ); |
238 | } |
239 | |
240 | /** |
241 | * Removes the site with the specified global site identifier. |
242 | * The site needs to exist, so if not sure, call hasGlobalId first. |
243 | * |
244 | * @since 1.21 |
245 | * |
246 | * @param string $globalSiteId |
247 | */ |
248 | public function removeSite( $globalSiteId ) { |
249 | $this->offsetUnset( $this->byGlobalId[$globalSiteId] ); |
250 | } |
251 | |
252 | /** |
253 | * Whether the list contains no sites. |
254 | * |
255 | * @since 1.21 |
256 | * @return bool |
257 | */ |
258 | public function isEmpty() { |
259 | return $this->byGlobalId === []; |
260 | } |
261 | |
262 | /** |
263 | * Returns if the list contains the site with the provided site id. |
264 | * |
265 | * @param int $id |
266 | * |
267 | * @return bool |
268 | */ |
269 | public function hasInternalId( $id ) { |
270 | return array_key_exists( $id, $this->byInternalId ); |
271 | } |
272 | |
273 | /** |
274 | * Returns the Site with the provided site id. |
275 | * The site needs to exist, so if not sure, call has first. |
276 | * |
277 | * @since 1.21 |
278 | * |
279 | * @param int $id |
280 | * |
281 | * @return Site |
282 | */ |
283 | public function getSiteByInternalId( $id ) { |
284 | return $this->offsetGet( $this->byInternalId[$id] ); |
285 | } |
286 | |
287 | /** |
288 | * Removes the site with the specified site id. |
289 | * The site needs to exist, so if not sure, call has first. |
290 | * |
291 | * @since 1.21 |
292 | * |
293 | * @param int $id |
294 | */ |
295 | public function removeSiteByInternalId( $id ) { |
296 | $this->offsetUnset( $this->byInternalId[$id] ); |
297 | } |
298 | |
299 | /** |
300 | * Returns if the list contains the site with the provided navigational site id. |
301 | * |
302 | * @param string $id |
303 | * |
304 | * @return bool |
305 | */ |
306 | public function hasNavigationId( $id ) { |
307 | return array_key_exists( $id, $this->byNavigationId ); |
308 | } |
309 | |
310 | /** |
311 | * Returns the Site with the provided navigational site id. |
312 | * The site needs to exist, so if not sure, call has first. |
313 | * |
314 | * @since 1.23 |
315 | * |
316 | * @param string $id |
317 | * |
318 | * @return Site |
319 | */ |
320 | public function getSiteByNavigationId( $id ) { |
321 | return $this->offsetGet( $this->byNavigationId[$id] ); |
322 | } |
323 | |
324 | /** |
325 | * Removes the site with the specified navigational site id. |
326 | * The site needs to exist, so if not sure, call has first. |
327 | * |
328 | * @since 1.23 |
329 | * |
330 | * @param string $id |
331 | */ |
332 | public function removeSiteByNavigationId( $id ) { |
333 | $this->offsetUnset( $this->byNavigationId[$id] ); |
334 | } |
335 | |
336 | /** |
337 | * Sets a site in the list. If the site was not there, |
338 | * it will be added. If it was, it will be updated. |
339 | * |
340 | * @since 1.21 |
341 | * |
342 | * @param Site $site |
343 | */ |
344 | public function setSite( Site $site ) { |
345 | $this[] = $site; |
346 | } |
347 | |
348 | /** |
349 | * Returns the sites that are in the provided group. |
350 | * |
351 | * @since 1.21 |
352 | * |
353 | * @param string $groupName |
354 | * |
355 | * @return SiteList |
356 | */ |
357 | public function getGroup( $groupName ) { |
358 | $group = new self(); |
359 | |
360 | /** |
361 | * @var Site $site |
362 | */ |
363 | foreach ( $this as $site ) { |
364 | if ( $site->getGroup() === $groupName ) { |
365 | $group[] = $site; |
366 | } |
367 | } |
368 | |
369 | return $group; |
370 | } |
371 | |
372 | /** |
373 | * A version ID that identifies the serialization structure used by __serialize() |
374 | * and unserialize(). This is useful for constructing cache keys in cases where the cache relies |
375 | * on serialization for storing the SiteList. |
376 | * |
377 | * @var string A string uniquely identifying the version of the serialization structure, |
378 | * not including any sub-structures. |
379 | */ |
380 | private const SERIAL_VERSION_ID = '2014-03-17'; |
381 | |
382 | /** |
383 | * Returns the version ID that identifies the serialization structure used by |
384 | * __serialize() and unserialize(), including the structure of any nested structures. |
385 | * This is useful for constructing cache keys in cases where the cache relies |
386 | * on serialization for storing the SiteList. |
387 | * |
388 | * @return string A string uniquely identifying the version of the serialization structure, |
389 | * including any sub-structures. |
390 | */ |
391 | public static function getSerialVersionId() { |
392 | return self::SERIAL_VERSION_ID . '+Site:' . Site::SERIAL_VERSION_ID; |
393 | } |
394 | |
395 | /** |
396 | * @see Overrides Serializable::serialize |
397 | * @since 1.38 |
398 | * @return array |
399 | */ |
400 | public function __serialize(): array { |
401 | // Data that should go into serialization calls. |
402 | // |
403 | // NOTE: When changing the structure, either implement unserialize() to handle the |
404 | // old structure too, or update SERIAL_VERSION_ID to kill any caches. |
405 | return [ |
406 | 'data' => $this->getArrayCopy(), |
407 | 'index' => $this->indexOffset, |
408 | 'internalIds' => $this->byInternalId, |
409 | 'globalIds' => $this->byGlobalId, |
410 | 'navigationIds' => $this->byNavigationId, |
411 | ]; |
412 | } |
413 | |
414 | /** |
415 | * @see Overrides Serializable::unserialize |
416 | * @since 1.38 |
417 | * @param array $serializationData |
418 | */ |
419 | public function __unserialize( $serializationData ): void { |
420 | foreach ( $serializationData['data'] as $offset => $value ) { |
421 | // Just set the element, bypassing checks and offset resolving, |
422 | // as these elements have already gone through this. |
423 | parent::offsetSet( $offset, $value ); |
424 | } |
425 | |
426 | $this->indexOffset = $serializationData['index']; |
427 | |
428 | $this->byInternalId = $serializationData['internalIds']; |
429 | $this->byGlobalId = $serializationData['globalIds']; |
430 | $this->byNavigationId = $serializationData['navigationIds']; |
431 | } |
432 | } |
433 | |
434 | /** @deprecated class alias since 1.42 */ |
435 | class_alias( SiteList::class, 'SiteList' ); |