MediaWiki  master
ApiQueryBlocks.php
Go to the documentation of this file.
1 <?php
25 use Wikimedia\IPUtils;
27 
34 
35  public function __construct( ApiQuery $query, $moduleName ) {
36  parent::__construct( $query, $moduleName, 'bk' );
37  }
38 
39  public function execute() {
40  $db = $this->getDB();
41  $commentStore = CommentStore::getStore();
42  $params = $this->extractRequestParams();
43  $this->requireMaxOneParameter( $params, 'users', 'ip' );
44 
45  $prop = array_flip( $params['prop'] );
46  $fld_id = isset( $prop['id'] );
47  $fld_user = isset( $prop['user'] );
48  $fld_userid = isset( $prop['userid'] );
49  $fld_by = isset( $prop['by'] );
50  $fld_byid = isset( $prop['byid'] );
51  $fld_timestamp = isset( $prop['timestamp'] );
52  $fld_expiry = isset( $prop['expiry'] );
53  $fld_reason = isset( $prop['reason'] );
54  $fld_range = isset( $prop['range'] );
55  $fld_flags = isset( $prop['flags'] );
56  $fld_restrictions = isset( $prop['restrictions'] );
57 
58  $result = $this->getResult();
59 
60  $this->addTables( 'ipblocks' );
61  $this->addFields( [ 'ipb_auto', 'ipb_id', 'ipb_timestamp' ] );
62 
63  $this->addFieldsIf( [ 'ipb_address', 'ipb_user' ], $fld_user || $fld_userid );
64  if ( $fld_by || $fld_byid ) {
65  $actorQuery = ActorMigration::newMigration()->getJoin( 'ipb_by' );
66  $this->addTables( $actorQuery['tables'] );
67  $this->addFields( $actorQuery['fields'] );
68  $this->addJoinConds( $actorQuery['joins'] );
69  }
70  $this->addFieldsIf( 'ipb_expiry', $fld_expiry );
71  $this->addFieldsIf( [ 'ipb_range_start', 'ipb_range_end' ], $fld_range );
72  $this->addFieldsIf( [ 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock',
73  'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk', 'ipb_sitewide' ],
74  $fld_flags );
75  $this->addFieldsIf( 'ipb_sitewide', $fld_restrictions );
76 
77  if ( $fld_reason ) {
78  $commentQuery = $commentStore->getJoin( 'ipb_reason' );
79  $this->addTables( $commentQuery['tables'] );
80  $this->addFields( $commentQuery['fields'] );
81  $this->addJoinConds( $commentQuery['joins'] );
82  }
83 
84  $this->addOption( 'LIMIT', $params['limit'] + 1 );
86  'ipb_timestamp',
87  $params['dir'],
88  $params['start'],
89  $params['end']
90  );
91  // Include in ORDER BY for uniqueness
92  $this->addWhereRange( 'ipb_id', $params['dir'], null, null );
93 
94  if ( $params['continue'] !== null ) {
95  $cont = explode( '|', $params['continue'] );
96  $this->dieContinueUsageIf( count( $cont ) != 2 );
97  $op = ( $params['dir'] == 'newer' ? '>' : '<' );
98  $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
99  $continueId = (int)$cont[1];
100  $this->dieContinueUsageIf( $continueId != $cont[1] );
101  $this->addWhere( "ipb_timestamp $op $continueTimestamp OR " .
102  "(ipb_timestamp = $continueTimestamp AND " .
103  "ipb_id $op= $continueId)"
104  );
105  }
106 
107  if ( isset( $params['ids'] ) ) {
108  $this->addWhereIDsFld( 'ipblocks', 'ipb_id', $params['ids'] );
109  }
110  if ( isset( $params['users'] ) ) {
111  $usernames = [];
112  foreach ( (array)$params['users'] as $u ) {
113  $usernames[] = $this->prepareUsername( $u );
114  }
115  $this->addWhereFld( 'ipb_address', $usernames );
116  $this->addWhereFld( 'ipb_auto', 0 );
117  }
118  if ( isset( $params['ip'] ) ) {
119  $blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' );
120  if ( IPUtils::isIPv4( $params['ip'] ) ) {
121  $type = 'IPv4';
122  $cidrLimit = $blockCIDRLimit['IPv4'];
123  $prefixLen = 0;
124  } elseif ( IPUtils::isIPv6( $params['ip'] ) ) {
125  $type = 'IPv6';
126  $cidrLimit = $blockCIDRLimit['IPv6'];
127  $prefixLen = 3; // IPUtils::toHex output is prefixed with "v6-"
128  } else {
129  $this->dieWithError( 'apierror-badip', 'param_ip' );
130  }
131 
132  # Check range validity, if it's a CIDR
133  list( $ip, $range ) = IPUtils::parseCIDR( $params['ip'] );
134  if ( $ip !== false && $range !== false && $range < $cidrLimit ) {
135  $this->dieWithError( [ 'apierror-cidrtoobroad', $type, $cidrLimit ] );
136  }
137 
138  # Let IPUtils::parseRange handle calculating $upper, instead of duplicating the logic here.
139  list( $lower, $upper ) = IPUtils::parseRange( $params['ip'] );
140 
141  # Extract the common prefix to any rangeblock affecting this IP/CIDR
142  $prefix = substr( $lower, 0, $prefixLen + floor( $cidrLimit / 4 ) );
143 
144  # Fairly hard to make a malicious SQL statement out of hex characters,
145  # but it is good practice to add quotes
146  $lower = $db->addQuotes( $lower );
147  $upper = $db->addQuotes( $upper );
148 
149  $this->addWhere( [
150  'ipb_range_start' . $db->buildLike( $prefix, $db->anyString() ),
151  'ipb_range_start <= ' . $lower,
152  'ipb_range_end >= ' . $upper,
153  'ipb_auto' => 0
154  ] );
155  }
156 
157  if ( $params['show'] !== null ) {
158  $show = array_flip( $params['show'] );
159 
160  /* Check for conflicting parameters. */
161  if ( ( isset( $show['account'] ) && isset( $show['!account'] ) )
162  || ( isset( $show['ip'] ) && isset( $show['!ip'] ) )
163  || ( isset( $show['range'] ) && isset( $show['!range'] ) )
164  || ( isset( $show['temp'] ) && isset( $show['!temp'] ) )
165  ) {
166  $this->dieWithError( 'apierror-show' );
167  }
168 
169  $this->addWhereIf( 'ipb_user = 0', isset( $show['!account'] ) );
170  $this->addWhereIf( 'ipb_user != 0', isset( $show['account'] ) );
171  $this->addWhereIf( 'ipb_user != 0 OR ipb_range_end > ipb_range_start', isset( $show['!ip'] ) );
172  $this->addWhereIf( 'ipb_user = 0 AND ipb_range_end = ipb_range_start', isset( $show['ip'] ) );
173  $this->addWhereIf( 'ipb_expiry = ' .
174  $db->addQuotes( $db->getInfinity() ), isset( $show['!temp'] ) );
175  $this->addWhereIf( 'ipb_expiry != ' .
176  $db->addQuotes( $db->getInfinity() ), isset( $show['temp'] ) );
177  $this->addWhereIf( 'ipb_range_end = ipb_range_start', isset( $show['!range'] ) );
178  $this->addWhereIf( 'ipb_range_end > ipb_range_start', isset( $show['range'] ) );
179  }
180 
181  if ( !$this->getPermissionManager()->userHasRight( $this->getUser(), 'hideuser' ) ) {
182  $this->addWhereFld( 'ipb_deleted', 0 );
183  }
184 
185  # Filter out expired rows
186  $this->addWhere( 'ipb_expiry > ' . $db->addQuotes( $db->timestamp() ) );
187 
188  $res = $this->select( __METHOD__ );
189 
190  $restrictions = [];
191  if ( $fld_restrictions ) {
192  $restrictions = self::getRestrictionData( $res, $params['limit'] );
193  }
194 
195  $count = 0;
196  foreach ( $res as $row ) {
197  if ( ++$count > $params['limit'] ) {
198  // We've had enough
199  $this->setContinueEnumParameter( 'continue', "$row->ipb_timestamp|$row->ipb_id" );
200  break;
201  }
202  $block = [
203  ApiResult::META_TYPE => 'assoc',
204  ];
205  if ( $fld_id ) {
206  $block['id'] = (int)$row->ipb_id;
207  }
208  if ( $fld_user && !$row->ipb_auto ) {
209  $block['user'] = $row->ipb_address;
210  }
211  if ( $fld_userid && !$row->ipb_auto ) {
212  $block['userid'] = (int)$row->ipb_user;
213  }
214  if ( $fld_by ) {
215  $block['by'] = $row->ipb_by_text;
216  }
217  if ( $fld_byid ) {
218  $block['byid'] = (int)$row->ipb_by;
219  }
220  if ( $fld_timestamp ) {
221  $block['timestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
222  }
223  if ( $fld_expiry ) {
224  $block['expiry'] = ApiResult::formatExpiry( $row->ipb_expiry );
225  }
226  if ( $fld_reason ) {
227  $block['reason'] = $commentStore->getComment( 'ipb_reason', $row )->text;
228  }
229  if ( $fld_range && !$row->ipb_auto ) {
230  $block['rangestart'] = IPUtils::formatHex( $row->ipb_range_start );
231  $block['rangeend'] = IPUtils::formatHex( $row->ipb_range_end );
232  }
233  if ( $fld_flags ) {
234  // For clarity, these flags use the same names as their action=block counterparts
235  $block['automatic'] = (bool)$row->ipb_auto;
236  $block['anononly'] = (bool)$row->ipb_anon_only;
237  $block['nocreate'] = (bool)$row->ipb_create_account;
238  $block['autoblock'] = (bool)$row->ipb_enable_autoblock;
239  $block['noemail'] = (bool)$row->ipb_block_email;
240  $block['hidden'] = (bool)$row->ipb_deleted;
241  $block['allowusertalk'] = (bool)$row->ipb_allow_usertalk;
242  $block['partial'] = !(bool)$row->ipb_sitewide;
243  }
244 
245  if ( $fld_restrictions ) {
246  $block['restrictions'] = [];
247  if ( !$row->ipb_sitewide && isset( $restrictions[$row->ipb_id] ) ) {
248  $block['restrictions'] = $restrictions[$row->ipb_id];
249  }
250  }
251 
252  $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $block );
253  if ( !$fit ) {
254  $this->setContinueEnumParameter( 'continue', "$row->ipb_timestamp|$row->ipb_id" );
255  break;
256  }
257  }
258  $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'block' );
259  }
260 
261  protected function prepareUsername( $user ) {
262  if ( !$user ) {
263  $encParamName = $this->encodeParamName( 'users' );
264  $this->dieWithError( [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $user ) ],
265  "baduser_{$encParamName}"
266  );
267  }
268  $name = User::isIP( $user )
269  ? $user
270  : User::getCanonicalName( $user, 'valid' );
271  if ( $name === false ) {
272  $encParamName = $this->encodeParamName( 'users' );
273  $this->dieWithError( [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $user ) ],
274  "baduser_{$encParamName}"
275  );
276  }
277  return $name;
278  }
279 
288  private static function getRestrictionData( IResultWrapper $result, $limit ) {
289  $partialIds = [];
290  $count = 0;
291  foreach ( $result as $row ) {
292  if ( ++$count <= $limit && !$row->ipb_sitewide ) {
293  $partialIds[] = (int)$row->ipb_id;
294  }
295  }
296 
297  $blockRestrictionStore = MediaWikiServices::getInstance()->getBlockRestrictionStore();
298  $restrictions = $blockRestrictionStore->loadByBlockId( $partialIds );
299 
300  $data = [];
301  $keys = [
302  'page' => 'pages',
303  'ns' => 'namespaces',
304  ];
305  foreach ( $restrictions as $restriction ) {
306  $key = $keys[$restriction->getType()];
307  $id = $restriction->getBlockId();
308  switch ( $restriction->getType() ) {
309  case 'page':
311  '@phan-var \MediaWiki\Block\Restriction\PageRestriction $restriction';
312  $value = [ 'id' => $restriction->getValue() ];
313  if ( $restriction->getTitle() ) {
314  self::addTitleInfo( $value, $restriction->getTitle() );
315  }
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( 'BlockCIDRLimit' );
333 
334  return [
335  'start' => [
336  ApiBase::PARAM_TYPE => 'timestamp'
337  ],
338  'end' => [
339  ApiBase::PARAM_TYPE => 'timestamp',
340  ],
341  'dir' => [
343  'newer',
344  'older'
345  ],
346  ApiBase::PARAM_DFLT => 'older',
347  ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
348  ],
349  'ids' => [
350  ApiBase::PARAM_TYPE => 'integer',
352  ],
353  'users' => [
354  ApiBase::PARAM_TYPE => 'user',
355  UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'cidr' ],
357  ],
358  'ip' => [
360  'apihelp-query+blocks-param-ip',
361  $blockCIDRLimit['IPv4'],
362  $blockCIDRLimit['IPv6'],
363  ],
364  ],
365  'limit' => [
366  ApiBase::PARAM_DFLT => 10,
367  ApiBase::PARAM_TYPE => 'limit',
368  ApiBase::PARAM_MIN => 1,
371  ],
372  'prop' => [
373  ApiBase::PARAM_DFLT => 'id|user|by|timestamp|expiry|reason|flags',
375  'id',
376  'user',
377  'userid',
378  'by',
379  'byid',
380  'timestamp',
381  'expiry',
382  'reason',
383  'range',
384  'flags',
385  'restrictions',
386  ],
387  ApiBase::PARAM_ISMULTI => true,
389  ],
390  'show' => [
392  'account',
393  '!account',
394  'temp',
395  '!temp',
396  'ip',
397  '!ip',
398  'range',
399  '!range',
400  ],
402  ],
403  'continue' => [
404  ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
405  ],
406  ];
407  }
408 
409  protected function getExamplesMessages() {
410  return [
411  'action=query&list=blocks'
412  => 'apihelp-query+blocks-example-simple',
413  'action=query&list=blocks&bkusers=Alice|Bob'
414  => 'apihelp-query+blocks-example-users',
415  ];
416  }
417 
418  public function getHelpUrls() {
419  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Blocks';
420  }
421 }
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:63
ApiQueryBase\addWhereIDsFld
addWhereIDsFld( $table, $field, $ids)
Like addWhereFld for an integer list of IDs.
Definition: ApiQueryBase.php:312
ApiQueryBase\addFields
addFields( $value)
Add a set of fields to select to the internal array.
Definition: ApiQueryBase.php:215
ApiQuery
This is the main query class.
Definition: ApiQuery.php:37
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:152
ApiResult\META_TYPE
const META_TYPE
Key for the 'type' metadata item.
Definition: ApiResult.php:110
ApiBase\dieWithError
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1436
ApiQueryBase\addTimestampWhereRange
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
Definition: ApiQueryBase.php:369
ApiBase\PARAM_HELP_MSG
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:106
ApiQueryBlocks\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiQueryBlocks.php:331
true
return true
Definition: router.php:90
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1808
ApiBase\PARAM_TYPE
const PARAM_TYPE
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:70
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:564
ApiQueryBlocks\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiQueryBlocks.php:409
ApiQueryBlocks\getRestrictionData
static getRestrictionData(IResultWrapper $result, $limit)
Retrieves the restrictions based on the query result.
Definition: ApiQueryBlocks.php:288
ApiQueryBase\addOption
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
Definition: ApiQueryBase.php:381
$res
$res
Definition: testCompression.php:57
ContextSource\getUser
getUser()
Definition: ContextSource.php:120
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:139
ApiQueryBase\addFieldsIf
addFieldsIf( $value, $condition)
Same as addFields(), but add the fields only if a condition is met.
Definition: ApiQueryBase.php:225
ApiQueryBlocks\getHelpUrls
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiQueryBlocks.php:418
Wikimedia\ParamValidator\ParamValidator::TypeDef\UserDef
Type definition for user types.
Definition: UserDef.php:23
ApiQueryBlocks
Query module to enumerate all user blocks.
Definition: ApiQueryBlocks.php:33
ApiBase\PARAM_MIN
const PARAM_MIN
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:73
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:24
ApiQueryBase
This is a base class for all Query modules.
Definition: ApiQueryBase.php:37
ApiBase\LIMIT_BIG1
const LIMIT_BIG1
Fast query, standard limit.
Definition: ApiBase.php:164
ApiQueryBase\getDB
getDB()
Get the Query database connection (read-only) Stable for overriding.
Definition: ApiQueryBase.php:119
ApiBase\PARAM_MAX
const PARAM_MAX
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:71
ApiQueryBlocks\__construct
__construct(ApiQuery $query, $moduleName)
Definition: ApiQueryBlocks.php:35
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:950
ApiQueryBase\addTables
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
Definition: ApiQueryBase.php:185
ApiQueryBase\select
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
Definition: ApiQueryBase.php:402
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:716
ApiQueryBlocks\prepareUsername
prepareUsername( $user)
Definition: ApiQueryBlocks.php:261
ApiQueryBase\addWhereRange
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.
Definition: ApiQueryBase.php:340
ApiResult\setIndexedTagName
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:604
ApiBase\requireMaxOneParameter
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:888
ApiBase\dieContinueUsageIf
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
Definition: ApiBase.php:1616
ApiBase\encodeParamName
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition: ApiBase.php:694
ApiBase\getPermissionManager
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:636
ApiQueryBase\addJoinConds
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
Definition: ApiQueryBase.php:204
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1487
ApiQueryBase\addWhereFld
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
Definition: ApiQueryBase.php:285
ApiQueryBlocks\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiQueryBlocks.php:39
ApiBase\LIMIT_BIG2
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition: ApiBase.php:166
User\getCanonicalName
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition: User.php:1129
ApiBase\PARAM_DFLT
const PARAM_DFLT
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:68
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:443
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:69
$keys
$keys
Definition: testCompression.php:72
ApiBase\PARAM_MAX2
const PARAM_MAX2
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:72
ApiResult\formatExpiry
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
Definition: ApiResult.php:1193
ApiQueryBase\addWhere
addWhere( $value)
Add a set of WHERE clauses to the internal array.
Definition: ApiQueryBase.php:248
ApiQueryBase\setContinueEnumParameter
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
Definition: ApiQueryBase.php:519
ApiBase\PARAM_HELP_MSG_PER_VALUE
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:139
CommentStore\getStore
static getStore()
Definition: CommentStore.php:109
ApiQueryBase\addWhereIf
addWhereIf( $value, $condition)
Same as addWhere(), but add the WHERE clauses only if a condition is met.
Definition: ApiQueryBase.php:266
ApiQueryBase\addTitleInfo
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
Definition: ApiQueryBase.php:470
$type
$type
Definition: testCompression.php:52