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