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