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