MediaWiki master
ApiQueryBlocks.php
Go to the documentation of this file.
1<?php
31use Wikimedia\IPUtils;
35
42
43 private DatabaseBlockStore $blockStore;
44 private BlockActionInfo $blockActionInfo;
45 private BlockRestrictionStore $blockRestrictionStore;
46 private CommentStore $commentStore;
47 private HideUserUtils $hideUserUtils;
48
50 private $blockTargetReadStage;
51
61 public function __construct(
62 ApiQuery $query,
63 $moduleName,
64 DatabaseBlockStore $blockStore,
65 BlockActionInfo $blockActionInfo,
66 BlockRestrictionStore $blockRestrictionStore,
67 CommentStore $commentStore,
68 HideUserUtils $hideUserUtils
69 ) {
70 parent::__construct( $query, $moduleName, 'bk' );
71 $this->blockStore = $blockStore;
72 $this->blockActionInfo = $blockActionInfo;
73 $this->blockRestrictionStore = $blockRestrictionStore;
74 $this->commentStore = $commentStore;
75 $this->hideUserUtils = $hideUserUtils;
76 $this->blockTargetReadStage = $this->getConfig()
77 ->get( MainConfigNames::BlockTargetMigrationStage ) & SCHEMA_COMPAT_READ_MASK;
78 }
79
80 public function execute() {
81 $db = $this->getDB();
83 $this->requireMaxOneParameter( $params, 'users', 'ip' );
84
85 $prop = array_fill_keys( $params['prop'], true );
86 $fld_id = isset( $prop['id'] );
87 $fld_user = isset( $prop['user'] );
88 $fld_userid = isset( $prop['userid'] );
89 $fld_by = isset( $prop['by'] );
90 $fld_byid = isset( $prop['byid'] );
91 $fld_timestamp = isset( $prop['timestamp'] );
92 $fld_expiry = isset( $prop['expiry'] );
93 $fld_reason = isset( $prop['reason'] );
94 $fld_range = isset( $prop['range'] );
95 $fld_flags = isset( $prop['flags'] );
96 $fld_restrictions = isset( $prop['restrictions'] );
97
98 $result = $this->getResult();
99
100 if ( $this->blockTargetReadStage === SCHEMA_COMPAT_READ_OLD ) {
101 $this->addTables( 'ipblocks' );
102 $this->addFields( [ 'ipb_auto', 'ipb_id', 'ipb_timestamp', 'ipb_user' ] );
103 $this->addFieldsIf( [ 'ipb_address', 'ipb_user' ], $fld_user || $fld_userid );
104 $bt_range_start = 'ipb_range_start';
105 $bt_range_end = 'ipb_range_end';
106 $bt_user = 'ipb_user';
107 $bt_auto = 'ipb_auto';
108 $bt_address_or_user_name = 'ipb_address';
109 $bl_by_actor = 'ipb_by_actor';
110 $bl_expiry = 'ipb_expiry';
111 $bl_anon_only = 'ipb_anon_only';
112 $bl_create_account = 'ipb_create_account';
113 $bl_enable_autoblock = 'ipb_enable_autoblock';
114 $bl_block_email = 'ipb_block_email';
115 $bl_deleted = 'ipb_deleted';
116 $bl_allow_usertalk = 'ipb_allow_usertalk';
117 $bl_sitewide = 'ipb_sitewide';
118 $bl_reason = 'ipb_reason';
119 $bl_timestamp = 'ipb_timestamp';
120 $bl_id = 'ipb_id';
121 $blockTable = 'ipblocks';
122 } else {
123 $this->addTables( [ 'block', 'block_target', 'block_target_user' => 'user' ] );
124 $this->addJoinConds( [
125 'block_target' => [ 'JOIN', 'bt_id=bl_target' ],
126 'block_target_user' => [ 'LEFT JOIN', 'user_id=bt_user' ]
127 ] );
128 $this->addFields( [ 'bt_auto', 'bl_id', 'bl_timestamp' ] );
129 $this->addFieldsIf(
130 [
131 'bt_address',
132 'bt_user',
133 'bt_address_or_user_name' => 'COALESCE(bt_address, bt_user_text)'
134 ],
135 $fld_user || $fld_userid
136 );
137 $bt_range_start = 'bt_range_start';
138 $bt_range_end = 'bt_range_end';
139 $bt_user = 'bt_user';
140 $bt_auto = 'bt_auto';
141 $bt_address_or_user_name = 'bt_address_or_user_name';
142 $bl_by_actor = 'bl_by_actor';
143 $bl_expiry = 'bl_expiry';
144 $bl_anon_only = 'bl_anon_only';
145 $bl_create_account = 'bl_create_account';
146 $bl_enable_autoblock = 'bl_enable_autoblock';
147 $bl_block_email = 'bl_block_email';
148 $bl_deleted = 'bl_deleted';
149 $bl_allow_usertalk = 'bl_allow_usertalk';
150 $bl_sitewide = 'bl_sitewide';
151 $bl_reason = 'bl_reason';
152 $bl_timestamp = 'bl_timestamp';
153 $bl_id = 'bl_id';
154 $blockTable = 'block';
155 }
156
157 if ( $fld_by || $fld_byid ) {
158 $this->addTables( 'actor' );
159 $this->addFields( [ 'actor_user', 'actor_name' ] );
160 $this->addJoinConds( [ 'actor' => [ 'JOIN', "actor_id=$bl_by_actor" ] ] );
161 }
162 $this->addFieldsIf( $bl_expiry, $fld_expiry );
163 $this->addFieldsIf( [ $bt_range_start, $bt_range_end ], $fld_range );
164 $this->addFieldsIf( [ $bl_anon_only, $bl_create_account, $bl_enable_autoblock,
165 $bl_block_email, $bl_deleted, $bl_allow_usertalk, $bl_sitewide ],
166 $fld_flags );
167 $this->addFieldsIf( $bl_sitewide, $fld_restrictions );
168
169 if ( $fld_reason ) {
170 $commentQuery = $this->commentStore->getJoin( $bl_reason );
171 $this->addTables( $commentQuery['tables'] );
172 $this->addFields( $commentQuery['fields'] );
173 $this->addJoinConds( $commentQuery['joins'] );
174 }
175
176 $this->addOption( 'LIMIT', $params['limit'] + 1 );
178 $bl_timestamp,
179 $params['dir'],
180 $params['start'],
181 $params['end']
182 );
183 // Include in ORDER BY for uniqueness
184 $this->addWhereRange( $bl_id, $params['dir'], null, null );
185
186 if ( $params['continue'] !== null ) {
187 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'timestamp', 'int' ] );
188 $op = ( $params['dir'] == 'newer' ? '>=' : '<=' );
189 $this->addWhere( $db->buildComparison( $op, [
190 $bl_timestamp => $db->timestamp( $cont[0] ),
191 $bl_id => $cont[1],
192 ] ) );
193 }
194
195 if ( $params['ids'] ) {
196 $this->addWhereIDsFld( $blockTable, $bl_id, $params['ids'] );
197 }
198 if ( $params['users'] ) {
199 if ( $this->blockTargetReadStage === SCHEMA_COMPAT_READ_OLD ) {
200 $this->addWhereFld( 'ipb_address', $params['users'] );
201 } else {
202 $addresses = [];
203 $userNames = [];
204 foreach ( $params['users'] as $target ) {
205 if ( IPUtils::isValid( $target ) || IPUtils::isValidRange( $target ) ) {
206 $addresses[] = $target;
207 } else {
208 $userNames[] = $target;
209 }
210 }
211 if ( $addresses && $userNames ) {
212 // Use a union, not "OR" (T360088)
213 $ids = $db->newUnionQueryBuilder()
214 ->add( $db->newSelectQueryBuilder()
215 ->select( 'bt_id' )
216 ->from( 'block_target' )
217 ->where( [ 'bt_address' => $addresses ] )
218 )
219 ->add( $db->newSelectQueryBuilder()
220 ->select( 'bt_id' )
221 ->from( 'block_target' )
222 ->join( 'user', null, 'user_id=bt_user' )
223 ->where( [ 'user_name' => $userNames ] )
224 )
225 ->caller( __METHOD__ )
226 ->fetchFieldValues();
227 if ( $ids ) {
228 $this->addWhere( [ 'bt_id' => $ids ] );
229 } else {
230 $this->addWhere( '1=0' );
231 }
232 } elseif ( $addresses ) {
233 $this->addWhere( [ 'bt_address' => $addresses ] );
234 } elseif ( $userNames ) {
235 $this->addWhere( [ 'block_target_user.user_name' => $userNames ] );
236 } else {
237 // Unreachable since $params['users'] is non-empty
238 $this->addWhere( '1=0' );
239 }
240 }
241 $this->addWhereFld( $bt_auto, 0 );
242 }
243 if ( $params['ip'] !== null ) {
244 $blockCIDRLimit = $this->getConfig()->get( MainConfigNames::BlockCIDRLimit );
245 if ( IPUtils::isIPv4( $params['ip'] ) ) {
246 $type = 'IPv4';
247 $cidrLimit = $blockCIDRLimit['IPv4'];
248 } elseif ( IPUtils::isIPv6( $params['ip'] ) ) {
249 $type = 'IPv6';
250 $cidrLimit = $blockCIDRLimit['IPv6'];
251 } else {
252 $this->dieWithError( 'apierror-badip', 'param_ip' );
253 }
254
255 // Check range validity, if it's a CIDR
256 [ $ip, $range ] = IPUtils::parseCIDR( $params['ip'] );
257 if ( $ip !== false && $range !== false && $range < $cidrLimit ) {
258 $this->dieWithError( [ 'apierror-cidrtoobroad', $type, $cidrLimit ] );
259 }
260
261 // Let IPUtils::parseRange handle calculating $upper, instead of duplicating the logic here.
262 [ $lower, $upper ] = IPUtils::parseRange( $params['ip'] );
263
264 $this->addWhere( $this->blockStore->getRangeCond(
265 $lower, $upper, DatabaseBlockStore::SCHEMA_CURRENT ) );
266 $this->addWhere( [ $bt_auto => 0 ] );
267 }
268
269 if ( $params['show'] !== null ) {
270 $show = array_fill_keys( $params['show'], true );
271
272 // Check for conflicting parameters.
273 if ( ( isset( $show['account'] ) && isset( $show['!account'] ) )
274 || ( isset( $show['ip'] ) && isset( $show['!ip'] ) )
275 || ( isset( $show['range'] ) && isset( $show['!range'] ) )
276 || ( isset( $show['temp'] ) && isset( $show['!temp'] ) )
277 ) {
278 $this->dieWithError( 'apierror-show' );
279 }
280
281 $this->addWhereIf( [ $bt_user => 0 ], isset( $show['!account'] ) );
282 $this->addWhereIf( "$bt_user != 0", isset( $show['account'] ) );
283 $this->addWhereIf( "$bt_user != 0 OR $bt_range_end > $bt_range_start", isset( $show['!ip'] ) );
284 $this->addWhereIf( "$bt_user = 0 AND $bt_range_end = $bt_range_start", isset( $show['ip'] ) );
285 $this->addWhereIf( [ $bl_expiry => $db->getInfinity() ], isset( $show['!temp'] ) );
286 $this->addWhereIf( $db->expr( $bl_expiry, '!=', $db->getInfinity() ), isset( $show['temp'] ) );
287 $this->addWhereIf( "$bt_range_end = $bt_range_start", isset( $show['!range'] ) );
288 $this->addWhereIf( "$bt_range_end > $bt_range_start", isset( $show['range'] ) );
289 }
290
291 if ( !$this->getAuthority()->isAllowed( 'hideuser' ) ) {
292 if ( $this->blockTargetReadStage === SCHEMA_COMPAT_READ_OLD ) {
293 $this->addWhereFld( $bl_deleted, 0 );
294 } else {
295 $this->addWhere(
296 $this->hideUserUtils->getExpression( $db, 'block_target.bt_user' )
297 );
298 }
299 }
300
301 // Filter out expired rows
302 $this->addWhere( $db->expr( $bl_expiry, '>', $db->timestamp() ) );
303
304 $res = $this->select( __METHOD__ );
305
306 $restrictions = [];
307 if ( $fld_restrictions ) {
308 $restrictions = $this->getRestrictionData( $res, $params['limit'] );
309 }
310
311 $count = 0;
312 foreach ( $res as $row ) {
313 if ( ++$count > $params['limit'] ) {
314 // We've had enough
315 $this->setContinueEnumParameter( 'continue', "{$row->$bl_timestamp}|{$row->$bl_id}" );
316 break;
317 }
318 $block = [
319 ApiResult::META_TYPE => 'assoc',
320 ];
321 if ( $fld_id ) {
322 $block['id'] = (int)$row->$bl_id;
323 }
324 if ( $fld_user && !$row->$bt_auto ) {
325 $block['user'] = $row->$bt_address_or_user_name;
326 }
327 if ( $fld_userid && !$row->$bt_auto ) {
328 $block['userid'] = (int)$row->$bt_user;
329 }
330 if ( $fld_by ) {
331 $block['by'] = $row->actor_name;
332 }
333 if ( $fld_byid ) {
334 $block['byid'] = (int)$row->actor_user;
335 }
336 if ( $fld_timestamp ) {
337 $block['timestamp'] = wfTimestamp( TS_ISO_8601, $row->$bl_timestamp );
338 }
339 if ( $fld_expiry ) {
340 $block['expiry'] = ApiResult::formatExpiry( $row->$bl_expiry );
341 }
342 if ( $fld_reason ) {
343 $block['reason'] = $this->commentStore->getComment( $bl_reason, $row )->text;
344 }
345 if ( $fld_range && !$row->$bt_auto && $row->$bt_range_start !== null ) {
346 $block['rangestart'] = IPUtils::formatHex( $row->$bt_range_start );
347 $block['rangeend'] = IPUtils::formatHex( $row->$bt_range_end );
348 }
349 if ( $fld_flags ) {
350 // For clarity, these flags use the same names as their action=block counterparts
351 $block['automatic'] = (bool)$row->$bt_auto;
352 $block['anononly'] = (bool)$row->$bl_anon_only;
353 $block['nocreate'] = (bool)$row->$bl_create_account;
354 $block['autoblock'] = (bool)$row->$bl_enable_autoblock;
355 $block['noemail'] = (bool)$row->$bl_block_email;
356 $block['hidden'] = (bool)$row->$bl_deleted;
357 $block['allowusertalk'] = (bool)$row->$bl_allow_usertalk;
358 $block['partial'] = !(bool)$row->$bl_sitewide;
359 }
360
361 if ( $fld_restrictions ) {
362 $block['restrictions'] = [];
363 if ( !$row->$bl_sitewide && isset( $restrictions[$row->$bl_id] ) ) {
364 $block['restrictions'] = $restrictions[$row->$bl_id];
365 }
366 }
367
368 $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $block );
369 if ( !$fit ) {
370 $this->setContinueEnumParameter( 'continue', "{$row->$bl_timestamp}|{$row->$bl_id}" );
371 break;
372 }
373 }
374 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'block' );
375 }
376
385 private function getRestrictionData( IResultWrapper $result, $limit ) {
386 $partialIds = [];
387 $count = 0;
388 foreach ( $result as $row ) {
389 if ( ++$count <= $limit && !( $row->ipb_sitewide ?? $row->bl_sitewide ) ) {
390 $partialIds[] = (int)( $row->ipb_id ?? $row->bl_id );
391 }
392 }
393
394 $restrictions = $this->blockRestrictionStore->loadByBlockId( $partialIds );
395
396 $data = [];
397 $keys = [
398 'page' => 'pages',
399 'ns' => 'namespaces',
400 ];
401 if ( $this->getConfig()->get( MainConfigNames::EnablePartialActionBlocks ) ) {
402 $keys['action'] = 'actions';
403 }
404
405 foreach ( $restrictions as $restriction ) {
406 $key = $keys[$restriction->getType()];
407 $id = $restriction->getBlockId();
408 switch ( $restriction->getType() ) {
409 case 'page':
411 '@phan-var \MediaWiki\Block\Restriction\PageRestriction $restriction';
412 $value = [ 'id' => $restriction->getValue() ];
413 if ( $restriction->getTitle() ) {
414 self::addTitleInfo( $value, $restriction->getTitle() );
415 }
416 break;
417 case 'action':
418 $value = $this->blockActionInfo->getActionFromId( $restriction->getValue() );
419 break;
420 default:
421 $value = $restriction->getValue();
422 }
423
424 if ( !isset( $data[$id][$key] ) ) {
425 $data[$id][$key] = [];
426 ApiResult::setIndexedTagName( $data[$id][$key], $restriction->getType() );
427 }
428 $data[$id][$key][] = $value;
429 }
430
431 return $data;
432 }
433
434 public function getAllowedParams() {
435 $blockCIDRLimit = $this->getConfig()->get( MainConfigNames::BlockCIDRLimit );
436
437 return [
438 'start' => [
439 ParamValidator::PARAM_TYPE => 'timestamp'
440 ],
441 'end' => [
442 ParamValidator::PARAM_TYPE => 'timestamp',
443 ],
444 'dir' => [
445 ParamValidator::PARAM_TYPE => [
446 'newer',
447 'older'
448 ],
449 ParamValidator::PARAM_DEFAULT => 'older',
450 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
452 'newer' => 'api-help-paramvalue-direction-newer',
453 'older' => 'api-help-paramvalue-direction-older',
454 ],
455 ],
456 'ids' => [
457 ParamValidator::PARAM_TYPE => 'integer',
458 ParamValidator::PARAM_ISMULTI => true
459 ],
460 'users' => [
461 ParamValidator::PARAM_TYPE => 'user',
462 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'cidr' ],
463 ParamValidator::PARAM_ISMULTI => true
464 ],
465 'ip' => [
467 'apihelp-query+blocks-param-ip',
468 $blockCIDRLimit['IPv4'],
469 $blockCIDRLimit['IPv6'],
470 ],
471 ],
472 'limit' => [
473 ParamValidator::PARAM_DEFAULT => 10,
474 ParamValidator::PARAM_TYPE => 'limit',
475 IntegerDef::PARAM_MIN => 1,
476 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
477 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
478 ],
479 'prop' => [
480 ParamValidator::PARAM_DEFAULT => 'id|user|by|timestamp|expiry|reason|flags',
481 ParamValidator::PARAM_TYPE => [
482 'id',
483 'user',
484 'userid',
485 'by',
486 'byid',
487 'timestamp',
488 'expiry',
489 'reason',
490 'range',
491 'flags',
492 'restrictions',
493 ],
494 ParamValidator::PARAM_ISMULTI => true,
496 ],
497 'show' => [
498 ParamValidator::PARAM_TYPE => [
499 'account',
500 '!account',
501 'temp',
502 '!temp',
503 'ip',
504 '!ip',
505 'range',
506 '!range',
507 ],
508 ParamValidator::PARAM_ISMULTI => true
509 ],
510 'continue' => [
511 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
512 ],
513 ];
514 }
515
516 protected function getExamplesMessages() {
517 return [
518 'action=query&list=blocks'
519 => 'apihelp-query+blocks-example-simple',
520 'action=query&list=blocks&bkusers=Alice|Bob'
521 => 'apihelp-query+blocks-example-users',
522 ];
523 }
524
525 public function getHelpUrls() {
526 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Blocks';
527 }
528}
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:275
const SCHEMA_COMPAT_READ_MASK
Definition Defines.php:281
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
array $params
The job parameters.
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1542
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
Definition ApiBase.php:1734
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:211
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:236
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition ApiBase.php:994
getResult()
Get the result object.
Definition ApiBase.php:680
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:820
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:171
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:238
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:541
This is a base class for all Query modules.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
addWhereIf( $value, $condition)
Same as addWhere(), but add the WHERE clauses only if a condition is met.
addWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, and an ORDER BY clause to sort in the right direction.
addFields( $value)
Add a set of fields to select to the internal array.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
getDB()
Get the Query database connection (read-only)
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addFieldsIf( $value, $condition)
Same as addFields(), but add the fields only if a condition is met.
addWhereIDsFld( $table, $field, $ids)
Like addWhereFld for an integer list of IDs.
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
addWhere( $value)
Add a set of WHERE clauses to the internal array.
Query module to enumerate all user blocks.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
__construct(ApiQuery $query, $moduleName, DatabaseBlockStore $blockStore, BlockActionInfo $blockActionInfo, BlockRestrictionStore $blockRestrictionStore, CommentStore $commentStore, HideUserUtils $hideUserUtils)
getExamplesMessages()
Returns usage examples for this module.
getHelpUrls()
Return links to more detailed help pages about the module.
This is the main query class.
Definition ApiQuery.php:43
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Defines the actions that can be blocked by a partial block.
Helpers for building queries that determine whether a user is hidden.
Handle database storage of comments such as edit summaries and log reasons.
A class containing constants representing the names of configuration variables.
Type definition for user types.
Definition UserDef.php:27
Service for formatting and validating API parameters.
Type definition for integer types.
Result wrapper for grabbing data queried from an IDatabase object.