Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 161
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryGlobalAllUsers
0.00% covered (danger)
0.00%
0 / 161
0.00% covered (danger)
0.00%
0 / 7
812
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getDB
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getCanonicalUserName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 1
420
 getGlobalGroups
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
6
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\CentralAuth\Api;
4
5use ApiBase;
6use ApiQuery;
7use ApiQueryBase;
8use MediaWiki\Extension\CentralAuth\CentralAuthDatabaseManager;
9use MediaWiki\Extension\CentralAuth\GlobalGroup\GlobalGroupLookup;
10use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
11use MediaWiki\WikiMap\WikiMap;
12use Wikimedia\ParamValidator\ParamValidator;
13use Wikimedia\ParamValidator\TypeDef\IntegerDef;
14use Wikimedia\Rdbms\IDatabase;
15use Wikimedia\Rdbms\IExpression;
16use Wikimedia\Rdbms\IResultWrapper;
17use Wikimedia\Rdbms\LikeValue;
18
19/**
20 * Api module for CentralAuth extension to list all global users.
21 * Partly based on ApiQueryAllUsers.
22 *
23 * This program is free software; you can redistribute it and/or modify
24 * it under the terms of the GNU General Public License as published by
25 * the Free Software Foundation; either version 2 of the License, or
26 * (at your option) any later version.
27 *
28 * This program is distributed in the hope that it will be useful,
29 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 * GNU General Public License for more details.
32 *
33 * You should have received a copy of the GNU General Public License along
34 * with this program; if not, write to the Free Software Foundation, Inc.,
35 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
36 * http://www.gnu.org/copyleft/gpl.html
37 *
38 * @ingroup API
39 * @ingroup Extensions
40 *
41 * @license GPL-2.0-or-later
42 * @author Marius Hoch < hoo@online.de >
43 */
44class ApiQueryGlobalAllUsers extends ApiQueryBase {
45
46    /** @var CentralAuthDatabaseManager */
47    private $databaseManager;
48
49    /** @var GlobalGroupLookup */
50    private $globalGroupLookup;
51
52    /**
53     * @param ApiQuery $query
54     * @param string $moduleName
55     * @param CentralAuthDatabaseManager $databaseManager
56     * @param GlobalGroupLookup $globalGroupLookup
57     */
58    public function __construct(
59        $query,
60        $moduleName,
61        CentralAuthDatabaseManager $databaseManager,
62        GlobalGroupLookup $globalGroupLookup
63    ) {
64        parent::__construct( $query, $moduleName, 'agu' );
65        $this->databaseManager = $databaseManager;
66        $this->globalGroupLookup = $globalGroupLookup;
67    }
68
69    /**
70     * Get the Query database connection (read-only)
71     *
72     * @see ApiQueryBase::getDB
73     * @return IDatabase
74     */
75    protected function getDB() {
76        static $db = null;
77
78        if ( $db === null ) {
79            $db = $this->databaseManager->getCentralDB( DB_REPLICA );
80        }
81        return $db;
82    }
83
84    /**
85     * This function converts the user name to a canonical form
86     * which is stored in the database.
87     * @param string $name
88     * @return string
89     */
90    private function getCanonicalUserName( $name ) {
91        return str_replace( '_', ' ', $name );
92    }
93
94    public function execute() {
95        $params = $this->extractRequestParams();
96
97        $prop = array_flip( (array)$params['prop'] );
98
99        $dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
100
101        $db = $this->getDB();
102        $this->addTables( 'globaluser' );
103        $this->addFields( [ 'gu_id', 'gu_name' ] );
104        $limit = intval( $params['limit'] ) + 1;
105
106        $this->addWhereRange(
107            'gu_name',
108            $dir,
109            isset( $params['from'] ) ? $this->getCanonicalUserName( $params['from'] ) : null,
110            isset( $params['to'] ) ? $this->getCanonicalUserName( $params['to'] ) : null
111        );
112
113        if ( $params['prefix'] !== null ) {
114            $this->addWhere(
115                $db->expr( 'gu_name', IExpression::LIKE,
116                    new LikeValue( $this->getCanonicalUserName( $params['prefix'] ), $db->anyString() ) )
117            );
118        }
119
120        $this->requireMaxOneParameter( $params, 'group', 'excludegroup' );
121
122        if ( !empty( $params['group'] ) ) {
123            $this->addTables( 'global_user_groups' );
124            // Request more rows than needed to avoid not getting all rows
125            // that belong to one user, because a user might be in multiple groups
126            $limit += count( $params['group'] ) + 1;
127
128            $this->addJoinConds( [
129                'global_user_groups' =>
130                [ 'INNER JOIN', 'gug_user = gu_id' ]
131            ] );
132
133            $this->addWhere( [ 'gug_group' => $params['group'] ] );
134            $this->addWhere( [
135                $this->getDB()->expr( 'gug_expiry', '=', null )
136                    ->or( 'gug_expiry', '>=', $this->getDB()->timestamp() )
137            ] );
138        }
139
140        if ( !empty( $params['excludegroup'] ) ) {
141            $this->addTables( 'global_user_groups', 'gug2' );
142
143            $this->addJoinConds( [
144                'gug2' =>
145                [ 'LEFT OUTER JOIN', [ 'gug2.gug_user = gu_id', 'gug2.gug_group' => $params['excludegroup'] ] ]
146            ] );
147
148            $this->addWhere( 'gug2.gug_user IS NULL' );
149            $this->addWhere( [
150                $this->getDB()->expr( 'gug2.gug_expiry', '=', null )
151                    ->or( 'gug2.gug_expiry', '>=', $this->getDB()->timestamp() )
152            ] );
153        }
154
155        $this->addWhere( [ 'gu_hidden_level' => CentralAuthUser::HIDDEN_LEVEL_NONE ] );
156
157        if ( isset( $prop['lockinfo'] ) ) {
158            $this->addFields( 'gu_locked' );
159        }
160
161        if ( isset( $prop['existslocally'] ) ) {
162            $this->addTables( 'localuser' );
163            $this->addFields( 'lu_wiki' );
164            $this->addJoinConds( [
165                'localuser' =>
166                [ 'LEFT OUTER JOIN', [ 'gu_name=lu_name', 'lu_wiki' => WikiMap::getCurrentWikiId() ] ]
167            ] );
168        }
169
170        $this->addOption( 'LIMIT', $limit );
171
172        $result = $this->select( __METHOD__ );
173
174        $groupsOfUser = [];
175        if ( isset( $prop['groups'] ) && $result->numRows() ) {
176            $groupsOfUser = $this->getGlobalGroups( $result, $dir );
177        }
178
179        $data = [];
180        $previousName = '';
181        $i = 1;
182        foreach ( $result as $row ) {
183            if ( $row->gu_name === $previousName ) {
184                continue;
185            }
186            $previousName = $row->gu_name;
187            if ( $i > $params['limit'] ) {
188                $this->setContinueEnumParameter( 'from', $row->gu_name );
189                break;
190            }
191            $i++;
192
193            $entry = [];
194            $entry['id'] = (int)$row->gu_id;
195            $entry['name'] = $row->gu_name;
196
197            if ( isset( $prop['groups'] ) ) {
198                if ( !empty( $groupsOfUser[$row->gu_id] ) ) {
199                    $entry['groups'] = $groupsOfUser[$row->gu_id];
200                } else {
201                    $entry['groups'] = [];
202                }
203                $this->getResult()->setIndexedTagName( $entry['groups'], 'group' );
204            }
205
206            if ( isset( $prop['existslocally'] ) && $row->lu_wiki != null ) {
207                $entry['existslocally'] = '';
208            }
209
210            if ( isset( $prop['lockinfo'] ) && $row->gu_locked ) {
211                $entry['locked'] = '';
212            }
213
214            $data[] = $entry;
215        }
216
217        $this->getResult()->setIndexedTagName( $data, 'globaluser' );
218
219        $this->getResult()->addValue( 'query', $this->getModuleName(), $data );
220    }
221
222    /**
223     * Get the global groups for the given global user result set.
224     *
225     * @param IResultWrapper $result Result of a globaluser table select
226     * @param string $dir Sorting directory
227     *
228     * @return string[][]
229     */
230    protected function getGlobalGroups( IResultWrapper $result, $dir ) {
231        $this->resetQueryParams();
232
233        // Get all global groups now. We do this by using a WHERE
234        // range build from the given results
235        $groupsOfUser = [];
236
237        $this->addTables( [ 'globaluser', 'global_user_groups' ] );
238        $this->addFields( [ 'gug_user', 'gug_group' ] );
239
240        $result->seek( 0 );
241        $firstUser = $result->fetchObject()->gu_name;
242        $result->seek( $result->numRows() - 1 );
243        $lastUser = $result->fetchObject()->gu_name;
244
245        $this->addWhereRange(
246            'gu_name',
247            $dir,
248            $firstUser,
249            $lastUser,
250            false
251        );
252
253        // Use an INNER JOIN to only get users with global groups
254        $this->addJoinConds( [
255            'global_user_groups' =>
256            [ 'INNER JOIN', 'gug_user = gu_id' ]
257        ] );
258
259        $this->addWhere( [
260            $this->getDB()->expr( 'gug_expiry', '=', null )
261                ->or( 'gug_expiry', '>=', $this->getDB()->timestamp() )
262        ] );
263
264        $groupResult = $this->select( __METHOD__ );
265
266        foreach ( $groupResult as $groupRow ) {
267            $groupsOfUser[$groupRow->gug_user][] = $groupRow->gug_group;
268        }
269
270        return $groupsOfUser;
271    }
272
273    /** @inheritDoc */
274    public function getAllowedParams() {
275        $globalGroups = $this->globalGroupLookup->getDefinedGroups();
276        return [
277            'from' => null,
278            'to' => null,
279            'prefix' => null,
280            'dir' => [
281                ParamValidator::PARAM_DEFAULT => 'ascending',
282                ParamValidator::PARAM_TYPE => [
283                    'ascending',
284                    'descending'
285                ],
286            ],
287            'group' => [
288                ParamValidator::PARAM_TYPE => $globalGroups,
289                ParamValidator::PARAM_ISMULTI => true,
290            ],
291            'excludegroup' => [
292                ParamValidator::PARAM_TYPE => $globalGroups,
293                ParamValidator::PARAM_ISMULTI => true,
294            ],
295            'prop' => [
296                ParamValidator::PARAM_ISMULTI => true,
297                ParamValidator::PARAM_TYPE => [
298                    'lockinfo',
299                    'groups',
300                    'existslocally'
301                ],
302                ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
303            ],
304            'limit' => [
305                ParamValidator::PARAM_DEFAULT => 10,
306                ParamValidator::PARAM_TYPE => 'limit',
307                IntegerDef::PARAM_MIN => 1,
308                IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
309                IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
310            ]
311        ];
312    }
313
314    /** @inheritDoc */
315    protected function getExamplesMessages() {
316        return [
317            'action=query&list=globalallusers'
318                => 'apihelp-query+globalallusers-example-1',
319            'action=query&list=globalallusers&agufrom=ABC&aguprop=lockinfo|groups|existslocally'
320                => 'apihelp-query+globalallusers-example-2',
321        ];
322    }
323}