MediaWiki  master
BlockRestrictionStore.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\Block;
24 
30 use MWException;
31 use 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(
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 }
$success
MediaWiki exception.
Definition: MWException.php:31
updateByParentBlockId( $parentBlockId, array $restrictions)
Updates the list of restrictions by parent id.
restrictionsToRemove(array $existing, array $new)
Get the restrictions that should be removed, which are existing restrictions that are not in the new ...
restrictionsByBlockId(array $restrictions)
Converts an array of restrictions to an associative array of restrictions where the keys are the bloc...
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)
resultToRestrictions(IResultWrapper $result)
Convert an Result Wrapper to an array of restrictions.
rowToRestriction(stdClass $row)
Convert a result row from the database into a restriction object.
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.
const TYPES_MAP
Map of all of the restriction types.
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.
Database cluster connection, tracking, load balancing, and transaction manager interface.
Result wrapper for grabbing data queried from an IDatabase object.
const DB_REPLICA
Definition: defines.php:25
const DB_PRIMARY
Definition: defines.php:27