MediaWiki  master
ActiveUsersPager.php
Go to the documentation of this file.
1 <?php
23 
32 
36  protected $opts;
37 
41  protected $groups;
42 
47 
49  private $RCMaxAge;
50 
52  private $excludegroups;
53 
59  parent::__construct( $context );
60 
61  $this->RCMaxAge = $this->getConfig()->get( 'ActiveUserDays' );
62  $this->requestedUser = '';
63 
64  $un = $opts->getValue( 'username' );
65  if ( $un != '' ) {
66  $username = Title::makeTitleSafe( NS_USER, $un );
67  if ( !is_null( $username ) ) {
68  $this->requestedUser = $username->getText();
69  }
70  }
71 
72  $this->groups = $opts->getValue( 'groups' );
73  $this->excludegroups = $opts->getValue( 'excludegroups' );
74  // Backwards-compatibility with old URLs
75  if ( $opts->getValue( 'hidebots' ) ) {
76  $this->excludegroups[] = 'bot';
77  }
78  if ( $opts->getValue( 'hidesysops' ) ) {
79  $this->excludegroups[] = 'sysop';
80  }
81  }
82 
83  function getIndexField() {
84  return 'qcc_title';
85  }
86 
87  function getQueryInfo( $data = null ) {
88  $dbr = $this->getDatabase();
89 
90  $activeUserSeconds = $this->getConfig()->get( 'ActiveUserDays' ) * 86400;
91  $timestamp = $dbr->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
92  $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')';
93 
94  // Inner subselect to pull the active users out of querycachetwo
95  $tables = [ 'querycachetwo', 'user', 'actor' ];
96  $fields = [ 'qcc_title', 'user_id', 'actor_id' ];
97  $jconds = [
98  'user' => [ 'JOIN', 'user_name = qcc_title' ],
99  'actor' => [ 'JOIN', 'actor_user = user_id' ],
100  ];
101  $conds = [
102  'qcc_type' => 'activeusers',
103  'qcc_namespace' => NS_USER,
104  ];
105  $options = [];
106  if ( $data !== null ) {
107  $options['ORDER BY'] = 'qcc_title ' . $data['order'];
108  $options['LIMIT'] = $data['limit'];
109  $conds = array_merge( $conds, $data['conds'] );
110  }
111  if ( $this->requestedUser != '' ) {
112  $conds[] = 'qcc_title >= ' . $dbr->addQuotes( $this->requestedUser );
113  }
114  if ( $this->groups !== [] ) {
115  $tables['ug1'] = 'user_groups';
116  $jconds['ug1'] = [ 'JOIN', 'ug1.ug_user = user_id' ];
117  $conds['ug1.ug_group'] = $this->groups;
118  $conds[] = 'ug1.ug_expiry IS NULL OR ug1.ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() );
119  }
120  if ( $this->excludegroups !== [] ) {
121  $tables['ug2'] = 'user_groups';
122  $jconds['ug2'] = [ 'LEFT JOIN', [
123  'ug2.ug_user = user_id',
124  'ug2.ug_group' => $this->excludegroups,
125  'ug2.ug_expiry IS NULL OR ug2.ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() ),
126  ] ];
127  $conds['ug2.ug_user'] = null;
128  }
129  if ( !MediaWikiServices::getInstance()
130  ->getPermissionManager()
131  ->userHasRight( $this->getUser(), 'hideuser' )
132  ) {
133  $conds[] = 'NOT EXISTS (' . $dbr->selectSQLText(
134  'ipblocks', '1', [ 'ipb_user=user_id', 'ipb_deleted' => 1 ]
135  ) . ')';
136  }
137  $subquery = $dbr->buildSelectSubquery( $tables, $fields, $conds, $fname, $options, $jconds );
138 
139  // Outer query to select the recent edit counts for the selected active users
140  $tables = [ 'qcc_users' => $subquery, 'recentchanges' ];
141  $jconds = [ 'recentchanges' => [ 'LEFT JOIN', [
142  'rc_actor = actor_id',
143  'rc_type != ' . $dbr->addQuotes( RC_EXTERNAL ), // Don't count wikidata.
144  'rc_type != ' . $dbr->addQuotes( RC_CATEGORIZE ), // Don't count categorization changes.
145  'rc_log_type IS NULL OR rc_log_type != ' . $dbr->addQuotes( 'newusers' ),
146  'rc_timestamp >= ' . $dbr->addQuotes( $timestamp ),
147  ] ] ];
148  $conds = [];
149 
150  return [
151  'tables' => $tables,
152  'fields' => [
153  'qcc_title',
154  'user_name' => 'qcc_title',
155  'user_id' => 'user_id',
156  'recentedits' => 'COUNT(rc_id)'
157  ],
158  'options' => [ 'GROUP BY' => [ 'qcc_title', 'user_id' ] ],
159  'conds' => $conds,
160  'join_conds' => $jconds,
161  ];
162  }
163 
164  protected function buildQueryInfo( $offset, $limit, $order ) {
165  $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')';
166 
167  $sortColumns = array_merge( [ $this->mIndexField ], $this->mExtraSortFields );
168  if ( $order === self::QUERY_ASCENDING ) {
169  $dir = 'ASC';
170  $orderBy = $sortColumns;
171  $operator = $this->mIncludeOffset ? '>=' : '>';
172  } else {
173  $dir = 'DESC';
174  $orderBy = [];
175  foreach ( $sortColumns as $col ) {
176  $orderBy[] = $col . ' DESC';
177  }
178  $operator = $this->mIncludeOffset ? '<=' : '<';
179  }
180  $info = $this->getQueryInfo( [
181  'limit' => intval( $limit ),
182  'order' => $dir,
183  'conds' =>
184  $offset != '' ? [ $this->mIndexField . $operator . $this->mDb->addQuotes( $offset ) ] : [],
185  ] );
186 
187  $tables = $info['tables'];
188  $fields = $info['fields'];
189  $conds = $info['conds'];
190  $options = $info['options'];
191  $join_conds = $info['join_conds'];
192  $options['ORDER BY'] = $orderBy;
193  return [ $tables, $fields, $conds, $fname, $options, $join_conds ];
194  }
195 
196  protected function doBatchLookups() {
197  parent::doBatchLookups();
198 
199  $uids = [];
200  foreach ( $this->mResult as $row ) {
201  $uids[] = $row->user_id;
202  }
203  // Fetch the block status of the user for showing "(blocked)" text and for
204  // striking out names of suppressed users when privileged user views the list.
205  // Although the first query already hits the block table for un-privileged, this
206  // is done in two queries to avoid huge quicksorts and to make COUNT(*) correct.
207  $dbr = $this->getDatabase();
208  $res = $dbr->select( 'ipblocks',
209  [ 'ipb_user', 'MAX(ipb_deleted) AS deleted, MAX(ipb_sitewide) AS sitewide' ],
210  [ 'ipb_user' => $uids ],
211  __METHOD__,
212  [ 'GROUP BY' => [ 'ipb_user' ] ]
213  );
214  $this->blockStatusByUid = [];
215  foreach ( $res as $row ) {
216  $this->blockStatusByUid[$row->ipb_user] = [
217  'deleted' => $row->deleted,
218  'sitewide' => $row->sitewide,
219  ];
220  }
221  $this->mResult->seek( 0 );
222  }
223 
224  function formatRow( $row ) {
225  $userName = $row->user_name;
226 
227  $ulinks = Linker::userLink( $row->user_id, $userName );
228  $ulinks .= Linker::userToolLinks(
229  $row->user_id,
230  $userName,
231  // Should the contributions link be red if the user has no edits (using default)
232  false,
233  // Customisation flags (using default 0)
234  0,
235  // User edit count (using default)
236  null,
237  // do not wrap the message in parentheses (CSS will provide these)
238  false
239  );
240 
241  $lang = $this->getLanguage();
242 
243  $list = [];
244 
245  $ugms = self::getGroupMemberships( intval( $row->user_id ), $this->userGroupCache );
246  foreach ( $ugms as $ugm ) {
247  $list[] = $this->buildGroupLink( $ugm, $userName );
248  }
249 
250  $groups = $lang->commaList( $list );
251 
252  $item = $lang->specialList( $ulinks, $groups );
253 
254  // If there is a block, 'deleted' and 'sitewide' are both set on
255  // $this->blockStatusByUid[$row->user_id].
256  $blocked = '';
257  $isBlocked = isset( $this->blockStatusByUid[$row->user_id] );
258  if ( $isBlocked ) {
259  if ( $this->blockStatusByUid[$row->user_id]['deleted'] == 1 ) {
260  $item = "<span class=\"deleted\">$item</span>";
261  }
262  if ( $this->blockStatusByUid[$row->user_id]['sitewide'] == 1 ) {
263  $blocked = ' ' . $this->msg( 'listusers-blocked', $userName )->escaped();
264  }
265  }
266  $count = $this->msg( 'activeusers-count' )->numParams( $row->recentedits )
267  ->params( $userName )->numParams( $this->RCMaxAge )->escaped();
268 
269  return Html::rawElement( 'li', [], "{$item} [{$count}]{$blocked}" );
270  }
271 
272 }
Helper class to keep track of options when mixing links and form elements.
Definition: FormOptions.php:35
const RC_CATEGORIZE
Definition: Defines.php:126
This class is used to get a list of user.
Definition: UsersPager.php:35
This class is used to get a list of active users.
if(!isset( $args[0])) $lang
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
IContextSource $context
getDatabase()
Get the Database object in use.
Definition: IndexPager.php:224
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getSqlComment()
Get some text to go in brackets in the "function name" part of the SQL comment.
Definition: IndexPager.php:400
static userToolLinks( $userId, $userText, $redContribsWhenNoEdits=false, $flags=0, $edits=null, $useParentheses=true)
Generate standard user tool links (talk, contributions, block link, etc.)
Definition: Linker.php:943
string [] $excludegroups
buildQueryInfo( $offset, $limit, $order)
__construct(?IContextSource $context, FormOptions $opts)
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition: Linker.php:898
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:612
getQueryInfo( $data=null)
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
const RC_EXTERNAL
Definition: Defines.php:125
buildGroupLink( $group, $username)
Format a link to a group description page.
Definition: UsersPager.php:452
getValue( $name)
Get the value for the given option name.