MediaWiki  master
ApiQueryUsers.php
Go to the documentation of this file.
1 <?php
29 
35 class ApiQueryUsers extends ApiQueryBase {
37 
38  private $prop;
39 
41  private $userNameUtils;
42 
44  private $userFactory;
45 
47  private $userGroupManager;
48 
50  private $genderCache;
51 
53  private $authManager;
54 
60  protected static $publicProps = [
61  // everything except 'blockinfo' which might show hidden records if the user
62  // making the request has the appropriate permissions
63  'groups',
64  'groupmemberships',
65  'implicitgroups',
66  'rights',
67  'editcount',
68  'registration',
69  'emailable',
70  'gender',
71  'centralids',
72  'cancreate',
73  ];
74 
84  public function __construct(
85  ApiQuery $query,
86  $moduleName,
87  UserNameUtils $userNameUtils,
88  UserFactory $userFactory,
89  UserGroupManager $userGroupManager,
90  GenderCache $genderCache,
91  AuthManager $authManager
92  ) {
93  parent::__construct( $query, $moduleName, 'us' );
94  $this->userNameUtils = $userNameUtils;
95  $this->userFactory = $userFactory;
96  $this->userGroupManager = $userGroupManager;
97  $this->genderCache = $genderCache;
98  $this->authManager = $authManager;
99  }
100 
101  public function execute() {
102  $db = $this->getDB();
103  $params = $this->extractRequestParams();
104  $this->requireMaxOneParameter( $params, 'userids', 'users' );
105 
106  if ( $params['prop'] !== null ) {
107  $this->prop = array_fill_keys( $params['prop'], true );
108  } else {
109  $this->prop = [];
110  }
111  $useNames = $params['users'] !== null;
112 
113  $users = (array)$params['users'];
114  $userids = (array)$params['userids'];
115 
116  $goodNames = $done = [];
117  $result = $this->getResult();
118  // Canonicalize user names
119  foreach ( $users as $u ) {
120  $n = $this->userNameUtils->getCanonical( $u );
121  if ( $n === false || $n === '' ) {
122  $vals = [ 'name' => $u, 'invalid' => true ];
123  $fit = $result->addValue( [ 'query', $this->getModuleName() ],
124  null, $vals );
125  if ( !$fit ) {
126  $this->setContinueEnumParameter( 'users',
127  implode( '|', array_diff( $users, $done ) ) );
128  $goodNames = [];
129  break;
130  }
131  $done[] = $u;
132  } else {
133  $goodNames[] = $n;
134  }
135  }
136 
137  if ( $useNames ) {
138  $parameters = &$goodNames;
139  } else {
140  $parameters = &$userids;
141  }
142 
143  $result = $this->getResult();
144 
145  if ( count( $parameters ) ) {
146  $userQuery = User::getQueryInfo();
147  $this->addTables( $userQuery['tables'] );
148  $this->addFields( $userQuery['fields'] );
149  $this->addJoinConds( $userQuery['joins'] );
150  if ( $useNames ) {
151  $this->addWhereFld( 'user_name', $goodNames );
152  } else {
153  $this->addWhereFld( 'user_id', $userids );
154  }
155 
156  $this->addBlockInfoToQuery( isset( $this->prop['blockinfo'] ) );
157 
158  $data = [];
159  $res = $this->select( __METHOD__ );
160  $this->resetQueryParams();
161 
162  // get user groups if needed
163  if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) {
164  $userGroups = [];
165 
166  $this->addTables( 'user' );
167  if ( $useNames ) {
168  $this->addWhereFld( 'user_name', $goodNames );
169  } else {
170  $this->addWhereFld( 'user_id', $userids );
171  }
172 
173  $this->addTables( 'user_groups' );
174  $this->addJoinConds( [ 'user_groups' => [ 'JOIN', 'ug_user=user_id' ] ] );
175  $this->addFields( [ 'user_name' ] );
176  $this->addFields( $this->userGroupManager->getQueryInfo()['fields'] );
177  $this->addWhere( 'ug_expiry IS NULL OR ug_expiry >= ' .
178  $db->addQuotes( $db->timestamp() ) );
179  $userGroupsRes = $this->select( __METHOD__ );
180 
181  foreach ( $userGroupsRes as $row ) {
182  $userGroups[$row->user_name][] = $row;
183  }
184  }
185  if ( isset( $this->prop['gender'] ) ) {
186  $userNames = [];
187  foreach ( $res as $row ) {
188  $userNames[] = $row->user_name;
189  }
190  $this->genderCache->doQuery( $userNames, __METHOD__ );
191  }
192 
193  foreach ( $res as $row ) {
194  // create user object and pass along $userGroups if set
195  // that reduces the number of database queries needed in User dramatically
196  if ( !isset( $userGroups ) ) {
197  $user = $this->userFactory->newFromRow( $row );
198  } else {
199  if ( !isset( $userGroups[$row->user_name] ) || !is_array( $userGroups[$row->user_name] ) ) {
200  $userGroups[$row->user_name] = [];
201  }
202  $user = $this->userFactory->newFromRow( $row, [ 'user_groups' => $userGroups[$row->user_name] ] );
203  }
204  if ( $useNames ) {
205  $key = $user->getName();
206  } else {
207  $key = $user->getId();
208  }
209  $data[$key]['userid'] = $user->getId();
210  $data[$key]['name'] = $user->getName();
211 
212  if ( isset( $this->prop['editcount'] ) ) {
213  $data[$key]['editcount'] = $user->getEditCount();
214  }
215 
216  if ( isset( $this->prop['registration'] ) ) {
217  $data[$key]['registration'] = wfTimestampOrNull( TS_ISO_8601, $user->getRegistration() );
218  }
219 
220  if ( isset( $this->prop['groups'] ) ) {
221  $data[$key]['groups'] = $this->userGroupManager->getUserEffectiveGroups( $user );
222  }
223 
224  if ( isset( $this->prop['groupmemberships'] ) ) {
225  $data[$key]['groupmemberships'] = array_map( static function ( $ugm ) {
226  return [
227  'group' => $ugm->getGroup(),
228  'expiry' => ApiResult::formatExpiry( $ugm->getExpiry() ),
229  ];
230  }, $this->userGroupManager->getUserGroupMemberships( $user ) );
231  }
232 
233  if ( isset( $this->prop['implicitgroups'] ) ) {
234  $data[$key]['implicitgroups'] = $this->userGroupManager->getUserImplicitGroups( $user );
235  }
236 
237  if ( isset( $this->prop['rights'] ) ) {
238  $data[$key]['rights'] = $this->getPermissionManager()
239  ->getUserPermissions( $user );
240  }
241  if ( $row->ipb_deleted ) {
242  $data[$key]['hidden'] = true;
243  }
244  if ( isset( $this->prop['blockinfo'] ) && $row->ipb_by_text !== null ) {
245  $data[$key] += $this->getBlockDetails( DatabaseBlock::newFromRow( $row ) );
246  }
247 
248  if ( isset( $this->prop['emailable'] ) ) {
249  $data[$key]['emailable'] = $user->canReceiveEmail();
250  }
251 
252  if ( isset( $this->prop['gender'] ) ) {
253  $data[$key]['gender'] = $this->genderCache->getGenderOf( $user, __METHOD__ );
254  }
255 
256  if ( isset( $this->prop['centralids'] ) ) {
258  $this->getConfig(), $user, $params['attachedwiki']
259  );
260  }
261  }
262  }
263 
264  $context = $this->getContext();
265  // Second pass: add result data to $retval
266  foreach ( $parameters as $u ) {
267  if ( !isset( $data[$u] ) ) {
268  if ( $useNames ) {
269  $data[$u] = [ 'name' => $u ];
270  $urPage = new UserrightsPage;
271  $urPage->setContext( $context );
272 
273  $iwUser = $urPage->fetchUser( $u );
274 
275  if ( $iwUser instanceof UserRightsProxy ) {
276  $data[$u]['interwiki'] = true;
277  } else {
278  $data[$u]['missing'] = true;
279  if ( isset( $this->prop['cancreate'] ) ) {
280  $status = $this->authManager->canCreateAccount( $u );
281  $data[$u]['cancreate'] = $status->isGood();
282  if ( !$status->isGood() ) {
283  $data[$u]['cancreateerror'] = $this->getErrorFormatter()->arrayFromStatus( $status );
284  }
285  }
286  }
287  } else {
288  $data[$u] = [ 'userid' => $u, 'missing' => true ];
289  }
290 
291  } else {
292  if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) {
293  ApiResult::setArrayType( $data[$u]['groups'], 'array' );
294  ApiResult::setIndexedTagName( $data[$u]['groups'], 'g' );
295  }
296  if ( isset( $this->prop['groupmemberships'] ) && isset( $data[$u]['groupmemberships'] ) ) {
297  ApiResult::setArrayType( $data[$u]['groupmemberships'], 'array' );
298  ApiResult::setIndexedTagName( $data[$u]['groupmemberships'], 'groupmembership' );
299  }
300  if ( isset( $this->prop['implicitgroups'] ) && isset( $data[$u]['implicitgroups'] ) ) {
301  ApiResult::setArrayType( $data[$u]['implicitgroups'], 'array' );
302  ApiResult::setIndexedTagName( $data[$u]['implicitgroups'], 'g' );
303  }
304  if ( isset( $this->prop['rights'] ) && isset( $data[$u]['rights'] ) ) {
305  ApiResult::setArrayType( $data[$u]['rights'], 'array' );
306  ApiResult::setIndexedTagName( $data[$u]['rights'], 'r' );
307  }
308  }
309 
310  $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $data[$u] );
311  if ( !$fit ) {
312  if ( $useNames ) {
313  $this->setContinueEnumParameter( 'users',
314  implode( '|', array_diff( $users, $done ) ) );
315  } else {
316  $this->setContinueEnumParameter( 'userids',
317  implode( '|', array_diff( $userids, $done ) ) );
318  }
319  break;
320  }
321  $done[] = $u;
322  }
323  $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'user' );
324  }
325 
326  public function getCacheMode( $params ) {
327  if ( array_diff( (array)$params['prop'], static::$publicProps ) ) {
328  return 'anon-public-user-private';
329  } else {
330  return 'public';
331  }
332  }
333 
334  public function getAllowedParams() {
335  return [
336  'prop' => [
337  ParamValidator::PARAM_ISMULTI => true,
338  ParamValidator::PARAM_TYPE => [
339  'blockinfo',
340  'groups',
341  'groupmemberships',
342  'implicitgroups',
343  'rights',
344  'editcount',
345  'registration',
346  'emailable',
347  'gender',
348  'centralids',
349  'cancreate',
350  // When adding a prop, consider whether it should be added
351  // to self::$publicProps
352  ],
354  ],
355  'attachedwiki' => null,
356  'users' => [
357  ParamValidator::PARAM_ISMULTI => true
358  ],
359  'userids' => [
360  ParamValidator::PARAM_ISMULTI => true,
361  ParamValidator::PARAM_TYPE => 'integer'
362  ],
363  ];
364  }
365 
366  protected function getExamplesMessages() {
367  return [
368  'action=query&list=users&ususers=Example&usprop=groups|editcount|gender'
369  => 'apihelp-query+users-example-simple',
370  ];
371  }
372 
373  public function getHelpUrls() {
374  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Users';
375  }
376 }
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
getErrorFormatter()
Definition: ApiBase.php:640
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:686
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
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
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.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
resetQueryParams()
Blank the internal arrays with query parameters.
addFields( $value)
Add a set of fields to select to the internal array.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
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.
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.
static getCentralUserInfo(Config $config, UserIdentity $user, $attachedWiki=UserIdentity::LOCAL)
Get central user info.
Query module to get information about a list of users.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
static array $publicProps
Properties whose contents does not depend on who is looking at them.
getHelpUrls()
Return links to more detailed help pages about the module.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
getExamplesMessages()
Returns usage examples for this module.
__construct(ApiQuery $query, $moduleName, UserNameUtils $userNameUtils, UserFactory $userFactory, UserGroupManager $userGroupManager, GenderCache $genderCache, AuthManager $authManager)
getCacheMode( $params)
Get the cache mode for the data generated by this module.
This is the main query class.
Definition: ApiQuery.php:41
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:716
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
getContext()
Get the base IContextSource object.
Caches user genders when needed to use correct namespace aliases.
Definition: GenderCache.php:34
This serves as the entry point to the authentication system.
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Creates User objects.
Definition: UserFactory.php:38
UserNameUtils service.
setContext( $context)
Sets the context this SpecialPage is executed in.
Cut-down copy of User interface for local-interwiki-database user rights manipulation.
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:3385
Special page to allow managing user group membership.
Service for formatting and validating API parameters.
trait ApiQueryBlockInfoTrait
return true
Definition: router.php:90