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(
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  $blockRestrictions[$restriction->getBlockId()][] = $restriction;
417  }
418 
419  return $blockRestrictions;
420  }
421 
428  private function resultToRestrictions( IResultWrapper $result ) {
429  $restrictions = [];
430  foreach ( $result as $row ) {
431  $restriction = $this->rowToRestriction( $row );
432 
433  if ( !$restriction ) {
434  continue;
435  }
436 
437  $restrictions[] = $restriction;
438  }
439 
440  return $restrictions;
441  }
442 
449  private function rowToRestriction( stdClass $row ) {
450  if ( array_key_exists( (int)$row->ir_type, self::TYPES_MAP ) ) {
451  $class = self::TYPES_MAP[ (int)$row->ir_type ];
452  return call_user_func( [ $class, 'newFromRow' ], $row );
453  }
454 
455  return null;
456  }
457 }
$success
MediaWiki exception.
Definition: MWException.php:30
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.
This class is a delegate to ILBFactory 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