MediaWiki master
BlockRestrictionStore.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Block;
10
16use stdClass;
19
21
22 private IConnectionProvider $dbProvider;
23
24 private string|false $wikiId;
25
26 public function __construct(
27 IConnectionProvider $dbProvider,
28 string|false $wikiId = WikiAwareEntity::LOCAL
29 ) {
30 $this->dbProvider = $dbProvider;
31 $this->wikiId = $wikiId;
32 }
33
41 public function loadByBlockId( $blockId ) {
42 if ( $blockId === null || $blockId === [] ) {
43 return [];
44 }
45
46 $result = $this->dbProvider->getReplicaDatabase( $this->wikiId )
47 ->newSelectQueryBuilder()
48 ->select( [ 'ir_ipb_id', 'ir_type', 'ir_value', 'page_namespace', 'page_title' ] )
49 ->from( 'ipblocks_restrictions' )
50 ->leftJoin( 'page', null, [ 'ir_type' => PageRestriction::TYPE_ID, 'ir_value=page_id' ] )
51 ->where( [ 'ir_ipb_id' => $blockId ] )
52 ->caller( __METHOD__ )->fetchResultSet();
53
54 return $this->resultToRestrictions( $result );
55 }
56
64 public function insert( array $restrictions ) {
65 if ( !$restrictions ) {
66 return false;
67 }
68
69 $rows = [];
70 foreach ( $restrictions as $restriction ) {
71 $rows[] = $restriction->toRow();
72 }
73
74 $dbw = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
75
76 $dbw->newInsertQueryBuilder()
77 ->insertInto( 'ipblocks_restrictions' )
78 ->ignore()
79 ->rows( $rows )
80 ->caller( __METHOD__ )->execute();
81
82 return true;
83 }
84
93 public function update( array $restrictions ) {
94 $dbw = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
95
96 $dbw->startAtomic( __METHOD__ );
97
98 // Organize the restrictions by block ID.
99 $restrictionList = $this->restrictionsByBlockId( $restrictions );
100
101 // Load the existing restrictions and organize by block ID. Any block IDs
102 // that were passed into this function will be used to load all of the
103 // existing restrictions. This list might be the same, or may be completely
104 // different.
105 $existingList = [];
106 $blockIds = array_keys( $restrictionList );
107 if ( $blockIds ) {
108 $result = $dbw->newSelectQueryBuilder()
109 ->select( [ 'ir_ipb_id', 'ir_type', 'ir_value' ] )
110 ->forUpdate()
111 ->from( 'ipblocks_restrictions' )
112 ->where( [ 'ir_ipb_id' => $blockIds ] )
113 ->caller( __METHOD__ )->fetchResultSet();
114
115 $existingList = $this->restrictionsByBlockId(
116 $this->resultToRestrictions( $result )
117 );
118 }
119
120 $result = true;
121 // Perform the actions on a per block-ID basis.
122 foreach ( $restrictionList as $blockId => $blockRestrictions ) {
123 // Insert all of the restrictions first, ignoring ones that already exist.
124 $success = $this->insert( $blockRestrictions );
125
126 $result = $success && $result;
127
128 $restrictionsToRemove = $this->restrictionsToRemove(
129 $existingList[$blockId] ?? [],
130 $restrictions
131 );
132
133 if ( !$restrictionsToRemove ) {
134 continue;
135 }
136
137 $success = $this->delete( $restrictionsToRemove );
138
139 $result = $success && $result;
140 }
141
142 $dbw->endAtomic( __METHOD__ );
143
144 return $result;
145 }
146
155 public function updateByParentBlockId( $parentBlockId, array $restrictions ) {
156 $parentBlockId = (int)$parentBlockId;
157
158 $db = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
159
160 $blockIds = $db->newSelectQueryBuilder()
161 ->select( 'bl_id' )
162 ->forUpdate()
163 ->from( 'block' )
164 ->where( [ 'bl_parent_block_id' => $parentBlockId ] )
165 ->caller( __METHOD__ )->fetchFieldValues();
166 if ( !$blockIds ) {
167 return true;
168 }
169
170 // If removing all of the restrictions, then just delete them all.
171 if ( !$restrictions ) {
172 $blockIds = array_map( 'intval', $blockIds );
173 return $this->deleteByBlockId( $blockIds );
174 }
175
176 $db->startAtomic( __METHOD__ );
177
178 $result = true;
179 foreach ( $blockIds as $id ) {
180 $success = $this->update( $this->setBlockId( $id, $restrictions ) );
181 $result = $success && $result;
182 }
183
184 $db->endAtomic( __METHOD__ );
185
186 return $result;
187 }
188
196 public function delete( array $restrictions ) {
197 $dbw = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
198 foreach ( $restrictions as $restriction ) {
199 $dbw->newDeleteQueryBuilder()
200 ->deleteFrom( 'ipblocks_restrictions' )
201 // The restriction row is made up of a compound primary key. Therefore,
202 // the row and the delete conditions are the same.
203 ->where( $restriction->toRow() )
204 ->caller( __METHOD__ )->execute();
205 }
206
207 return true;
208 }
209
217 public function deleteByBlockId( $blockId ) {
218 $this->dbProvider->getPrimaryDatabase( $this->wikiId )
219 ->newDeleteQueryBuilder()
220 ->deleteFrom( 'ipblocks_restrictions' )
221 ->where( [ 'ir_ipb_id' => $blockId ] )
222 ->caller( __METHOD__ )->execute();
223 return true;
224 }
225
236 public function equals( array $a, array $b ) {
237 $aCount = count( $a );
238 $bCount = count( $b );
239
240 // If the count is different, then they are obviously a different set.
241 if ( $aCount !== $bCount ) {
242 return false;
243 }
244
245 // If both sets contain no items, then they are the same set.
246 if ( $aCount === 0 && $bCount === 0 ) {
247 return true;
248 }
249
250 $hasher = static fn ( Restriction $r ) => $r->getHash();
251
252 $aHashes = array_map( $hasher, $a );
253 $bHashes = array_map( $hasher, $b );
254
255 sort( $aHashes );
256 sort( $bHashes );
257
258 return $aHashes === $bHashes;
259 }
260
269 public function setBlockId( $blockId, array $restrictions ) {
270 $blockRestrictions = [];
271
272 foreach ( $restrictions as $restriction ) {
273 // Clone the restriction so any references to the current restriction are
274 // not suddenly changed to a different blockId.
275 $restriction = clone $restriction;
276 $restriction->setBlockId( $blockId );
277
278 $blockRestrictions[] = $restriction;
279 }
280
281 return $blockRestrictions;
282 }
283
292 private function restrictionsToRemove( array $existing, array $new ) {
293 $restrictionsByHash = [];
294 foreach ( $existing as $restriction ) {
295 $restrictionsByHash[$restriction->getHash()] = $restriction;
296 }
297 foreach ( $new as $restriction ) {
298 unset( $restrictionsByHash[$restriction->getHash()] );
299 }
300 return array_values( $restrictionsByHash );
301 }
302
310 private function restrictionsByBlockId( array $restrictions ) {
311 $blockRestrictions = [];
312
313 foreach ( $restrictions as $restriction ) {
314 $blockRestrictions[$restriction->getBlockId()][] = $restriction;
315 }
316
317 return $blockRestrictions;
318 }
319
326 private function resultToRestrictions( IResultWrapper $result ) {
327 $restrictions = [];
328 foreach ( $result as $row ) {
329 $restriction = $this->rowToRestriction( $row );
330
331 if ( !$restriction ) {
332 continue;
333 }
334
335 $restrictions[] = $restriction;
336 }
337
338 return $restrictions;
339 }
340
347 private function rowToRestriction( stdClass $row ) {
348 switch ( (int)$row->ir_type ) {
349 case PageRestriction::TYPE_ID:
350 return PageRestriction::newFromRow( $row );
351 case NamespaceRestriction::TYPE_ID:
352 return NamespaceRestriction::newFromRow( $row );
353 case ActionRestriction::TYPE_ID:
354 return ActionRestriction::newFromRow( $row );
355 default:
356 return null;
357 }
358 }
359}
updateByParentBlockId( $parentBlockId, array $restrictions)
Updates the list of restrictions by parent ID.
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.
__construct(IConnectionProvider $dbProvider, string|false $wikiId=WikiAwareEntity::LOCAL)
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.
getHash()
Create a unique hash of the block restriction based on the type and value.
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.