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