MediaWiki master
BlockRestrictionStore.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Block;
24
30use stdClass;
33
35
39 private $dbProvider;
40
44 private $wikiId;
45
50 private $readStage;
51
57 public function __construct(
58 IConnectionProvider $dbProvider,
59 $blockTargetMigrationStage,
60 $wikiId = WikiAwareEntity::LOCAL
61 ) {
62 $this->dbProvider = $dbProvider;
63 $this->wikiId = $wikiId;
64 $this->readStage = $blockTargetMigrationStage & SCHEMA_COMPAT_READ_MASK;
65 }
66
74 public function loadByBlockId( $blockId ) {
75 if ( $blockId === null || $blockId === [] ) {
76 return [];
77 }
78
79 $result = $this->dbProvider->getReplicaDatabase( $this->wikiId )
80 ->newSelectQueryBuilder()
81 ->select( [ 'ir_ipb_id', 'ir_type', 'ir_value', 'page_namespace', 'page_title' ] )
82 ->from( 'ipblocks_restrictions' )
83 ->leftJoin( 'page', null, [ 'ir_type' => PageRestriction::TYPE_ID, 'ir_value=page_id' ] )
84 ->where( [ 'ir_ipb_id' => $blockId ] )
85 ->caller( __METHOD__ )->fetchResultSet();
86
87 return $this->resultToRestrictions( $result );
88 }
89
97 public function insert( array $restrictions ) {
98 if ( !$restrictions ) {
99 return false;
100 }
101
102 $rows = [];
103 foreach ( $restrictions as $restriction ) {
104 $rows[] = $restriction->toRow();
105 }
106
107 $dbw = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
108
109 $dbw->newInsertQueryBuilder()
110 ->insertInto( 'ipblocks_restrictions' )
111 ->ignore()
112 ->rows( $rows )
113 ->caller( __METHOD__ )->execute();
114
115 return true;
116 }
117
126 public function update( array $restrictions ) {
127 $dbw = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
128
129 $dbw->startAtomic( __METHOD__ );
130
131 // Organize the restrictions by block ID.
132 $restrictionList = $this->restrictionsByBlockId( $restrictions );
133
134 // Load the existing restrictions and organize by block ID. Any block IDs
135 // that were passed into this function will be used to load all of the
136 // existing restrictions. This list might be the same, or may be completely
137 // different.
138 $existingList = [];
139 $blockIds = array_keys( $restrictionList );
140 if ( $blockIds ) {
141 $result = $dbw->newSelectQueryBuilder()
142 ->select( [ 'ir_ipb_id', 'ir_type', 'ir_value' ] )
143 ->forUpdate()
144 ->from( 'ipblocks_restrictions' )
145 ->where( [ 'ir_ipb_id' => $blockIds ] )
146 ->caller( __METHOD__ )->fetchResultSet();
147
148 $existingList = $this->restrictionsByBlockId(
149 $this->resultToRestrictions( $result )
150 );
151 }
152
153 $result = true;
154 // Perform the actions on a per block-ID basis.
155 foreach ( $restrictionList as $blockId => $blockRestrictions ) {
156 // Insert all of the restrictions first, ignoring ones that already exist.
157 $success = $this->insert( $blockRestrictions );
158
159 $result = $success && $result;
160
161 $restrictionsToRemove = $this->restrictionsToRemove(
162 $existingList[$blockId] ?? [],
163 $restrictions
164 );
165
166 if ( !$restrictionsToRemove ) {
167 continue;
168 }
169
170 $success = $this->delete( $restrictionsToRemove );
171
172 $result = $success && $result;
173 }
174
175 $dbw->endAtomic( __METHOD__ );
176
177 return $result;
178 }
179
188 public function updateByParentBlockId( $parentBlockId, array $restrictions ) {
189 $parentBlockId = (int)$parentBlockId;
190
191 $db = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
192
193 if ( $this->readStage === SCHEMA_COMPAT_READ_OLD ) {
194 $blockIds = $db->newSelectQueryBuilder()
195 ->select( [ 'ipb_id' ] )
196 ->forUpdate()
197 ->from( 'ipblocks' )
198 ->where( [ 'ipb_parent_block_id' => $parentBlockId ] )
199 ->caller( __METHOD__ )->fetchFieldValues();
200 } else {
201 $blockIds = $db->newSelectQueryBuilder()
202 ->select( 'bl_id' )
203 ->forUpdate()
204 ->from( 'block' )
205 ->where( [ 'bl_parent_block_id' => $parentBlockId ] )
206 ->caller( __METHOD__ )->fetchFieldValues();
207 }
208 if ( !$blockIds ) {
209 return true;
210 }
211
212 // If removing all of the restrictions, then just delete them all.
213 if ( !$restrictions ) {
214 $blockIds = array_map( 'intval', $blockIds );
215 return $this->deleteByBlockId( $blockIds );
216 }
217
218 $db->startAtomic( __METHOD__ );
219
220 $result = true;
221 foreach ( $blockIds as $id ) {
222 $success = $this->update( $this->setBlockId( $id, $restrictions ) );
223 $result = $success && $result;
224 }
225
226 $db->endAtomic( __METHOD__ );
227
228 return $result;
229 }
230
238 public function delete( array $restrictions ) {
239 $dbw = $this->dbProvider->getPrimaryDatabase( $this->wikiId );
240 foreach ( $restrictions as $restriction ) {
241 $dbw->newDeleteQueryBuilder()
242 ->deleteFrom( 'ipblocks_restrictions' )
243 // The restriction row is made up of a compound primary key. Therefore,
244 // the row and the delete conditions are the same.
245 ->where( $restriction->toRow() )
246 ->caller( __METHOD__ )->execute();
247 }
248
249 return true;
250 }
251
259 public function deleteByBlockId( $blockId ) {
260 $this->dbProvider->getPrimaryDatabase( $this->wikiId )
261 ->newDeleteQueryBuilder()
262 ->deleteFrom( 'ipblocks_restrictions' )
263 ->where( [ 'ir_ipb_id' => $blockId ] )
264 ->caller( __METHOD__ )->execute();
265 return true;
266 }
267
278 public function equals( array $a, array $b ) {
279 $aCount = count( $a );
280 $bCount = count( $b );
281
282 // If the count is different, then they are obviously a different set.
283 if ( $aCount !== $bCount ) {
284 return false;
285 }
286
287 // If both sets contain no items, then they are the same set.
288 if ( $aCount === 0 && $bCount === 0 ) {
289 return true;
290 }
291
292 $hasher = static function ( Restriction $r ) {
293 return $r->getHash();
294 };
295
296 $aHashes = array_map( $hasher, $a );
297 $bHashes = array_map( $hasher, $b );
298
299 sort( $aHashes );
300 sort( $bHashes );
301
302 return $aHashes === $bHashes;
303 }
304
313 public function setBlockId( $blockId, array $restrictions ) {
314 $blockRestrictions = [];
315
316 foreach ( $restrictions as $restriction ) {
317 // Clone the restriction so any references to the current restriction are
318 // not suddenly changed to a different blockId.
319 $restriction = clone $restriction;
320 $restriction->setBlockId( $blockId );
321
322 $blockRestrictions[] = $restriction;
323 }
324
325 return $blockRestrictions;
326 }
327
336 private function restrictionsToRemove( array $existing, array $new ) {
337 $restrictionsByHash = [];
338 foreach ( $existing as $restriction ) {
339 $restrictionsByHash[$restriction->getHash()] = $restriction;
340 }
341 foreach ( $new as $restriction ) {
342 unset( $restrictionsByHash[$restriction->getHash()] );
343 }
344 return array_values( $restrictionsByHash );
345 }
346
354 private function restrictionsByBlockId( array $restrictions ) {
355 $blockRestrictions = [];
356
357 foreach ( $restrictions as $restriction ) {
358 $blockRestrictions[$restriction->getBlockId()][] = $restriction;
359 }
360
361 return $blockRestrictions;
362 }
363
370 private function resultToRestrictions( IResultWrapper $result ) {
371 $restrictions = [];
372 foreach ( $result as $row ) {
373 $restriction = $this->rowToRestriction( $row );
374
375 if ( !$restriction ) {
376 continue;
377 }
378
379 $restrictions[] = $restriction;
380 }
381
382 return $restrictions;
383 }
384
391 private function rowToRestriction( stdClass $row ) {
392 switch ( (int)$row->ir_type ) {
393 case PageRestriction::TYPE_ID:
394 return PageRestriction::newFromRow( $row );
395 case NamespaceRestriction::TYPE_ID:
396 return NamespaceRestriction::newFromRow( $row );
397 case ActionRestriction::TYPE_ID:
398 return ActionRestriction::newFromRow( $row );
399 default:
400 return null;
401 }
402 }
403}
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:273
const SCHEMA_COMPAT_READ_MASK
Definition Defines.php:279
updateByParentBlockId( $parentBlockId, array $restrictions)
Updates the list of restrictions by parent ID.
__construct(IConnectionProvider $dbProvider, $blockTargetMigrationStage, $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.