MediaWiki 1.44.0
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();
75 $params = $this->extractRequestParams();
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( [ 'bl_deleted' => 0 ] );
243 $this->addWhere(
244 $this->hideUserUtils->getExpression( $db, 'block_target.bt_user' )
245 );
246 }
247
248 // Filter out expired rows
249 $this->addWhere( $db->expr( 'bl_expiry', '>', $db->timestamp() ) );
250
251 $res = $this->select( __METHOD__ );
252
253 $restrictions = [];
254 if ( $fld_restrictions ) {
255 $restrictions = $this->getRestrictionData( $res, $params['limit'] );
256 }
257
258 $count = 0;
259 foreach ( $res as $row ) {
260 if ( ++$count > $params['limit'] ) {
261 // We've had enough
262 $this->setContinueEnumParameter( 'continue', "{$row->bl_timestamp}|{$row->bl_id}" );
263 break;
264 }
265 $block = [
266 ApiResult::META_TYPE => 'assoc',
267 ];
268 if ( $fld_id ) {
269 $block['id'] = (int)$row->bl_id;
270 }
271 if ( $fld_user && !$row->bt_auto ) {
272 $block['user'] = $row->bt_address_or_user_name;
273 }
274 if ( $fld_userid && !$row->bt_auto ) {
275 $block['userid'] = (int)$row->bt_user;
276 }
277 if ( $fld_by ) {
278 $block['by'] = $row->actor_name;
279 }
280 if ( $fld_byid ) {
281 $block['byid'] = (int)$row->actor_user;
282 }
283 if ( $fld_timestamp ) {
284 $block['timestamp'] = wfTimestamp( TS_ISO_8601, $row->bl_timestamp );
285 }
286 if ( $fld_expiry ) {
287 $block['expiry'] = ApiResult::formatExpiry( $row->bl_expiry );
288 if ( wfIsInfinity( $row->bl_expiry ) ) {
289 $duration = $this->msg( 'infiniteblock' )->plain();
290 } else {
291 $duration = $this->getLanguage()->formatDurationBetweenTimestamps(
292 (int)wfTimestamp( TS_UNIX, $row->bl_timestamp ),
293 (int)wfTimestamp( TS_UNIX, $row->bl_expiry )
294 );
295 }
296 $block['duration-l10n'] = $duration;
297 }
298 if ( $fld_reason ) {
299 $block['reason'] = $this->commentStore->getComment( 'bl_reason', $row )->text;
300 }
301 if ( $fld_parsedreason ) {
302 $block['parsedreason'] = $this->commentFormatter->format(
303 $this->commentStore->getComment( 'bl_reason', $row )->text
304 );
305 }
306 if ( $fld_range && !$row->bt_auto && $row->bt_range_start !== null ) {
307 $block['rangestart'] = IPUtils::formatHex( $row->bt_range_start );
308 $block['rangeend'] = IPUtils::formatHex( $row->bt_range_end );
309 }
310 if ( $fld_flags ) {
311 // For clarity, these flags use the same names as their action=block counterparts
312 $block['automatic'] = (bool)$row->bt_auto;
313 $block['anononly'] = (bool)$row->bl_anon_only;
314 $block['nocreate'] = (bool)$row->bl_create_account;
315 $block['autoblock'] = (bool)$row->bl_enable_autoblock;
316 $block['noemail'] = (bool)$row->bl_block_email;
317 $block['hidden'] = (bool)$row->bl_deleted;
318 $block['allowusertalk'] = (bool)$row->bl_allow_usertalk;
319 $block['partial'] = !(bool)$row->bl_sitewide;
320 }
321
322 if ( $fld_restrictions ) {
323 $block['restrictions'] = [];
324 if ( !$row->bl_sitewide && isset( $restrictions[$row->bl_id] ) ) {
325 $block['restrictions'] = $restrictions[$row->bl_id];
326 }
327 }
328
329 $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $block );
330 if ( !$fit ) {
331 $this->setContinueEnumParameter( 'continue', "{$row->bl_timestamp}|{$row->bl_id}" );
332 break;
333 }
334 }
335 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'block' );
336 }
337
346 private function getRestrictionData( IResultWrapper $result, $limit ) {
347 $partialIds = [];
348 $count = 0;
349 foreach ( $result as $row ) {
350 if ( ++$count <= $limit && !( $row->ipb_sitewide ?? $row->bl_sitewide ) ) {
351 $partialIds[] = (int)( $row->ipb_id ?? $row->bl_id );
352 }
353 }
354
355 $restrictions = $this->blockRestrictionStore->loadByBlockId( $partialIds );
356
357 $data = [];
358 $keys = [
359 'page' => 'pages',
360 'ns' => 'namespaces',
361 ];
363 $keys['action'] = 'actions';
364 }
365
366 foreach ( $restrictions as $restriction ) {
367 $key = $keys[$restriction->getType()];
368 $id = $restriction->getBlockId();
369 switch ( $restriction->getType() ) {
370 case 'page':
371 $value = [ 'id' => $restriction->getValue() ];
373 '@phan-var \MediaWiki\Block\Restriction\PageRestriction $restriction';
374 if ( !$restriction->getTitle() ) {
375 continue 2;
376 }
377 self::addTitleInfo( $value, $restriction->getTitle() );
378 break;
379 case 'action':
380 $value = $this->blockActionInfo->getActionFromId( $restriction->getValue() );
381 break;
382 default:
383 $value = $restriction->getValue();
384 }
385
386 if ( !isset( $data[$id][$key] ) ) {
387 $data[$id][$key] = [];
388 ApiResult::setIndexedTagName( $data[$id][$key], $restriction->getType() );
389 }
390 $data[$id][$key][] = $value;
391 }
392
393 return $data;
394 }
395
396 public function getAllowedParams() {
397 $blockCIDRLimit = $this->getConfig()->get( MainConfigNames::BlockCIDRLimit );
398
399 return [
400 'start' => [
401 ParamValidator::PARAM_TYPE => 'timestamp'
402 ],
403 'end' => [
404 ParamValidator::PARAM_TYPE => 'timestamp',
405 ],
406 'dir' => [
407 ParamValidator::PARAM_TYPE => [
408 'newer',
409 'older'
410 ],
411 ParamValidator::PARAM_DEFAULT => 'older',
412 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
414 'newer' => 'api-help-paramvalue-direction-newer',
415 'older' => 'api-help-paramvalue-direction-older',
416 ],
417 ],
418 'ids' => [
419 ParamValidator::PARAM_TYPE => 'integer',
420 ParamValidator::PARAM_ISMULTI => true
421 ],
422 'users' => [
423 ParamValidator::PARAM_TYPE => 'user',
424 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'cidr' ],
425 ParamValidator::PARAM_ISMULTI => true
426 ],
427 'ip' => [
429 'apihelp-query+blocks-param-ip',
430 $blockCIDRLimit['IPv4'],
431 $blockCIDRLimit['IPv6'],
432 ],
433 ],
434 'limit' => [
435 ParamValidator::PARAM_DEFAULT => 10,
436 ParamValidator::PARAM_TYPE => 'limit',
437 IntegerDef::PARAM_MIN => 1,
438 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
439 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
440 ],
441 'prop' => [
442 ParamValidator::PARAM_DEFAULT => 'id|user|by|timestamp|expiry|reason|flags',
443 ParamValidator::PARAM_TYPE => [
444 'id',
445 'user',
446 'userid',
447 'by',
448 'byid',
449 'timestamp',
450 'expiry',
451 'reason',
452 'parsedreason',
453 'range',
454 'flags',
455 'restrictions',
456 ],
457 ParamValidator::PARAM_ISMULTI => true,
459 ],
460 'show' => [
461 ParamValidator::PARAM_TYPE => [
462 'account',
463 '!account',
464 'temp',
465 '!temp',
466 'ip',
467 '!ip',
468 'range',
469 '!range',
470 ],
471 ParamValidator::PARAM_ISMULTI => true
472 ],
473 'continue' => [
474 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
475 ],
476 ];
477 }
478
479 protected function getExamplesMessages() {
480 return [
481 'action=query&list=blocks'
482 => 'apihelp-query+blocks-example-simple',
483 'action=query&list=blocks&bkusers=Alice|Bob'
484 => 'apihelp-query+blocks-example-users',
485 ];
486 }
487
488 public function getHelpUrls() {
489 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Blocks';
490 }
491}
492
494class_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.
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:50
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.