<?php
if ( defined( 'WS_TOC' ) ) {
$info['description'] = "Lists currently active users, filtered by admin flag, babel-box, etc";
return;
}
#TODO:
# language-dropdown (project-specific?!)
# use RC table if replag is low
# cache rss feed for a minute or so
# show all tags & categories for each user
# allow listing all users (no group) => privacy?!
define( 'WS_WEB', true );
require_once("common/WebInit.php");
require_once("WikiQuery.php");
define('RSS_CACHE_DURATION', 5); //minutes
function extractActiveUsers($url) {
$fh = fopen($url, 'r');
if (!$fh) return false;
$users = array();
$time = NULL;
$user = NULL;
$since = NULL;
while ($s = fgets($fh)) {
$s = trim($s);
if ($s==='') continue;
if (strpos($s, '<')===false) continue;
//print "- LINE ".escapeHtml($s)." <br>\n";
if (preg_match('!<item>!', $s)) {
//new item, reset; recovery in case we get confused.
$time = NULL;
$user = NULL;
#print "- RESET<br>\n";
continue;
}
if (preg_match('!<pubDate>([^<>]+)</pubDate>!', $s, $m)) {
$time = wfTimestamp( TS_MW, strtotime( trim( $m[1] ) ) );
#print "- TIME $time<br>\n";
}
if (preg_match('!<dc:creator>([^<>]+)</dc:creator>!', $s, $m)) {
$user = Sanitizer::decodeCharReferences( trim( $m[1] ) );
//the user name should already be in canonical form. Note that we want spaces here, not underscores.
#print "- USER $user<br>\n";
}
if ($time) $since = $time; //find oldest entry (rely on newest-first ordering of feed)
if ($time && $user) {
if (!preg_match('!^\d{1,3}(\.\d{1,3}){3}$|^(:[0-9a-fA-F]{0,4}){1,8}:?$!', $user)) { //ignore IPs (v4 and v6)
//set time once per user (rely on newest-first ordering of feed)
if (!isset($users[$user])) {
#print "- REGISTERED $user => $time<br>\n";
$users[$user] = $time;
}
}
#print "- DISCARDING $user => $time<br>\n";
$time = NULL;
$user = NULL;
}
}
fclose($fh);
$users['*'] = $since; //pseudo-entry for first edit in the feed
#print_r($users);
return serialize($users);
}
function fetchActiveUsers(&$wiki, &$db, $table, $makeTempTable) {
#NOTE: scriptversion avoids conflicts between different versions of this script, by making the cache key unique.
$u= $wiki->baseURL.'?title=Special:Recentchanges&feed=rss&limit=2000&scriptversion=3';
$users = fetch_via_disk_cache($u, RSS_CACHE_DURATION, 'extractActiveUsers');
if (!$users) throw new Exception("failed to fetch feed from $u");
$users = unserialize($users);
if (!$users) throw new Exception("failed to unserialize data for $u");
if ($makeTempTable) {
$db->query("create temporary table $table (
`user` varbinary(255) NOT NULL,
`page` varbinary(255) NOT NULL,
`timestamp` char(14) NOT NULL,
PRIMARY KEY (`user`),
KEY `timestamp` (`timestamp`)
)");
}
$from = $users['*'];
foreach ($users as $user => $time) {
if ($user == '*') continue;
$sql = "INSERT INTO $table (user, page, timestamp) ";
$sql .= "VALUES ( ".$db->addQuotes($user).", ".$db->addQuotes(str_replace(' ', '_', $user)).", ".$db->addQuotes($time).") ";
$sql .= "ON DUPLICATE KEY UPDATE timestamp = IF(timestamp > VALUES(timestamp), timestamp, VALUES(timestamp))";
$db->query($sql); #todo: use prepared statement; or collect all users into one big statement - and check for block size limit.
}
return $from;
}
class ActiveUsersQuery extends WikiQuery {
function ActiveUsersQuery($name, $sql) {
WikiQuery::WikiQuery($name, $sql);
}
function isComplete() {
global $mode, $ulang;
if ($mode=='commons' && !$ulang) return false;
return WikiQuery::isComplete();
}
function printHTMLNavi( $base, $ofs, $max = 100, $hasMore = true ) {
//no paging.
}
function printReplag() {
//ignore, we are mainly using RSS
}
function getQuery() {
$wiki =& $this->getWiki();
$auxDbName = preg_replace('!^.*/!', '', $GLOBALS['wsgQCacheDB']);
$auxTableName = "$auxDbName.active_users";
$this->feedFrom = fetchActiveUsers($wiki, $wiki->wikiDB, $auxTableName, true);
$this->query = str_replace('$active_users', $auxTableName, $this->query);
#print "<pre>".$this->query."</pre>";
return $this->query;
}
function printHTMLHeader() {
if (isset($this->feedFrom)) {
$t = wfTimestamp(TS_DB, $this->feedFrom);
print "\n\t<p>Activity since $t</p>\n";
}
WikiQuery::printHTMLHeader();
}
function printOrderAndFormat( ) {
global $mode;
if ($mode!='commons') WikiQuery::printOrderAndFormat();
}
function close() {
if (isset($this->auxDB)) {
$this->auxDB->close();
}
WikiQuery::close();
}
function printOptions( ) {
global $wgRequest, $groupOptions;
global $ulang, $languages, $wgLanguageNames;
global $mode;
if ($mode == 'commons') {
print "\n\t\t<p>";
print "<a href='".$_SERVER['PHP_SELF']."?".$this->getURLParameters('mode')."'>switch to generic mode</a>";
print "</p>\n";
}
else {
print "\n\t\t<p>";
print "<a href='".$_SERVER['PHP_SELF']."?".$this->getURLParameters('mode')."&mode=commons'>switch to commons mode</a>";
print "</p>\n";
}
print "\n\t\t<p>";
if ($mode != 'commons') {
print "<label for='groups'>Group:</label> ";
printSelector('groups', $groupOptions, $wgRequest->getVal('groups'));
}
if ($mode == 'commons') {
$langOptions = array();
foreach ($languages as $lang) {
$langOptions[$lang] = $lang.' - '.$wgLanguageNames[$lang];
}
print "<label for='ulang'>Babel-Language:</label> ";
printSelector('ulang', $langOptions, $ulang);
print "\t\t<input type='hidden' name='mode' value='commons'/>\n";
}
else {
print "<label for='tags'>Tags:</label> ";
print "<input type='text' name='tags' value='".escapeHtml($wgRequest->getVal('tags'), ENT_QUOTES)."'/>";
print "<label for='cats'>Categories:</label> ";
print "<input type='text' name='cats' value='".escapeHtml($wgRequest->getVal('cats'), ENT_QUOTES)."'/>";
}
print "</p>\n";
return true;
}
}
function sqlSet($items) {
global $wiki;
if (is_string($items)) $items = preg_split('!\s*[|,;/]+\s*!', trim($items));
$s = '';
$i = 0;
foreach ($items as $t) {
$i+= 1;
if ($i>10) break; //max 10!
if ($s) $s.= ', ';
if ($t === NULL) $s.= 'NULL';
else {
$t = $wiki->asDBKey($t);
$s.= $wiki->wikiDB->addQuotes($t);
}
}
return '(' . $s . ')';
}
function getUserTags($userpage) {
global $wq, $wiki;
$sql = "select tl_namespace, tl_title from templatelinks
join page on tl_from = page_id
where page_namespace = ".NS_USER." and page_title = ".$wiki->wikiDB->addQuotes($userpage)."";
$s = '';
$res = $wiki->wikiDB->query($sql, "getUserTags");
while ($row = $wiki->wikiDB->fetchRow($res)) {
if ($s!=='') $s.= ', ';
$s.= $wq->makeLink($row['tl_title'], $row['tl_namespace'], $row['tl_namespace'] != NS_TEMPLATE);
}
return $s;
}
function getUserLanguages($userpage) {
global $wq, $wiki, $ulang;
$sql = "select tl_title from templatelinks
join page on tl_from = page_id
where tl_namespace = ".NS_TEMPLATE." and page_namespace = ".NS_USER." and page_title = ".$wiki->wikiDB->addQuotes($userpage)."";
$lang = array();
$res = $wiki->wikiDB->query($sql, "getUserLanguages");
while ($row = $wiki->wikiDB->fetchRow($res)) {
$tag = $row['tl_title'];
if (!preg_match('!User_([a-z]+)(-([\d\w]))?!', $tag, $m)) continue;
$lng = $m[1];
$lvl = @$m[3];
if ($lng=='admin') continue;
if ($lvl===NULL) $lvl = 'N';
if (!isset($lang[$lng]) || !$lang[$lng] || $lang[$lng]<$lvl) $lang[$lng] = $lvl;
}
asort($lang);
$lang = array_reverse($lang);
$s = '';
foreach ($lang as $lng => $lvl) {
if ($lvl == '0') continue;
$st = '';
if ($lng == $ulang) $st = ' wanted';
if ($s!=='') $s.= ', ';
$s.= "<span class='level-$lvl$st'>$lng-$lvl</span>";
}
return $s;
}
function getUserCategories($userpage) {
global $wq, $wiki;
$sql = "select cl_to from categorylinks
join page on cl_from = page_id
where page_namespace = ".NS_USER." and page_title = ".$wiki->wikiDB->addQuotes($userpage)."";
$s = '';
$res = $wiki->wikiDB->query($sql, "getUserCategories");
while ($row = $wiki->wikiDB->fetchRow($res)) {
if ($s!=='') $s.= ', ';
$s.= $wq->makeLink($row['cl_to'], NS_CATEGORY, false);
}
return $s;
}
function timeDiff( $timestamp ) {
$s = (time() - wfTimestamp(TS_UNIX, $timestamp));
$h = floor($s / 3600);
$s -= $h * 3600;
$m = floor($s / 60);
$s -= $m * 60;
return sprintf('<span class="idle idle-%d"><span class="hours">%d</span><span class="minutes">:%02d</span></span>', $h, $h, $m);
#if ($h) return sprintf('<span class="hours">%d</span><span class="minutes">:%02d</span><span class="seconds">:%02d</span>', $h, $m, $s);
#else return sprintf('<span class="minutes">%d</span><span class="seconds">:%02d</span>', $m, $s);;
}
$groupOptions = array('sysop' => 'Admins',
'bureaucrat' => 'Bureaucrat',
'checkuser' => 'CheckUser',
'steward' => 'Steward',
'import' => 'Import',
'oversight' => 'Oversight');
$languages = array(
'af', 'an', 'ar', 'ast', 'ay', 'be', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'en', 'el', 'eo', 'es', 'et',
'fi', 'fa', 'fr', 'fy', 'he', 'hi', 'hu', 'hr', 'id', 'is', 'it', 'ja', 'ko', 'la', 'lb', 'mi', 'ms',
'nl', 'no', 'nrm', 'pl', 'pt', 'qu', 'ro', 'ru', 'sk', 'sl', 'sq', 'sr', 'sv', 'sw', 'th', 'zh', 'zh-min-nan'
);
$wsgLogLevel = $wgRequest->getVal('ll', -2);
$wq= new ActiveUsersQuery('ActiveUsers', '*dummy*');
$mode = $wgRequest->getVal('mode');
if ($mode == 'commons') {
$wq->fixedWiki = true;
$wq->wiki = 'commons.wikimedia.org';
$groups= $wgRequest->getVal('groups');
if (!$groups) $groups = 'sysop';
$tags= NULL;
$cats= NULL;
$ulang = $wgRequest->getVal('ulang');
if (!$ulang) $ulang = 'en';
$cats = 'User_'.$ulang;
}
else {
$groups= $wgRequest->getVal('groups');
$tags= $wgRequest->getVal('tags');
$cats= $wgRequest->getVal('cats');
if (!$groups) $groups = 'sysop';
}
$wiki =& $wq->getWiki();
$wq->query= "SELECT user, page, timestamp FROM \$active_users";
$where = array();
if ($groups && $wiki) {
if ($wq->scope) $wq->scope .= ' and ';
$wq->scope .= 'group ' . $groups;
$wq->query .= " JOIN user ON user_name = active_users.user";
$wq->query .= " JOIN user_groups ON ug_user = user_id";
$where[] = " ug_group IN " . sqlSet($groups);
}
if ($tags && $wiki) {
if ($wq->scope) $wq->scope .= ' and ';
$wq->scope .= 'tags ' . $tags;
$wq->query .= " JOIN page ON page_namespace = ".NS_USER." AND page_title = active_users.page";
$wq->query .= " JOIN templatelinks ON tl_from = page_id";
$where[] = " tl_namespace = ".NS_TEMPLATE." AND tl_title IN " . sqlSet($tags);
}
if ($cats && $wiki) {
if ($wq->scope) $wq->scope .= ' and ';
$wq->scope .= 'categories ' . $cats;
if (!$tags) $wq->query .= " JOIN page ON page_namespace = ".NS_USER." AND page_title = active_users.page";
$wq->query .= " JOIN categorylinks ON cl_from = page_id";
$where[] = " cl_to IN " . sqlSet($cats);
}
if ($where) {
$wq->query .= " WHERE " . implode(" AND ", $where);
}
if ($groups || $tags || $cats) {
$wq->query .= " GROUP BY active_users.user";
}
#$wq->query= "SELECT rc_user_text as user, rc_timestamp as timestamp FROM recentchanges";
$wq->defaultOrder= '-timestamp';
$wq->fields['user']= array(
'label' => 'User',
'pattern' => 'return $this->makeLink( $row["user"], NS_USER, false ) . "<small> (".$this->makeLink( $row["user"], NS_USER +1, false, NULL, "talk")." ".$this->makeLink( $row["user"], NS_USER +1, false, NULL, "+", "&action=edit§ion=new").")</small>";',
'html' => true,
#'filter' => true,
'attributes' => 'style="white-space:nowrap;"',
);
$wq->fields['page']= array(
'label' => 'Page',
'hidden' => true,
);
$wq->fields['timestamp']= array(
'label' => 'Idle',
'pattern' => 'return timeDiff($row["timestamp"]);',
'sort' => true,
'html' => true,
'attributes' => 'style="white-space:nowrap; text-align:right;"',
);
if ($mode=='commons') {
$wq->fields['languages']= array(
'label' => 'Languages',
'pattern' => 'return getUserLanguages($row["page"]);',
'html' => true,
'attributes' => 'style="font-size:86%;"',
);
}
else {
$wq->fields['alltags']= array(
'label' => 'User Tags',
'pattern' => 'return getUserTags($row["page"]);',
'html' => true,
'attributes' => 'style="font-size:77%;"',
);
$wq->fields['allcats']= array(
'label' => 'User Categories',
'pattern' => 'return getUserCategories($row["page"]);',
'html' => true,
'attributes' => 'style="font-size:77%;"',
);
}
$wq->styles[]= '.idle { color:#888888; }';
$wq->styles[]= '.idle-3 { font-wight:normal; color:#666666; }';
$wq->styles[]= '.idle-2 { font-wight:normal; color:#333333; }';
$wq->styles[]= '.idle-1 { font-wight:normal; color:#000000; }';
$wq->styles[]= '.idle-0 { font-wight:normal; color:#000000; background-color:#FFFF88; }';
$wq->styles[]= '.seconds { font-size:77%; }';
$wq->styles[]= '.minutes { font-size:86%; }';
$wq->styles[]= '.wanted { background-color:#FFFF88; }';
$wq->styles[]= '.level-1 { color:#888888; }';
$wq->styles[]= '.level-2 { color:#444444; }';
$wq->styles[]= '.level-3 { color:#000000; font-weight:bold; }';
$wq->styles[]= '.level-4 { color:#004400; font-weight:bold; }';
$wq->styles[]= '.level-N { color:#008800; font-weight:bold; }';
$wq->helpPageName= 'ActiveUsers';
$wq->printPage();
$wq->close();
?>ActiveUsers.php
application/x-php, 13540 bytes (load raw)

