X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/19e297c21b10b1b8a3acad5e73fc71dcb35db44a..6932310fd58ebef145fa01eb76edf7150284d8ea:/includes/api/ApiQueryUsers.php diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php index 0632aeee..fbf1f9eb 100644 --- a/includes/api/ApiQueryUsers.php +++ b/includes/api/ApiQueryUsers.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2007 Roan Kattouw ".@gmail.com" * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,21 +24,36 @@ * @file */ -if ( !defined( 'MEDIAWIKI' ) ) { - // Eclipse helper - will be ignored in production - require_once( 'ApiQueryBase.php' ); -} - /** * Query module to get information about a list of users * * @ingroup API */ - class ApiQueryUsers extends ApiQueryBase { +class ApiQueryUsers extends ApiQueryBase { private $tokenFunctions, $prop; - public function __construct( $query, $moduleName ) { + /** + * Properties whose contents does not depend on who is looking at them. If the usprops field + * contains anything not listed here, the cache mode will never be public for logged-in users. + * @var array + */ + protected static $publicProps = [ + // everything except 'blockinfo' which might show hidden records if the user + // making the request has the appropriate permissions + 'groups', + 'groupmemberships', + 'implicitgroups', + 'rights', + 'editcount', + 'registration', + 'emailable', + 'gender', + 'centralids', + 'cancreate', + ]; + + public function __construct( ApiQuery $query, $moduleName ) { parent::__construct( $query, $moduleName, 'us' ); } @@ -46,7 +61,8 @@ if ( !defined( 'MEDIAWIKI' ) ) { * Get an array mapping token names to their handler functions. * The prototype for a token function is func($user) * it should return a token or false (permission denied) - * @return Array tokenname => function + * @deprecated since 1.24 + * @return array Array of tokenname => function */ protected function getTokenFunctions() { // Don't call the hooks twice @@ -54,48 +70,63 @@ if ( !defined( 'MEDIAWIKI' ) ) { return $this->tokenFunctions; } - // If we're in JSON callback mode, no tokens can be obtained - if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) { - return array(); + // If we're in a mode that breaks the same-origin policy, no tokens can + // be obtained + if ( $this->lacksSameOriginSecurity() ) { + return []; } - $this->tokenFunctions = array( - 'userrights' => array( 'ApiQueryUsers', 'getUserrightsToken' ), - ); - wfRunHooks( 'APIQueryUsersTokens', array( &$this->tokenFunctions ) ); + $this->tokenFunctions = [ + 'userrights' => [ 'ApiQueryUsers', 'getUserrightsToken' ], + ]; + Hooks::run( 'APIQueryUsersTokens', [ &$this->tokenFunctions ] ); + return $this->tokenFunctions; } + /** + * @deprecated since 1.24 + * @param User $user + * @return string + */ public static function getUserrightsToken( $user ) { global $wgUser; + // Since the permissions check for userrights is non-trivial, // don't bother with it here - return $wgUser->editToken( $user->getName() ); + return $wgUser->getEditToken( $user->getName() ); } public function execute() { + $db = $this->getDB(); + $commentStore = new CommentStore( 'ipb_reason' ); + $params = $this->extractRequestParams(); + $this->requireMaxOneParameter( $params, 'userids', 'users' ); if ( !is_null( $params['prop'] ) ) { $this->prop = array_flip( $params['prop'] ); } else { - $this->prop = array(); + $this->prop = []; } + $useNames = !is_null( $params['users'] ); $users = (array)$params['users']; - $goodNames = $done = array(); + $userids = (array)$params['userids']; + + $goodNames = $done = []; $result = $this->getResult(); // Canonicalize user names foreach ( $users as $u ) { $n = User::getCanonicalName( $u ); if ( $n === false || $n === '' ) { - $vals = array( 'name' => $u, 'invalid' => '' ); - $fit = $result->addValue( array( 'query', $this->getModuleName() ), - null, $vals ); + $vals = [ 'name' => $u, 'invalid' => true ]; + $fit = $result->addValue( [ 'query', $this->getModuleName() ], + null, $vals ); if ( !$fit ) { $this->setContinueEnumParameter( 'users', - implode( '|', array_diff( $users, $done ) ) ); - $goodNames = array(); + implode( '|', array_diff( $users, $done ) ) ); + $goodNames = []; break; } $done[] = $u; @@ -104,59 +135,114 @@ if ( !defined( 'MEDIAWIKI' ) ) { } } - if ( count( $goodNames ) ) { - $this->addTables( 'user', 'u1' ); - $this->addFields( 'u1.*' ); - $this->addWhereFld( 'u1.user_name', $goodNames ); + if ( $useNames ) { + $parameters = &$goodNames; + } else { + $parameters = &$userids; + } - if ( isset( $this->prop['groups'] ) ) { - $this->addTables( 'user_groups' ); - $this->addJoinConds( array( 'user_groups' => array( 'LEFT JOIN', 'ug_user=u1.user_id' ) ) ); - $this->addFields( 'ug_group' ); + $result = $this->getResult(); + + if ( count( $parameters ) ) { + $this->addTables( 'user' ); + $this->addFields( User::selectFields() ); + if ( $useNames ) { + $this->addWhereFld( 'user_name', $goodNames ); + } else { + $this->addWhereFld( 'user_id', $userids ); } $this->showHiddenUsersAddBlockInfo( isset( $this->prop['blockinfo'] ) ); - $data = array(); + $data = []; $res = $this->select( __METHOD__ ); + $this->resetQueryParams(); + + // get user groups if needed + if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) { + $userGroups = []; + + $this->addTables( 'user' ); + if ( $useNames ) { + $this->addWhereFld( 'user_name', $goodNames ); + } else { + $this->addWhereFld( 'user_id', $userids ); + } + + $this->addTables( 'user_groups' ); + $this->addJoinConds( [ 'user_groups' => [ 'INNER JOIN', 'ug_user=user_id' ] ] ); + $this->addFields( [ 'user_name' ] ); + $this->addFields( UserGroupMembership::selectFields() ); + $this->addWhere( 'ug_expiry IS NULL OR ug_expiry >= ' . + $db->addQuotes( $db->timestamp() ) ); + $userGroupsRes = $this->select( __METHOD__ ); + + foreach ( $userGroupsRes as $row ) { + $userGroups[$row->user_name][] = $row; + } + } + foreach ( $res as $row ) { - $user = User::newFromRow( $row ); - $name = $user->getName(); - $data[$name]['name'] = $name; + // create user object and pass along $userGroups if set + // that reduces the number of database queries needed in User dramatically + if ( !isset( $userGroups ) ) { + $user = User::newFromRow( $row ); + } else { + if ( !isset( $userGroups[$row->user_name] ) || !is_array( $userGroups[$row->user_name] ) ) { + $userGroups[$row->user_name] = []; + } + $user = User::newFromRow( $row, [ 'user_groups' => $userGroups[$row->user_name] ] ); + } + if ( $useNames ) { + $key = $user->getName(); + } else { + $key = $user->getId(); + } + $data[$key]['userid'] = $user->getId(); + $data[$key]['name'] = $user->getName(); if ( isset( $this->prop['editcount'] ) ) { - $data[$name]['editcount'] = intval( $user->getEditCount() ); + $data[$key]['editcount'] = $user->getEditCount(); } if ( isset( $this->prop['registration'] ) ) { - $data[$name]['registration'] = wfTimestampOrNull( TS_ISO_8601, $user->getRegistration() ); + $data[$key]['registration'] = wfTimestampOrNull( TS_ISO_8601, $user->getRegistration() ); } - if ( isset( $this->prop['groups'] ) && !is_null( $row->ug_group ) ) { - // This row contains only one group, others will be added from other rows - $data[$name]['groups'][] = $row->ug_group; + if ( isset( $this->prop['groups'] ) ) { + $data[$key]['groups'] = $user->getEffectiveGroups(); } - if ( isset( $this->prop['rights'] ) && !is_null( $row->ug_group ) ) { - if ( !isset( $data[$name]['rights'] ) ) { - $data[$name]['rights'] = User::getGroupPermissions( User::getImplicitGroups() ); - } + if ( isset( $this->prop['groupmemberships'] ) ) { + $data[$key]['groupmemberships'] = array_map( function ( $ugm ) { + return [ + 'group' => $ugm->getGroup(), + 'expiry' => ApiResult::formatExpiry( $ugm->getExpiry() ), + ]; + }, $user->getGroupMemberships() ); + } + + if ( isset( $this->prop['implicitgroups'] ) ) { + $data[$key]['implicitgroups'] = $user->getAutomaticGroups(); + } - $data[$name]['rights'] = array_unique( array_merge( $data[$name]['rights'], - User::getGroupPermissions( array( $row->ug_group ) ) ) ); - $result->setIndexedTagName( $data[$name]['rights'], 'r' ); + if ( isset( $this->prop['rights'] ) ) { + $data[$key]['rights'] = $user->getRights(); } if ( $row->ipb_deleted ) { - $data[$name]['hidden'] = ''; + $data[$key]['hidden'] = true; } if ( isset( $this->prop['blockinfo'] ) && !is_null( $row->ipb_by_text ) ) { - $data[$name]['blockedby'] = $row->ipb_by_text; - $data[$name]['blockreason'] = $row->ipb_reason; - $data[$name]['blockexpiry'] = $row->ipb_expiry; + $data[$key]['blockid'] = (int)$row->ipb_id; + $data[$key]['blockedby'] = $row->ipb_by_text; + $data[$key]['blockedbyid'] = (int)$row->ipb_by; + $data[$key]['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp ); + $data[$key]['blockreason'] = $commentStore->getComment( $row )->text; + $data[$key]['blockexpiry'] = $row->ipb_expiry; } - if ( isset( $this->prop['emailable'] ) && $user->canReceiveEmail() ) { - $data[$name]['emailable'] = ''; + if ( isset( $this->prop['emailable'] ) ) { + $data[$key]['emailable'] = $user->canReceiveEmail(); } if ( isset( $this->prop['gender'] ) ) { @@ -164,7 +250,13 @@ if ( !defined( 'MEDIAWIKI' ) ) { if ( strval( $gender ) === '' ) { $gender = 'unknown'; } - $data[$name]['gender'] = $gender; + $data[$key]['gender'] = $gender; + } + + if ( isset( $this->prop['centralids'] ) ) { + $data[$key] += ApiQueryUserInfo::getCentralUserInfo( + $this->getConfig(), $user, $params['attachedwiki'] + ); } if ( !is_null( $params['token'] ) ) { @@ -172,132 +264,146 @@ if ( !defined( 'MEDIAWIKI' ) ) { foreach ( $params['token'] as $t ) { $val = call_user_func( $tokenFunctions[$t], $user ); if ( $val === false ) { - $this->setWarning( "Action '$t' is not allowed for the current user" ); + $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] ); } else { - $data[$name][$t . 'token'] = $val; + $data[$key][$t . 'token'] = $val; } } } } } + + $context = $this->getContext(); // Second pass: add result data to $retval - foreach ( $goodNames as $u ) { + foreach ( $parameters as $u ) { if ( !isset( $data[$u] ) ) { - $data[$u] = array( 'name' => $u ); - $urPage = new UserrightsPage; - $iwUser = $urPage->fetchUser( $u ); - - if ( $iwUser instanceof UserRightsProxy ) { - $data[$u]['interwiki'] = ''; - - if ( !is_null( $params['token'] ) ) { - $tokenFunctions = $this->getTokenFunctions(); - - foreach ( $params['token'] as $t ) { - $val = call_user_func( $tokenFunctions[$t], $iwUser ); - if ( $val === false ) { - $this->setWarning( "Action '$t' is not allowed for the current user" ); - } else { - $data[$u][$t . 'token'] = $val; + if ( $useNames ) { + $data[$u] = [ 'name' => $u ]; + $urPage = new UserrightsPage; + $urPage->setContext( $context ); + + $iwUser = $urPage->fetchUser( $u ); + + if ( $iwUser instanceof UserRightsProxy ) { + $data[$u]['interwiki'] = true; + + if ( !is_null( $params['token'] ) ) { + $tokenFunctions = $this->getTokenFunctions(); + + foreach ( $params['token'] as $t ) { + $val = call_user_func( $tokenFunctions[$t], $iwUser ); + if ( $val === false ) { + $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] ); + } else { + $data[$u][$t . 'token'] = $val; + } + } + } + } else { + $data[$u]['missing'] = true; + if ( isset( $this->prop['cancreate'] ) ) { + $status = MediaWiki\Auth\AuthManager::singleton()->canCreateAccount( $u ); + $data[$u]['cancreate'] = $status->isGood(); + if ( !$status->isGood() ) { + $data[$u]['cancreateerror'] = $this->getErrorFormatter()->arrayFromStatus( $status ); } } } } else { - $data[$u]['missing'] = ''; + $data[$u] = [ 'userid' => $u, 'missing' => true ]; } + } else { if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) { - $autolist = ApiQueryUsers::getAutoGroups( User::newFromName( $u ) ); - - $data[$u]['groups'] = array_merge( $autolist, $data[$u]['groups'] ); - - $this->getResult()->setIndexedTagName( $data[$u]['groups'], 'g' ); + ApiResult::setArrayType( $data[$u]['groups'], 'array' ); + ApiResult::setIndexedTagName( $data[$u]['groups'], 'g' ); + } + if ( isset( $this->prop['groupmemberships'] ) && isset( $data[$u]['groupmemberships'] ) ) { + ApiResult::setArrayType( $data[$u]['groupmemberships'], 'array' ); + ApiResult::setIndexedTagName( $data[$u]['groupmemberships'], 'groupmembership' ); + } + if ( isset( $this->prop['implicitgroups'] ) && isset( $data[$u]['implicitgroups'] ) ) { + ApiResult::setArrayType( $data[$u]['implicitgroups'], 'array' ); + ApiResult::setIndexedTagName( $data[$u]['implicitgroups'], 'g' ); + } + if ( isset( $this->prop['rights'] ) && isset( $data[$u]['rights'] ) ) { + ApiResult::setArrayType( $data[$u]['rights'], 'array' ); + ApiResult::setIndexedTagName( $data[$u]['rights'], 'r' ); } } - $fit = $result->addValue( array( 'query', $this->getModuleName() ), - null, $data[$u] ); + + $fit = $result->addValue( [ 'query', $this->getModuleName() ], + null, $data[$u] ); if ( !$fit ) { - $this->setContinueEnumParameter( 'users', + if ( $useNames ) { + $this->setContinueEnumParameter( 'users', implode( '|', array_diff( $users, $done ) ) ); + } else { + $this->setContinueEnumParameter( 'userids', + implode( '|', array_diff( $userids, $done ) ) ); + } break; } $done[] = $u; } - return $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' ); - } - - /** - * Gets all the groups that a user is automatically a member of - * @return array - */ - public static function getAutoGroups( $user ) { - $groups = array( '*' ); - - if ( !$user->isAnon() ) { - $groups[] = 'user'; - } - - return array_merge( $groups, Autopromote::getAutopromoteGroups( $user ) ); + $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'user' ); } public function getCacheMode( $params ) { if ( isset( $params['token'] ) ) { return 'private'; - } else { + } elseif ( array_diff( (array)$params['prop'], static::$publicProps ) ) { return 'anon-public-user-private'; + } else { + return 'public'; } } public function getAllowedParams() { - return array( - 'prop' => array( - ApiBase::PARAM_DFLT => null, + return [ + 'prop' => [ ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_TYPE => array( + ApiBase::PARAM_TYPE => [ 'blockinfo', 'groups', + 'groupmemberships', + 'implicitgroups', + 'rights', 'editcount', 'registration', 'emailable', 'gender', - ) - ), - 'users' => array( + 'centralids', + 'cancreate', + // When adding a prop, consider whether it should be added + // to self::$publicProps + ], + ApiBase::PARAM_HELP_MSG_PER_VALUE => [], + ], + 'attachedwiki' => null, + 'users' => [ ApiBase::PARAM_ISMULTI => true - ), - 'token' => array( + ], + 'userids' => [ + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_TYPE => 'integer' + ], + 'token' => [ + ApiBase::PARAM_DEPRECATED => true, ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ), ApiBase::PARAM_ISMULTI => true - ), - ); - } - - public function getParamDescription() { - return array( - 'prop' => array( - 'What pieces of information to include', - ' blockinfo - Tags if the user is blocked, by whom, and for what reason', - ' groups - Lists all the groups the user(s) belongs to', - ' rights - Lists all the rights the user(s) has', - ' editcount - Adds the user\'s edit count', - ' registration - Adds the user\'s registration timestamp', - ' emailable - Tags if the user can and wants to receive e-mail through [[Special:Emailuser]]', - ' gender - Tags the gender of the user. Returns "male", "female", or "unknown"', - ), - 'users' => 'A list of users to obtain the same information for', - 'token' => 'Which tokens to obtain for each user', - ); - } - - public function getDescription() { - return 'Get information about a list of users'; + ], + ]; } - protected function getExamples() { - return 'api.php?action=query&list=users&ususers=brion|TimStarling&usprop=groups|editcount|gender'; + protected function getExamplesMessages() { + return [ + 'action=query&list=users&ususers=Example&usprop=groups|editcount|gender' + => 'apihelp-query+users-example-simple', + ]; } - public function getVersion() { - return __CLASS__ . ': $Id$'; + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Users'; } }