MediaWiki REL1_35
ApiQueryBlocks.php
Go to the documentation of this file.
1<?php
25use 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' => [
367 ApiBase::PARAM_TYPE => 'limit',
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 ],
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}
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1437
const PARAM_MAX2
Definition ApiBase.php:86
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition ApiBase.php:750
const PARAM_MAX
Definition ApiBase.php:82
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
Definition ApiBase.php:1617
const PARAM_TYPE
Definition ApiBase.php:78
const PARAM_DFLT
Definition ApiBase.php:70
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:692
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:195
const PARAM_MIN
Definition ApiBase.php:90
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:220
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:944
getResult()
Get the result object.
Definition ApiBase.php:620
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:772
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:162
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:222
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:499
const PARAM_ISMULTI
Definition ApiBase.php:74
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) Stable to override.
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)
getExamplesMessages()
Returns usage examples for this module.
getHelpUrls()
Return links to more detailed help pages about the module.
static getRestrictionData(IResultWrapper $result, $limit)
Retrieves the restrictions based on the query result.
This is the main query class.
Definition ApiQuery.php:37
getUser()
Stable to override.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Type definition for user types.
Definition UserDef.php:23
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition User.php:1130
static isIP( $name)
Does the string match an anonymous IP address?
Definition User.php:951
Result wrapper for grabbing data queried from an IDatabase object.
return true
Definition router.php:92