MediaWiki master
ActiveUsersPager.php
Go to the documentation of this file.
1<?php
22namespace MediaWiki\Pager;
23
37
49 protected $opts;
50
54 protected $groups;
55
59 private $blockStatusByUid;
60
62 private $RCMaxAge;
63
65 private $excludegroups;
66
77 public function __construct(
78 IContextSource $context,
79 HookContainer $hookContainer,
80 LinkBatchFactory $linkBatchFactory,
81 IConnectionProvider $dbProvider,
82 UserGroupManager $userGroupManager,
83 UserIdentityLookup $userIdentityLookup,
86 ) {
87 parent::__construct(
88 $context,
89 $hookContainer,
90 $linkBatchFactory,
91 $dbProvider,
92 $userGroupManager,
93 $userIdentityLookup,
95 null,
96 null
97 );
98
99 $this->RCMaxAge = $this->getConfig()->get( MainConfigNames::ActiveUserDays );
100 $this->requestedUser = '';
101
102 $un = $opts->getValue( 'username' );
103 if ( $un != '' ) {
104 $username = Title::makeTitleSafe( NS_USER, $un );
105 if ( $username !== null ) {
106 $this->requestedUser = $username->getText();
107 }
108 }
109
110 $this->groups = $opts->getValue( 'groups' );
111 $this->excludegroups = $opts->getValue( 'excludegroups' );
112 // Backwards-compatibility with old URLs
113 if ( $opts->getValue( 'hidebots' ) ) {
114 $this->excludegroups[] = 'bot';
115 }
116 if ( $opts->getValue( 'hidesysops' ) ) {
117 $this->excludegroups[] = 'sysop';
118 }
119 }
120
121 public function getIndexField() {
122 return 'qcc_title';
123 }
124
125 public function getQueryInfo( $data = null ) {
126 $dbr = $this->getDatabase();
127
128 $activeUserSeconds = $this->getConfig()->get( MainConfigNames::ActiveUserDays ) * 86400;
129 $timestamp = $dbr->timestamp( (int)wfTimestamp( TS_UNIX ) - $activeUserSeconds );
130 $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')';
131
132 // Inner subselect to pull the active users out of querycachetwo
133 $tables = [ 'querycachetwo', 'user', 'actor' ];
134 $fields = [ 'qcc_title', 'user_id', 'actor_id' ];
135 $jconds = [
136 'user' => [ 'JOIN', 'user_name = qcc_title' ],
137 'actor' => [ 'JOIN', 'actor_user = user_id' ],
138 ];
139 $conds = [
140 'qcc_type' => 'activeusers',
141 'qcc_namespace' => NS_USER,
142 ];
143 $options = [];
144 if ( $data !== null ) {
145 $options['ORDER BY'] = 'qcc_title ' . $data['order'];
146 $options['LIMIT'] = $data['limit'];
147 $conds = array_merge( $conds, $data['conds'] );
148 }
149 if ( $this->requestedUser != '' ) {
150 $conds[] = $dbr->expr( 'qcc_title', '>=', $this->requestedUser );
151 }
152 if ( $this->groups !== [] ) {
153 $tables['ug1'] = 'user_groups';
154 $jconds['ug1'] = [ 'JOIN', 'ug1.ug_user = user_id' ];
155 $conds['ug1.ug_group'] = $this->groups;
156 $conds[] = 'ug1.ug_expiry IS NULL OR ug1.ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() );
157 }
158 if ( $this->excludegroups !== [] ) {
159 $tables['ug2'] = 'user_groups';
160 $jconds['ug2'] = [ 'LEFT JOIN', [
161 'ug2.ug_user = user_id',
162 'ug2.ug_group' => $this->excludegroups,
163 'ug2.ug_expiry IS NULL OR ug2.ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() ),
164 ] ];
165 $conds['ug2.ug_user'] = null;
166 }
167 if ( !$this->canSeeHideuser() ) {
168 $conds[] = $this->hideUserUtils->getExpression( $dbr );
169 }
170 $subquery = $dbr->buildSelectSubquery( $tables, $fields, $conds, $fname, $options, $jconds );
171
172 // Outer query to select the recent edit counts for the selected active users
173 $tables = [ 'qcc_users' => $subquery, 'recentchanges' ];
174 $jconds = [ 'recentchanges' => [ 'LEFT JOIN', [
175 'rc_actor = actor_id',
176 $dbr->expr( 'rc_type', '!=', RC_EXTERNAL ), // Don't count wikidata.
177 $dbr->expr( 'rc_type', '!=', RC_CATEGORIZE ), // Don't count categorization changes.
178 $dbr->expr( 'rc_log_type', '=', null )->or( 'rc_log_type', '!=', 'newusers' ),
179 $dbr->expr( 'rc_timestamp', '>=', $timestamp ),
180 ] ] ];
181 $conds = [];
182
183 return [
184 'tables' => $tables,
185 'fields' => [
186 'qcc_title',
187 'user_name' => 'qcc_title',
188 'user_id' => 'user_id',
189 'recentedits' => 'COUNT(DISTINCT rc_id)'
190 ],
191 'options' => [ 'GROUP BY' => [ 'qcc_title', 'user_id' ] ],
192 'conds' => $conds,
193 'join_conds' => $jconds,
194 ];
195 }
196
197 protected function buildQueryInfo( $offset, $limit, $order ) {
198 $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')';
199
200 $sortColumns = array_merge( [ $this->mIndexField ], $this->mExtraSortFields );
201 if ( $order === self::QUERY_ASCENDING ) {
202 $dir = 'ASC';
203 $orderBy = $sortColumns;
204 $operator = $this->mIncludeOffset ? '>=' : '>';
205 } else {
206 $dir = 'DESC';
207 $orderBy = [];
208 foreach ( $sortColumns as $col ) {
209 $orderBy[] = $col . ' DESC';
210 }
211 $operator = $this->mIncludeOffset ? '<=' : '<';
212 }
213 $info = $this->getQueryInfo( [
214 'limit' => intval( $limit ),
215 'order' => $dir,
216 'conds' =>
217 $offset != '' ? [ $this->mIndexField . $operator . $this->getDatabase()->addQuotes( $offset ) ] : [],
218 ] );
219
220 $tables = $info['tables'];
221 $fields = $info['fields'];
222 $conds = $info['conds'];
223 $options = $info['options'];
224 $join_conds = $info['join_conds'];
225 $options['ORDER BY'] = $orderBy;
226 return [ $tables, $fields, $conds, $fname, $options, $join_conds ];
227 }
228
229 protected function doBatchLookups() {
230 parent::doBatchLookups();
231
232 $uids = [];
233 foreach ( $this->mResult as $row ) {
234 $uids[] = (int)$row->user_id;
235 }
236 // Fetch the block status of the user for showing "(blocked)" text and for
237 // striking out names of suppressed users when privileged user views the list.
238 // Although the first query already hits the block table for un-privileged, this
239 // is done in two queries to avoid huge quicksorts and to make COUNT(*) correct.
240 $dbr = $this->getDatabase();
241 $res = $dbr->newSelectQueryBuilder()
242 ->select( [
243 'bt_user',
244 'deleted' => 'MAX(bl_deleted)',
245 'sitewide' => 'MAX(bl_sitewide)'
246 ] )
247 ->from( 'block_target' )
248 ->join( 'block', null, 'bl_target=bt_id' )
249 ->where( [ 'bt_user' => $uids ] )
250 ->groupBy( [ 'bt_user' ] )
251 ->caller( __METHOD__ )->fetchResultSet();
252 $this->blockStatusByUid = [];
253 foreach ( $res as $row ) {
254 $this->blockStatusByUid[$row->bt_user] = [
255 'deleted' => $row->deleted,
256 'sitewide' => $row->sitewide,
257 ];
258 }
259 $this->mResult->seek( 0 );
260 }
261
262 public function formatRow( $row ) {
263 $userName = $row->user_name;
264
265 $ulinks = Linker::userLink( $row->user_id, $userName );
266 $ulinks .= Linker::userToolLinks(
267 $row->user_id,
268 $userName,
269 // Should the contributions link be red if the user has no edits (using default)
270 false,
271 // Customisation flags (using default 0)
272 0,
273 // User edit count (using default)
274 null,
275 // do not wrap the message in parentheses (CSS will provide these)
276 false
277 );
278
279 $lang = $this->getLanguage();
280
281 $list = [];
282
283 $userIdentity = new UserIdentityValue( intval( $row->user_id ), $userName );
284 $ugms = $this->getGroupMemberships( $userIdentity );
285 foreach ( $ugms as $ugm ) {
286 $list[] = $this->buildGroupLink( $ugm, $userName );
287 }
288
289 $groups = $lang->commaList( $list );
290
291 $item = $lang->specialList( $ulinks, $groups );
292
293 // If there is a block, 'deleted' and 'sitewide' are both set on
294 // $this->blockStatusByUid[$row->user_id].
295 $blocked = '';
296 $isBlocked = isset( $this->blockStatusByUid[$row->user_id] );
297 if ( $isBlocked ) {
298 if ( $this->blockStatusByUid[$row->user_id]['deleted'] == 1 ) {
299 $item = "<span class=\"deleted\">$item</span>";
300 }
301 if ( $this->blockStatusByUid[$row->user_id]['sitewide'] == 1 ) {
302 $blocked = ' ' . $this->msg( 'listusers-blocked', $userName )->escaped();
303 }
304 }
305 $count = $this->msg( 'activeusers-count' )->numParams( $row->recentedits )
306 ->params( $userName )->numParams( $this->RCMaxAge )->escaped();
307
308 return Html::rawElement( 'li', [], "{$item} [{$count}]{$blocked}" );
309 }
310
311}
312
317class_alias( ActiveUsersPager::class, 'ActiveUsersPager' );
const NS_USER
Definition Defines.php:67
const RC_EXTERNAL
Definition Defines.php:120
const RC_CATEGORIZE
Definition Defines.php:121
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
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:56
Some internal bits split of from Skin.php.
Definition Linker.php:63
A class containing constants representing the names of configuration variables.
const ActiveUserDays
Name constant for the ActiveUserDays setting, for use with Config::get()
This class is used to get a list of active users.
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
buildQueryInfo( $offset, $limit, $order)
Build variables to use by the database wrapper.
__construct(IContextSource $context, HookContainer $hookContainer, LinkBatchFactory $linkBatchFactory, IConnectionProvider $dbProvider, UserGroupManager $userGroupManager, UserIdentityLookup $userIdentityLookup, HideUserUtils $hideUserUtils, FormOptions $opts)
getDatabase()
Get the Database object in use.
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 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.
HideUserUtils $hideUserUtils
Represents a title within MediaWiki.
Definition Title.php:78
Value object representing a user's identity.
Interface for objects which can provide a MediaWiki context on request.
Provide primary and replica IDatabase connections.