Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 158 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
GlobalUsersPager | |
0.00% |
0 / 158 |
|
0.00% |
0 / 11 |
1056 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
setGroup | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
setUsername | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getIndexField | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultQuery | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
getQueryInfo | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
12 | |||
formatRow | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
doBatchLookups | |
0.00% |
0 / 37 |
|
0.00% |
0 / 1 |
56 | |||
getPageHeader | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
6 | |||
getUserGroups | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
30 | |||
getAllGroups | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\CentralAuth\Special; |
4 | |
5 | use MediaWiki\Cache\LinkBatchFactory; |
6 | use MediaWiki\Context\IContextSource; |
7 | use MediaWiki\Extension\CentralAuth\CentralAuthDatabaseManager; |
8 | use MediaWiki\Extension\CentralAuth\GlobalGroup\GlobalGroupLookup; |
9 | use MediaWiki\Extension\CentralAuth\User\CentralAuthUser; |
10 | use MediaWiki\Extension\CentralAuth\WikiSet; |
11 | use MediaWiki\Html\Html; |
12 | use MediaWiki\HTMLForm\HTMLForm; |
13 | use MediaWiki\Pager\AlphabeticPager; |
14 | use MediaWiki\Title\Title; |
15 | use MediaWiki\User\UserGroupMembership; |
16 | use MediaWiki\WikiMap\WikiMap; |
17 | use stdClass; |
18 | |
19 | class GlobalUsersPager extends AlphabeticPager { |
20 | |
21 | /** @var string|false */ |
22 | protected $requestedGroup = false; |
23 | /** @var string|false */ |
24 | protected $requestedUser = false; |
25 | /** @var array[] */ |
26 | protected $globalIDGroups = []; |
27 | /** @var string[] */ |
28 | private $localWikisets = []; |
29 | |
30 | private GlobalGroupLookup $globalGroupLookup; |
31 | private LinkBatchFactory $linkBatchFactory; |
32 | |
33 | public function __construct( |
34 | IContextSource $context, |
35 | CentralAuthDatabaseManager $dbManager, |
36 | GlobalGroupLookup $globalGroupLookup, |
37 | LinkBatchFactory $linkBatchFactory |
38 | ) { |
39 | $this->mDb = $dbManager->getCentralReplicaDB(); |
40 | parent::__construct( $context ); |
41 | $this->mDefaultDirection = $this->getRequest()->getBool( 'desc' ); |
42 | $this->globalGroupLookup = $globalGroupLookup; |
43 | $this->linkBatchFactory = $linkBatchFactory; |
44 | } |
45 | |
46 | /** |
47 | * @param string $group |
48 | */ |
49 | public function setGroup( string $group = '' ) { |
50 | if ( $group === '' ) { |
51 | $this->requestedGroup = false; |
52 | return; |
53 | } |
54 | $this->requestedGroup = $group; |
55 | } |
56 | |
57 | /** |
58 | * @param string $username |
59 | */ |
60 | public function setUsername( string $username = '' ) { |
61 | if ( $username === '' ) { |
62 | $this->requestedUser = false; |
63 | return; |
64 | } |
65 | $this->requestedUser = $username; |
66 | } |
67 | |
68 | /** |
69 | * @return string |
70 | */ |
71 | public function getIndexField() { |
72 | return 'gu_name'; |
73 | } |
74 | |
75 | /** |
76 | * @return array |
77 | */ |
78 | public function getDefaultQuery() { |
79 | $query = parent::getDefaultQuery(); |
80 | if ( !isset( $query['group'] ) && $this->requestedGroup !== false ) { |
81 | $query['group'] = $this->requestedGroup; |
82 | } |
83 | $this->mDefaultQuery = $query; |
84 | return $this->mDefaultQuery; |
85 | } |
86 | |
87 | /** |
88 | * @return array |
89 | */ |
90 | public function getQueryInfo() { |
91 | $tables = [ 'globaluser', 'localuser' ]; |
92 | |
93 | $conds = [ 'gu_hidden_level' => CentralAuthUser::HIDDEN_LEVEL_NONE ]; |
94 | |
95 | $join_conds = [ |
96 | 'localuser' => [ 'LEFT JOIN', [ 'gu_name = lu_name', 'lu_wiki' => WikiMap::getCurrentWikiId() ] ], |
97 | ]; |
98 | |
99 | if ( $this->requestedGroup !== false ) { |
100 | $tables[] = 'global_user_groups'; |
101 | $conds['gug_group'] = $this->requestedGroup; |
102 | $join_conds['global_user_groups'] = [ |
103 | 'LEFT JOIN', |
104 | 'gu_id = gug_user' |
105 | ]; |
106 | |
107 | $conds[] = $this->mDb->expr( 'gug_expiry', '=', null )->or( 'gug_expiry', '>=', $this->mDb->timestamp() ); |
108 | } |
109 | |
110 | if ( $this->requestedUser !== false ) { |
111 | $conds[] = $this->mDb->expr( 'gu_name', '>=', $this->requestedUser ); |
112 | } |
113 | |
114 | return [ |
115 | 'tables' => $tables, |
116 | 'fields' => [ |
117 | 'gu_name', |
118 | 'gu_id' => 'MAX(gu_id)', |
119 | 'gu_locked' => 'MAX(gu_locked)', |
120 | 'lu_attached_method' => 'MAX(lu_attached_method)', |
121 | ], |
122 | 'conds' => $conds, |
123 | 'options' => [ 'GROUP BY' => 'gu_name' ], |
124 | 'join_conds' => $join_conds, |
125 | ]; |
126 | } |
127 | |
128 | /** |
129 | * Formats a row |
130 | * @param stdClass $row The row to be formatted for output |
131 | * @return string HTML li element with username and info about this user |
132 | */ |
133 | public function formatRow( $row ) { |
134 | $user = htmlspecialchars( $row->gu_name ); |
135 | $info = []; |
136 | if ( $row->gu_locked ) { |
137 | $info[] = $this->msg( 'centralauth-listusers-locked' )->text(); |
138 | } |
139 | if ( $row->lu_attached_method ) { |
140 | $info[] = $this->msg( 'centralauth-listusers-attached', $row->gu_name )->text(); |
141 | } else { |
142 | array_unshift( $info, $this->msg( 'centralauth-listusers-nolocal' )->text() ); |
143 | } |
144 | |
145 | $groups = $this->getUserGroups( $row->gu_id, $row->gu_name ); |
146 | if ( $groups ) { |
147 | $info[] = $groups; |
148 | } |
149 | |
150 | $info = $this->getLanguage()->commaList( $info ); |
151 | return Html::rawElement( 'li', [], |
152 | $this->msg( 'centralauth-listusers-item', $user, $info )->parse() ); |
153 | } |
154 | |
155 | protected function doBatchLookups() { |
156 | $batch = $this->linkBatchFactory->newLinkBatch(); |
157 | |
158 | foreach ( $this->mResult as $row ) { |
159 | // userpage existence link cache |
160 | $batch->addObj( Title::makeTitleSafe( NS_USER, $row->gu_name ) ); |
161 | $this->globalIDGroups[$row->gu_id] = []; |
162 | } |
163 | |
164 | $batch->execute(); |
165 | |
166 | $groups = $this->mDb->newSelectQueryBuilder() |
167 | ->select( [ 'gug_user', 'gug_group', 'gug_expiry' ] ) |
168 | ->from( 'global_user_groups' ) |
169 | ->where( [ |
170 | 'gug_user' => array_keys( $this->globalIDGroups ), |
171 | $this->mDb->expr( 'gug_expiry', '=', null )->or( 'gug_expiry', '>=', $this->mDb->timestamp() ) |
172 | ] ) |
173 | ->caller( __METHOD__ ) |
174 | ->fetchResultSet(); |
175 | |
176 | // Make an array of global groups for all users in the current result set |
177 | $allGroups = []; |
178 | |
179 | foreach ( $groups as $row ) { |
180 | $this->globalIDGroups[$row->gug_user][$row->gug_group] = $row->gug_expiry; |
181 | $allGroups[] = $row->gug_group; |
182 | } |
183 | |
184 | foreach ( $this->globalIDGroups as $user => &$groups ) { |
185 | // Ensure temporary groups are displayed first, to avoid ambiguity like |
186 | // "first, second (expires at some point)" (unclear if only second expires or if both expire) |
187 | uasort( $groups, static fn ( $first, $second ) => (bool)$second <=> (bool)$first ); |
188 | } |
189 | |
190 | if ( count( $allGroups ) > 0 ) { |
191 | $wsQuery = $this->mDb->newSelectQueryBuilder() |
192 | ->select( [ 'ggr_group', 'ws_id', 'ws_name', 'ws_type', 'ws_wikis' ] ) |
193 | ->from( 'global_group_restrictions' ) |
194 | ->join( 'wikiset', null, 'ggr_set=ws_id' ) |
195 | ->where( [ 'ggr_group' => array_unique( $allGroups ) ] ) |
196 | ->caller( __METHOD__ ) |
197 | ->fetchResultSet(); |
198 | |
199 | $notLocalWikiSets = []; |
200 | |
201 | // Make an array of locally enabled wikisets |
202 | foreach ( $wsQuery as $wsRow ) { |
203 | if ( !WikiSet::newFromRow( $wsRow )->inSet() ) { |
204 | $notLocalWikiSets[] = $wsRow->ggr_group; |
205 | } |
206 | } |
207 | |
208 | // This is reversed so that wiki sets active everywhere (without |
209 | // global_group_restrictions rows) are shown as enabled everywhere |
210 | $this->localWikisets = array_diff( |
211 | array_unique( $allGroups ), |
212 | $notLocalWikiSets |
213 | ); |
214 | } |
215 | |
216 | $this->mResult->rewind(); |
217 | } |
218 | |
219 | /** |
220 | * @return bool |
221 | */ |
222 | public function getPageHeader() { |
223 | $options = []; |
224 | $options[$this->msg( 'group-all' )->text()] = ''; |
225 | foreach ( $this->getAllGroups() as $group => $groupText ) { |
226 | $options[$groupText] = $group; |
227 | } |
228 | |
229 | $formDescriptor = [ |
230 | 'usernameText' => [ |
231 | 'type' => 'text', |
232 | 'name' => 'username', |
233 | 'id' => 'offset', |
234 | 'label' => $this->msg( 'listusersfrom' )->text(), |
235 | 'size' => 20, |
236 | 'default' => $this->requestedUser, |
237 | 'autofocus' => true, |
238 | ], |
239 | 'groupSelect' => [ |
240 | 'type' => 'select', |
241 | 'name' => 'group', |
242 | 'id' => 'group', |
243 | 'label-message' => 'group', |
244 | 'options' => $options, |
245 | 'default' => $this->requestedGroup, |
246 | ], |
247 | 'descCheck' => [ |
248 | 'type' => 'check', |
249 | 'name' => 'desc', |
250 | 'id' => 'desc', |
251 | 'label-message' => 'listusers-desc', |
252 | 'default' => $this->mDefaultDirection, |
253 | ] |
254 | ]; |
255 | |
256 | $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() ); |
257 | $htmlForm |
258 | ->addHiddenField( 'limit', $this->mLimit ) |
259 | ->setMethod( 'get' ) |
260 | ->setId( 'mw-listusers-form' ) |
261 | ->setSubmitTextMsg( 'allpagessubmit' ) |
262 | ->setWrapperLegendMsg( 'listusers' ) |
263 | ->prepareForm() |
264 | ->displayForm( false ); |
265 | |
266 | return true; |
267 | } |
268 | |
269 | /** |
270 | * Note: Works only for users with $this->globalIDGroups set |
271 | * |
272 | * @param string $id |
273 | * @param string $username |
274 | * @return string|null |
275 | */ |
276 | protected function getUserGroups( $id, $username ): ?string { |
277 | $rights = []; |
278 | foreach ( $this->globalIDGroups[$id] as $group => $expiry ) { |
279 | $ugm = new UserGroupMembership( |
280 | (int)$id, |
281 | $group, |
282 | $expiry !== 'null' ? $expiry : null |
283 | ); |
284 | |
285 | $wikitextLink = UserGroupMembership::getLinkWiki( $ugm, $this->getContext(), $username ); |
286 | |
287 | if ( !in_array( $group, $this->localWikisets ) ) { |
288 | // Mark if the group is not applied on this wiki |
289 | $rights[] = Html::rawElement( 'span', |
290 | [ 'class' => 'groupnotappliedhere' ], |
291 | $wikitextLink |
292 | ); |
293 | } else { |
294 | $rights[] = $wikitextLink; |
295 | } |
296 | } |
297 | |
298 | if ( count( $rights ) > 0 ) { |
299 | return $this->getLanguage()->listToText( $rights ); |
300 | } |
301 | |
302 | return null; |
303 | } |
304 | |
305 | /** |
306 | * @return string[] |
307 | */ |
308 | public function getAllGroups() { |
309 | $result = []; |
310 | foreach ( $this->globalGroupLookup->getDefinedGroups() as $group ) { |
311 | $result[$group] = $this->getLanguage()->getGroupName( $group ); |
312 | } |
313 | asort( $result ); |
314 | return $result; |
315 | } |
316 | } |