MediaWiki  master
BlockRestrictionStore.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\Block;
24 
28 use MWException;
29 use stdClass;
33 
35 
39  private $types = [
40  PageRestriction::TYPE_ID => PageRestriction::class,
41  NamespaceRestriction::TYPE_ID => NamespaceRestriction::class,
42  ];
43 
47  private $loadBalancer;
48 
53  $this->loadBalancer = $loadBalancer;
54  }
55 
64  public function loadByBlockId( $blockId, IDatabase $db = null ) {
65  if ( $blockId === null || $blockId === [] ) {
66  return [];
67  }
68 
69  $db = $db ?: $this->loadBalancer->getConnectionRef( DB_REPLICA );
70 
71  $result = $db->select(
72  [ 'ipblocks_restrictions', 'page' ],
73  [ 'ir_ipb_id', 'ir_type', 'ir_value', 'page_namespace', 'page_title' ],
74  [ 'ir_ipb_id' => $blockId ],
75  __METHOD__,
76  [],
77  [ 'page' => [ 'LEFT JOIN', [ 'ir_type' => PageRestriction::TYPE_ID, 'ir_value=page_id' ] ] ]
78  );
79 
80  return $this->resultToRestrictions( $result );
81  }
82 
90  public function insert( array $restrictions ) {
91  if ( !$restrictions ) {
92  return false;
93  }
94 
95  $rows = [];
96  foreach ( $restrictions as $restriction ) {
97  if ( !$restriction instanceof Restriction ) {
98  continue;
99  }
100  $rows[] = $restriction->toRow();
101  }
102 
103  if ( !$rows ) {
104  return false;
105  }
106 
107  $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
108 
109  $dbw->insert(
110  'ipblocks_restrictions',
111  $rows,
112  __METHOD__,
113  [ 'IGNORE' ]
114  );
115 
116  return true;
117  }
118 
127  public function update( array $restrictions ) {
128  $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
129 
130  $dbw->startAtomic( __METHOD__ );
131 
132  // Organize the restrictions by blockid.
133  $restrictionList = $this->restrictionsByBlockId( $restrictions );
134 
135  // Load the existing restrictions and organize by block id. Any block ids
136  // that were passed into this function will be used to load all of the
137  // existing restrictions. This list might be the same, or may be completely
138  // different.
139  $existingList = [];
140  $blockIds = array_keys( $restrictionList );
141  if ( !empty( $blockIds ) ) {
142  $result = $dbw->select(
143  [ 'ipblocks_restrictions' ],
144  [ 'ir_ipb_id', 'ir_type', 'ir_value' ],
145  [ 'ir_ipb_id' => $blockIds ],
146  __METHOD__,
147  [ 'FOR UPDATE' ]
148  );
149 
150  $existingList = $this->restrictionsByBlockId(
151  $this->resultToRestrictions( $result )
152  );
153  }
154 
155  $result = true;
156  // Perform the actions on a per block-id basis.
157  foreach ( $restrictionList as $blockId => $blockRestrictions ) {
158  // Insert all of the restrictions first, ignoring ones that already exist.
159  $success = $this->insert( $blockRestrictions );
160 
161  // Update the result. The first false is the result, otherwise, true.
162  $result = $success && $result;
163 
164  $restrictionsToRemove = $this->restrictionsToRemove(
165  $existingList[$blockId] ?? [],
166  $restrictions
167  );
168 
169  if ( empty( $restrictionsToRemove ) ) {
170  continue;
171  }
172 
173  $success = $this->delete( $restrictionsToRemove );
174 
175  // Update the result. The first false is the result, otherwise, true.
176  $result = $success && $result;
177  }
178 
179  $dbw->endAtomic( __METHOD__ );
180 
181  return $result;
182  }
183 
192  public function updateByParentBlockId( $parentBlockId, array $restrictions ) {
193  // If removing all of the restrictions, then just delete them all.
194  if ( empty( $restrictions ) ) {
195  return $this->deleteByParentBlockId( $parentBlockId );
196  }
197 
198  $parentBlockId = (int)$parentBlockId;
199 
200  $db = $this->loadBalancer->getConnectionRef( DB_MASTER );
201 
202  $db->startAtomic( __METHOD__ );
203 
204  $blockIds = $db->selectFieldValues(
205  'ipblocks',
206  'ipb_id',
207  [ 'ipb_parent_block_id' => $parentBlockId ],
208  __METHOD__,
209  [ 'FOR UPDATE' ]
210  );
211 
212  $result = true;
213  foreach ( $blockIds as $id ) {
214  $success = $this->update( $this->setBlockId( $id, $restrictions ) );
215  // Update the result. The first false is the result, otherwise, true.
216  $result = $success && $result;
217  }
218 
219  $db->endAtomic( __METHOD__ );
220 
221  return $result;
222  }
223 
232  public function delete( array $restrictions ) {
233  $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
234  $result = true;
235  foreach ( $restrictions as $restriction ) {
236  if ( !$restriction instanceof Restriction ) {
237  continue;
238  }
239 
240  $success = $dbw->delete(
241  'ipblocks_restrictions',
242  // The restriction row is made up of a compound primary key. Therefore,
243  // the row and the delete conditions are the same.
244  $restriction->toRow(),
245  __METHOD__
246  );
247  // Update the result. The first false is the result, otherwise, true.
248  $result = $success && $result;
249  }
250 
251  return $result;
252  }
253 
262  public function deleteByBlockId( $blockId ) {
263  $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
264  return $dbw->delete(
265  'ipblocks_restrictions',
266  [ 'ir_ipb_id' => $blockId ],
267  __METHOD__
268  );
269  }
270 
279  public function deleteByParentBlockId( $parentBlockId ) {
280  $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
281  return $dbw->deleteJoin(
282  'ipblocks_restrictions',
283  'ipblocks',
284  'ir_ipb_id',
285  'ipb_id',
286  [ 'ipb_parent_block_id' => $parentBlockId ],
287  __METHOD__
288  );
289  }
290 
301  public function equals( array $a, array $b ) {
302  $filter = function ( $restriction ) {
303  return $restriction instanceof Restriction;
304  };
305 
306  // Ensure that every item in the array is a Restriction. This prevents a
307  // fatal error from calling Restriction::getHash if something in the array
308  // is not a restriction.
309  $a = array_filter( $a, $filter );
310  $b = array_filter( $b, $filter );
311 
312  $aCount = count( $a );
313  $bCount = count( $b );
314 
315  // If the count is different, then they are obviously a different set.
316  if ( $aCount !== $bCount ) {
317  return false;
318  }
319 
320  // If both sets contain no items, then they are the same set.
321  if ( $aCount === 0 && $bCount === 0 ) {
322  return true;
323  }
324 
325  $hasher = function ( $r ) {
326  return $r->getHash();
327  };
328 
329  $aHashes = array_map( $hasher, $a );
330  $bHashes = array_map( $hasher, $b );
331 
332  sort( $aHashes );
333  sort( $bHashes );
334 
335  return $aHashes === $bHashes;
336  }
337 
346  public function setBlockId( $blockId, array $restrictions ) {
347  $blockRestrictions = [];
348 
349  foreach ( $restrictions as $restriction ) {
350  if ( !$restriction instanceof Restriction ) {
351  continue;
352  }
353 
354  // Clone the restriction so any references to the current restriction are
355  // not suddenly changed to a different blockId.
356  $restriction = clone $restriction;
357  $restriction->setBlockId( $blockId );
358 
359  $blockRestrictions[] = $restriction;
360  }
361 
362  return $blockRestrictions;
363  }
364 
373  private function restrictionsToRemove( array $existing, array $new ) {
374  return array_filter( $existing, function ( $e ) use ( $new ) {
375  foreach ( $new as $restriction ) {
376  if ( !$restriction instanceof Restriction ) {
377  continue;
378  }
379 
380  if ( $restriction->equals( $e ) ) {
381  return false;
382  }
383  }
384 
385  return true;
386  } );
387  }
388 
396  private function restrictionsByBlockId( array $restrictions ) {
397  $blockRestrictions = [];
398 
399  foreach ( $restrictions as $restriction ) {
400  // Ensure that all of the items in the array are restrictions.
401  if ( !$restriction instanceof Restriction ) {
402  continue;
403  }
404 
405  if ( !isset( $blockRestrictions[$restriction->getBlockId()] ) ) {
406  $blockRestrictions[$restriction->getBlockId()] = [];
407  }
408 
409  $blockRestrictions[$restriction->getBlockId()][] = $restriction;
410  }
411 
412  return $blockRestrictions;
413  }
414 
421  private function resultToRestrictions( IResultWrapper $result ) {
422  $restrictions = [];
423  foreach ( $result as $row ) {
424  $restriction = $this->rowToRestriction( $row );
425 
426  if ( !$restriction ) {
427  continue;
428  }
429 
430  $restrictions[] = $restriction;
431  }
432 
433  return $restrictions;
434  }
435 
442  private function rowToRestriction( stdClass $row ) {
443  if ( array_key_exists( (int)$row->ir_type, $this->types ) ) {
444  $class = $this->types[ (int)$row->ir_type ];
445  return call_user_func( [ $class, 'newFromRow' ], $row );
446  }
447 
448  return null;
449  }
450 }
resultToRestrictions(IResultWrapper $result)
Convert an Result Wrapper to an array of restrictions.
$success
rowToRestriction(stdClass $row)
Convert a result row from the database into a restriction object.
const DB_MASTER
Definition: defines.php:26
insert(array $restrictions)
Inserts the restrictions into the database.
updateByParentBlockId( $parentBlockId, array $restrictions)
Updates the list of restrictions by parent id.
deleteByParentBlockId( $parentBlockId)
Delete the restrictions by parent block ID.
loadByBlockId( $blockId, IDatabase $db=null)
Retrieves the restrictions from the database by block id.
Result wrapper for grabbing data queried from an IDatabase object.
setBlockId( $blockId, array $restrictions)
Set the blockId on a set of restrictions and return a new set.
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...
$filter
Database cluster connection, tracking, load balancing, and transaction manager interface.
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
$types
Map of all of the restriction types.
__construct(ILoadBalancer $loadBalancer)
update(array $restrictions)
Updates the list of restrictions.
const DB_REPLICA
Definition: defines.php:25
equals(array $a, array $b)
Checks if two arrays of Restrictions are effectively equal.
deleteByBlockId( $blockId)
Delete the restrictions by block ID.