MediaWiki  REL1_24
ApiQueryAllUsers.php
Go to the documentation of this file.
00001 <?php
00032 class ApiQueryAllUsers extends ApiQueryBase {
00033     public function __construct( ApiQuery $query, $moduleName ) {
00034         parent::__construct( $query, $moduleName, 'au' );
00035     }
00036 
00043     private function getCanonicalUserName( $name ) {
00044         return str_replace( '_', ' ', $name );
00045     }
00046 
00047     public function execute() {
00048         $params = $this->extractRequestParams();
00049         $activeUserDays = $this->getConfig()->get( 'ActiveUserDays' );
00050 
00051         if ( $params['activeusers'] ) {
00052             // Update active user cache
00053             SpecialActiveUsers::mergeActiveUsers( 600, $activeUserDays );
00054         }
00055 
00056         $db = $this->getDB();
00057 
00058         $prop = $params['prop'];
00059         if ( !is_null( $prop ) ) {
00060             $prop = array_flip( $prop );
00061             $fld_blockinfo = isset( $prop['blockinfo'] );
00062             $fld_editcount = isset( $prop['editcount'] );
00063             $fld_groups = isset( $prop['groups'] );
00064             $fld_rights = isset( $prop['rights'] );
00065             $fld_registration = isset( $prop['registration'] );
00066             $fld_implicitgroups = isset( $prop['implicitgroups'] );
00067         } else {
00068             $fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration =
00069                 $fld_rights = $fld_implicitgroups = false;
00070         }
00071 
00072         $limit = $params['limit'];
00073 
00074         $this->addTables( 'user' );
00075         $useIndex = true;
00076 
00077         $dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
00078         $from = is_null( $params['from'] ) ? null : $this->getCanonicalUserName( $params['from'] );
00079         $to = is_null( $params['to'] ) ? null : $this->getCanonicalUserName( $params['to'] );
00080 
00081         # MySQL can't figure out that 'user_name' and 'qcc_title' are the same
00082         # despite the JOIN condition, so manually sort on the correct one.
00083         $userFieldToSort = $params['activeusers'] ? 'qcc_title' : 'user_name';
00084 
00085         $this->addWhereRange( $userFieldToSort, $dir, $from, $to );
00086 
00087         if ( !is_null( $params['prefix'] ) ) {
00088             $this->addWhere( $userFieldToSort .
00089                 $db->buildLike( $this->getCanonicalUserName( $params['prefix'] ), $db->anyString() ) );
00090         }
00091 
00092         if ( !is_null( $params['rights'] ) && count( $params['rights'] ) ) {
00093             $groups = array();
00094             foreach ( $params['rights'] as $r ) {
00095                 $groups = array_merge( $groups, User::getGroupsWithPermission( $r ) );
00096             }
00097 
00098             // no group with the given right(s) exists, no need for a query
00099             if ( !count( $groups ) ) {
00100                 $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), '' );
00101 
00102                 return;
00103             }
00104 
00105             $groups = array_unique( $groups );
00106 
00107             if ( is_null( $params['group'] ) ) {
00108                 $params['group'] = $groups;
00109             } else {
00110                 $params['group'] = array_unique( array_merge( $params['group'], $groups ) );
00111             }
00112         }
00113 
00114         if ( !is_null( $params['group'] ) && !is_null( $params['excludegroup'] ) ) {
00115             $this->dieUsage( 'group and excludegroup cannot be used together', 'group-excludegroup' );
00116         }
00117 
00118         if ( !is_null( $params['group'] ) && count( $params['group'] ) ) {
00119             $useIndex = false;
00120             // Filter only users that belong to a given group
00121             $this->addTables( 'user_groups', 'ug1' );
00122             $this->addJoinConds( array( 'ug1' => array( 'INNER JOIN', array( 'ug1.ug_user=user_id',
00123                 'ug1.ug_group' => $params['group'] ) ) ) );
00124         }
00125 
00126         if ( !is_null( $params['excludegroup'] ) && count( $params['excludegroup'] ) ) {
00127             $useIndex = false;
00128             // Filter only users don't belong to a given group
00129             $this->addTables( 'user_groups', 'ug1' );
00130 
00131             if ( count( $params['excludegroup'] ) == 1 ) {
00132                 $exclude = array( 'ug1.ug_group' => $params['excludegroup'][0] );
00133             } else {
00134                 $exclude = array( $db->makeList(
00135                     array( 'ug1.ug_group' => $params['excludegroup'] ),
00136                     LIST_OR
00137                 ) );
00138             }
00139             $this->addJoinConds( array( 'ug1' => array( 'LEFT OUTER JOIN',
00140                 array_merge( array( 'ug1.ug_user=user_id' ), $exclude )
00141             ) ) );
00142             $this->addWhere( 'ug1.ug_user IS NULL' );
00143         }
00144 
00145         if ( $params['witheditsonly'] ) {
00146             $this->addWhere( 'user_editcount > 0' );
00147         }
00148 
00149         $this->showHiddenUsersAddBlockInfo( $fld_blockinfo );
00150 
00151         if ( $fld_groups || $fld_rights ) {
00152             // Show the groups the given users belong to
00153             // request more than needed to avoid not getting all rows that belong to one user
00154             $groupCount = count( User::getAllGroups() );
00155             $sqlLimit = $limit + $groupCount + 1;
00156 
00157             $this->addTables( 'user_groups', 'ug2' );
00158             $this->addJoinConds( array( 'ug2' => array( 'LEFT JOIN', 'ug2.ug_user=user_id' ) ) );
00159             $this->addFields( array( 'ug_group2' => 'ug2.ug_group' ) );
00160         } else {
00161             $sqlLimit = $limit + 1;
00162         }
00163 
00164         if ( $params['activeusers'] ) {
00165             $activeUserSeconds = $activeUserDays * 86400;
00166 
00167             // Filter query to only include users in the active users cache
00168             $this->addTables( 'querycachetwo' );
00169             $this->addJoinConds( array( 'querycachetwo' => array(
00170                 'INNER JOIN', array(
00171                     'qcc_type' => 'activeusers',
00172                     'qcc_namespace' => NS_USER,
00173                     'qcc_title=user_name',
00174                 ),
00175             ) ) );
00176 
00177             // Actually count the actions using a subquery (bug 64505 and bug 64507)
00178             $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
00179             $this->addFields( array(
00180                 'recentactions' => '(' . $db->selectSQLText(
00181                     'recentchanges',
00182                     'COUNT(*)',
00183                     array(
00184                         'rc_user_text = user_name',
00185                         'rc_type != ' . $db->addQuotes( RC_EXTERNAL ), // no wikidata
00186                         'rc_log_type IS NULL OR rc_log_type != ' . $db->addQuotes( 'newusers' ),
00187                         'rc_timestamp >= ' . $db->addQuotes( $timestamp ),
00188                     )
00189                 ) . ')'
00190             ) );
00191         }
00192 
00193         $this->addOption( 'LIMIT', $sqlLimit );
00194 
00195         $this->addFields( array(
00196             'user_name',
00197             'user_id'
00198         ) );
00199         $this->addFieldsIf( 'user_editcount', $fld_editcount );
00200         $this->addFieldsIf( 'user_registration', $fld_registration );
00201 
00202         if ( $useIndex ) {
00203             $this->addOption( 'USE INDEX', array( 'user' => 'user_name' ) );
00204         }
00205 
00206         $res = $this->select( __METHOD__ );
00207 
00208         $count = 0;
00209         $lastUserData = false;
00210         $lastUser = false;
00211         $result = $this->getResult();
00212 
00213         // This loop keeps track of the last entry. For each new row, if the
00214         // new row is for different user then the last, the last entry is added
00215         // to results. Otherwise, the group of the new row is appended to the
00216         // last entry. The setContinue... is more complex because of this, and
00217         // takes into account the higher sql limit to make sure all rows that
00218         // belong to the same user are received.
00219 
00220         foreach ( $res as $row ) {
00221             $count++;
00222 
00223             if ( $lastUser !== $row->user_name ) {
00224                 // Save the last pass's user data
00225                 if ( is_array( $lastUserData ) ) {
00226                     if ( $params['activeusers'] && $lastUserData['recentactions'] === 0 ) {
00227                         // activeusers cache was out of date
00228                         $fit = true;
00229                     } else {
00230                         $fit = $result->addValue( array( 'query', $this->getModuleName() ),
00231                             null, $lastUserData );
00232                     }
00233 
00234                     $lastUserData = null;
00235 
00236                     if ( !$fit ) {
00237                         $this->setContinueEnumParameter( 'from', $lastUserData['name'] );
00238                         break;
00239                     }
00240                 }
00241 
00242                 if ( $count > $limit ) {
00243                     // We've reached the one extra which shows that there are
00244                     // additional pages to be had. Stop here...
00245                     $this->setContinueEnumParameter( 'from', $row->user_name );
00246                     break;
00247                 }
00248 
00249                 // Record new user's data
00250                 $lastUser = $row->user_name;
00251                 $lastUserData = array(
00252                     'userid' => $row->user_id,
00253                     'name' => $lastUser,
00254                 );
00255                 if ( $fld_blockinfo && !is_null( $row->ipb_by_text ) ) {
00256                     $lastUserData['blockid'] = $row->ipb_id;
00257                     $lastUserData['blockedby'] = $row->ipb_by_text;
00258                     $lastUserData['blockedbyid'] = $row->ipb_by;
00259                     $lastUserData['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
00260                     $lastUserData['blockreason'] = $row->ipb_reason;
00261                     $lastUserData['blockexpiry'] = $row->ipb_expiry;
00262                 }
00263                 if ( $row->ipb_deleted ) {
00264                     $lastUserData['hidden'] = '';
00265                 }
00266                 if ( $fld_editcount ) {
00267                     $lastUserData['editcount'] = intval( $row->user_editcount );
00268                 }
00269                 if ( $params['activeusers'] ) {
00270                     $lastUserData['recentactions'] = intval( $row->recentactions );
00271                     // @todo 'recenteditcount' is set for BC, remove in 1.25
00272                     $lastUserData['recenteditcount'] = $lastUserData['recentactions'];
00273                 }
00274                 if ( $fld_registration ) {
00275                     $lastUserData['registration'] = $row->user_registration ?
00276                         wfTimestamp( TS_ISO_8601, $row->user_registration ) : '';
00277                 }
00278             }
00279 
00280             if ( $sqlLimit == $count ) {
00281                 // @todo BUG!  database contains group name that User::getAllGroups() does not return
00282                 // Should handle this more gracefully
00283                 ApiBase::dieDebug(
00284                     __METHOD__,
00285                     'MediaWiki configuration error: The database contains more ' .
00286                         'user groups than known to User::getAllGroups() function'
00287                 );
00288             }
00289 
00290             $lastUserObj = User::newFromId( $row->user_id );
00291 
00292             // Add user's group info
00293             if ( $fld_groups ) {
00294                 if ( !isset( $lastUserData['groups'] ) ) {
00295                     if ( $lastUserObj ) {
00296                         $lastUserData['groups'] = $lastUserObj->getAutomaticGroups();
00297                     } else {
00298                         // This should not normally happen
00299                         $lastUserData['groups'] = array();
00300                     }
00301                 }
00302 
00303                 if ( !is_null( $row->ug_group2 ) ) {
00304                     $lastUserData['groups'][] = $row->ug_group2;
00305                 }
00306 
00307                 $result->setIndexedTagName( $lastUserData['groups'], 'g' );
00308             }
00309 
00310             if ( $fld_implicitgroups && !isset( $lastUserData['implicitgroups'] ) && $lastUserObj ) {
00311                 $lastUserData['implicitgroups'] = $lastUserObj->getAutomaticGroups();
00312                 $result->setIndexedTagName( $lastUserData['implicitgroups'], 'g' );
00313             }
00314             if ( $fld_rights ) {
00315                 if ( !isset( $lastUserData['rights'] ) ) {
00316                     if ( $lastUserObj ) {
00317                         $lastUserData['rights'] = User::getGroupPermissions( $lastUserObj->getAutomaticGroups() );
00318                     } else {
00319                         // This should not normally happen
00320                         $lastUserData['rights'] = array();
00321                     }
00322                 }
00323 
00324                 if ( !is_null( $row->ug_group2 ) ) {
00325                     $lastUserData['rights'] = array_unique( array_merge( $lastUserData['rights'],
00326                         User::getGroupPermissions( array( $row->ug_group2 ) ) ) );
00327                 }
00328 
00329                 $result->setIndexedTagName( $lastUserData['rights'], 'r' );
00330             }
00331         }
00332 
00333         if ( is_array( $lastUserData ) &&
00334             !( $params['activeusers'] && $lastUserData['recentactions'] === 0 )
00335         ) {
00336             $fit = $result->addValue( array( 'query', $this->getModuleName() ),
00337                 null, $lastUserData );
00338             if ( !$fit ) {
00339                 $this->setContinueEnumParameter( 'from', $lastUserData['name'] );
00340             }
00341         }
00342 
00343         $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'u' );
00344     }
00345 
00346     public function getCacheMode( $params ) {
00347         return 'anon-public-user-private';
00348     }
00349 
00350     public function getAllowedParams() {
00351         $userGroups = User::getAllGroups();
00352 
00353         return array(
00354             'from' => null,
00355             'to' => null,
00356             'prefix' => null,
00357             'dir' => array(
00358                 ApiBase::PARAM_DFLT => 'ascending',
00359                 ApiBase::PARAM_TYPE => array(
00360                     'ascending',
00361                     'descending'
00362                 ),
00363             ),
00364             'group' => array(
00365                 ApiBase::PARAM_TYPE => $userGroups,
00366                 ApiBase::PARAM_ISMULTI => true,
00367             ),
00368             'excludegroup' => array(
00369                 ApiBase::PARAM_TYPE => $userGroups,
00370                 ApiBase::PARAM_ISMULTI => true,
00371             ),
00372             'rights' => array(
00373                 ApiBase::PARAM_TYPE => User::getAllRights(),
00374                 ApiBase::PARAM_ISMULTI => true,
00375             ),
00376             'prop' => array(
00377                 ApiBase::PARAM_ISMULTI => true,
00378                 ApiBase::PARAM_TYPE => array(
00379                     'blockinfo',
00380                     'groups',
00381                     'implicitgroups',
00382                     'rights',
00383                     'editcount',
00384                     'registration'
00385                 )
00386             ),
00387             'limit' => array(
00388                 ApiBase::PARAM_DFLT => 10,
00389                 ApiBase::PARAM_TYPE => 'limit',
00390                 ApiBase::PARAM_MIN => 1,
00391                 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
00392                 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
00393             ),
00394             'witheditsonly' => false,
00395             'activeusers' => false,
00396         );
00397     }
00398 
00399     public function getParamDescription() {
00400         return array(
00401             'from' => 'The user name to start enumerating from',
00402             'to' => 'The user name to stop enumerating at',
00403             'prefix' => 'Search for all users that begin with this value',
00404             'dir' => 'Direction to sort in',
00405             'group' => 'Limit users to given group name(s)',
00406             'excludegroup' => 'Exclude users in given group name(s)',
00407             'rights' => 'Limit users to given right(s) (does not include rights ' .
00408                 'granted by implicit or auto-promoted groups like *, user, or autoconfirmed)',
00409             'prop' => array(
00410                 'What pieces of information to include.',
00411                 ' blockinfo      - Adds the information about a current block on the user',
00412                 ' groups         - Lists groups that the user is in. This uses ' .
00413                     'more server resources and may return fewer results than the limit',
00414                 ' implicitgroups - Lists all the groups the user is automatically in',
00415                 ' rights         - Lists rights that the user has',
00416                 ' editcount      - Adds the edit count of the user',
00417                 ' registration   - Adds the timestamp of when the user registered if available (may be blank)',
00418             ),
00419             'limit' => 'How many total user names to return',
00420             'witheditsonly' => 'Only list users who have made edits',
00421             'activeusers' => "Only list users active in the last {$this->getConfig()->get( 'ActiveUserDays' )} days(s)"
00422         );
00423     }
00424 
00425     public function getDescription() {
00426         return 'Enumerate all registered users.';
00427     }
00428 
00429     public function getExamples() {
00430         return array(
00431             'api.php?action=query&list=allusers&aufrom=Y',
00432         );
00433     }
00434 
00435     public function getHelpUrls() {
00436         return 'https://www.mediawiki.org/wiki/API:Allusers';
00437     }
00438 }