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