[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Implements Special:Activeusers 4 * 5 * Copyright © 2008 Aaron Schulz 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License along 18 * with this program; if not, write to the Free Software Foundation, Inc., 19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 * http://www.gnu.org/copyleft/gpl.html 21 * 22 * @file 23 * @ingroup SpecialPage 24 */ 25 26 /** 27 * This class is used to get a list of active users. The ones with specials 28 * rights (sysop, bureaucrat, developer) will have them displayed 29 * next to their names. 30 * 31 * @ingroup SpecialPage 32 */ 33 class ActiveUsersPager extends UsersPager { 34 /** 35 * @var FormOptions 36 */ 37 protected $opts; 38 39 /** 40 * @var array 41 */ 42 protected $hideGroups = array(); 43 44 /** 45 * @var array 46 */ 47 protected $hideRights = array(); 48 49 /** 50 * @var array 51 */ 52 private $blockStatusByUid; 53 54 /** 55 * @param IContextSource $context 56 * @param null $group Unused 57 * @param string $par Parameter passed to the page 58 */ 59 function __construct( IContextSource $context = null, $group = null, $par = null ) { 60 parent::__construct( $context ); 61 62 $this->RCMaxAge = $this->getConfig()->get( 'ActiveUserDays' ); 63 $un = $this->getRequest()->getText( 'username', $par ); 64 $this->requestedUser = ''; 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->setupOptions(); 73 } 74 75 public function setupOptions() { 76 $this->opts = new FormOptions(); 77 78 $this->opts->add( 'hidebots', false, FormOptions::BOOL ); 79 $this->opts->add( 'hidesysops', false, FormOptions::BOOL ); 80 81 $this->opts->fetchValuesFromRequest( $this->getRequest() ); 82 83 if ( $this->opts->getValue( 'hidebots' ) == 1 ) { 84 $this->hideRights[] = 'bot'; 85 } 86 if ( $this->opts->getValue( 'hidesysops' ) == 1 ) { 87 $this->hideGroups[] = 'sysop'; 88 } 89 } 90 91 function getIndexField() { 92 return 'qcc_title'; 93 } 94 95 function getQueryInfo() { 96 $dbr = $this->getDatabase(); 97 98 $activeUserSeconds = $this->getConfig()->get( 'ActiveUserDays' ) * 86400; 99 $timestamp = $dbr->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds ); 100 $conds = array( 101 'qcc_type' => 'activeusers', 102 'qcc_namespace' => NS_USER, 103 'user_name = qcc_title', 104 'rc_user_text = qcc_title', 105 'rc_type != ' . $dbr->addQuotes( RC_EXTERNAL ), // Don't count wikidata. 106 'rc_log_type IS NULL OR rc_log_type != ' . $dbr->addQuotes( 'newusers' ), 107 'rc_timestamp >= ' . $dbr->addQuotes( $timestamp ), 108 ); 109 if ( $this->requestedUser != '' ) { 110 $conds[] = 'qcc_title >= ' . $dbr->addQuotes( $this->requestedUser ); 111 } 112 if ( !$this->getUser()->isAllowed( 'hideuser' ) ) { 113 $conds[] = 'NOT EXISTS (' . $dbr->selectSQLText( 114 'ipblocks', '1', array( 'ipb_user=user_id', 'ipb_deleted' => 1 ) 115 ) . ')'; 116 } 117 118 return array( 119 'tables' => array( 'querycachetwo', 'user', 'recentchanges' ), 120 'fields' => array( 'user_name', 'user_id', 'recentedits' => 'COUNT(*)', 'qcc_title' ), 121 'options' => array( 'GROUP BY' => array( 'qcc_title' ) ), 122 'conds' => $conds 123 ); 124 } 125 126 function doBatchLookups() { 127 $uids = array(); 128 foreach ( $this->mResult as $row ) { 129 $uids[] = $row->user_id; 130 } 131 // Fetch the block status of the user for showing "(blocked)" text and for 132 // striking out names of suppressed users when privileged user views the list. 133 // Although the first query already hits the block table for un-privileged, this 134 // is done in two queries to avoid huge quicksorts and to make COUNT(*) correct. 135 $dbr = $this->getDatabase(); 136 $res = $dbr->select( 'ipblocks', 137 array( 'ipb_user', 'MAX(ipb_deleted) AS block_status' ), 138 array( 'ipb_user' => $uids ), 139 __METHOD__, 140 array( 'GROUP BY' => array( 'ipb_user' ) ) 141 ); 142 $this->blockStatusByUid = array(); 143 foreach ( $res as $row ) { 144 $this->blockStatusByUid[$row->ipb_user] = $row->block_status; // 0 or 1 145 } 146 $this->mResult->seek( 0 ); 147 } 148 149 function formatRow( $row ) { 150 $userName = $row->user_name; 151 152 $ulinks = Linker::userLink( $row->user_id, $userName ); 153 $ulinks .= Linker::userToolLinks( $row->user_id, $userName ); 154 155 $lang = $this->getLanguage(); 156 157 $list = array(); 158 $user = User::newFromId( $row->user_id ); 159 160 // User right filter 161 foreach ( $this->hideRights as $right ) { 162 // Calling User::getRights() within the loop so that 163 // if the hideRights() filter is empty, we don't have to 164 // trigger the lazy-init of the big userrights array in the 165 // User object 166 if ( in_array( $right, $user->getRights() ) ) { 167 return ''; 168 } 169 } 170 171 // User group filter 172 // Note: This is a different loop than for user rights, 173 // because we're reusing it to build the group links 174 // at the same time 175 foreach ( $user->getGroups() as $group ) { 176 if ( in_array( $group, $this->hideGroups ) ) { 177 return ''; 178 } 179 $list[] = self::buildGroupLink( $group, $userName ); 180 } 181 182 $groups = $lang->commaList( $list ); 183 184 $item = $lang->specialList( $ulinks, $groups ); 185 186 $isBlocked = isset( $this->blockStatusByUid[$row->user_id] ); 187 if ( $isBlocked && $this->blockStatusByUid[$row->user_id] == 1 ) { 188 $item = "<span class=\"deleted\">$item</span>"; 189 } 190 $count = $this->msg( 'activeusers-count' )->numParams( $row->recentedits ) 191 ->params( $userName )->numParams( $this->RCMaxAge )->escaped(); 192 $blocked = $isBlocked ? ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() : ''; 193 194 return Html::rawElement( 'li', array(), "{$item} [{$count}]{$blocked}" ); 195 } 196 197 function getPageHeader() { 198 $self = $this->getTitle(); 199 $limit = $this->mLimit ? Html::hidden( 'limit', $this->mLimit ) : ''; 200 201 # Form tag 202 $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) ); 203 $out .= Xml::fieldset( $this->msg( 'activeusers' )->text() ) . "\n"; 204 $out .= Html::hidden( 'title', $self->getPrefixedDBkey() ) . $limit . "\n"; 205 206 # Username field 207 $out .= Xml::inputLabel( $this->msg( 'activeusers-from' )->text(), 208 'username', 'offset', 20, $this->requestedUser, array( 'tabindex' => 1 ) ) . '<br />'; 209 210 $out .= Xml::checkLabel( $this->msg( 'activeusers-hidebots' )->text(), 211 'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ), array( 'tabindex' => 2 ) ); 212 213 $out .= Xml::checkLabel( 214 $this->msg( 'activeusers-hidesysops' )->text(), 215 'hidesysops', 216 'hidesysops', 217 $this->opts->getValue( 'hidesysops' ), 218 array( 'tabindex' => 3 ) 219 ) . '<br />'; 220 221 # Submit button and form bottom 222 $out .= Xml::submitButton( 223 $this->msg( 'allpagessubmit' )->text(), 224 array( 'tabindex' => 4 ) 225 ) . "\n"; 226 $out .= Xml::closeElement( 'fieldset' ); 227 $out .= Xml::closeElement( 'form' ); 228 229 return $out; 230 } 231 } 232 233 /** 234 * @ingroup SpecialPage 235 */ 236 class SpecialActiveUsers extends SpecialPage { 237 238 /** 239 * Constructor 240 */ 241 public function __construct() { 242 parent::__construct( 'Activeusers' ); 243 } 244 245 /** 246 * Show the special page 247 * 248 * @param string $par Parameter passed to the page or null 249 */ 250 public function execute( $par ) { 251 $days = $this->getConfig()->get( 'ActiveUserDays' ); 252 253 $this->setHeaders(); 254 $this->outputHeader(); 255 256 $out = $this->getOutput(); 257 $out->wrapWikiMsg( "<div class='mw-activeusers-intro'>\n$1\n</div>", 258 array( 'activeusers-intro', $this->getLanguage()->formatNum( $days ) ) ); 259 260 // Occasionally merge in new updates 261 $seconds = min( self::mergeActiveUsers( 600, $days ), $days * 86400 ); 262 // Mention the level of staleness 263 $out->addWikiMsg( 'cachedspecial-viewing-cached-ttl', 264 $this->getLanguage()->formatDuration( $seconds ) ); 265 266 $up = new ActiveUsersPager( $this->getContext(), null, $par ); 267 268 # getBody() first to check, if empty 269 $usersbody = $up->getBody(); 270 271 $out->addHTML( $up->getPageHeader() ); 272 if ( $usersbody ) { 273 $out->addHTML( 274 $up->getNavigationBar() . 275 Html::rawElement( 'ul', array(), $usersbody ) . 276 $up->getNavigationBar() 277 ); 278 } else { 279 $out->addWikiMsg( 'activeusers-noresult' ); 280 } 281 } 282 283 protected function getGroupName() { 284 return 'users'; 285 } 286 287 /** 288 * @param int $period Seconds (do updates no more often than this) 289 * @param int $days How many days user must be idle before he is considered inactive 290 * @return int How many seconds old the cache is 291 */ 292 public static function mergeActiveUsers( $period, $days ) { 293 $dbr = wfGetDB( DB_SLAVE ); 294 $cTime = $dbr->selectField( 'querycache_info', 295 'qci_timestamp', 296 array( 'qci_type' => 'activeusers' ) 297 ); 298 299 if ( !wfReadOnly() ) { 300 if ( !$cTime || ( time() - wfTimestamp( TS_UNIX, $cTime ) ) > $period ) { 301 $dbw = wfGetDB( DB_MASTER ); 302 if ( $dbw->estimateRowCount( 'recentchanges' ) <= 10000 ) { 303 $window = $days * 86400; // small wiki 304 } else { 305 $window = $period * 2; 306 } 307 $cTime = self::doQueryCacheUpdate( $dbw, $days, $window ) ?: $cTime; 308 } 309 } 310 311 return ( time() - 312 ( $cTime ? wfTimestamp( TS_UNIX, $cTime ) : $days * 86400 ) ); 313 } 314 315 /** 316 * @param DatabaseBase $dbw Passed in from updateSpecialPages.php 317 * @return void 318 */ 319 public static function cacheUpdate( DatabaseBase $dbw ) { 320 global $wgActiveUserDays; 321 322 self::doQueryCacheUpdate( $dbw, $wgActiveUserDays, $wgActiveUserDays * 86400 ); 323 } 324 325 /** 326 * Update the query cache as needed 327 * 328 * @param DatabaseBase $dbw 329 * @param int $days How many days user must be idle before he is considered inactive 330 * @param int $window Maximum time range of new data to scan (in seconds) 331 * @return int|bool UNIX timestamp the cache is now up-to-date as of (false on error) 332 */ 333 protected static function doQueryCacheUpdate( DatabaseBase $dbw, $days, $window ) { 334 $lockKey = wfWikiID() . '-activeusers'; 335 if ( !$dbw->lock( $lockKey, __METHOD__, 1 ) ) { 336 return false; // exclusive update (avoids duplicate entries) 337 } 338 339 $now = time(); 340 $cTime = $dbw->selectField( 'querycache_info', 341 'qci_timestamp', 342 array( 'qci_type' => 'activeusers' ) 343 ); 344 $cTimeUnix = $cTime ? wfTimestamp( TS_UNIX, $cTime ) : 1; 345 346 // Pick the date range to fetch from. This is normally from the last 347 // update to till the present time, but has a limited window for sanity. 348 // If the window is limited, multiple runs are need to fully populate it. 349 $sTimestamp = max( $cTimeUnix, $now - $days * 86400 ); 350 $eTimestamp = min( $sTimestamp + $window, $now ); 351 352 // Get all the users active since the last update 353 $res = $dbw->select( 354 array( 'recentchanges' ), 355 array( 'rc_user_text', 'lastedittime' => 'MAX(rc_timestamp)' ), 356 array( 357 'rc_user > 0', // actual accounts 358 'rc_type != ' . $dbw->addQuotes( RC_EXTERNAL ), // no wikidata 359 'rc_log_type IS NULL OR rc_log_type != ' . $dbw->addQuotes( 'newusers' ), 360 'rc_timestamp >= ' . $dbw->addQuotes( $dbw->timestamp( $sTimestamp ) ), 361 'rc_timestamp <= ' . $dbw->addQuotes( $dbw->timestamp( $eTimestamp ) ) 362 ), 363 __METHOD__, 364 array( 365 'GROUP BY' => array( 'rc_user_text' ), 366 'ORDER BY' => 'NULL' // avoid filesort 367 ) 368 ); 369 $names = array(); 370 foreach ( $res as $row ) { 371 $names[$row->rc_user_text] = $row->lastedittime; 372 } 373 374 // Rotate out users that have not edited in too long (according to old data set) 375 $dbw->delete( 'querycachetwo', 376 array( 377 'qcc_type' => 'activeusers', 378 'qcc_value < ' . $dbw->addQuotes( $now - $days * 86400 ) // TS_UNIX 379 ), 380 __METHOD__ 381 ); 382 383 // Find which of the recently active users are already accounted for 384 if ( count( $names ) ) { 385 $res = $dbw->select( 'querycachetwo', 386 array( 'user_name' => 'qcc_title' ), 387 array( 388 'qcc_type' => 'activeusers', 389 'qcc_namespace' => NS_USER, 390 'qcc_title' => array_keys( $names ) ), 391 __METHOD__ 392 ); 393 foreach ( $res as $row ) { 394 unset( $names[$row->user_name] ); 395 } 396 } 397 398 // Insert the users that need to be added to the list (which their last edit time 399 if ( count( $names ) ) { 400 $newRows = array(); 401 foreach ( $names as $name => $lastEditTime ) { 402 $newRows[] = array( 403 'qcc_type' => 'activeusers', 404 'qcc_namespace' => NS_USER, 405 'qcc_title' => $name, 406 'qcc_value' => wfTimestamp( TS_UNIX, $lastEditTime ), 407 'qcc_namespacetwo' => 0, // unused 408 'qcc_titletwo' => '' // unused 409 ); 410 } 411 foreach ( array_chunk( $newRows, 500 ) as $rowBatch ) { 412 $dbw->insert( 'querycachetwo', $rowBatch, __METHOD__ ); 413 if ( !$dbw->trxLevel() ) { 414 wfWaitForSlaves(); 415 } 416 } 417 } 418 419 // Touch the data freshness timestamp 420 $dbw->replace( 'querycache_info', 421 array( 'qci_type' ), 422 array( 'qci_type' => 'activeusers', 423 'qci_timestamp' => $dbw->timestamp( $eTimestamp ) ), // not always $now 424 __METHOD__ 425 ); 426 427 $dbw->unlock( $lockKey, __METHOD__ ); 428 429 return $eTimestamp; 430 } 431 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |