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