MediaWiki  master
ApiQueryBlocks.php
Go to the documentation of this file.
1 <?php
29 use Wikimedia\IPUtils;
33 
40 
41  private BlockActionInfo $blockActionInfo;
42  private BlockRestrictionStore $blockRestrictionStore;
43  private CommentStore $commentStore;
44 
52  public function __construct(
53  ApiQuery $query,
54  $moduleName,
55  BlockActionInfo $blockActionInfo,
56  BlockRestrictionStore $blockRestrictionStore,
57  CommentStore $commentStore
58  ) {
59  parent::__construct( $query, $moduleName, 'bk' );
60  $this->blockActionInfo = $blockActionInfo;
61  $this->blockRestrictionStore = $blockRestrictionStore;
62  $this->commentStore = $commentStore;
63  }
64 
65  public function execute() {
66  $db = $this->getDB();
67  $params = $this->extractRequestParams();
68  $this->requireMaxOneParameter( $params, 'users', 'ip' );
69 
70  $prop = array_fill_keys( $params['prop'], true );
71  $fld_id = isset( $prop['id'] );
72  $fld_user = isset( $prop['user'] );
73  $fld_userid = isset( $prop['userid'] );
74  $fld_by = isset( $prop['by'] );
75  $fld_byid = isset( $prop['byid'] );
76  $fld_timestamp = isset( $prop['timestamp'] );
77  $fld_expiry = isset( $prop['expiry'] );
78  $fld_reason = isset( $prop['reason'] );
79  $fld_range = isset( $prop['range'] );
80  $fld_flags = isset( $prop['flags'] );
81  $fld_restrictions = isset( $prop['restrictions'] );
82 
83  $result = $this->getResult();
84 
85  $this->addTables( 'ipblocks' );
86  $this->addFields( [ 'ipb_auto', 'ipb_id', 'ipb_timestamp' ] );
87 
88  $this->addFieldsIf( [ 'ipb_address', 'ipb_user' ], $fld_user || $fld_userid );
89  if ( $fld_by || $fld_byid ) {
90  $this->addTables( 'actor' );
91  $this->addFields( [ 'actor_user', 'actor_name' ] );
92  $this->addJoinConds( [ 'actor' => [ 'JOIN', 'actor_id=ipb_by_actor' ] ] );
93  }
94  $this->addFieldsIf( 'ipb_expiry', $fld_expiry );
95  $this->addFieldsIf( [ 'ipb_range_start', 'ipb_range_end' ], $fld_range );
96  $this->addFieldsIf( [ 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock',
97  'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk', 'ipb_sitewide' ],
98  $fld_flags );
99  $this->addFieldsIf( 'ipb_sitewide', $fld_restrictions );
100 
101  if ( $fld_reason ) {
102  $commentQuery = $this->commentStore->getJoin( 'ipb_reason' );
103  $this->addTables( $commentQuery['tables'] );
104  $this->addFields( $commentQuery['fields'] );
105  $this->addJoinConds( $commentQuery['joins'] );
106  }
107 
108  $this->addOption( 'LIMIT', $params['limit'] + 1 );
109  $this->addTimestampWhereRange(
110  'ipb_timestamp',
111  $params['dir'],
112  $params['start'],
113  $params['end']
114  );
115  // Include in ORDER BY for uniqueness
116  $this->addWhereRange( 'ipb_id', $params['dir'], null, null );
117 
118  if ( $params['continue'] !== null ) {
119  $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'timestamp', 'int' ] );
120  $op = ( $params['dir'] == 'newer' ? '>=' : '<=' );
121  $this->addWhere( $db->buildComparison( $op, [
122  'ipb_timestamp' => $db->timestamp( $cont[0] ),
123  'ipb_id' => $cont[1],
124  ] ) );
125  }
126 
127  if ( $params['ids'] ) {
128  $this->addWhereIDsFld( 'ipblocks', 'ipb_id', $params['ids'] );
129  }
130  if ( $params['users'] ) {
131  $this->addWhereFld( 'ipb_address', $params['users'] );
132  $this->addWhereFld( 'ipb_auto', 0 );
133  }
134  if ( $params['ip'] !== null ) {
135  $blockCIDRLimit = $this->getConfig()->get( MainConfigNames::BlockCIDRLimit );
136  if ( IPUtils::isIPv4( $params['ip'] ) ) {
137  $type = 'IPv4';
138  $cidrLimit = $blockCIDRLimit['IPv4'];
139  $prefixLen = 0;
140  } elseif ( IPUtils::isIPv6( $params['ip'] ) ) {
141  $type = 'IPv6';
142  $cidrLimit = $blockCIDRLimit['IPv6'];
143  $prefixLen = 3; // IPUtils::toHex output is prefixed with "v6-"
144  } else {
145  $this->dieWithError( 'apierror-badip', 'param_ip' );
146  }
147 
148  // Check range validity, if it's a CIDR
149  [ $ip, $range ] = IPUtils::parseCIDR( $params['ip'] );
150  if ( $ip !== false && $range !== false && $range < $cidrLimit ) {
151  $this->dieWithError( [ 'apierror-cidrtoobroad', $type, $cidrLimit ] );
152  }
153 
154  // Let IPUtils::parseRange handle calculating $upper, instead of duplicating the logic here.
155  [ $lower, $upper ] = IPUtils::parseRange( $params['ip'] );
156 
157  // Extract the common prefix to any range block affecting this IP/CIDR
158  $prefix = substr( $lower, 0, $prefixLen + (int)floor( $cidrLimit / 4 ) );
159 
160  $lower = $db->addQuotes( $lower );
161  $upper = $db->addQuotes( $upper );
162 
163  $this->addWhere( [
164  'ipb_range_start' . $db->buildLike( $prefix, $db->anyString() ),
165  'ipb_range_start <= ' . $lower,
166  'ipb_range_end >= ' . $upper,
167  'ipb_auto' => 0
168  ] );
169  }
170 
171  if ( $params['show'] !== null ) {
172  $show = array_fill_keys( $params['show'], true );
173 
174  // Check for conflicting parameters.
175  if ( ( isset( $show['account'] ) && isset( $show['!account'] ) )
176  || ( isset( $show['ip'] ) && isset( $show['!ip'] ) )
177  || ( isset( $show['range'] ) && isset( $show['!range'] ) )
178  || ( isset( $show['temp'] ) && isset( $show['!temp'] ) )
179  ) {
180  $this->dieWithError( 'apierror-show' );
181  }
182 
183  $this->addWhereIf( [ 'ipb_user' => 0 ], isset( $show['!account'] ) );
184  $this->addWhereIf( 'ipb_user != 0', isset( $show['account'] ) );
185  $this->addWhereIf( 'ipb_user != 0 OR ipb_range_end > ipb_range_start', isset( $show['!ip'] ) );
186  $this->addWhereIf( 'ipb_user = 0 AND ipb_range_end = ipb_range_start', isset( $show['ip'] ) );
187  $this->addWhereIf( [ 'ipb_expiry' => $db->getInfinity() ], isset( $show['!temp'] ) );
188  $this->addWhereIf( 'ipb_expiry != ' .
189  $db->addQuotes( $db->getInfinity() ), isset( $show['temp'] ) );
190  $this->addWhereIf( 'ipb_range_end = ipb_range_start', isset( $show['!range'] ) );
191  $this->addWhereIf( 'ipb_range_end > ipb_range_start', isset( $show['range'] ) );
192  }
193 
194  if ( !$this->getAuthority()->isAllowed( 'hideuser' ) ) {
195  $this->addWhereFld( 'ipb_deleted', 0 );
196  }
197 
198  // Filter out expired rows
199  $this->addWhere( 'ipb_expiry > ' . $db->addQuotes( $db->timestamp() ) );
200 
201  $res = $this->select( __METHOD__ );
202 
203  $restrictions = [];
204  if ( $fld_restrictions ) {
205  $restrictions = $this->getRestrictionData( $res, $params['limit'] );
206  }
207 
208  $count = 0;
209  foreach ( $res as $row ) {
210  if ( ++$count > $params['limit'] ) {
211  // We've had enough
212  $this->setContinueEnumParameter( 'continue', "$row->ipb_timestamp|$row->ipb_id" );
213  break;
214  }
215  $block = [
216  ApiResult::META_TYPE => 'assoc',
217  ];
218  if ( $fld_id ) {
219  $block['id'] = (int)$row->ipb_id;
220  }
221  if ( $fld_user && !$row->ipb_auto ) {
222  $block['user'] = $row->ipb_address;
223  }
224  if ( $fld_userid && !$row->ipb_auto ) {
225  $block['userid'] = (int)$row->ipb_user;
226  }
227  if ( $fld_by ) {
228  $block['by'] = $row->actor_name;
229  }
230  if ( $fld_byid ) {
231  $block['byid'] = (int)$row->actor_user;
232  }
233  if ( $fld_timestamp ) {
234  $block['timestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
235  }
236  if ( $fld_expiry ) {
237  $block['expiry'] = ApiResult::formatExpiry( $row->ipb_expiry );
238  }
239  if ( $fld_reason ) {
240  $block['reason'] = $this->commentStore->getComment( 'ipb_reason', $row )->text;
241  }
242  if ( $fld_range && !$row->ipb_auto ) {
243  $block['rangestart'] = IPUtils::formatHex( $row->ipb_range_start );
244  $block['rangeend'] = IPUtils::formatHex( $row->ipb_range_end );
245  }
246  if ( $fld_flags ) {
247  // For clarity, these flags use the same names as their action=block counterparts
248  $block['automatic'] = (bool)$row->ipb_auto;
249  $block['anononly'] = (bool)$row->ipb_anon_only;
250  $block['nocreate'] = (bool)$row->ipb_create_account;
251  $block['autoblock'] = (bool)$row->ipb_enable_autoblock;
252  $block['noemail'] = (bool)$row->ipb_block_email;
253  $block['hidden'] = (bool)$row->ipb_deleted;
254  $block['allowusertalk'] = (bool)$row->ipb_allow_usertalk;
255  $block['partial'] = !(bool)$row->ipb_sitewide;
256  }
257 
258  if ( $fld_restrictions ) {
259  $block['restrictions'] = [];
260  if ( !$row->ipb_sitewide && isset( $restrictions[$row->ipb_id] ) ) {
261  $block['restrictions'] = $restrictions[$row->ipb_id];
262  }
263  }
264 
265  $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $block );
266  if ( !$fit ) {
267  $this->setContinueEnumParameter( 'continue', "$row->ipb_timestamp|$row->ipb_id" );
268  break;
269  }
270  }
271  $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'block' );
272  }
273 
282  private function getRestrictionData( IResultWrapper $result, $limit ) {
283  $partialIds = [];
284  $count = 0;
285  foreach ( $result as $row ) {
286  if ( ++$count <= $limit && !$row->ipb_sitewide ) {
287  $partialIds[] = (int)$row->ipb_id;
288  }
289  }
290 
291  $restrictions = $this->blockRestrictionStore->loadByBlockId( $partialIds );
292 
293  $data = [];
294  $keys = [
295  'page' => 'pages',
296  'ns' => 'namespaces',
297  ];
298  if ( $this->getConfig()->get( MainConfigNames::EnablePartialActionBlocks ) ) {
299  $keys['action'] = 'actions';
300  }
301 
302  foreach ( $restrictions as $restriction ) {
303  $key = $keys[$restriction->getType()];
304  $id = $restriction->getBlockId();
305  switch ( $restriction->getType() ) {
306  case 'page':
308  '@phan-var \MediaWiki\Block\Restriction\PageRestriction $restriction';
309  $value = [ 'id' => $restriction->getValue() ];
310  if ( $restriction->getTitle() ) {
311  self::addTitleInfo( $value, $restriction->getTitle() );
312  }
313  break;
314  case 'action':
315  $value = $this->blockActionInfo->getActionFromId( $restriction->getValue() );
316  break;
317  default:
318  $value = $restriction->getValue();
319  }
320 
321  if ( !isset( $data[$id][$key] ) ) {
322  $data[$id][$key] = [];
323  ApiResult::setIndexedTagName( $data[$id][$key], $restriction->getType() );
324  }
325  $data[$id][$key][] = $value;
326  }
327 
328  return $data;
329  }
330 
331  public function getAllowedParams() {
332  $blockCIDRLimit = $this->getConfig()->get( MainConfigNames::BlockCIDRLimit );
333 
334  return [
335  'start' => [
336  ParamValidator::PARAM_TYPE => 'timestamp'
337  ],
338  'end' => [
339  ParamValidator::PARAM_TYPE => 'timestamp',
340  ],
341  'dir' => [
342  ParamValidator::PARAM_TYPE => [
343  'newer',
344  'older'
345  ],
346  ParamValidator::PARAM_DEFAULT => 'older',
347  ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
349  'newer' => 'api-help-paramvalue-direction-newer',
350  'older' => 'api-help-paramvalue-direction-older',
351  ],
352  ],
353  'ids' => [
354  ParamValidator::PARAM_TYPE => 'integer',
355  ParamValidator::PARAM_ISMULTI => true
356  ],
357  'users' => [
358  ParamValidator::PARAM_TYPE => 'user',
359  UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'cidr' ],
360  ParamValidator::PARAM_ISMULTI => true
361  ],
362  'ip' => [
364  'apihelp-query+blocks-param-ip',
365  $blockCIDRLimit['IPv4'],
366  $blockCIDRLimit['IPv6'],
367  ],
368  ],
369  'limit' => [
370  ParamValidator::PARAM_DEFAULT => 10,
371  ParamValidator::PARAM_TYPE => 'limit',
372  IntegerDef::PARAM_MIN => 1,
373  IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
374  IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
375  ],
376  'prop' => [
377  ParamValidator::PARAM_DEFAULT => 'id|user|by|timestamp|expiry|reason|flags',
378  ParamValidator::PARAM_TYPE => [
379  'id',
380  'user',
381  'userid',
382  'by',
383  'byid',
384  'timestamp',
385  'expiry',
386  'reason',
387  'range',
388  'flags',
389  'restrictions',
390  ],
391  ParamValidator::PARAM_ISMULTI => true,
393  ],
394  'show' => [
395  ParamValidator::PARAM_TYPE => [
396  'account',
397  '!account',
398  'temp',
399  '!temp',
400  'ip',
401  '!ip',
402  'range',
403  '!range',
404  ],
405  ParamValidator::PARAM_ISMULTI => true
406  ],
407  'continue' => [
408  ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
409  ],
410  ];
411  }
412 
413  protected function getExamplesMessages() {
414  return [
415  'action=query&list=blocks'
416  => 'apihelp-query+blocks-example-simple',
417  'action=query&list=blocks&bkusers=Alice|Bob'
418  => 'apihelp-query+blocks-example-users',
419  ];
420  }
421 
422  public function getHelpUrls() {
423  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Blocks';
424  }
425 }
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:1516
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
Definition: ApiBase.php:1718
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition: ApiBase.php:210
const LIMIT_BIG1
Fast query, standard limit.
Definition: ApiBase.php:235
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition: ApiBase.php:982
getResult()
Get the result object.
Definition: ApiBase.php:668
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:808
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:170
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition: ApiBase.php:237
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:529
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, BlockActionInfo $blockActionInfo, BlockRestrictionStore $blockRestrictionStore, CommentStore $commentStore)
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
const META_TYPE
Key for the 'type' metadata item.
Definition: ApiResult.php:110
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:604
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
Definition: ApiResult.php:1199
Defines the actions that can be blocked by a partial block.
Handle database storage of comments such as edit summaries and log reasons.
A class containing constants representing the names of configuration variables.
Service for formatting and validating API parameters.
Type definition for integer types.
Definition: IntegerDef.php:23
Result wrapper for grabbing data queried from an IDatabase object.
return true
Definition: router.php:90