MediaWiki master
ActiveUsersPager.php
Go to the documentation of this file.
1<?php
9
26use Wikimedia\Timestamp\ConvertibleTimestamp;
27use Wikimedia\Timestamp\TimestampFormat as TS;
28
37 private RecentChangeLookup $recentChangeLookup;
38
42 protected $opts;
43
47 protected $groups;
48
52 private $blockStatusByUid;
53
55 private $RCMaxAge;
56
58 private $excludegroups;
59
60 public function __construct(
61 IContextSource $context,
62 HookContainer $hookContainer,
63 LinkBatchFactory $linkBatchFactory,
64 IConnectionProvider $dbProvider,
65 UserGroupManager $userGroupManager,
66 UserIdentityLookup $userIdentityLookup,
68 TempUserConfig $tempUserConfig,
69 RecentChangeLookup $recentChangeLookup,
71 ) {
72 parent::__construct(
73 $context,
74 $hookContainer,
75 $linkBatchFactory,
76 $dbProvider,
77 $userGroupManager,
78 $userIdentityLookup,
80 $tempUserConfig,
81 null,
82 null
83 );
84
85 $this->recentChangeLookup = $recentChangeLookup;
86 $this->RCMaxAge = $this->getConfig()->get( MainConfigNames::ActiveUserDays );
87 $this->requestedUser = '';
88
89 $un = $opts->getValue( 'username' );
90 if ( $un != '' ) {
91 $username = Title::makeTitleSafe( NS_USER, $un );
92 if ( $username !== null ) {
93 $this->requestedUser = $username->getText();
94 }
95 }
96
97 $this->groups = $opts->getValue( 'groups' );
98 $this->excludegroups = $opts->getValue( 'excludegroups' );
99 // Backwards-compatibility with old URLs
100 if ( $opts->getValue( 'hidebots' ) ) {
101 $this->excludegroups[] = 'bot';
102 }
103 if ( $opts->getValue( 'hidesysops' ) ) {
104 $this->excludegroups[] = 'sysop';
105 }
106 }
107
109 public function getIndexField() {
110 return 'qcc_title';
111 }
112
114 public function getQueryInfo( $data = null ) {
115 $dbr = $this->getDatabase();
116
117 $activeUserSeconds = $this->getConfig()->get( MainConfigNames::ActiveUserDays ) * 86400;
118 $timestamp = $dbr->timestamp( (int)ConvertibleTimestamp::now( TS::UNIX ) - $activeUserSeconds );
119 $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')';
120
121 // Inner subselect to pull the active users out of querycachetwo
122 $subquery = $dbr->newSelectQueryBuilder()
123 ->select( [ 'qcc_title', 'user_id', 'actor_id' ] )
124 ->from( 'querycachetwo' )
125 ->join( 'user', null, 'user_name = qcc_title' )
126 ->join( 'actor', null, 'actor_user = user_id' )
127 ->where( [
128 'qcc_type' => 'activeusers',
129 'qcc_namespace' => NS_USER,
130 ] )
131 ->caller( $fname );
132 if ( $data !== null ) {
133 $subquery
134 ->orderBy( 'qcc_title', $data['order'] )
135 ->limit( $data['limit'] )
136 ->andWhere( $data['conds'] );
137 }
138 if ( $this->requestedUser != '' ) {
139 $subquery->andWhere( $dbr->expr( 'qcc_title', '>=', $this->requestedUser ) );
140 }
141 if ( $this->groups !== [] ) {
142 $subquery
143 ->join( 'user_groups', 'ug1', 'ug1.ug_user = user_id' )
144 ->andWhere( [
145 'ug1.ug_group' => $this->groups,
146 $dbr->expr( 'ug1.ug_expiry', '=', null )->or( 'ug1.ug_expiry', '>=', $dbr->timestamp() ),
147 ] );
148 }
149 if ( $this->excludegroups !== [] ) {
150 $subquery
151 ->leftJoin( 'user_groups', 'ug2', [
152 'ug2.ug_user = user_id',
153 'ug2.ug_group' => $this->excludegroups,
154 $dbr->expr( 'ug2.ug_expiry', '=', null )->or( 'ug2.ug_expiry', '>=', $dbr->timestamp() ),
155 ] )
156 ->andWhere( [ 'ug2.ug_user' => null ] );
157 }
158 if ( !$this->canSeeHideuser() ) {
159 $subquery->andWhere( $this->hideUserUtils->getExpression( $dbr ) );
160 }
161
162 // Outer query to select the recent edit counts for the selected active users
163 return [
164 'tables' => [ 'qcc_users' => new Subquery( $subquery->getSQL() ), 'recentchanges' ],
165 'fields' => [
166 'qcc_title',
167 'user_name' => 'qcc_title',
168 'user_id' => 'user_id',
169 'recentedits' => 'COUNT(DISTINCT rc_id)'
170 ],
171 'options' => [ 'GROUP BY' => [ 'qcc_title', 'user_id' ] ],
172 'conds' => [],
173 'join_conds' => [ 'recentchanges' => [ 'LEFT JOIN', [
174 'rc_actor = actor_id',
175 $dbr->expr( 'rc_source', '=', $this->recentChangeLookup->getPrimarySources() ),
176 $dbr->expr( 'rc_log_type', '=', null )->or( 'rc_log_type', '!=', 'newusers' ),
177 $dbr->expr( 'rc_timestamp', '>=', $timestamp ),
178 ] ] ],
179 ];
180 }
181
183 protected function buildQueryInfo( $offset, $limit, $order ) {
184 $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')';
185
186 $sortColumns = [ $this->mIndexField, ...$this->mExtraSortFields ];
187 if ( $order === self::QUERY_ASCENDING ) {
188 $dir = 'ASC';
189 $orderBy = $sortColumns;
190 $operator = $this->mIncludeOffset ? '>=' : '>';
191 } else {
192 $dir = 'DESC';
193 $orderBy = [];
194 foreach ( $sortColumns as $col ) {
195 $orderBy[] = $col . ' DESC';
196 }
197 $operator = $this->mIncludeOffset ? '<=' : '<';
198 }
199 $info = $this->getQueryInfo( [
200 'limit' => intval( $limit ),
201 'order' => $dir,
202 'conds' =>
203 $offset != '' ? [ $this->getDatabase()->expr( $this->mIndexField, $operator, $offset ) ] : [],
204 ] );
205
206 $tables = $info['tables'];
207 $fields = $info['fields'];
208 $conds = $info['conds'];
209 $options = $info['options'];
210 $join_conds = $info['join_conds'];
211 $options['ORDER BY'] = $orderBy;
212 return [ $tables, $fields, $conds, $fname, $options, $join_conds ];
213 }
214
215 protected function doBatchLookups() {
216 parent::doBatchLookups();
217
218 $uids = [];
219 foreach ( $this->mResult as $row ) {
220 $uids[] = (int)$row->user_id;
221 }
222 // Fetch the block status of the user for showing "(blocked)" text and for
223 // striking out names of suppressed users when privileged user views the list.
224 // Although the first query already hits the block table for un-privileged, this
225 // is done in two queries to avoid huge quicksorts and to make COUNT(*) correct.
226 $dbr = $this->getDatabase();
227 $res = $dbr->newSelectQueryBuilder()
228 ->select( [
229 'bt_user',
230 'deleted' => 'MAX(bl_deleted)',
231 'sitewide' => 'MAX(bl_sitewide)'
232 ] )
233 ->from( 'block_target' )
234 ->join( 'block', null, 'bl_target=bt_id' )
235 ->where( [
236 'bt_user' => $uids,
237 $dbr->expr( 'bl_expiry', '>=', $dbr->timestamp() ),
238 ] )
239 ->groupBy( [ 'bt_user' ] )
240 ->caller( __METHOD__ )->fetchResultSet();
241 $this->blockStatusByUid = [];
242 foreach ( $res as $row ) {
243 $this->blockStatusByUid[$row->bt_user] = [
244 'deleted' => $row->deleted,
245 'sitewide' => $row->sitewide,
246 ];
247 }
248 $this->mResult->seek( 0 );
249 }
250
252 public function formatRow( $row ) {
253 $userName = $row->user_name;
254
255 $ulinks = Linker::userLink( $row->user_id, $userName );
256 $ulinks .= Linker::userToolLinks(
257 $row->user_id,
258 $userName,
259 // Should the contributions link be red if the user has no edits (using default)
260 false,
261 // Customisation flags (using default 0)
262 0,
263 // User edit count (using default)
264 null,
265 // do not wrap the message in parentheses (CSS will provide these)
266 false
267 );
268
269 $lang = $this->getLanguage();
270
271 $list = [];
272
273 $userIdentity = new UserIdentityValue( intval( $row->user_id ), $userName );
274 $ugms = $this->getGroupMemberships( $userIdentity );
275 foreach ( $ugms as $ugm ) {
276 $list[] = $this->buildGroupLink( $ugm, $userName );
277 }
278
279 $groups = $lang->commaList( $list );
280
281 $item = $lang->specialList( $ulinks, $groups );
282
283 // If there is a block, 'deleted' and 'sitewide' are both set on
284 // $this->blockStatusByUid[$row->user_id].
285 $blocked = '';
286 $isBlocked = isset( $this->blockStatusByUid[$row->user_id] );
287 if ( $isBlocked ) {
288 if ( $this->blockStatusByUid[$row->user_id]['deleted'] == 1 ) {
289 $item = "<span class=\"deleted\">$item</span>";
290 }
291 if ( $this->blockStatusByUid[$row->user_id]['sitewide'] == 1 ) {
292 $blocked = ' ' . $this->msg( 'listusers-blocked', $userName )->escaped();
293 }
294 }
295 $count = $this->msg( 'activeusers-count' )->numParams( $row->recentedits )
296 ->params( $userName )->numParams( $this->RCMaxAge )->escaped();
297
298 return Html::rawElement( 'li', [], "{$item} [{$count}]{$blocked}" );
299 }
300
301}
302
307class_alias( ActiveUsersPager::class, 'ActiveUsersPager' );
308
310class_alias( ActiveUsersPager::class, 'MediaWiki\\Pager\\ActiveUsersPager' );
const NS_USER
Definition Defines.php:53
Helpers for building queries that determine whether a user is hidden.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Helper class to keep track of options when mixing links and form elements.
getValue( $name)
Get the value for the given option name.
This class is a collection of static functions that serve two purposes:
Definition Html.php:43
Some internal bits split of from Skin.php.
Definition Linker.php:47
A class containing constants representing the names of configuration variables.
const ActiveUserDays
Name constant for the ActiveUserDays setting, for use with Config::get()
Factory for LinkBatch objects to batch query page metadata.
getDatabase()
Get the Database object in use.
string string[] $mIndexField
The index to actually be used for ordering.
getSqlComment()
Get some text to go in brackets in the "function name" part of the SQL comment.
This class is used to get a list of active users.
buildQueryInfo( $offset, $limit, $order)
Build variables to use by the database wrapper.For b/c, query direction is true for ascending and fal...
__construct(IContextSource $context, HookContainer $hookContainer, LinkBatchFactory $linkBatchFactory, IConnectionProvider $dbProvider, UserGroupManager $userGroupManager, UserIdentityLookup $userIdentityLookup, HideUserUtils $hideUserUtils, TempUserConfig $tempUserConfig, RecentChangeLookup $recentChangeLookup, FormOptions $opts)
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called....
This class is used to get a list of user.
getGroupMemberships( $user)
Get an associative array containing groups the specified user belongs to, and the relevant UserGroupM...
buildGroupLink( $group, $username)
Format a link to a group description page.
Represents a title within MediaWiki.
Definition Title.php:69
Manage user group memberships.
Value object representing a user's identity.
Interface for objects which can provide a MediaWiki context on request.
Interface for temporary user creation config and name matching.
Service for looking up UserIdentity.
Provide primary and replica IDatabase connections.