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