root > WikiSense-trunk > web > ActiveUsers.php

ActiveUsers.php

application/x-php, 13540 bytes (load raw)
<?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')."&amp;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);;
}

$groupOptionsarray('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&section=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();
?>