MediaWiki master
BlockRestrictionStore.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Block;
24
30use stdClass;
33
35
36 private IConnectionProvider $dbProvider;
37
41 private $wikiId;
42
43 public function __construct(
44 IConnectionProvider $dbProvider,
45 /* string|false */ $wikiId = WikiAwareEntity::LOCAL
46 ) {
47 $this->dbProvider = $dbProvider;
48 $this->wikiId = $wikiId;
49 }
50
58 public function loadByBlockId( $blockId ) {
59 if ( $blockId === null || $blockId === [] ) {
60 return [];
61 }
62
63 $result = $this->dbProvider->getReplicaDatabase( $this->wikiId )
64 ->newSelectQueryBuilder()
65 ->select( [ 'ir_ipb_id', 'ir_type', 'ir_value', 'page_namespace', 'page_title' ] )
66 ->from( 'ipblocks_restrictions' )
67 ->leftJoin( 'page', null, [ 'ir_type' => PageRestriction::TYPE_ID, 'ir_value=page_id' ] )
68 ->where( [ 'ir_ipb_id' => $blockId ] )
69 ->caller( __METHOD__ )->fetchResultSet();
70
71 return $this->resultToRestrictions( $result );
72 }
73
81 public function insert( array $restrictions ) {
82 if ( !$restrictions ) {
83 return false;
84 }
85
86 $rows = [];
87 foreach ( $restrictions as $restriction ) {
88 $rows[] = $restriction->toRow();
89 }
90
91 $dbw = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
92
93 $dbw->newInsertQueryBuilder()
94 ->insertInto( 'ipblocks_restrictions' )
95 ->ignore()
96 ->rows( $rows )
97 ->caller( __METHOD__ )->execute();
98
99 return true;
100 }
101
110 public function update( array $restrictions ) {
111 $dbw = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
112
113 $dbw->startAtomic( __METHOD__ );
114
115 // Organize the restrictions by block ID.
116 $restrictionList = $this->restrictionsByBlockId( $restrictions );
117
118 // Load the existing restrictions and organize by block ID. Any block IDs
119 // that were passed into this function will be used to load all of the
120 // existing restrictions. This list might be the same, or may be completely
121 // different.
122 $existingList = [];
123 $blockIds = array_keys( $restrictionList );
124 if ( $blockIds ) {
125 $result = $dbw->newSelectQueryBuilder()
126 ->select( [ 'ir_ipb_id', 'ir_type', 'ir_value' ] )
127 ->forUpdate()
128 ->from( 'ipblocks_restrictions' )
129 ->where( [ 'ir_ipb_id' => $blockIds ] )
130 ->caller( __METHOD__ )->fetchResultSet();
131
132 $existingList = $this->restrictionsByBlockId(
133 $this->resultToRestrictions( $result )
134 );
135 }
136
137 $result = true;
138 // Perform the actions on a per block-ID basis.
139 foreach ( $restrictionList as $blockId => $blockRestrictions ) {
140 // Insert all of the restrictions first, ignoring ones that already exist.
141 $success = $this->insert( $blockRestrictions );
142
143 $result = $success && $result;
144
145 $restrictionsToRemove = $this->restrictionsToRemove(
146 $existingList[$blockId] ?? [],
147 $restrictions
148 );
149
150 if ( !$restrictionsToRemove ) {
151 continue;
152 }
153
154 $success = $this->delete( $restrictionsToRemove );
155
156 $result = $success && $result;
157 }
158
159 $dbw->endAtomic( __METHOD__ );
160
161 return $result;
162 }
163
172 public function updateByParentBlockId( $parentBlockId, array $restrictions ) {
173 $parentBlockId = (int)$parentBlockId;
174
175 $db = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
176
177 $blockIds = $db->newSelectQueryBuilder()
178 ->select( 'bl_id' )
179 ->forUpdate()
180 ->from( 'block' )
181 ->where( [ 'bl_parent_block_id' => $parentBlockId ] )
182 ->caller( __METHOD__ )->fetchFieldValues();
183 if ( !$blockIds ) {
184 return true;
185 }
186
187 // If removing all of the restrictions, then just delete them all.
188 if ( !$restrictions ) {
189 $blockIds = array_map( 'intval', $blockIds );
190 return $this->deleteByBlockId( $blockIds );
191 }
192
193 $db->startAtomic( __METHOD__ );
194
195 $result = true;
196 foreach ( $blockIds as $id ) {
197 $success = $this->update( $this->setBlockId( $id, $restrictions ) );
198 $result = $success && $result;
199 }
200
201 $db->endAtomic( __METHOD__ );
202
203 return $result;
204 }
205
213 public function delete( array $restrictions ) {
214 $dbw = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
215 foreach ( $restrictions as $restriction ) {
216 $dbw->newDeleteQueryBuilder()
217 ->deleteFrom( 'ipblocks_restrictions' )
218 // The restriction row is made up of a compound primary key. Therefore,
219 // the row and the delete conditions are the same.
220 ->where( $restriction->toRow() )
221 ->caller( __METHOD__ )->execute();
222 }
223
224 return true;
225 }
226
234 public function deleteByBlockId( $blockId ) {
235 $this->dbProvider->getPrimaryDatabase( $this->wikiId )
236 ->newDeleteQueryBuilder()
237 ->deleteFrom( 'ipblocks_restrictions' )
238 ->where( [ 'ir_ipb_id' => $blockId ] )
239 ->caller( __METHOD__ )->execute();
240 return true;
241 }
242
253 public function equals( array $a, array $b ) {
254 $aCount = count( $a );
255 $bCount = count( $b );
256
257 // If the count is different, then they are obviously a different set.
258 if ( $aCount !== $bCount ) {
259 return false;
260 }
261
262 // If both sets contain no items, then they are the same set.
263 if ( $aCount === 0 && $bCount === 0 ) {
264 return true;
265 }
266
267 $hasher = static function ( Restriction $r ) {
268 return $r->getHash();
269 };
270
271 $aHashes = array_map( $hasher, $a );
272 $bHashes = array_map( $hasher, $b );
273
274 sort( $aHashes );
275 sort( $bHashes );
276
277 return $aHashes === $bHashes;
278 }
279
288 public function setBlockId( $blockId, array $restrictions ) {
289 $blockRestrictions = [];
290
291 foreach ( $restrictions as $restriction ) {
292 // Clone the restriction so any references to the current restriction are
293 // not suddenly changed to a different blockId.
294 $restriction = clone $restriction;
295 $restriction->setBlockId( $blockId );
296
297 $blockRestrictions[] = $restriction;
298 }
299
300 return $blockRestrictions;
301 }
302
311 private function restrictionsToRemove( array $existing, array $new ) {
312 $restrictionsByHash = [];
313 foreach ( $existing as $restriction ) {
314 $restrictionsByHash[$restriction->getHash()] = $restriction;
315 }
316 foreach ( $new as $restriction ) {
317 unset( $restrictionsByHash[$restriction->getHash()] );
318 }
319 return array_values( $restrictionsByHash );
320 }
321
329 private function restrictionsByBlockId( array $restrictions ) {
330 $blockRestrictions = [];
331
332 foreach ( $restrictions as $restriction ) {
333 $blockRestrictions[$restriction->getBlockId()][] = $restriction;
334 }
335
336 return $blockRestrictions;
337 }
338
345 private function resultToRestrictions( IResultWrapper $result ) {
346 $restrictions = [];
347 foreach ( $result as $row ) {
348 $restriction = $this->rowToRestriction( $row );
349
350 if ( !$restriction ) {
351 continue;
352 }
353
354 $restrictions[] = $restriction;
355 }
356
357 return $restrictions;
358 }
359
366 private function rowToRestriction( stdClass $row ) {
367 switch ( (int)$row->ir_type ) {
368 case PageRestriction::TYPE_ID:
369 return PageRestriction::newFromRow( $row );
370 case NamespaceRestriction::TYPE_ID:
371 return NamespaceRestriction::newFromRow( $row );
372 case ActionRestriction::TYPE_ID:
373 return ActionRestriction::newFromRow( $row );
374 default:
375 return null;
376 }
377 }
378}
updateByParentBlockId( $parentBlockId, array $restrictions)
Updates the list of restrictions by parent ID.
__construct(IConnectionProvider $dbProvider, $wikiId=WikiAwareEntity::LOCAL)
loadByBlockId( $blockId)
Retrieve 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.
insert(array $restrictions)
Insert the restrictions into the database.
deleteByBlockId( $blockId)
Delete the restrictions by block ID.
equals(array $a, array $b)
Check if two arrays of Restrictions are effectively equal.
update(array $restrictions)
Update the list of restrictions.
Restriction for partial blocks of actions.
Marker interface for entities aware of the wiki they belong to.
Provide primary and replica IDatabase connections.
Result wrapper for grabbing data queried from an IDatabase object.