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