MediaWiki REL1_39
BlockRestrictionStore.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Block;
24
30use MWException;
31use stdClass;
34
36
40 private const TYPES_MAP = [
41 PageRestriction::TYPE_ID => PageRestriction::class,
42 NamespaceRestriction::TYPE_ID => NamespaceRestriction::class,
43 ActionRestriction::TYPE_ID => ActionRestriction::class,
44 ];
45
49 private $loadBalancer;
50
54 private $wikiId;
55
60 public function __construct(
61 ILoadBalancer $loadBalancer,
62 $wikiId = WikiAwareEntity::LOCAL
63 ) {
64 $this->loadBalancer = $loadBalancer;
65 $this->wikiId = $wikiId;
66 }
67
75 public function loadByBlockId( $blockId ) {
76 if ( $blockId === null || $blockId === [] ) {
77 return [];
78 }
79
80 $db = $this->loadBalancer->getConnectionRef( DB_REPLICA, [], $this->wikiId );
81
82 $result = $db->select(
83 [ 'ipblocks_restrictions', 'page' ],
84 [ 'ir_ipb_id', 'ir_type', 'ir_value', 'page_namespace', 'page_title' ],
85 [ 'ir_ipb_id' => $blockId ],
86 __METHOD__,
87 [],
88 [ 'page' => [ 'LEFT JOIN', [ 'ir_type' => PageRestriction::TYPE_ID, 'ir_value=page_id' ] ] ]
89 );
90
91 return $this->resultToRestrictions( $result );
92 }
93
101 public function insert( array $restrictions ) {
102 if ( !$restrictions ) {
103 return false;
104 }
105
106 $rows = [];
107 foreach ( $restrictions as $restriction ) {
108 if ( !$restriction instanceof Restriction ) {
109 continue;
110 }
111 $rows[] = $restriction->toRow();
112 }
113
114 if ( !$rows ) {
115 return false;
116 }
117
118 $dbw = $this->loadBalancer->getConnectionRef( DB_PRIMARY, [], $this->wikiId );
119
120 $dbw->insert(
121 'ipblocks_restrictions',
122 $rows,
123 __METHOD__,
124 [ 'IGNORE' ]
125 );
126
127 return true;
128 }
129
138 public function update( array $restrictions ) {
139 $dbw = $this->loadBalancer->getConnectionRef( DB_PRIMARY, [], $this->wikiId );
140
141 $dbw->startAtomic( __METHOD__ );
142
143 // Organize the restrictions by blockid.
144 $restrictionList = $this->restrictionsByBlockId( $restrictions );
145
146 // Load the existing restrictions and organize by block id. Any block ids
147 // that were passed into this function will be used to load all of the
148 // existing restrictions. This list might be the same, or may be completely
149 // different.
150 $existingList = [];
151 $blockIds = array_keys( $restrictionList );
152 if ( !empty( $blockIds ) ) {
153 $result = $dbw->select(
154 [ 'ipblocks_restrictions' ],
155 [ 'ir_ipb_id', 'ir_type', 'ir_value' ],
156 [ 'ir_ipb_id' => $blockIds ],
157 __METHOD__,
158 [ 'FOR UPDATE' ]
159 );
160
161 $existingList = $this->restrictionsByBlockId(
162 $this->resultToRestrictions( $result )
163 );
164 }
165
166 $result = true;
167 // Perform the actions on a per block-id basis.
168 foreach ( $restrictionList as $blockId => $blockRestrictions ) {
169 // Insert all of the restrictions first, ignoring ones that already exist.
170 $success = $this->insert( $blockRestrictions );
171
172 // Update the result. The first false is the result, otherwise, true.
173 $result = $success && $result;
174
175 $restrictionsToRemove = $this->restrictionsToRemove(
176 $existingList[$blockId] ?? [],
177 $restrictions
178 );
179
180 if ( empty( $restrictionsToRemove ) ) {
181 continue;
182 }
183
184 $success = $this->delete( $restrictionsToRemove );
185
186 // Update the result. The first false is the result, otherwise, true.
187 $result = $success && $result;
188 }
189
190 $dbw->endAtomic( __METHOD__ );
191
192 return $result;
193 }
194
203 public function updateByParentBlockId( $parentBlockId, array $restrictions ) {
204 // If removing all of the restrictions, then just delete them all.
205 if ( empty( $restrictions ) ) {
206 return $this->deleteByParentBlockId( $parentBlockId );
207 }
208
209 $parentBlockId = (int)$parentBlockId;
210
211 $db = $this->loadBalancer->getConnectionRef( DB_PRIMARY, [], $this->wikiId );
212
213 $db->startAtomic( __METHOD__ );
214
215 $blockIds = $db->selectFieldValues(
216 'ipblocks',
217 'ipb_id',
218 [ 'ipb_parent_block_id' => $parentBlockId ],
219 __METHOD__,
220 [ 'FOR UPDATE' ]
221 );
222
223 $result = true;
224 foreach ( $blockIds as $id ) {
225 $success = $this->update( $this->setBlockId( $id, $restrictions ) );
226 // Update the result. The first false is the result, otherwise, true.
227 $result = $success && $result;
228 }
229
230 $db->endAtomic( __METHOD__ );
231
232 return $result;
233 }
234
243 public function delete( array $restrictions ) {
244 $dbw = $this->loadBalancer->getConnectionRef( DB_PRIMARY, [], $this->wikiId );
245 $result = true;
246 foreach ( $restrictions as $restriction ) {
247 if ( !$restriction instanceof Restriction ) {
248 continue;
249 }
250
251 $success = $dbw->delete(
252 'ipblocks_restrictions',
253 // The restriction row is made up of a compound primary key. Therefore,
254 // the row and the delete conditions are the same.
255 $restriction->toRow(),
256 __METHOD__
257 );
258 // Update the result. The first false is the result, otherwise, true.
259 $result = $success && $result;
260 }
261
262 return $result;
263 }
264
273 public function deleteByBlockId( $blockId ) {
274 $dbw = $this->loadBalancer->getConnectionRef( DB_PRIMARY, [], $this->wikiId );
275 return $dbw->delete(
276 'ipblocks_restrictions',
277 [ 'ir_ipb_id' => $blockId ],
278 __METHOD__
279 );
280 }
281
290 public function deleteByParentBlockId( $parentBlockId ) {
291 $dbw = $this->loadBalancer->getConnectionRef( DB_PRIMARY, [], $this->wikiId );
292 return $dbw->deleteJoin(
293 'ipblocks_restrictions',
294 'ipblocks',
295 'ir_ipb_id',
296 'ipb_id',
297 [ 'ipb_parent_block_id' => $parentBlockId ],
298 __METHOD__
299 );
300 }
301
312 public function equals( array $a, array $b ) {
313 $filter = static function ( $restriction ) {
314 return $restriction instanceof Restriction;
315 };
316
317 // Ensure that every item in the array is a Restriction. This prevents a
318 // fatal error from calling Restriction::getHash if something in the array
319 // is not a restriction.
320 $a = array_filter( $a, $filter );
321 $b = array_filter( $b, $filter );
322
323 $aCount = count( $a );
324 $bCount = count( $b );
325
326 // If the count is different, then they are obviously a different set.
327 if ( $aCount !== $bCount ) {
328 return false;
329 }
330
331 // If both sets contain no items, then they are the same set.
332 if ( $aCount === 0 && $bCount === 0 ) {
333 return true;
334 }
335
336 $hasher = static function ( $r ) {
337 return $r->getHash();
338 };
339
340 $aHashes = array_map( $hasher, $a );
341 $bHashes = array_map( $hasher, $b );
342
343 sort( $aHashes );
344 sort( $bHashes );
345
346 return $aHashes === $bHashes;
347 }
348
357 public function setBlockId( $blockId, array $restrictions ) {
358 $blockRestrictions = [];
359
360 foreach ( $restrictions as $restriction ) {
361 if ( !$restriction instanceof Restriction ) {
362 continue;
363 }
364
365 // Clone the restriction so any references to the current restriction are
366 // not suddenly changed to a different blockId.
367 $restriction = clone $restriction;
368 $restriction->setBlockId( $blockId );
369
370 $blockRestrictions[] = $restriction;
371 }
372
373 return $blockRestrictions;
374 }
375
384 private function restrictionsToRemove( array $existing, array $new ) {
385 return array_filter( $existing, static function ( $e ) use ( $new ) {
386 foreach ( $new as $restriction ) {
387 if ( !$restriction instanceof Restriction ) {
388 continue;
389 }
390
391 if ( $restriction->equals( $e ) ) {
392 return false;
393 }
394 }
395
396 return true;
397 } );
398 }
399
407 private function restrictionsByBlockId( array $restrictions ) {
408 $blockRestrictions = [];
409
410 foreach ( $restrictions as $restriction ) {
411 // Ensure that all of the items in the array are restrictions.
412 if ( !$restriction instanceof Restriction ) {
413 continue;
414 }
415
416 if ( !isset( $blockRestrictions[$restriction->getBlockId()] ) ) {
417 $blockRestrictions[$restriction->getBlockId()] = [];
418 }
419
420 $blockRestrictions[$restriction->getBlockId()][] = $restriction;
421 }
422
423 return $blockRestrictions;
424 }
425
432 private function resultToRestrictions( IResultWrapper $result ) {
433 $restrictions = [];
434 foreach ( $result as $row ) {
435 $restriction = $this->rowToRestriction( $row );
436
437 if ( !$restriction ) {
438 continue;
439 }
440
441 $restrictions[] = $restriction;
442 }
443
444 return $restrictions;
445 }
446
453 private function rowToRestriction( stdClass $row ) {
454 if ( array_key_exists( (int)$row->ir_type, self::TYPES_MAP ) ) {
455 $class = self::TYPES_MAP[ (int)$row->ir_type ];
456 return call_user_func( [ $class, 'newFromRow' ], $row );
457 }
458
459 return null;
460 }
461}
MediaWiki exception.
updateByParentBlockId( $parentBlockId, array $restrictions)
Updates the list of restrictions by parent id.
loadByBlockId( $blockId)
Retrieves the restrictions from the database by block id.
setBlockId( $blockId, array $restrictions)
Set the blockId on a set of restrictions and return a new set.
__construct(ILoadBalancer $loadBalancer, $wikiId=WikiAwareEntity::LOCAL)
deleteByParentBlockId( $parentBlockId)
Delete the restrictions by parent block ID.
insert(array $restrictions)
Inserts the restrictions into the database.
deleteByBlockId( $blockId)
Delete the restrictions by block ID.
equals(array $a, array $b)
Checks if two arrays of Restrictions are effectively equal.
update(array $restrictions)
Updates the list of restrictions.
Restriction for partial blocks of actions.
Marker interface for entities aware of the wiki they belong to.
Create and track the database connections and transactions for a given database cluster.
Result wrapper for grabbing data queried from an IDatabase object.
const DB_REPLICA
Definition defines.php:26
const DB_PRIMARY
Definition defines.php:28