MediaWiki master
ApiQueryBlocks.php
Go to the documentation of this file.
1<?php
31use Wikimedia\IPUtils;
36
43
44 private DatabaseBlockStore $blockStore;
45 private BlockActionInfo $blockActionInfo;
46 private BlockRestrictionStore $blockRestrictionStore;
47 private CommentStore $commentStore;
48 private HideUserUtils $hideUserUtils;
49
59 public function __construct(
60 ApiQuery $query,
61 $moduleName,
62 DatabaseBlockStore $blockStore,
63 BlockActionInfo $blockActionInfo,
64 BlockRestrictionStore $blockRestrictionStore,
65 CommentStore $commentStore,
66 HideUserUtils $hideUserUtils
67 ) {
68 parent::__construct( $query, $moduleName, 'bk' );
69 $this->blockStore = $blockStore;
70 $this->blockActionInfo = $blockActionInfo;
71 $this->blockRestrictionStore = $blockRestrictionStore;
72 $this->commentStore = $commentStore;
73 $this->hideUserUtils = $hideUserUtils;
74 }
75
76 public function execute() {
77 $db = $this->getDB();
79 $this->requireMaxOneParameter( $params, 'users', 'ip' );
80
81 $prop = array_fill_keys( $params['prop'], true );
82 $fld_id = isset( $prop['id'] );
83 $fld_user = isset( $prop['user'] );
84 $fld_userid = isset( $prop['userid'] );
85 $fld_by = isset( $prop['by'] );
86 $fld_byid = isset( $prop['byid'] );
87 $fld_timestamp = isset( $prop['timestamp'] );
88 $fld_expiry = isset( $prop['expiry'] );
89 $fld_reason = isset( $prop['reason'] );
90 $fld_range = isset( $prop['range'] );
91 $fld_flags = isset( $prop['flags'] );
92 $fld_restrictions = isset( $prop['restrictions'] );
93
94 $result = $this->getResult();
95
96 $this->addTables( [ 'block', 'block_target', 'block_target_user' => 'user' ] );
97 $this->addJoinConds( [
98 'block_target' => [ 'JOIN', 'bt_id=bl_target' ],
99 'block_target_user' => [ 'LEFT JOIN', 'user_id=bt_user' ]
100 ] );
101 $this->addFields( [ 'bt_auto', 'bl_id', 'bl_timestamp' ] );
102 $this->addFieldsIf(
103 [
104 'bt_address',
105 'bt_user',
106 'bt_address_or_user_name' => 'COALESCE(bt_address, bt_user_text)'
107 ],
108 $fld_user || $fld_userid
109 );
110
111 if ( $fld_by || $fld_byid ) {
112 $this->addTables( 'actor' );
113 $this->addFields( [ 'actor_user', 'actor_name' ] );
114 $this->addJoinConds( [ 'actor' => [ 'JOIN', 'actor_id=bl_by_actor' ] ] );
115 }
116 $this->addFieldsIf( 'bl_expiry', $fld_expiry );
117 $this->addFieldsIf( [ 'bt_range_start', 'bt_range_end' ], $fld_range );
118 $this->addFieldsIf( [ 'bl_anon_only', 'bl_create_account', 'bl_enable_autoblock',
119 'bl_block_email', 'bl_deleted', 'bl_allow_usertalk', 'bl_sitewide' ],
120 $fld_flags );
121 $this->addFieldsIf( 'bl_sitewide', $fld_restrictions );
122
123 if ( $fld_reason ) {
124 $commentQuery = $this->commentStore->getJoin( 'bl_reason' );
125 $this->addTables( $commentQuery['tables'] );
126 $this->addFields( $commentQuery['fields'] );
127 $this->addJoinConds( $commentQuery['joins'] );
128 }
129
130 $this->addOption( 'LIMIT', $params['limit'] + 1 );
132 'bl_timestamp',
133 $params['dir'],
134 $params['start'],
135 $params['end']
136 );
137 // Include in ORDER BY for uniqueness
138 $this->addWhereRange( 'bl_id', $params['dir'], null, null );
139
140 if ( $params['continue'] !== null ) {
141 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'timestamp', 'int' ] );
142 $op = ( $params['dir'] == 'newer' ? '>=' : '<=' );
143 $this->addWhere( $db->buildComparison( $op, [
144 'bl_timestamp' => $db->timestamp( $cont[0] ),
145 'bl_id' => $cont[1],
146 ] ) );
147 }
148
149 if ( $params['ids'] ) {
150 $this->addWhereIDsFld( 'block', 'bl_id', $params['ids'] );
151 }
152 if ( $params['users'] ) {
153 $addresses = [];
154 $userNames = [];
155 foreach ( $params['users'] as $target ) {
156 if ( IPUtils::isValid( $target ) || IPUtils::isValidRange( $target ) ) {
157 $addresses[] = $target;
158 } else {
159 $userNames[] = $target;
160 }
161 }
162 if ( $addresses && $userNames ) {
163 // Use a union, not "OR" (T360088)
164 $ids = $db->newUnionQueryBuilder()
165 ->add( $db->newSelectQueryBuilder()
166 ->select( 'bt_id' )
167 ->from( 'block_target' )
168 ->where( [ 'bt_address' => $addresses ] )
169 )
170 ->add( $db->newSelectQueryBuilder()
171 ->select( 'bt_id' )
172 ->from( 'block_target' )
173 ->join( 'user', null, 'user_id=bt_user' )
174 ->where( [ 'user_name' => $userNames ] )
175 )
176 ->caller( __METHOD__ )
177 ->fetchFieldValues();
178 if ( $ids ) {
179 $this->addWhere( [ 'bt_id' => $ids ] );
180 } else {
181 $this->addWhere( '1=0' );
182 }
183 } elseif ( $addresses ) {
184 $this->addWhere( [ 'bt_address' => $addresses ] );
185 } elseif ( $userNames ) {
186 $this->addWhere( [ 'block_target_user.user_name' => $userNames ] );
187 } else {
188 // Unreachable since $params['users'] is non-empty
189 $this->addWhere( '1=0' );
190 }
191 $this->addWhereFld( 'bt_auto', 0 );
192 }
193 if ( $params['ip'] !== null ) {
194 $blockCIDRLimit = $this->getConfig()->get( MainConfigNames::BlockCIDRLimit );
195 if ( IPUtils::isIPv4( $params['ip'] ) ) {
196 $type = 'IPv4';
197 $cidrLimit = $blockCIDRLimit['IPv4'];
198 } elseif ( IPUtils::isIPv6( $params['ip'] ) ) {
199 $type = 'IPv6';
200 $cidrLimit = $blockCIDRLimit['IPv6'];
201 } else {
202 $this->dieWithError( 'apierror-badip', 'param_ip' );
203 }
204
205 // Check range validity, if it's a CIDR
206 [ $ip, $range ] = IPUtils::parseCIDR( $params['ip'] );
207 if ( $ip !== false && $range !== false && $range < $cidrLimit ) {
208 $this->dieWithError( [ 'apierror-cidrtoobroad', $type, $cidrLimit ] );
209 }
210
211 // Let IPUtils::parseRange handle calculating $upper, instead of duplicating the logic here.
212 [ $lower, $upper ] = IPUtils::parseRange( $params['ip'] );
213
214 $this->addWhere( $this->blockStore->getRangeCond( $lower, $upper ) );
215 $this->addWhere( [ 'bt_auto' => 0 ] );
216 }
217
218 if ( $params['show'] !== null ) {
219 $show = array_fill_keys( $params['show'], true );
220
221 // Check for conflicting parameters.
222 if ( ( isset( $show['account'] ) && isset( $show['!account'] ) )
223 || ( isset( $show['ip'] ) && isset( $show['!ip'] ) )
224 || ( isset( $show['range'] ) && isset( $show['!range'] ) )
225 || ( isset( $show['temp'] ) && isset( $show['!temp'] ) )
226 ) {
227 $this->dieWithError( 'apierror-show' );
228 }
229
230 $this->addWhereIf( [ 'bt_user' => 0 ], isset( $show['!account'] ) );
231 $this->addWhereIf( $db->expr( 'bt_user', '!=', 0 ), isset( $show['account'] ) );
232 $this->addWhereIf(
233 $db->expr( 'bt_user', '!=', 0 )->orExpr( new RawSQLExpression( 'bt_range_end > bt_range_start' ) ),
234 isset( $show['!ip'] )
235 );
236 $this->addWhereIf( [ 'bt_user' => 0, 'bt_range_end = bt_range_start' ], isset( $show['ip'] ) );
237 $this->addWhereIf( [ 'bl_expiry' => $db->getInfinity() ], isset( $show['!temp'] ) );
238 $this->addWhereIf( $db->expr( 'bl_expiry', '!=', $db->getInfinity() ), isset( $show['temp'] ) );
239 $this->addWhereIf( 'bt_range_end = bt_range_start', isset( $show['!range'] ) );
240 $this->addWhereIf( 'bt_range_end > bt_range_start', isset( $show['range'] ) );
241 }
242
243 if ( !$this->getAuthority()->isAllowed( 'hideuser' ) ) {
244 $this->addWhere(
245 $this->hideUserUtils->getExpression( $db, $db->tableName( 'block_target' ) . '.bt_user' )
246 );
247 }
248
249 // Filter out expired rows
250 $this->addWhere( $db->expr( 'bl_expiry', '>', $db->timestamp() ) );
251
252 $res = $this->select( __METHOD__ );
253
254 $restrictions = [];
255 if ( $fld_restrictions ) {
256 $restrictions = $this->getRestrictionData( $res, $params['limit'] );
257 }
258
259 $count = 0;
260 foreach ( $res as $row ) {
261 if ( ++$count > $params['limit'] ) {
262 // We've had enough
263 $this->setContinueEnumParameter( 'continue', "{$row->bl_timestamp}|{$row->bl_id}" );
264 break;
265 }
266 $block = [
267 ApiResult::META_TYPE => 'assoc',
268 ];
269 if ( $fld_id ) {
270 $block['id'] = (int)$row->bl_id;
271 }
272 if ( $fld_user && !$row->bt_auto ) {
273 $block['user'] = $row->bt_address_or_user_name;
274 }
275 if ( $fld_userid && !$row->bt_auto ) {
276 $block['userid'] = (int)$row->bt_user;
277 }
278 if ( $fld_by ) {
279 $block['by'] = $row->actor_name;
280 }
281 if ( $fld_byid ) {
282 $block['byid'] = (int)$row->actor_user;
283 }
284 if ( $fld_timestamp ) {
285 $block['timestamp'] = wfTimestamp( TS_ISO_8601, $row->bl_timestamp );
286 }
287 if ( $fld_expiry ) {
288 $block['expiry'] = ApiResult::formatExpiry( $row->bl_expiry );
289 }
290 if ( $fld_reason ) {
291 $block['reason'] = $this->commentStore->getComment( 'bl_reason', $row )->text;
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 ];
349 if ( $this->getConfig()->get( MainConfigNames::EnablePartialActionBlocks ) ) {
350 $keys['action'] = 'actions';
351 }
352
353 foreach ( $restrictions as $restriction ) {
354 $key = $keys[$restriction->getType()];
355 $id = $restriction->getBlockId();
356 switch ( $restriction->getType() ) {
357 case 'page':
359 '@phan-var \MediaWiki\Block\Restriction\PageRestriction $restriction';
360 $value = [ 'id' => $restriction->getValue() ];
361 if ( $restriction->getTitle() ) {
362 self::addTitleInfo( $value, $restriction->getTitle() );
363 }
364 break;
365 case 'action':
366 $value = $this->blockActionInfo->getActionFromId( $restriction->getValue() );
367 break;
368 default:
369 $value = $restriction->getValue();
370 }
371
372 if ( !isset( $data[$id][$key] ) ) {
373 $data[$id][$key] = [];
374 ApiResult::setIndexedTagName( $data[$id][$key], $restriction->getType() );
375 }
376 $data[$id][$key][] = $value;
377 }
378
379 return $data;
380 }
381
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 'range',
439 'flags',
440 'restrictions',
441 ],
442 ParamValidator::PARAM_ISMULTI => true,
444 ],
445 'show' => [
446 ParamValidator::PARAM_TYPE => [
447 'account',
448 '!account',
449 'temp',
450 '!temp',
451 'ip',
452 '!ip',
453 'range',
454 '!range',
455 ],
456 ParamValidator::PARAM_ISMULTI => true
457 ],
458 'continue' => [
459 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
460 ],
461 ];
462 }
463
464 protected function getExamplesMessages() {
465 return [
466 'action=query&list=blocks'
467 => 'apihelp-query+blocks-example-simple',
468 'action=query&list=blocks&bkusers=Alice|Bob'
469 => 'apihelp-query+blocks-example-users',
470 ];
471 }
472
473 public function getHelpUrls() {
474 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Blocks';
475 }
476}
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:1540
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
Definition ApiBase.php:1731
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:212
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:237
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition ApiBase.php:995
getResult()
Get the result object.
Definition ApiBase.php:681
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:821
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:172
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:239
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:542
This is a base class for all Query modules.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
addWhereIf( $value, $condition)
Same as addWhere(), but add the WHERE clauses only if a condition is met.
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.
addFields( $value)
Add a set of fields to select to the internal array.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addTables( $tables, $alias=null)
Add a set of tables 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...
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.
addFieldsIf( $value, $condition)
Same as addFields(), but add the fields only if a condition is met.
addWhereIDsFld( $table, $field, $ids)
Like addWhereFld for an integer list of IDs.
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
addWhere( $value)
Add a set of WHERE clauses to the internal array.
Query module to enumerate all user blocks.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
__construct(ApiQuery $query, $moduleName, DatabaseBlockStore $blockStore, BlockActionInfo $blockActionInfo, BlockRestrictionStore $blockRestrictionStore, CommentStore $commentStore, HideUserUtils $hideUserUtils)
getExamplesMessages()
Returns usage examples for this module.
getHelpUrls()
Return links to more detailed help pages about the module.
This is the main query class.
Definition ApiQuery.php:43
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
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.
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.