MediaWiki
REL1_23
|
00001 <?php 00033 class ActiveUsersPager extends UsersPager { 00034 00038 protected $opts; 00039 00043 protected $hideGroups = array(); 00044 00048 protected $hideRights = array(); 00049 00055 function __construct( IContextSource $context = null, $group = null, $par = null ) { 00056 global $wgActiveUserDays; 00057 00058 parent::__construct( $context ); 00059 00060 $this->RCMaxAge = $wgActiveUserDays; 00061 $un = $this->getRequest()->getText( 'username', $par ); 00062 $this->requestedUser = ''; 00063 if ( $un != '' ) { 00064 $username = Title::makeTitleSafe( NS_USER, $un ); 00065 if ( !is_null( $username ) ) { 00066 $this->requestedUser = $username->getText(); 00067 } 00068 } 00069 00070 $this->setupOptions(); 00071 } 00072 00073 public function setupOptions() { 00074 $this->opts = new FormOptions(); 00075 00076 $this->opts->add( 'hidebots', false, FormOptions::BOOL ); 00077 $this->opts->add( 'hidesysops', false, FormOptions::BOOL ); 00078 00079 $this->opts->fetchValuesFromRequest( $this->getRequest() ); 00080 00081 if ( $this->opts->getValue( 'hidebots' ) == 1 ) { 00082 $this->hideRights[] = 'bot'; 00083 } 00084 if ( $this->opts->getValue( 'hidesysops' ) == 1 ) { 00085 $this->hideGroups[] = 'sysop'; 00086 } 00087 } 00088 00089 function getIndexField() { 00090 return 'qcc_title'; 00091 } 00092 00093 function getQueryInfo() { 00094 $dbr = $this->getDatabase(); 00095 00096 $conds = array( 00097 'qcc_type' => 'activeusers', 00098 'qcc_namespace' => NS_USER, 00099 'user_name = qcc_title', 00100 'rc_user_text = qcc_title', 00101 'rc_type != ' . $dbr->addQuotes( RC_EXTERNAL ) // Don't count wikidata. 00102 ); 00103 if ( $this->requestedUser != '' ) { 00104 $conds[] = 'qcc_title >= ' . $dbr->addQuotes( $this->requestedUser ); 00105 } 00106 if ( !$this->getUser()->isAllowed( 'hideuser' ) ) { 00107 $conds[] = 'NOT EXISTS (' . $dbr->selectSQLText( 00108 'ipblocks', '1', array( 'ipb_user=user_id', 'ipb_deleted' => 1 ) 00109 ) . ')'; 00110 } 00111 00112 return array( 00113 'tables' => array( 'querycachetwo', 'user', 'recentchanges' ), 00114 'fields' => array( 'user_name', 'user_id', 'recentedits' => 'COUNT(*)', 'qcc_title' ), 00115 'options' => array( 'GROUP BY' => array( 'qcc_title' ) ), 00116 'conds' => $conds 00117 ); 00118 } 00119 00120 function doBatchLookups() { 00121 $uids = array(); 00122 foreach ( $this->mResult as $row ) { 00123 $uids[] = $row->user_id; 00124 } 00125 // Fetch the block status of the user for showing "(blocked)" text and for 00126 // striking out names of suppressed users when privileged user views the list. 00127 // Although the first query already hits the block table for un-privileged, this 00128 // is done in two queries to avoid huge quicksorts and to make COUNT(*) correct. 00129 $dbr = $this->getDatabase(); 00130 $res = $dbr->select( 'ipblocks', 00131 array( 'ipb_user', 'MAX(ipb_deleted) AS block_status' ), 00132 array( 'ipb_user' => $uids ), 00133 __METHOD__, 00134 array( 'GROUP BY' => array( 'ipb_user' ) ) 00135 ); 00136 $this->blockStatusByUid = array(); 00137 foreach ( $res as $row ) { 00138 $this->blockStatusByUid[$row->ipb_user] = $row->block_status; // 0 or 1 00139 } 00140 $this->mResult->seek( 0 ); 00141 } 00142 00143 function formatRow( $row ) { 00144 $userName = $row->user_name; 00145 00146 $ulinks = Linker::userLink( $row->user_id, $userName ); 00147 $ulinks .= Linker::userToolLinks( $row->user_id, $userName ); 00148 00149 $lang = $this->getLanguage(); 00150 00151 $list = array(); 00152 $user = User::newFromId( $row->user_id ); 00153 00154 // User right filter 00155 foreach ( $this->hideRights as $right ) { 00156 // Calling User::getRights() within the loop so that 00157 // if the hideRights() filter is empty, we don't have to 00158 // trigger the lazy-init of the big userrights array in the 00159 // User object 00160 if ( in_array( $right, $user->getRights() ) ) { 00161 return ''; 00162 } 00163 } 00164 00165 // User group filter 00166 // Note: This is a different loop than for user rights, 00167 // because we're reusing it to build the group links 00168 // at the same time 00169 foreach ( $user->getGroups() as $group ) { 00170 if ( in_array( $group, $this->hideGroups ) ) { 00171 return ''; 00172 } 00173 $list[] = self::buildGroupLink( $group, $userName ); 00174 } 00175 00176 $groups = $lang->commaList( $list ); 00177 00178 $item = $lang->specialList( $ulinks, $groups ); 00179 00180 $isBlocked = isset( $this->blockStatusByUid[$row->user_id] ); 00181 if ( $isBlocked && $this->blockStatusByUid[$row->user_id] == 1 ) { 00182 $item = "<span class=\"deleted\">$item</span>"; 00183 } 00184 $count = $this->msg( 'activeusers-count' )->numParams( $row->recentedits ) 00185 ->params( $userName )->numParams( $this->RCMaxAge )->escaped(); 00186 $blocked = $isBlocked ? ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() : ''; 00187 00188 return Html::rawElement( 'li', array(), "{$item} [{$count}]{$blocked}" ); 00189 } 00190 00191 function getPageHeader() { 00192 global $wgScript; 00193 00194 $self = $this->getTitle(); 00195 $limit = $this->mLimit ? Html::hidden( 'limit', $this->mLimit ) : ''; 00196 00197 $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); # Form tag 00198 $out .= Xml::fieldset( $this->msg( 'activeusers' )->text() ) . "\n"; 00199 $out .= Html::hidden( 'title', $self->getPrefixedDBkey() ) . $limit . "\n"; 00200 00201 $out .= Xml::inputLabel( $this->msg( 'activeusers-from' )->text(), 00202 'username', 'offset', 20, $this->requestedUser, array( 'tabindex' => 1 ) ) . '<br />';# Username field 00203 00204 $out .= Xml::checkLabel( $this->msg( 'activeusers-hidebots' )->text(), 00205 'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ), array( 'tabindex' => 2 ) ); 00206 00207 $out .= Xml::checkLabel( $this->msg( 'activeusers-hidesysops' )->text(), 00208 'hidesysops', 'hidesysops', $this->opts->getValue( 'hidesysops' ), array( 'tabindex' => 3 ) ) . '<br />'; 00209 00210 $out .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text(), array( 'tabindex' => 4 ) ) . "\n";# Submit button and form bottom 00211 $out .= Xml::closeElement( 'fieldset' ); 00212 $out .= Xml::closeElement( 'form' ); 00213 00214 return $out; 00215 } 00216 } 00217 00221 class SpecialActiveUsers extends SpecialPage { 00222 00226 public function __construct() { 00227 parent::__construct( 'Activeusers' ); 00228 } 00229 00235 public function execute( $par ) { 00236 global $wgActiveUserDays; 00237 00238 $this->setHeaders(); 00239 $this->outputHeader(); 00240 00241 $out = $this->getOutput(); 00242 $out->wrapWikiMsg( "<div class='mw-activeusers-intro'>\n$1\n</div>", 00243 array( 'activeusers-intro', $this->getLanguage()->formatNum( $wgActiveUserDays ) ) ); 00244 00245 // Occasionally merge in new updates 00246 $seconds = min( self::mergeActiveUsers( 600 ), $wgActiveUserDays * 86400 ); 00247 // Mention the level of staleness 00248 $out->addWikiMsg( 'cachedspecial-viewing-cached-ttl', 00249 $this->getLanguage()->formatDuration( $seconds ) ); 00250 00251 $up = new ActiveUsersPager( $this->getContext(), null, $par ); 00252 00253 # getBody() first to check, if empty 00254 $usersbody = $up->getBody(); 00255 00256 $out->addHTML( $up->getPageHeader() ); 00257 if ( $usersbody ) { 00258 $out->addHTML( 00259 $up->getNavigationBar() . 00260 Html::rawElement( 'ul', array(), $usersbody ) . 00261 $up->getNavigationBar() 00262 ); 00263 } else { 00264 $out->addWikiMsg( 'activeusers-noresult' ); 00265 } 00266 } 00267 00268 protected function getGroupName() { 00269 return 'users'; 00270 } 00271 00276 public static function mergeActiveUsers( $period ) { 00277 global $wgActiveUserDays; 00278 00279 $dbr = wfGetDB( DB_SLAVE ); 00280 $cTime = $dbr->selectField( 'querycache_info', 00281 'qci_timestamp', 00282 array( 'qci_type' => 'activeusers' ) 00283 ); 00284 if ( !wfReadOnly() ) { 00285 if ( !$cTime || ( time() - wfTimestamp( TS_UNIX, $cTime ) ) > $period ) { 00286 $dbw = wfGetDB( DB_MASTER ); 00287 if ( $dbw->estimateRowCount( 'recentchanges' ) <= 10000 ) { 00288 $window = $wgActiveUserDays * 86400; // small wiki 00289 } else { 00290 $window = $period * 2; 00291 } 00292 self::doQueryCacheUpdate( $dbw, $window ); 00293 } 00294 } 00295 00296 return ( time() - 00297 ( $cTime ? wfTimestamp( TS_UNIX, $cTime ) : $wgActiveUserDays * 86400 ) ); 00298 } 00299 00304 public static function cacheUpdate( DatabaseBase $dbw ) { 00305 global $wgActiveUserDays; 00306 00307 self::doQueryCacheUpdate( $dbw, $wgActiveUserDays * 86400 ); 00308 } 00309 00317 protected static function doQueryCacheUpdate( DatabaseBase $dbw, $window ) { 00318 global $wgActiveUserDays; 00319 00320 $lockKey = wfWikiID() . '-activeusers'; 00321 if ( !$dbw->lock( $lockKey, __METHOD__, 1 ) ) { 00322 return false; // exclusive update (avoids duplicate entries) 00323 } 00324 00325 $now = time(); 00326 $cTime = $dbw->selectField( 'querycache_info', 00327 'qci_timestamp', 00328 array( 'qci_type' => 'activeusers' ) 00329 ); 00330 $cTimeUnix = $cTime ? wfTimestamp( TS_UNIX, $cTime ) : 1; 00331 00332 // Pick the date range to fetch from. This is normally from the last 00333 // update to till the present time, but has a limited window for sanity. 00334 // If the window is limited, multiple runs are need to fully populate it. 00335 $sTimestamp = max( $cTimeUnix, $now - $wgActiveUserDays * 86400 ); 00336 $eTimestamp = min( $sTimestamp + $window, $now ); 00337 00338 // Get all the users active since the last update 00339 $res = $dbw->select( 00340 array( 'recentchanges' ), 00341 array( 'rc_user_text', 'lastedittime' => 'MAX(rc_timestamp)' ), 00342 array( 00343 'rc_user > 0', // actual accounts 00344 'rc_type != ' . $dbw->addQuotes( RC_EXTERNAL ), // no wikidata 00345 'rc_log_type IS NULL OR rc_log_type != ' . $dbw->addQuotes( 'newusers' ), 00346 'rc_timestamp >= ' . $dbw->addQuotes( $dbw->timestamp( $sTimestamp ) ), 00347 'rc_timestamp <= ' . $dbw->addQuotes( $dbw->timestamp( $eTimestamp ) ) 00348 ), 00349 __METHOD__, 00350 array( 00351 'GROUP BY' => array( 'rc_user_text' ), 00352 'ORDER BY' => 'NULL' // avoid filesort 00353 ) 00354 ); 00355 $names = array(); 00356 foreach ( $res as $row ) { 00357 $names[$row->rc_user_text] = $row->lastedittime; 00358 } 00359 00360 // Rotate out users that have not edited in too long (according to old data set) 00361 $dbw->delete( 'querycachetwo', 00362 array( 00363 'qcc_type' => 'activeusers', 00364 'qcc_value < ' . $dbw->addQuotes( $now - $wgActiveUserDays * 86400 ) // TS_UNIX 00365 ), 00366 __METHOD__ 00367 ); 00368 00369 // Find which of the recently active users are already accounted for 00370 if ( count( $names ) ) { 00371 $res = $dbw->select( 'querycachetwo', 00372 array( 'user_name' => 'qcc_title' ), 00373 array( 00374 'qcc_type' => 'activeusers', 00375 'qcc_namespace' => NS_USER, 00376 'qcc_title' => array_keys( $names ) ), 00377 __METHOD__ 00378 ); 00379 foreach ( $res as $row ) { 00380 unset( $names[$row->user_name] ); 00381 } 00382 } 00383 00384 // Insert the users that need to be added to the list (which their last edit time 00385 if ( count( $names ) ) { 00386 $newRows = array(); 00387 foreach ( $names as $name => $lastEditTime ) { 00388 $newRows[] = array( 00389 'qcc_type' => 'activeusers', 00390 'qcc_namespace' => NS_USER, 00391 'qcc_title' => $name, 00392 'qcc_value' => wfTimestamp( TS_UNIX, $lastEditTime ), 00393 'qcc_namespacetwo' => 0, // unused 00394 'qcc_titletwo' => '' // unused 00395 ); 00396 } 00397 foreach ( array_chunk( $newRows, 500 ) as $rowBatch ) { 00398 $dbw->insert( 'querycachetwo', $rowBatch, __METHOD__ ); 00399 wfWaitForSlaves(); 00400 } 00401 } 00402 00403 // Touch the data freshness timestamp 00404 $dbw->replace( 'querycache_info', 00405 array( 'qci_type' ), 00406 array( 'qci_type' => 'activeusers', 00407 'qci_timestamp' => $dbw->timestamp( $eTimestamp ) ), // not always $now 00408 __METHOD__ 00409 ); 00410 00411 $dbw->unlock( $lockKey, __METHOD__ ); 00412 00413 return true; 00414 } 00415 }