NetBeans Active Users statistics scripts
diff --git a/pp3/au/Db.php b/pp3/au/Db.php
new file mode 100755
index 0000000..85a803d
--- /dev/null
+++ b/pp3/au/Db.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * Super simple mysql wrapper
+ *
+ */
+class Db {
+ public static $counter=0;
+
+ private static $_isConnected = false;
+ private static $_insData=array();
+ private static $_insCounter=0;
+
+ private static function connect() {
+ $link = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
+ if (!$link) {
+ throw new Exception('Can\'t connect to DB: '.DB_USER.':*****@'.DB_HOST);
+ }
+ $db_selected = mysql_select_db(DB_NAME, $link);
+ if (!$db_selected) {
+ throw new Exception('Can\'t select DB: '.DB_USER.':*****@'.DB_HOST.'/'.DB_NAME);
+ }
+ self::$_isConnected = true;
+ }
+
+ /**
+ * Simple mysql query wrapper which will log the SQL errors for us
+ * @param string $sql SQL statement to perform
+ * @return resource
+ */
+ public static function query($sql) {
+ if (self::$_isConnected == false) {
+ self::connect();
+ }
+ $result = mysql_query($sql);
+ if ($result != false) {
+ self::$counter++;
+ return $result;
+ } else {
+ // log error
+ Logger::write(Logger::DEBUG, 'SQL error for: '.$sql.'; ERR: '.mysql_error());
+ return false;
+ }
+ }
+
+ public static function deferredPingsInsert($data) {
+ self::$_insCounter++;
+ self::$_insData[]=$data;
+ if(0==(self::$_insCounter%1000)) {
+ $ret=self::query('INSERT INTO pings (ip_id, ts, path_id, distro_id, config_id, user_id, user2_id, response, size) VALUES '.implode(',', self::$_insData));
+ self::$_insCounter=0;
+ self::$_insData=array();
+ return $ret;
+ }
+ return true;
+ }
+
+}
+
+?>
diff --git a/pp3/au/Importer.php b/pp3/au/Importer.php
new file mode 100755
index 0000000..ffa4af1
--- /dev/null
+++ b/pp3/au/Importer.php
@@ -0,0 +1,255 @@
+<?php
+
+/**
+ * Importer takes parsed data and import it to DB
+ *
+ */
+class Importer {
+
+ public static $ipCache;
+ public static $userCache;
+ public static $user2Cache;
+ public static $catalogCache;
+ public static $distroCache;
+ public static $configCache;
+ private static $_cacheLoaded = false;
+ private static $_source;
+ private static $_packs;
+
+ public static function import($row, $source) {
+ //die(var_dump($row));
+ self::$_source = $source;
+ // load up catalogs, distros, configs to cache
+ if (self::$_cacheLoaded == false) {
+ self::loadCache();
+ }
+ // let's import that
+ $ip = $row['ip'];
+ $ts = $row['ts'];
+ $path = $row['path'];
+ $distro = $row['distro'];
+ $user_id = $row['user_id'];
+ $user2_id = $row['user2_id'];
+ $response = $row['response'];
+ $size = $row['size'];
+
+ // resolve the catalog
+ if (!self::$catalogCache[$path]) {
+ Db::query("INSERT INTO catalogs (path, source) VALUES ('$path', '$source')");
+ $catalogId = mysql_insert_id();
+ self::$catalogCache[$path] = $catalogId;
+ } else {
+ $catalogId = self::$catalogCache[$path];
+ }
+
+ // resolve the distro
+ if (preg_match('/(NB[A-Z]*)?_?([_A-Z]+)?/', $distro, $match)) {
+ $distro_code = "";
+ $config = "";
+ $config_sig = 0;
+ if (isset($match[1])) {
+ $distro_code = $match[1];
+ }
+ if (isset($match[2])) {
+ $config = $match[2];
+ $config_parts = explode("_", $config);
+ foreach ($config_parts as $part) {
+ if (isset(self::$_packs[$part])) {
+ $config_sig += self::$_packs[$part];
+ } else {
+ $warning_count++;
+ Logger::write(Logger::INFO, "Uknown pack ID: $part; $config; $distro_code");
+ }
+ }
+ }
+ }
+ if (!self::$distroCache[$distro_code]) {
+ Db::query("INSERT INTO distros (distro) VALUES ('$distro_code')");
+ $distroId = mysql_insert_id();
+ self::$distroCache[$distro_code] = $distroId;
+ } else {
+ $distroId = self::$distroCache[$distro_code];
+ }
+
+ // resolve config
+ if (!self::$configCache[$config]) {
+ Db::query("INSERT INTO configs (config, signature) VALUES ('$config', $config_sig)");
+ $configId = mysql_insert_id();
+ self::$configCache[$config] = $configId;
+ } else {
+ $configId = self::$configCache[$config];
+ }
+
+ // resolve the IP address
+// if (!self::$ipCache[$ip]) {
+// // lookup db for it
+// $res = Db::query('SELECT id FROM ips WHERE ip="' . $ip . '" LIMIT 1');
+// if ($res) {
+// if (mysql_num_rows($res) > 0) {
+// $row = mysql_fetch_assoc($res);
+// $ipId = $row['id'];
+// self::$ipCache[$ip] = $ipId;
+// } else {
+// Db::query("INSERT INTO ips (ip, country) VALUES ('$ip' ,'" . getCCByIP($ip) . "')");
+// $ipId = mysql_insert_id();
+// self::$ipCache[$ip] = $ipId;
+// }
+// }
+// } else {
+// $ipId = self::$ipCache[$ip];
+// }
+ $ipId=0;
+
+ // resolve userId
+ $tsInt = strtotime($ts);
+ if (!self::$userCache[$user_id]) {
+ $res = Db::query('SELECT id, since, last_seen FROM users WHERE unique_id="' . $user_id . '"');
+ if ($res) {
+ if (mysql_num_rows($res) > 0) {
+ $row = mysql_fetch_assoc($res);
+ $userId = $row['id'];
+ // if this ping is newer then one in DB, save it as last seen and calc delay
+ if ($tsInt > $row['last_seen']) {
+ // calc the delay since last ping
+ $delay = round(($tsInt - $row['last_seen']) / (60 * 60 * 24));
+ // update last seen and delay
+ if($delay>0) {
+ Db::query("UPDATE users SET last_seen=$tsInt, catalog_id=$catalogId, delay=$delay WHERE id=" . $userId);
+ }
+ }
+ self::$userCache[$user_id] = array('id' => $userId, 'since' => $row['since'], 'last_seen' => $tsInt, 'catalog_id' => $catalogId, 'delay' => $delay);
+ } else {
+ Db::query("INSERT INTO users (unique_id, since, last_seen, catalog_id, delay) VALUES ('$user_id', '$ts', $tsInt, $catalogId, 9999)");
+ $userId = mysql_insert_id();
+ self::$userCache[$user_id] = array('id' => $userId, 'since' => $ts, 'last_seen' => $tsInt, 'catalog_id' => $catalogId, 'delay' => 9999);
+ }
+ }
+ } else {
+ $userId=self::$userCache[$user_id]['id'];
+ // check if the hit from cache has newer timestamp, if so, mark user for update her timestamp
+ if (self::$userCache[$user_id]['since'] > $ts) {
+ $updateUsers = array('id' => self::$userCache[$user_id]['id'], 'since' => $ts);
+ }
+ }
+
+ // resolve userId
+ if (!self::$user2Cache[$user2_id]) {
+ $res = Db::query('SELECT id, since, last_seen FROM users2 WHERE unique_id="' . $user2_id . '"');
+ if ($res) {
+ if (mysql_num_rows($res) > 0) {
+ $row = mysql_fetch_assoc($res);
+ $user2Id = $row['id'];
+ // if this ping is newer then one in DB, save it as last seen and calc delay
+ if ($tsInt > $row['last_seen']) {
+ // calc the delay since last ping
+ $delay = round(($tsInt - $row['last_seen']) / (60 * 60 * 24));
+ // update last seen and delay
+ if($delay>0) {
+ Db::query("UPDATE users2 SET last_seen=$tsInt, delay=$delay WHERE id=" . $user2Id);
+ }
+ }
+ self::$user2Cache[$user2_id] = array('id' => $user2Id, 'since' => $row['since'], 'last_seen' => $tsInt, 'delay' => $delay);
+ } else {
+ Db::query("INSERT INTO users2 (unique_id, since, last_seen, delay) VALUES ('$user2_id', '$ts', $tsInt,9999)");
+ $user2Id = mysql_insert_id();
+ self::$user2Cache[$user2_id] = array('id' => $user2Id, 'since' => $ts, 'last_seen' => $tsInt, 'delay' => 9999);
+ }
+ }
+ } else {
+ $user2Id=self::$user2Cache[$user2_id]['id'];
+ // check if the hit from cache has newer timestamp, if so, mark user for update her timestamp
+ if (self::$user2Cache[$user2_id]['since'] > $ts) {
+ $updateUsers2 = array('id' => self::$user2Cache[$user2_id]['id'], 'since' => $ts);
+ }
+ }
+
+
+ // now save it to hits table finally
+ $res = Db::query("INSERT INTO pings (ip_id, ts, path_id, distro_id, config_id, user_id, user2_id, response, size) VALUES ($ipId, '$ts', $catalogId, $distroId, $configId, $userId, $user2Id, $response, $size)");
+ if ($res) {
+ $ret = true;
+ } else {
+ $ret = false;
+ }
+
+ // now update users since timestamps (might be case if we back import older logs)
+ if (!empty($updateUsers)) {
+ foreach ($updateUsers as $u) {
+ Db::query("UPDATE users SET since='" . $u['since'] . "' WHERE id=" . $u['id']);
+ }
+ }
+ if (!empty($updateUsers2)) {
+ foreach ($updateUsers2 as $u) {
+ Db::query("UPDATE users2 SET since='" . $u['since'] . "' WHERE id=" . $u['id']);
+ }
+ }
+ return $ret;
+ }
+
+ private function loadCache() {
+ $memBeforeCache = memory_get_usage();
+ $res = Db::query('SELECT id, path FROM catalogs WHERE source="' . self::$_source . '"');
+ if ($res) {
+ while ($r = mysql_fetch_assoc($res)) {
+ self::$catalogCache[$r['path']] = $r['id'];
+ }
+ }
+ $res = Db::query('SELECT id, config FROM configs');
+ if ($res) {
+ while ($r = mysql_fetch_assoc($res)) {
+ self::$configCache[$r['config']] = $r['id'];
+ }
+ }
+ $res = Db::query('SELECT id, distro FROM distros');
+ if ($res) {
+ while ($r = mysql_fetch_assoc($res)) {
+ self::$distroCache[$r['distro']] = $r['id'];
+ }
+ }
+
+ // log some stats on the memory usage so we know how is the cache expensive
+ Logger::write(Logger::INFO, 'Caching distros, catalogs, configs took ' . round((memory_get_usage() - $memBeforeCache) / 1024000, 1) . 'MB');
+
+ self::$_packs = array(
+ 'CND' => 0x0001,
+ 'CRE' => 0x0002,
+ 'ENT' => 0x0004,
+ 'MOB' => 0x0008,
+ 'PROF' => 0x0010,
+ 'CDC' => 0x0020,
+ 'CLDC' => 0x0040,
+ 'JAVA' => 0x0080,
+ 'JAVASE' => 0x0100,
+ 'JAVAEE' => 0x0200,
+ 'JAVAME' => 0x0400,
+ 'WEBEE' => 0x0800,
+ 'PROFILER' => 0x1000,
+ 'PHP' => 0x2000,
+ 'RUBY' => 0x4000,
+ 'MOBILITY' => 0x8000,
+ 'UML' => 0x10000,
+ 'SOA' => 0x20000,
+ 'GLASSFISH' => 0x40000,
+ 'SJSAS' => 0x80000,
+ 'TOMCAT' => 0x100000,
+ 'VISUALWEB' => 0x200000,
+ 'JDK' => 0x400000,
+ 'MYSQL' => 0x800000,
+ 'GROOVY' => 0x1000000,
+ 'GFMOD' => 0x2000000,
+ 'JAVAFX' => 0x4000000,
+ 'WEBCOMMON' => 0x8000000,
+ 'FX' => 0x10000000,
+ 'PY' => 0x20000000,
+ 'JC' => 0x40000000,
+ 'WEBLOGIC' => 0x80000000,
+ 'JAVAFXSDK' => 0x100000000,
+ 'NB' => 0x200000000,
+ 'EXTIDE' => 0x400000000
+ );
+self::$_cacheLoaded = true;
+}
+
+}
+
+?>
diff --git a/pp3/au/Logger.php b/pp3/au/Logger.php
new file mode 100755
index 0000000..52a59a5
--- /dev/null
+++ b/pp3/au/Logger.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * Simple logger class
+ *
+ */
+class Logger {
+
+ const INFO='INFO';
+ const ERROR='ERROR';
+ const DEBUG='DEBUG';
+ const FINE='FINE';
+
+ /**
+ * Sigleton instance
+ * @var Logger
+ */
+ private static $instance;
+
+ /**
+ * Info log file handle
+ * @var file handle
+ */
+ private $_logFileHandler;
+
+ /**
+ * Log info level message and echo it as well
+ * @param string $message
+ */
+ public static function write($level, $message) {
+ if (self::$instance) {
+ fwrite(self::$instance->_logFileHandler, date('Y-m-d H:i:s') . ' - [' . strtoupper($level) . '] - ' . $message . "\n");
+ }
+ if ($level!= self::DEBUG && $level!=self::FINE) {
+ // write out only info+error
+ echo date('Y-m-d H:i:s') . ' - [' . strtoupper($level) . '] - ' . $message . "\n";
+ }
+ }
+
+ private function __construct($logFile) {
+ $this->_logFileHandler = fopen($logFile, 'w');
+ if ($this->_logFileHandler == false) {
+ throw new Exception('Unable to create the info logfile ' . $logFile);
+ }
+ }
+
+ public function __destruct() {
+ // close file handlers when the object is going down
+ fclose($this->_logFileHandler);
+ }
+
+ /**
+ * Singleton init method
+ * @param string $logFile Path of the logfile
+ * @return Logger
+ */
+ public static function init($logFile) {
+ if (!isset(self::$instance)) {
+ self::$instance = new Logger($logFile);
+ }
+ return self::$instance;
+ }
+
+ public function __clone() {
+ trigger_error('Clone is not allowed.', E_USER_ERROR);
+ }
+
+ public function __wakeup() {
+ trigger_error('Unserializing is not allowed.', E_USER_ERROR);
+ }
+
+}
+
+?>
diff --git a/pp3/au/Runner.php b/pp3/au/Runner.php
new file mode 100755
index 0000000..6fbcf5a
--- /dev/null
+++ b/pp3/au/Runner.php
@@ -0,0 +1,237 @@
+<?php
+
+/**
+ * Main Runner which handles triggering individual tasks based on
+ * the cmdline params specifying what to run for what day
+ *
+ */
+class Runner {
+
+ static $currentDate;
+ static $grabbedLogFile;
+ static $product;
+
+
+ /**
+ * Run
+ * @param string $source Resource to run
+ * @return void
+ */
+ public static function run($source) {
+ $availaleSources = array('dlc', 'nb', 'vvm');
+ // prepare the queue of products to run for
+ if ($source == 'all') {
+ $runSources = $availaleSources;
+ } else {
+ if (in_array($source, $availaleSources)) {
+ $runSources = array($source);
+ } else {
+ //unknown product
+ throw new Exception('Unknown source specified ' . $source);
+ }
+ }
+ self::lock();
+ foreach ($runSources as $p) {
+ self::procesSource($p);
+ }
+ self::unlock();
+ }
+
+ private function procesSource($source) {
+ Logger::write(Logger::INFO, 'Starting processing soucre: ' . $source);
+ if (self::grabSourceLogfile($source) == true) {
+ // reset Db counter
+ Db::$counter = 0;
+ self::parseLogfile($source);
+ self::removeLogfile();
+ self::incrementRunDate($source);
+ }
+ }
+
+ //dsd
+
+ private function parseLogfile($source) {
+ $importCounter = 0;
+ Logger::write(Logger::INFO, 'Starting parsing of the ' . $source . ' logfile ' . self::$grabbedLogFile);
+ $months = array(
+ 'Jan' => '01',
+ 'Feb' => '02',
+ 'Mar' => '03',
+ 'Apr' => '04',
+ 'May' => '05',
+ 'Jun' => '06',
+ 'Jul' => '07',
+ 'Aug' => '08',
+ 'Sep' => '09',
+ 'Oct' => '10',
+ 'Nov' => '11',
+ 'Dec' => '12'
+ );
+ $handle = fopen(self::$grabbedLogFile, 'r');
+ if ($handle) {
+ $nl = $nok = 0;
+ while (($line = fgets($handle)) !== false) {
+ $hit = false;
+ // only interested in requests that include the 'unique' identifier as they represent the AU pings
+ if (!strstr($line, '?unique=') || strstr($line, '/hotfixes/') || strstr($line, '/thirdparty/')) {
+ $nl++;
+ continue;
+ }
+ switch ($source) {
+ case 'dlc':
+ $line=str_replace('<%JSON:httpd_access%> ','', $line);
+ $jsonLog = json_decode($line, true);
+ $preg='/^(.+?)\?unique=(unique%3D)?([_A-Z-]+)?(0)?([a-f0-9-]+)(_[a-f0-9-]+)?(.*)?/';
+ $m = array();
+ $hit = preg_match($preg, $jsonLog['request'], $match);
+ if($hit) {
+ $ip = $jsonLog['clientip'];
+ $date = date('Y-m-d', strtotime($jsonLog['time']));
+ $time = date('H:i:s', strtotime($jsonLog['time']));
+ //$timestamp = $match[1].' '.$match[2];
+ $path = $jsonLog['uri'];
+ $product_id = $match[3];
+ $user_id = $match[5];
+ if (isset($match[6]) && substr($match[6], 0, 1) == '_') {
+ $super_id = substr($match[6], 1);
+ } else {
+ $super_id = "";
+ }
+ $response = $jsonLog['status'];
+ $bytes = $jsonLog['bytes'];
+ }
+ break;
+
+ }
+ // let's parse it and fill these: $ip,$date $time,$path,$product_id,$user_id,$super_id,$response,$bytes
+ if($hit) {
+
+ // check the unique ID format
+ // prior to NB 5.5.1, the ID was just a timestamp (current time in millis),
+ // since NB 5.5.1, the ID is a standard UUID: 01234567-89ab-cdef-0123-456789abcdef
+ if (!preg_match('/^(([0-9]{5,12})|([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))/', $user_id, $match)) {
+ Logger::write(Logger::DEBUG, self::$grabbedLogFile . ": suspicious user ID '$user_id' in line: $nl - $line");
+ } else {
+ // in 6.5, a truly unique user ID (super ID) has been added,
+ // check the validity of this ID in UUID format, if available
+ if (strlen($super_id) > 0 && !preg_match('/^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/', $super_id, $match)) {
+ Logger::write(Logger::DEBUG, self::$grabbedLogFile . ": suspicious super ID '$super_id' in line: $nl - $line");
+ // log a warning, but don't stop here, if the super ID is invalid,
+ // just ignore it
+ $super_id = "";
+ }
+ // return data found, ignore invalid requests (404)
+ if (strcmp($response, "404")) {
+ try {
+ // import it to DB
+ if (Importer::import(array('ip' => $ip, 'ts' => $date . " " . $time, 'path' => $path, 'distro' => $product_id, 'user_id' => $user_id, 'user2_id' => $super_id, 'response' => $response, 'size' => $bytes), $source) == true) {
+ $importCounter++;
+ }
+ } catch (Exception $e) {
+ Logger::write(Logger::ERROR, 'Error happened during log entry import: ' . $e->getMessage());
+ }
+ $nok++;
+ }
+ }
+
+ }
+ $nl++;
+ }
+ Logger::write(Logger::INFO, 'Parsed ' . $nl . ' lines of the log, used ' . $nok . ' for importing');
+ Logger::write(Logger::INFO, 'Really inported into DB were ' . $importCounter . ' AU hits (needed ' . Db::$counter . ' queries)');
+ } else {
+ throw new Exception('Unable to open decompressed logfile for parsing ' . self::$grabbedLogFile);
+ }
+ return true;
+ }
+
+ private function grabSourceLogfile($source) {
+ // get the current date
+ $currentDate = strtotime('+1 day', self::getLastRunDate($source));
+ Logger::write(Logger::INFO, 'Current date set to ' . date('Y-m-d', $currentDate));
+ // put together the path to the source logfile
+ switch ($source) {
+ case 'nb': // CONSTANT to avoid typo!
+ $filename = 'access_' . date('Ymd', $currentDate);
+ $url = URL_PREFIX_NB . date('Y_m', $currentDate) . '/' . $filename . '.gz';
+ break;
+ case 'vvm':
+ $next_date = mktime(0, 0, 0, date("m", $currentDate), date("d", $currentDate) + 1, date("Y", $currentDate));
+ $filename = date('Ymd', $currentDate) . "-" . date('Ymd', $next_date) . ".log";
+ $url = URL_PREFIX_VVM . $filename . ".gz";
+ break;
+ case 'dlc':
+ $filename = 'netbeans-vm.apache.org_access.log_' . date('Ymd', $currentDate);
+ $url = URL_PREFIX_DLC . date('Y_m', $currentDate) . '/' . $filename . '.gz';
+ break;
+ default:
+ throw new Exception('Unknown source: ' . $source);
+ }
+ self::$grabbedLogFile = $filename;
+ self::$currentDate = $currentDate;
+ Logger::write(Logger::INFO, 'Going to download the source logfile: ' . $url);
+ // grab it using wget
+ system("wget --quiet $url", $returnVal);
+ if ($returnVal === 0) {
+ Logger::write(Logger::INFO, 'Source logfile downloaded');
+ // decompress it
+ system("gzip -fd $filename.gz", $returnVal);
+ if ($returnVal === 0) {
+ Logger::write(Logger::INFO, 'Source logfile decompressed');
+ return true;
+ }
+ } else {
+ Logger::write(Logger::INFO, 'Source logfile not available');
+ return false;
+ }
+ }
+
+ private function getLastRunDate($source) {
+ $ld = file(LAST_DATE_FILE_PREFIX . $source);
+ if ($ld) {
+ Logger::write(Logger::INFO, 'Last run date identified as ' . trim($ld[0], "\n"));
+ return strtotime(trim($ld[0], "\n"));
+ } else {
+ throw new Exception('Unable to get the last run date from ' . LAST_DATE_FILE_PREFIX . $source);
+ }
+ }
+
+ private function incrementRunDate($source) {
+ if (file_put_contents(LAST_DATE_FILE_PREFIX . $source, date('Y-m-d', self::$currentDate)) != false) {
+ Logger::write(Logger::INFO, 'Setting the last run date to ' . date('Y-m-d', self::$currentDate));
+ } else {
+ throw new Exception('Unable to set the last run date into ' . LAST_DATE_FILE_PREFIX . $source);
+ }
+ }
+
+ private function lock() {
+ if (file_exists(LOCKFILE)) {
+ throw new Exception('Previous run still runnig, lockfile ' . LOCKFILE . ' from ' . date("F d Y H:i:s.", filemtime(LOCKFILE)) . ". Remove lockfile first\n");
+ }
+ system('touch ' . LOCKFILE, $retval);
+ if ($retval === 0) {
+ Logger::write(Logger::INFO, 'Lockfile created');
+ } else {
+ throw new Exception('Can\'t create lockfile ' . LOCKFILE . "\n");
+ }
+ }
+
+ private function unlock() {
+ if (unlink(LOCKFILE) == true) {
+ Logger::write(Logger::INFO, 'Lockfile removed');
+ } else {
+ throw new Exception('Not possible to remove lockfile ' . LOCKFILE);
+ }
+ }
+
+ private function removeLogfile() {
+ if (unlink(self::$grabbedLogFile) == true) {
+ Logger::write(Logger::INFO, 'Source logfile removed');
+ } else {
+ throw new Exception('Not possible to remove source logfile ' . self::$grabbedLogFile);
+ }
+ }
+
+}
+
+?>
diff --git a/pp3/au/build-dashboard_v2.php b/pp3/au/build-dashboard_v2.php
new file mode 100644
index 0000000..3328d45
--- /dev/null
+++ b/pp3/au/build-dashboard_v2.php
@@ -0,0 +1,334 @@
+#!/usr/bin/php
+<?php
+/**
+ * Script for calculation of AU dashboard numbers from pings, users... tables
+ *
+ * Some background: dlc+nb logfiles are parsed for IDE callbacks to catalog.xml
+ * and those pings are analyzed and inserted into DB, tables pings, users, catalogs, releases...
+ * There is enourmous number of these records - hundrets of millions.
+ * This script then calculates basic stats from these numbers and saves result
+ * to DB again so we can use them on dashboard later on.
+ *
+ * CLI options:
+ * --month=yyyy-mm month for which generate numbers
+ * --debug
+ * --help
+ *
+ */
+// Connection config, edit to match your env
+$connection = array(
+ 'driver' => 'mysqli',
+ 'host' => 'localhost',
+ 'username' => 'jchalupa',
+ 'password' => 'root',
+ 'database' => 'root',
+ 'profiler' => TRUE,
+);
+
+// manual delay in days for counting data
+$delay = 2;
+
+// debug flag
+$debug = false;
+
+// lockfile
+$lockfile = '/tmp/run.lck';
+
+/*
+ * == DO NOT EDIT BELOW THIS LINE UNLESS YOU ARE SURE YOU KNOW WHAT YOU ARE DOING ==
+ */
+// read params from cli
+require_once './lib/Getopt.php';
+try {
+ $opts = new Zend_Console_Getopt(array('help|h' => 'Show help',
+ 'month|m=s' => 'Month for which generate stats, format yyyy-mm',
+ 'debug|d' => 'Show debug output',
+ 'product|p=s' => 'Product - netbeans, vvm',
+ 'stat|s=s' => 'Statistic to run - au, au2, countries, distros, packs, stickiness'));
+ $opts->parse();
+ $month = $opts->getOption('month');
+ $debug = $opts->getOption('debug');
+ $help = $opts->getOption('help');
+ $product = $opts->getOption('product');
+ $stat = ($opts->getOption('stat')) ? $opts->getOption('stat') : 'all';
+ if ($help) {
+ echo $opts->getUsageMessage();
+ exit(0);
+ }
+} catch (Zend_Console_Exception $e) {
+ echo $opts->getUsageMessage();
+ exit(1);
+}
+
+if (!file_exists($lockfile)) {
+ // lock the run
+ exec('touch ' . $lockfile);
+ echo "Lockfile created\n";
+ // use delayed value from today if it's not defined from cli
+ if (empty($month))
+ $month = date('Y-m', strtotime('-' . $delay . ' day'));
+ if (empty($product))
+ $product = 'netbeans';
+
+ // let's use DIBI for db abstraction, Uff there is PHP version check failure on nina.cz.oracle.com
+ //require_once(dirname(__FILE__) . "/include/dibi.min.php");
+ try {
+ //dibi::connect($connection);
+ require_once './db_connect.php.inc';
+ // setup stats params like dates and offsets
+ switch ($product) {
+ case 'netbeans':
+ $activityOffset = 7;
+ $monthStart = $month; // users counted monthly
+ break;
+ case 'vvm':
+ $activityOffset = 7;
+ $monthStart = date('Y-m', strtotime('-1 year', strtotime($month . '-01'))); // users counted yearly
+ break;
+ default:
+ $activityOffset = 7;
+ $monthStart = $month;
+ break;
+ }
+ // now put together statistics, query DB and insert results
+ if ($stat == 'all') {
+ au($month, $product, $activityOffset, $monthStart);
+ au2($month, $product, $activityOffset, $monthStart);
+ //countries($month, $product, $activityOffset, $monthStart);
+ //distros($month, $product, $activityOffset, $monthStart);
+ //packs($month, $product, $activityOffset, $monthStart);
+ //stickiness($month, $product);
+ } else {
+ $stat($month, $product, $activityOffset, $monthStart);
+ }
+ } catch (Exception $e) {
+ echo $e->getMessage() . "\n";
+ unlock();
+ exit(1);
+ }
+ unlock();
+ exit(0);
+} else {
+ echo "Previous run of the script seems to be still running, lockfile found: " . $lockfile . " from " . date('F d Y H:i:s', filemtime($lockfile)) . "\nRemove lockfile first!\n";
+}
+
+function unlock() {
+ global $lockfile;
+ unlink($lockfile);
+ echo "\nLockfile removed\n";
+}
+
+/*
+ * Active Users for selected month, by releases
+ */
+
+function au($month, $product, $activityOffset, $monthStart) {
+ global $dbc, $debug;
+ $qr = 'SELECT COUNT(DISTINCT p.user_id) AS count, r.id AS release_id, r.version AS release_version, r.lang
+ FROM pings p
+ INNER JOIN users u
+ ON (p.user_id=u.id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59")
+ INNER JOIN catalogs c
+ ON (p.path_id=c.id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59")
+ INNER JOIN releases r
+ ON (r.catalog_id=c.id AND r.product="' . $product . '")
+ WHERE IF(r.delay="Y", DATEDIFF(p.ts,u.since)>=' . $activityOffset . ', TRUE)
+ GROUP BY r.id';
+ try {
+ echo "Number of $product Active Users by release version for month $month\n";
+ if ($debug)
+ echo "Debug: " . $qr . "\n\n";
+ $res = mysqli_query($dbc, $qr);
+ echo "Found " . mysqli_num_rows($res) . " items\n";
+ while ($r = mysqli_fetch_array($res, MYSQLI_ASSOC)) {
+ // we have some data, let's insert into db
+ echo "\t" . $r['release_version'] . " " . $r['lang'] . " : " . $r['count'] . " users\n";
+ $qr = 'REPLACE INTO results_counts SET month="' . $month . '", release_id=' . $r['release_id'] . ', results_index_id=1, value="' . $r['count'] . '", product="' . $product . '", pack_signature=0, distro_id=0, country_id=0';
+ $res2 = mysqli_query($dbc, $qr);
+ if (!$res2) {
+ echo "ERR: " . mysqli_error() . $qr . "\n";
+ }
+ }
+ } catch (Exception $e) {
+ echo 'Failed Query: ' . $e->getMessage();
+ }
+}
+
+function au2($month, $product, $activityOffset, $monthStart) {
+ global $dbc, $debug;
+ $qr = 'SELECT COUNT(DISTINCT p.user2_id) AS count, r.id AS release_id, r.version AS release_version, r.lang
+ FROM pings p
+ INNER JOIN users2 u
+ ON (p.user2_id=u.id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59")
+ INNER JOIN catalogs c
+ ON (p.path_id=c.id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59")
+ INNER JOIN releases r
+ ON (r.catalog_id=c.id AND r.product="' . $product . '")
+ WHERE IF(r.delay="Y", DATEDIFF(p.ts,u.since)>=' . $activityOffset . ', TRUE)
+ GROUP BY r.id';
+ try {
+ echo "Number of $product UNIQUE Active Users by release version for month $month\n";
+ if ($debug)
+ echo "Debug: " . $qr . "\n\n";
+ $res = mysqli_query($dbc, $qr);
+ echo "Found " . mysqli_num_rows($res) . " items\n";
+ while ($r = mysqli_fetch_array($res, MYSQLI_ASSOC)) {
+ // we have some data, let's insert into db
+ echo "\t" . $r['release_version'] . " " . $r['lang'] . " : " . $r['count'] . " users\n";
+ $qr = 'REPLACE INTO results_counts SET month="' . $month . '", release_id=' . $r['release_id'] . ', results_index_id=3, value="' . $r['count'] . '", product="' . $product . '", country_id=0, pack_signature=0, distro_id=0';
+ $res2 = mysqli_query($dbc, $qr);
+ if (!$res2) {
+ echo "ERR: " . mysqli_error() . $qr . "\n";
+ }
+ }
+ } catch (Exception $e) {
+ echo 'Failed Query: ' . $e->getMessage();
+ }
+}
+
+/**
+ * Active users for selected month by countries
+ */
+function countries($month, $product, $activityOffset, $monthStart) {
+ global $dbc, $debug;
+ $qr = 'SELECT COUNT(DISTINCT p.user_id) AS count, ctr.name AS country_name, ctr.id as country_id
+ FROM pings p
+ INNER JOIN users u
+ ON (p.user_id=u.id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59")
+ INNER JOIN catalogs c
+ ON (p.path_id=c.id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59")
+ INNER JOIN releases r
+ ON (r.catalog_id=c.id AND r.product="' . $product . '")
+ INNER JOIN ips i
+ ON p.ip_id=i.id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59"
+ INNER JOIN countries ctr
+ ON ctr.code=i.country
+ WHERE IF(r.delay="Y", DATEDIFF(p.ts,u.since)>=' . $activityOffset . ', TRUE)
+ GROUP BY i.country ORDER BY count DESC';
+ try {
+ echo "\n\nNumber of $product Active users by countries for month $month\n";
+ if ($debug)
+ echo "Debug: " . $qr . "\n\n";
+ $res = mysqli_query($dbc, $qr);
+ echo "Found " . count($res) . " items\n";
+ while ($r = mysqli_fetch_array($res, MYSQLI_ASSOC)) {
+ // we have some data, let's insert into db
+ echo "\t" . $r['country_name'] . ": " . $r['count'] . " users\n";
+ $qr = 'REPLACE INTO results_counts SET month="' . $month . '", country_id=' . $r['country_id'] . ', results_index_id=2, value="' . $r['count'] . '", product="' . $product . '", release_id=0, distro_id=0, pack_signature=0';
+ $res2 = mysqli_query($dbc, $qr);
+ if (!$res2) {
+ echo "ERR: " . mysqli_error() . $qr . "\n";
+ }
+ }
+ } catch (Exception $e) {
+ echo 'Failed Query: ' . $e->getMessage();
+ }
+}
+
+function distros($month, $product, $activityOffset, $monthStart) {
+ global $dbc, $debug;
+ $qr = 'SELECT COUNT(DISTINCT p.user2_id) AS count, d.id AS distro_id, d.distro
+ FROM pings p
+ INNER JOIN users2 u
+ ON (p.user2_id=u.id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59")
+ INNER JOIN catalogs c
+ ON (p.path_id=c.id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59")
+ INNER JOIN releases r
+ ON (r.catalog_id=c.id AND r.product="' . $product . '")
+ INNER JOIN distros d ON (d.id=p.distro_id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59")
+ WHERE IF(r.delay="Y", DATEDIFF(p.ts,u.since)>=' . $activityOffset . ', TRUE)
+ GROUP BY d.id';
+ try {
+ echo "Number of $product Active Users by distribution for month $month\n";
+ if ($debug)
+ echo "Debug: " . $qr . "\n\n";
+ $res = mysqli_query($dbc, $qr);
+ echo "Found " . mysqli_num_rows($res) . " items\n";
+ while ($r = mysqli_fetch_array($res, MYSQLI_ASSOC)) {
+ // we have some data, let's insert into db
+ echo "\t" . $r['distro'] . " : " . $r['count'] . " users\n";
+ $qr = 'REPLACE INTO results_counts SET month="' . $month . '", distro_id=' . $r['distro_id'] . ', results_index_id=4, value="' . $r['count'] . '", product="' . $product . '", release_id=0, pack_signature=0, country_id=0';
+ $res2 = mysqli_query($dbc, $qr);
+ if (!$res2) {
+ echo "ERR: " . mysqli_error() . $qr . "\n";
+ }
+ }
+ } catch (Exception $e) {
+ echo 'Failed Query: ' . $e->getMessage();
+ }
+}
+
+function packs($month, $product, $activityOffset, $monthStart) {
+ global $dbc, $debug;
+ $signatures = array('CND', 'CRE', 'ENT', 'MOB', 'PROF', 'CDC', 'CLDC', 'JAVA', 'JAVASE', 'JAVAEE', 'JAVAME', 'WEBEE', 'PROFILER',
+ 'PHP', 'RUBY', 'MOBILITY', 'UML', 'SOA', 'GLASSFISH', 'SJSAS', 'TOMCAT', 'VISUALWEB', 'JDK', 'MYSQL',
+ 'GROOVY', 'GFMOD', 'JAVAFX', 'WEBCOMMON', 'FX', 'PY', 'JC', 'WEBLOGIC');
+ $qr = 'SELECT COUNT(DISTINCT p.user2_id) AS count, cf.id AS config_id, cf.signature
+ FROM pings p
+ INNER JOIN users2 u
+ ON (p.user2_id=u.id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59")
+ INNER JOIN catalogs c
+ ON (p.path_id=c.id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59")
+ INNER JOIN releases r
+ ON (r.catalog_id=c.id AND r.product="' . $product . '")
+ INNER JOIN configs cf ON (cf.id=p.config_id AND p.ts BETWEEN "' . $monthStart . '-01" AND "' . $month . '-31 23:59:59")
+ WHERE IF(r.delay="Y", DATEDIFF(p.ts,u.since)>=' . $activityOffset . ', TRUE)
+ GROUP BY cf.id';
+ try {
+ echo "Number of $product Active Users by packs for month $month\n";
+ if ($debug)
+ echo "Debug: " . $qr . "\n\n";
+ $res = mysqli_query($dbc, $qr);
+ echo "Found " . mysqli_num_rows($res) . " items\n";
+ while ($r = mysqli_fetch_array($res, MYSQLI_ASSOC)) {
+ // we have some data, let's count - parse the signature for packs and increment each pack according to it
+ $packs = explode(',', $r['signature']);
+ foreach ($packs as $pack) {
+ @$packsHits[$pack]+=$r['count'];
+ }
+ }
+ foreach ($packsHits as $pack => $hits) {
+ $qr = 'REPLACE INTO results_counts SET pack_signature="' . $pack . '", month="' . $month . '", results_index_id=5, value="' . $hits . '", product="' . $product . '", distro_id=0, release_id=0, country_id=0';
+ $res2 = mysqli_query($dbc, $qr);
+ echo "\t" . $pack . " : " . $hits . " users\n";
+ if (!$res2) {
+ echo "ERR: " . mysqli_error() . $qr . "\n";
+ }
+ }
+ } catch (Exception $e) {
+ echo 'Failed Query: ' . $e->getMessage();
+ }
+}
+
+function stickiness($month, $product) {
+ global $dbc, $debug;
+ // find all final releases and for each get the stickiness distribution
+ $releases = array();
+ $qr = 'SELECT * FROM releases WHERE product="' . $product . '" AND stable="Y"';
+ $res = mysqli_query($dbc, $qr);
+ while ($r = mysqli_fetch_array($res, MYSQLI_ASSOC)) {
+ $releases[$r['version']][$r['id']] = $r['catalog_id'];
+ }
+ //die(var_dump($releases));
+ if (!empty($releases)) {
+ echo "Found " . count($releases) . " stable releases\n";
+ foreach ($releases as $version => $catalogs) {
+ // now query for for stickiness distribution
+ $qr = 'SELECT delay, count(id) as users FROM users WHERE catalog_id in (' . implode(',', $catalogs) . ') AND delay<90 and last_seen>='.strtotime('-90 days').' GROUP BY delay ORDER BY delay';
+ $res2 = mysqli_query($dbc, $qr);
+ $data = array();
+ while ($r2 = mysqli_fetch_array($res2, MYSQLI_ASSOC)) {
+ $data[$r2['delay']] = $r2['users'];
+ }
+ if(!empty($data)) {
+ // store it
+ echo 'Storing results for release '.$version."\n";
+ $qr = 'REPLACE INTO results_counts SET month="' . $month . '", results_index_id=6, value="' . addslashes(serialize($data)) . '", product="' . $product . '", distro_id=0, release_id='. key($catalogs).', country_id=0, pack_signature="0"';
+ $res2 = mysqli_query($dbc, $qr);
+ }
+ }
+ } else {
+ echo "No stable releases found\n";
+ }
+}
+?>
diff --git a/pp3/au/config.php b/pp3/au/config.php
new file mode 100755
index 0000000..bc225c0
--- /dev/null
+++ b/pp3/au/config.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * Config variables
+ */
+DEFINE('LOGFILE_PATH', '/tmp');
+DEFINE('ADMIN_EMAIL', 'jan.pirek@oracle.com');
+DEFINE('LOCKFILE', '/tmp/run.lck');
+
+// last dates file prefix
+DEFINE('LAST_DATE_FILE_PREFIX', '/home/honza/checkout/web-content~au-statistics/apache-netbeans/last-date-');
+
+// DB connection
+DEFINE('DB_USER', 'root');
+DEFINE('DB_PASSWORD', 'root');
+DEFINE('DB_HOST', 'localhost');
+DEFINE('DB_NAME', 'jchalupa');
+
+// Path to logfiles served by local web server
+DEFINE('URL_PREFIX_DLC', 'http://localhost/work/oracle/au/logs/');
+?>
diff --git a/pp3/au/db_connect.php.inc b/pp3/au/db_connect.php.inc
new file mode 100755
index 0000000..b60f421
--- /dev/null
+++ b/pp3/au/db_connect.php.inc
@@ -0,0 +1,11 @@
+<?php
+
+DEFINE('DB_USER', 'root');
+DEFINE('DB_PASSWORD', 'root');
+DEFINE('DB_HOST', 'localhost');
+DEFINE('DB_NAME', 'jchalupa');
+
+$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME)
+ or die ('Could not connect: ' . mysqli_connect_error());
+?>
+
diff --git a/pp3/au/jchalupa.sql.gz b/pp3/au/jchalupa.sql.gz
new file mode 100644
index 0000000..5dd05b0
--- /dev/null
+++ b/pp3/au/jchalupa.sql.gz
Binary files differ
diff --git a/pp3/au/last-date-dlc b/pp3/au/last-date-dlc
new file mode 100755
index 0000000..a5b8a01
--- /dev/null
+++ b/pp3/au/last-date-dlc
@@ -0,0 +1 @@
+2020-10-31
\ No newline at end of file
diff --git a/pp3/au/lib/GeoIP.dat b/pp3/au/lib/GeoIP.dat
new file mode 100755
index 0000000..35bfeed
--- /dev/null
+++ b/pp3/au/lib/GeoIP.dat
Binary files differ
diff --git a/pp3/au/lib/Getopt.php b/pp3/au/lib/Getopt.php
new file mode 100755
index 0000000..d603566
--- /dev/null
+++ b/pp3/au/lib/Getopt.php
@@ -0,0 +1,970 @@
+<?php
+/**
+ * Zend_Console_Getopt is a class to parse options for command-line
+ * applications.
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Console_Getopt
+ * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Getopt.php 23775 2011-03-01 17:25:24Z ralph $
+ */
+
+/**
+ * Zend_Console_Getopt is a class to parse options for command-line
+ * applications.
+ *
+ * Terminology:
+ * Argument: an element of the argv array. This may be part of an option,
+ * or it may be a non-option command-line argument.
+ * Flag: the letter or word set off by a '-' or '--'. Example: in '--output filename',
+ * '--output' is the flag.
+ * Parameter: the additional argument that is associated with the option.
+ * Example: in '--output filename', the 'filename' is the parameter.
+ * Option: the combination of a flag and its parameter, if any.
+ * Example: in '--output filename', the whole thing is the option.
+ *
+ * The following features are supported:
+ *
+ * - Short flags like '-a'. Short flags are preceded by a single
+ * dash. Short flags may be clustered e.g. '-abc', which is the
+ * same as '-a' '-b' '-c'.
+ * - Long flags like '--verbose'. Long flags are preceded by a
+ * double dash. Long flags may not be clustered.
+ * - Options may have a parameter, e.g. '--output filename'.
+ * - Parameters for long flags may also be set off with an equals sign,
+ * e.g. '--output=filename'.
+ * - Parameters for long flags may be checked as string, word, or integer.
+ * - Automatic generation of a helpful usage message.
+ * - Signal end of options with '--'; subsequent arguments are treated
+ * as non-option arguments, even if they begin with '-'.
+ * - Raise exception Zend_Console_Getopt_Exception in several cases
+ * when invalid flags or parameters are given. Usage message is
+ * returned in the exception object.
+ *
+ * The format for specifying options uses a PHP associative array.
+ * The key is has the format of a list of pipe-separated flag names,
+ * followed by an optional '=' to indicate a required parameter or
+ * '-' to indicate an optional parameter. Following that, the type
+ * of parameter may be specified as 's' for string, 'w' for word,
+ * or 'i' for integer.
+ *
+ * Examples:
+ * - 'user|username|u=s' this means '--user' or '--username' or '-u'
+ * are synonyms, and the option requires a string parameter.
+ * - 'p=i' this means '-p' requires an integer parameter. No synonyms.
+ * - 'verbose|v-i' this means '--verbose' or '-v' are synonyms, and
+ * they take an optional integer parameter.
+ * - 'help|h' this means '--help' or '-h' are synonyms, and
+ * they take no parameter.
+ *
+ * The values in the associative array are strings that are used as
+ * brief descriptions of the options when printing a usage message.
+ *
+ * The simpler format for specifying options used by PHP's getopt()
+ * function is also supported. This is similar to GNU getopt and shell
+ * getopt format.
+ *
+ * Example: 'abc:' means options '-a', '-b', and '-c'
+ * are legal, and the latter requires a string parameter.
+ *
+ * @category Zend
+ * @package Zend_Console_Getopt
+ * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version Release: @package_version@
+ * @since Class available since Release 0.6.0
+ *
+ * @todo Handle params with multiple values, e.g. --colors=red,green,blue
+ * Set value of parameter to the array of values. Allow user to specify
+ * the separator with Zend_Console_Getopt::CONFIG_PARAMETER_SEPARATOR.
+ * If this config value is null or empty string, do not split values
+ * into arrays. Default separator is comma (',').
+ *
+ * @todo Handle params with multiple values specified with separate options
+ * e.g. --colors red --colors green --colors blue should give one
+ * option with an array(red, green, blue).
+ * Enable with Zend_Console_Getopt::CONFIG_CUMULATIVE_PARAMETERS.
+ * Default is that subsequent options overwrite the parameter value.
+ *
+ * @todo Handle flags occurring multiple times, e.g. -v -v -v
+ * Set value of the option's parameter to the integer count of instances
+ * instead of a boolean.
+ * Enable with Zend_Console_Getopt::CONFIG_CUMULATIVE_FLAGS.
+ * Default is that the value is simply boolean true regardless of
+ * how many instances of the flag appear.
+ *
+ * @todo Handle flags that implicitly print usage message, e.g. --help
+ *
+ * @todo Handle freeform options, e.g. --set-variable
+ * Enable with Zend_Console_Getopt::CONFIG_FREEFORM_FLAGS
+ * All flag-like syntax is recognized, no flag generates an exception.
+ *
+ * @todo Handle numeric options, e.g. -1, -2, -3, -1000
+ * Enable with Zend_Console_Getopt::CONFIG_NUMERIC_FLAGS
+ * The rule must specify a named flag and the '#' symbol as the
+ * parameter type. e.g., 'lines=#'
+ *
+ * @todo Enable user to specify header and footer content in the help message.
+ *
+ * @todo Feature request to handle option interdependencies.
+ * e.g. if -b is specified, -a must be specified or else the
+ * usage is invalid.
+ *
+ * @todo Feature request to implement callbacks.
+ * e.g. if -a is specified, run function 'handleOptionA'().
+ */
+class Zend_Console_Getopt
+{
+
+ /**
+ * The options for a given application can be in multiple formats.
+ * modeGnu is for traditional 'ab:c:' style getopt format.
+ * modeZend is for a more structured format.
+ */
+ const MODE_ZEND = 'zend';
+ const MODE_GNU = 'gnu';
+
+ /**
+ * Constant tokens for various symbols used in the mode_zend
+ * rule format.
+ */
+ const PARAM_REQUIRED = '=';
+ const PARAM_OPTIONAL = '-';
+ const TYPE_STRING = 's';
+ const TYPE_WORD = 'w';
+ const TYPE_INTEGER = 'i';
+
+ /**
+ * These are constants for optional behavior of this class.
+ * ruleMode is either 'zend' or 'gnu' or a user-defined mode.
+ * dashDash is true if '--' signifies the end of command-line options.
+ * ignoreCase is true if '--opt' and '--OPT' are implicitly synonyms.
+ * parseAll is true if all options on the command line should be parsed, regardless of
+ * whether an argument appears before them.
+ */
+ const CONFIG_RULEMODE = 'ruleMode';
+ const CONFIG_DASHDASH = 'dashDash';
+ const CONFIG_IGNORECASE = 'ignoreCase';
+ const CONFIG_PARSEALL = 'parseAll';
+
+ /**
+ * Defaults for getopt configuration are:
+ * ruleMode is 'zend' format,
+ * dashDash (--) token is enabled,
+ * ignoreCase is not enabled,
+ * parseAll is enabled.
+ */
+ protected $_getoptConfig = array(
+ self::CONFIG_RULEMODE => self::MODE_ZEND,
+ self::CONFIG_DASHDASH => true,
+ self::CONFIG_IGNORECASE => false,
+ self::CONFIG_PARSEALL => true,
+ );
+
+ /**
+ * Stores the command-line arguments for the calling applicaion.
+ *
+ * @var array
+ */
+ protected $_argv = array();
+
+ /**
+ * Stores the name of the calling applicaion.
+ *
+ * @var string
+ */
+ protected $_progname = '';
+
+ /**
+ * Stores the list of legal options for this application.
+ *
+ * @var array
+ */
+ protected $_rules = array();
+
+ /**
+ * Stores alternate spellings of legal options.
+ *
+ * @var array
+ */
+ protected $_ruleMap = array();
+
+ /**
+ * Stores options given by the user in the current invocation
+ * of the application, as well as parameters given in options.
+ *
+ * @var array
+ */
+ protected $_options = array();
+
+ /**
+ * Stores the command-line arguments other than options.
+ *
+ * @var array
+ */
+ protected $_remainingArgs = array();
+
+ /**
+ * State of the options: parsed or not yet parsed?
+ *
+ * @var boolean
+ */
+ protected $_parsed = false;
+
+ /**
+ * The constructor takes one to three parameters.
+ *
+ * The first parameter is $rules, which may be a string for
+ * gnu-style format, or a structured array for Zend-style format.
+ *
+ * The second parameter is $argv, and it is optional. If not
+ * specified, $argv is inferred from the global argv.
+ *
+ * The third parameter is an array of configuration parameters
+ * to control the behavior of this instance of Getopt; it is optional.
+ *
+ * @param array $rules
+ * @param array $argv
+ * @param array $getoptConfig
+ * @return void
+ */
+ public function __construct($rules, $argv = null, $getoptConfig = array())
+ {
+ if (!isset($_SERVER['argv'])) {
+ require_once 'Zend/Console/Getopt/Exception.php';
+ if (ini_get('register_argc_argv') == false) {
+ throw new Zend_Console_Getopt_Exception(
+ "argv is not available, because ini option 'register_argc_argv' is set Off"
+ );
+ } else {
+ throw new Zend_Console_Getopt_Exception(
+ '$_SERVER["argv"] is not set, but Zend_Console_Getopt cannot work without this information.'
+ );
+ }
+ }
+
+ $this->_progname = $_SERVER['argv'][0];
+ $this->setOptions($getoptConfig);
+ $this->addRules($rules);
+ if (!is_array($argv)) {
+ $argv = array_slice($_SERVER['argv'], 1);
+ }
+ if (isset($argv)) {
+ $this->addArguments((array)$argv);
+ }
+ }
+
+ /**
+ * Return the state of the option seen on the command line of the
+ * current application invocation. This function returns true, or the
+ * parameter to the option, if any. If the option was not given,
+ * this function returns null.
+ *
+ * The magic __get method works in the context of naming the option
+ * as a virtual member of this class.
+ *
+ * @param string $key
+ * @return string
+ */
+ public function __get($key)
+ {
+ return $this->getOption($key);
+ }
+
+ /**
+ * Test whether a given option has been seen.
+ *
+ * @param string $key
+ * @return boolean
+ */
+ public function __isset($key)
+ {
+ $this->parse();
+ if (isset($this->_ruleMap[$key])) {
+ $key = $this->_ruleMap[$key];
+ return isset($this->_options[$key]);
+ }
+ return false;
+ }
+
+ /**
+ * Set the value for a given option.
+ *
+ * @param string $key
+ * @param string $value
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ $this->parse();
+ if (isset($this->_ruleMap[$key])) {
+ $key = $this->_ruleMap[$key];
+ $this->_options[$key] = $value;
+ }
+ }
+
+ /**
+ * Return the current set of options and parameters seen as a string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * Unset an option.
+ *
+ * @param string $key
+ * @return void
+ */
+ public function __unset($key)
+ {
+ $this->parse();
+ if (isset($this->_ruleMap[$key])) {
+ $key = $this->_ruleMap[$key];
+ unset($this->_options[$key]);
+ }
+ }
+
+ /**
+ * Define additional command-line arguments.
+ * These are appended to those defined when the constructor was called.
+ *
+ * @param array $argv
+ * @throws Zend_Console_Getopt_Exception When not given an array as parameter
+ * @return Zend_Console_Getopt Provides a fluent interface
+ */
+ public function addArguments($argv)
+ {
+ if(!is_array($argv)) {
+ require_once 'Zend/Console/Getopt/Exception.php';
+ throw new Zend_Console_Getopt_Exception(
+ "Parameter #1 to addArguments should be an array");
+ }
+ $this->_argv = array_merge($this->_argv, $argv);
+ $this->_parsed = false;
+ return $this;
+ }
+
+ /**
+ * Define full set of command-line arguments.
+ * These replace any currently defined.
+ *
+ * @param array $argv
+ * @throws Zend_Console_Getopt_Exception When not given an array as parameter
+ * @return Zend_Console_Getopt Provides a fluent interface
+ */
+ public function setArguments($argv)
+ {
+ if(!is_array($argv)) {
+ require_once 'Zend/Console/Getopt/Exception.php';
+ throw new Zend_Console_Getopt_Exception(
+ "Parameter #1 to setArguments should be an array");
+ }
+ $this->_argv = $argv;
+ $this->_parsed = false;
+ return $this;
+ }
+
+ /**
+ * Define multiple configuration options from an associative array.
+ * These are not program options, but properties to configure
+ * the behavior of Zend_Console_Getopt.
+ *
+ * @param array $getoptConfig
+ * @return Zend_Console_Getopt Provides a fluent interface
+ */
+ public function setOptions($getoptConfig)
+ {
+ if (isset($getoptConfig)) {
+ foreach ($getoptConfig as $key => $value) {
+ $this->setOption($key, $value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Define one configuration option as a key/value pair.
+ * These are not program options, but properties to configure
+ * the behavior of Zend_Console_Getopt.
+ *
+ * @param string $configKey
+ * @param string $configValue
+ * @return Zend_Console_Getopt Provides a fluent interface
+ */
+ public function setOption($configKey, $configValue)
+ {
+ if ($configKey !== null) {
+ $this->_getoptConfig[$configKey] = $configValue;
+ }
+ return $this;
+ }
+
+ /**
+ * Define additional option rules.
+ * These are appended to the rules defined when the constructor was called.
+ *
+ * @param array $rules
+ * @return Zend_Console_Getopt Provides a fluent interface
+ */
+ public function addRules($rules)
+ {
+ $ruleMode = $this->_getoptConfig['ruleMode'];
+ switch ($this->_getoptConfig['ruleMode']) {
+ case self::MODE_ZEND:
+ if (is_array($rules)) {
+ $this->_addRulesModeZend($rules);
+ break;
+ }
+ // intentional fallthrough
+ case self::MODE_GNU:
+ $this->_addRulesModeGnu($rules);
+ break;
+ default:
+ /**
+ * Call addRulesModeFoo() for ruleMode 'foo'.
+ * The developer should subclass Getopt and
+ * provide this method.
+ */
+ $method = '_addRulesMode' . ucfirst($ruleMode);
+ $this->$method($rules);
+ }
+ $this->_parsed = false;
+ return $this;
+ }
+
+ /**
+ * Return the current set of options and parameters seen as a string.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ $this->parse();
+ $s = array();
+ foreach ($this->_options as $flag => $value) {
+ $s[] = $flag . '=' . ($value === true ? 'true' : $value);
+ }
+ return implode(' ', $s);
+ }
+
+ /**
+ * Return the current set of options and parameters seen
+ * as an array of canonical options and parameters.
+ *
+ * Clusters have been expanded, and option aliases
+ * have been mapped to their primary option names.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $this->parse();
+ $s = array();
+ foreach ($this->_options as $flag => $value) {
+ $s[] = $flag;
+ if ($value !== true) {
+ $s[] = $value;
+ }
+ }
+ return $s;
+ }
+
+ /**
+ * Return the current set of options and parameters seen in Json format.
+ *
+ * @return string
+ */
+ public function toJson()
+ {
+ $this->parse();
+ $j = array();
+ foreach ($this->_options as $flag => $value) {
+ $j['options'][] = array(
+ 'option' => array(
+ 'flag' => $flag,
+ 'parameter' => $value
+ )
+ );
+ }
+
+ /**
+ * @see Zend_Json
+ */
+ require_once 'Zend/Json.php';
+ $json = Zend_Json::encode($j);
+
+ return $json;
+ }
+
+ /**
+ * Return the current set of options and parameters seen in XML format.
+ *
+ * @return string
+ */
+ public function toXml()
+ {
+ $this->parse();
+ $doc = new DomDocument('1.0', 'utf-8');
+ $optionsNode = $doc->createElement('options');
+ $doc->appendChild($optionsNode);
+ foreach ($this->_options as $flag => $value) {
+ $optionNode = $doc->createElement('option');
+ $optionNode->setAttribute('flag', utf8_encode($flag));
+ if ($value !== true) {
+ $optionNode->setAttribute('parameter', utf8_encode($value));
+ }
+ $optionsNode->appendChild($optionNode);
+ }
+ $xml = $doc->saveXML();
+ return $xml;
+ }
+
+ /**
+ * Return a list of options that have been seen in the current argv.
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ $this->parse();
+ return array_keys($this->_options);
+ }
+
+ /**
+ * Return the state of the option seen on the command line of the
+ * current application invocation.
+ *
+ * This function returns true, or the parameter value to the option, if any.
+ * If the option was not given, this function returns false.
+ *
+ * @param string $flag
+ * @return mixed
+ */
+ public function getOption($flag)
+ {
+ $this->parse();
+ if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) {
+ $flag = strtolower($flag);
+ }
+ if (isset($this->_ruleMap[$flag])) {
+ $flag = $this->_ruleMap[$flag];
+ if (isset($this->_options[$flag])) {
+ return $this->_options[$flag];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the arguments from the command-line following all options found.
+ *
+ * @return array
+ */
+ public function getRemainingArgs()
+ {
+ $this->parse();
+ return $this->_remainingArgs;
+ }
+
+ /**
+ * Return a useful option reference, formatted for display in an
+ * error message.
+ *
+ * Note that this usage information is provided in most Exceptions
+ * generated by this class.
+ *
+ * @return string
+ */
+ public function getUsageMessage()
+ {
+ $usage = "Usage: {$this->_progname} [ options ]\n";
+ $maxLen = 20;
+ $lines = array();
+ foreach ($this->_rules as $rule) {
+ $flags = array();
+ if (is_array($rule['alias'])) {
+ foreach ($rule['alias'] as $flag) {
+ $flags[] = (strlen($flag) == 1 ? '-' : '--') . $flag;
+ }
+ }
+ $linepart['name'] = implode('|', $flags);
+ if (isset($rule['param']) && $rule['param'] != 'none') {
+ $linepart['name'] .= ' ';
+ switch ($rule['param']) {
+ case 'optional':
+ $linepart['name'] .= "[ <{$rule['paramType']}> ]";
+ break;
+ case 'required':
+ $linepart['name'] .= "<{$rule['paramType']}>";
+ break;
+ }
+ }
+ if (strlen($linepart['name']) > $maxLen) {
+ $maxLen = strlen($linepart['name']);
+ }
+ $linepart['help'] = '';
+ if (isset($rule['help'])) {
+ $linepart['help'] .= $rule['help'];
+ }
+ $lines[] = $linepart;
+ }
+ foreach ($lines as $linepart) {
+ $usage .= sprintf("%s %s\n",
+ str_pad($linepart['name'], $maxLen),
+ $linepart['help']);
+ }
+ return $usage;
+ }
+
+ /**
+ * Define aliases for options.
+ *
+ * The parameter $aliasMap is an associative array
+ * mapping option name (short or long) to an alias.
+ *
+ * @param array $aliasMap
+ * @throws Zend_Console_Getopt_Exception
+ * @return Zend_Console_Getopt Provides a fluent interface
+ */
+ public function setAliases($aliasMap)
+ {
+ foreach ($aliasMap as $flag => $alias)
+ {
+ if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) {
+ $flag = strtolower($flag);
+ $alias = strtolower($alias);
+ }
+ if (!isset($this->_ruleMap[$flag])) {
+ continue;
+ }
+ $flag = $this->_ruleMap[$flag];
+ if (isset($this->_rules[$alias]) || isset($this->_ruleMap[$alias])) {
+ $o = (strlen($alias) == 1 ? '-' : '--') . $alias;
+ require_once 'Zend/Console/Getopt/Exception.php';
+ throw new Zend_Console_Getopt_Exception(
+ "Option \"$o\" is being defined more than once.");
+ }
+ $this->_rules[$flag]['alias'][] = $alias;
+ $this->_ruleMap[$alias] = $flag;
+ }
+ return $this;
+ }
+
+ /**
+ * Define help messages for options.
+ *
+ * The parameter $help_map is an associative array
+ * mapping option name (short or long) to the help string.
+ *
+ * @param array $helpMap
+ * @return Zend_Console_Getopt Provides a fluent interface
+ */
+ public function setHelp($helpMap)
+ {
+ foreach ($helpMap as $flag => $help)
+ {
+ if (!isset($this->_ruleMap[$flag])) {
+ continue;
+ }
+ $flag = $this->_ruleMap[$flag];
+ $this->_rules[$flag]['help'] = $help;
+ }
+ return $this;
+ }
+
+ /**
+ * Parse command-line arguments and find both long and short
+ * options.
+ *
+ * Also find option parameters, and remaining arguments after
+ * all options have been parsed.
+ *
+ * @return Zend_Console_Getopt|null Provides a fluent interface
+ */
+ public function parse()
+ {
+ if ($this->_parsed === true) {
+ return;
+ }
+ $argv = $this->_argv;
+ $this->_options = array();
+ $this->_remainingArgs = array();
+ while (count($argv) > 0) {
+ if ($argv[0] == '--') {
+ array_shift($argv);
+ if ($this->_getoptConfig[self::CONFIG_DASHDASH]) {
+ $this->_remainingArgs = array_merge($this->_remainingArgs, $argv);
+ break;
+ }
+ }
+ if (substr($argv[0], 0, 2) == '--') {
+ $this->_parseLongOption($argv);
+ } else if (substr($argv[0], 0, 1) == '-' && ('-' != $argv[0] || count($argv) >1)) {
+ $this->_parseShortOptionCluster($argv);
+ } else if($this->_getoptConfig[self::CONFIG_PARSEALL]) {
+ $this->_remainingArgs[] = array_shift($argv);
+ } else {
+ /*
+ * We should put all other arguments in _remainingArgs and stop parsing
+ * since CONFIG_PARSEALL is false.
+ */
+ $this->_remainingArgs = array_merge($this->_remainingArgs, $argv);
+ break;
+ }
+ }
+ $this->_parsed = true;
+ return $this;
+ }
+
+ /**
+ * Parse command-line arguments for a single long option.
+ * A long option is preceded by a double '--' character.
+ * Long options may not be clustered.
+ *
+ * @param mixed &$argv
+ * @return void
+ */
+ protected function _parseLongOption(&$argv)
+ {
+ $optionWithParam = ltrim(array_shift($argv), '-');
+ $l = explode('=', $optionWithParam, 2);
+ $flag = array_shift($l);
+ $param = array_shift($l);
+ if (isset($param)) {
+ array_unshift($argv, $param);
+ }
+ $this->_parseSingleOption($flag, $argv);
+ }
+
+ /**
+ * Parse command-line arguments for short options.
+ * Short options are those preceded by a single '-' character.
+ * Short options may be clustered.
+ *
+ * @param mixed &$argv
+ * @return void
+ */
+ protected function _parseShortOptionCluster(&$argv)
+ {
+ $flagCluster = ltrim(array_shift($argv), '-');
+ foreach (str_split($flagCluster) as $flag) {
+ $this->_parseSingleOption($flag, $argv);
+ }
+ }
+
+ /**
+ * Parse command-line arguments for a single option.
+ *
+ * @param string $flag
+ * @param mixed $argv
+ * @throws Zend_Console_Getopt_Exception
+ * @return void
+ */
+ protected function _parseSingleOption($flag, &$argv)
+ {
+ if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) {
+ $flag = strtolower($flag);
+ }
+ if (!isset($this->_ruleMap[$flag])) {
+ require_once 'Zend/Console/Getopt/Exception.php';
+ throw new Zend_Console_Getopt_Exception(
+ "Option \"$flag\" is not recognized.",
+ $this->getUsageMessage());
+ }
+ $realFlag = $this->_ruleMap[$flag];
+ switch ($this->_rules[$realFlag]['param']) {
+ case 'required':
+ if (count($argv) > 0) {
+ $param = array_shift($argv);
+ $this->_checkParameterType($realFlag, $param);
+ } else {
+ require_once 'Zend/Console/Getopt/Exception.php';
+ throw new Zend_Console_Getopt_Exception(
+ "Option \"$flag\" requires a parameter.",
+ $this->getUsageMessage());
+ }
+ break;
+ case 'optional':
+ if (count($argv) > 0 && substr($argv[0], 0, 1) != '-') {
+ $param = array_shift($argv);
+ $this->_checkParameterType($realFlag, $param);
+ } else {
+ $param = true;
+ }
+ break;
+ default:
+ $param = true;
+ }
+ $this->_options[$realFlag] = $param;
+ }
+
+ /**
+ * Return true if the parameter is in a valid format for
+ * the option $flag.
+ * Throw an exception in most other cases.
+ *
+ * @param string $flag
+ * @param string $param
+ * @throws Zend_Console_Getopt_Exception
+ * @return bool
+ */
+ protected function _checkParameterType($flag, $param)
+ {
+ $type = 'string';
+ if (isset($this->_rules[$flag]['paramType'])) {
+ $type = $this->_rules[$flag]['paramType'];
+ }
+ switch ($type) {
+ case 'word':
+ if (preg_match('/\W/', $param)) {
+ require_once 'Zend/Console/Getopt/Exception.php';
+ throw new Zend_Console_Getopt_Exception(
+ "Option \"$flag\" requires a single-word parameter, but was given \"$param\".",
+ $this->getUsageMessage());
+ }
+ break;
+ case 'integer':
+ if (preg_match('/\D/', $param)) {
+ require_once 'Zend/Console/Getopt/Exception.php';
+ throw new Zend_Console_Getopt_Exception(
+ "Option \"$flag\" requires an integer parameter, but was given \"$param\".",
+ $this->getUsageMessage());
+ }
+ break;
+ case 'string':
+ default:
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Define legal options using the gnu-style format.
+ *
+ * @param string $rules
+ * @return void
+ */
+ protected function _addRulesModeGnu($rules)
+ {
+ $ruleArray = array();
+
+ /**
+ * Options may be single alphanumeric characters.
+ * Options may have a ':' which indicates a required string parameter.
+ * No long options or option aliases are supported in GNU style.
+ */
+ preg_match_all('/([a-zA-Z0-9]:?)/', $rules, $ruleArray);
+ foreach ($ruleArray[1] as $rule) {
+ $r = array();
+ $flag = substr($rule, 0, 1);
+ if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) {
+ $flag = strtolower($flag);
+ }
+ $r['alias'][] = $flag;
+ if (substr($rule, 1, 1) == ':') {
+ $r['param'] = 'required';
+ $r['paramType'] = 'string';
+ } else {
+ $r['param'] = 'none';
+ }
+ $this->_rules[$flag] = $r;
+ $this->_ruleMap[$flag] = $flag;
+ }
+ }
+
+ /**
+ * Define legal options using the Zend-style format.
+ *
+ * @param array $rules
+ * @throws Zend_Console_Getopt_Exception
+ * @return void
+ */
+ protected function _addRulesModeZend($rules)
+ {
+ foreach ($rules as $ruleCode => $helpMessage)
+ {
+ // this may have to translate the long parm type if there
+ // are any complaints that =string will not work (even though that use
+ // case is not documented)
+ if (in_array(substr($ruleCode, -2, 1), array('-', '='))) {
+ $flagList = substr($ruleCode, 0, -2);
+ $delimiter = substr($ruleCode, -2, 1);
+ $paramType = substr($ruleCode, -1);
+ } else {
+ $flagList = $ruleCode;
+ $delimiter = $paramType = null;
+ }
+ if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) {
+ $flagList = strtolower($flagList);
+ }
+ $flags = explode('|', $flagList);
+ $rule = array();
+ $mainFlag = $flags[0];
+ foreach ($flags as $flag) {
+ if (empty($flag)) {
+ require_once 'Zend/Console/Getopt/Exception.php';
+ throw new Zend_Console_Getopt_Exception(
+ "Blank flag not allowed in rule \"$ruleCode\".");
+ }
+ if (strlen($flag) == 1) {
+ if (isset($this->_ruleMap[$flag])) {
+ require_once 'Zend/Console/Getopt/Exception.php';
+ throw new Zend_Console_Getopt_Exception(
+ "Option \"-$flag\" is being defined more than once.");
+ }
+ $this->_ruleMap[$flag] = $mainFlag;
+ $rule['alias'][] = $flag;
+ } else {
+ if (isset($this->_rules[$flag]) || isset($this->_ruleMap[$flag])) {
+ require_once 'Zend/Console/Getopt/Exception.php';
+ throw new Zend_Console_Getopt_Exception(
+ "Option \"--$flag\" is being defined more than once.");
+ }
+ $this->_ruleMap[$flag] = $mainFlag;
+ $rule['alias'][] = $flag;
+ }
+ }
+ if (isset($delimiter)) {
+ switch ($delimiter) {
+ case self::PARAM_REQUIRED:
+ $rule['param'] = 'required';
+ break;
+ case self::PARAM_OPTIONAL:
+ default:
+ $rule['param'] = 'optional';
+ }
+ switch (substr($paramType, 0, 1)) {
+ case self::TYPE_WORD:
+ $rule['paramType'] = 'word';
+ break;
+ case self::TYPE_INTEGER:
+ $rule['paramType'] = 'integer';
+ break;
+ case self::TYPE_STRING:
+ default:
+ $rule['paramType'] = 'string';
+ }
+ } else {
+ $rule['param'] = 'none';
+ }
+ $rule['help'] = $helpMessage;
+ $this->_rules[$mainFlag] = $rule;
+ }
+ }
+
+}
diff --git a/pp3/au/lib/Getopt/Exception.php b/pp3/au/lib/Getopt/Exception.php
new file mode 100755
index 0000000..cabb406
--- /dev/null
+++ b/pp3/au/lib/Getopt/Exception.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Console_Getopt
+ * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Exception.php 23775 2011-03-01 17:25:24Z ralph $
+ */
+
+
+/**
+ * @see Zend_Console_Getopt_Exception
+ */
+require_once 'Zend/Exception.php';
+
+
+/**
+ * @category Zend
+ * @package Zend_Console_Getopt
+ * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Console_Getopt_Exception extends Zend_Exception
+{
+ /**
+ * Usage
+ *
+ * @var string
+ */
+ protected $usage = '';
+
+ /**
+ * Constructor
+ *
+ * @param string $message
+ * @param string $usage
+ * @return void
+ */
+ public function __construct($message, $usage = '')
+ {
+ $this->usage = $usage;
+ parent::__construct($message);
+ }
+
+ /**
+ * Returns the usage
+ *
+ * @return string
+ */
+ public function getUsageMessage()
+ {
+ return $this->usage;
+ }
+}
diff --git a/pp3/au/lib/geoip.inc b/pp3/au/lib/geoip.inc
new file mode 100755
index 0000000..4e367d2
--- /dev/null
+++ b/pp3/au/lib/geoip.inc
@@ -0,0 +1,509 @@
+<?php
+
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 2 -*- */
+/* geoip.inc
+ *
+ * Copyright (C) 2007 MaxMind LLC
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+define("GEOIP_COUNTRY_BEGIN", 16776960);
+define("GEOIP_STATE_BEGIN_REV0", 16700000);
+define("GEOIP_STATE_BEGIN_REV1", 16000000);
+define("GEOIP_STANDARD", 0);
+define("GEOIP_MEMORY_CACHE", 1);
+define("GEOIP_SHARED_MEMORY", 2);
+define("STRUCTURE_INFO_MAX_SIZE", 20);
+define("DATABASE_INFO_MAX_SIZE", 100);
+define("GEOIP_COUNTRY_EDITION", 106);
+define("GEOIP_PROXY_EDITION", 8);
+define("GEOIP_ASNUM_EDITION", 9);
+define("GEOIP_NETSPEED_EDITION", 10);
+define("GEOIP_REGION_EDITION_REV0", 112);
+define("GEOIP_REGION_EDITION_REV1", 3);
+define("GEOIP_CITY_EDITION_REV0", 111);
+define("GEOIP_CITY_EDITION_REV1", 2);
+define("GEOIP_ORG_EDITION", 110);
+define("GEOIP_ISP_EDITION", 4);
+define("SEGMENT_RECORD_LENGTH", 3);
+define("STANDARD_RECORD_LENGTH", 3);
+define("ORG_RECORD_LENGTH", 4);
+define("MAX_RECORD_LENGTH", 4);
+define("MAX_ORG_RECORD_LENGTH", 300);
+define("GEOIP_SHM_KEY", 0x4f415401);
+define("US_OFFSET", 1);
+define("CANADA_OFFSET", 677);
+define("WORLD_OFFSET", 1353);
+define("FIPS_RANGE", 360);
+define("GEOIP_UNKNOWN_SPEED", 0);
+define("GEOIP_DIALUP_SPEED", 1);
+define("GEOIP_CABLEDSL_SPEED", 2);
+define("GEOIP_CORPORATE_SPEED", 3);
+
+class GeoIP {
+ var $flags;
+ var $filehandle;
+ var $memory_buffer;
+ var $databaseType;
+ var $databaseSegments;
+ var $record_length;
+ var $shmid;
+ var $GEOIP_COUNTRY_CODE_TO_NUMBER = array(
+"" => 0, "AP" => 1, "EU" => 2, "AD" => 3, "AE" => 4, "AF" => 5,
+"AG" => 6, "AI" => 7, "AL" => 8, "AM" => 9, "AN" => 10, "AO" => 11,
+"AQ" => 12, "AR" => 13, "AS" => 14, "AT" => 15, "AU" => 16, "AW" => 17,
+"AZ" => 18, "BA" => 19, "BB" => 20, "BD" => 21, "BE" => 22, "BF" => 23,
+"BG" => 24, "BH" => 25, "BI" => 26, "BJ" => 27, "BM" => 28, "BN" => 29,
+"BO" => 30, "BR" => 31, "BS" => 32, "BT" => 33, "BV" => 34, "BW" => 35,
+"BY" => 36, "BZ" => 37, "CA" => 38, "CC" => 39, "CD" => 40, "CF" => 41,
+"CG" => 42, "CH" => 43, "CI" => 44, "CK" => 45, "CL" => 46, "CM" => 47,
+"CN" => 48, "CO" => 49, "CR" => 50, "CU" => 51, "CV" => 52, "CX" => 53,
+"CY" => 54, "CZ" => 55, "DE" => 56, "DJ" => 57, "DK" => 58, "DM" => 59,
+"DO" => 60, "DZ" => 61, "EC" => 62, "EE" => 63, "EG" => 64, "EH" => 65,
+"ER" => 66, "ES" => 67, "ET" => 68, "FI" => 69, "FJ" => 70, "FK" => 71,
+"FM" => 72, "FO" => 73, "FR" => 74, "FX" => 75, "GA" => 76, "GB" => 77,
+"GD" => 78, "GE" => 79, "GF" => 80, "GH" => 81, "GI" => 82, "GL" => 83,
+"GM" => 84, "GN" => 85, "GP" => 86, "GQ" => 87, "GR" => 88, "GS" => 89,
+"GT" => 90, "GU" => 91, "GW" => 92, "GY" => 93, "HK" => 94, "HM" => 95,
+"HN" => 96, "HR" => 97, "HT" => 98, "HU" => 99, "ID" => 100, "IE" => 101,
+"IL" => 102, "IN" => 103, "IO" => 104, "IQ" => 105, "IR" => 106, "IS" => 107,
+"IT" => 108, "JM" => 109, "JO" => 110, "JP" => 111, "KE" => 112, "KG" => 113,
+"KH" => 114, "KI" => 115, "KM" => 116, "KN" => 117, "KP" => 118, "KR" => 119,
+"KW" => 120, "KY" => 121, "KZ" => 122, "LA" => 123, "LB" => 124, "LC" => 125,
+"LI" => 126, "LK" => 127, "LR" => 128, "LS" => 129, "LT" => 130, "LU" => 131,
+"LV" => 132, "LY" => 133, "MA" => 134, "MC" => 135, "MD" => 136, "MG" => 137,
+"MH" => 138, "MK" => 139, "ML" => 140, "MM" => 141, "MN" => 142, "MO" => 143,
+"MP" => 144, "MQ" => 145, "MR" => 146, "MS" => 147, "MT" => 148, "MU" => 149,
+"MV" => 150, "MW" => 151, "MX" => 152, "MY" => 153, "MZ" => 154, "NA" => 155,
+"NC" => 156, "NE" => 157, "NF" => 158, "NG" => 159, "NI" => 160, "NL" => 161,
+"NO" => 162, "NP" => 163, "NR" => 164, "NU" => 165, "NZ" => 166, "OM" => 167,
+"PA" => 168, "PE" => 169, "PF" => 170, "PG" => 171, "PH" => 172, "PK" => 173,
+"PL" => 174, "PM" => 175, "PN" => 176, "PR" => 177, "PS" => 178, "PT" => 179,
+"PW" => 180, "PY" => 181, "QA" => 182, "RE" => 183, "RO" => 184, "RU" => 185,
+"RW" => 186, "SA" => 187, "SB" => 188, "SC" => 189, "SD" => 190, "SE" => 191,
+"SG" => 192, "SH" => 193, "SI" => 194, "SJ" => 195, "SK" => 196, "SL" => 197,
+"SM" => 198, "SN" => 199, "SO" => 200, "SR" => 201, "ST" => 202, "SV" => 203,
+"SY" => 204, "SZ" => 205, "TC" => 206, "TD" => 207, "TF" => 208, "TG" => 209,
+"TH" => 210, "TJ" => 211, "TK" => 212, "TM" => 213, "TN" => 214, "TO" => 215,
+"TL" => 216, "TR" => 217, "TT" => 218, "TV" => 219, "TW" => 220, "TZ" => 221,
+"UA" => 222, "UG" => 223, "UM" => 224, "US" => 225, "UY" => 226, "UZ" => 227,
+"VA" => 228, "VC" => 229, "VE" => 230, "VG" => 231, "VI" => 232, "VN" => 233,
+"VU" => 234, "WF" => 235, "WS" => 236, "YE" => 237, "YT" => 238, "RS" => 239,
+"ZA" => 240, "ZM" => 241, "ME" => 242, "ZW" => 243, "A1" => 244, "A2" => 245,
+"O1" => 246, "AX" => 247, "GG" => 248, "IM" => 249, "JE" => 250
+);
+ var $GEOIP_COUNTRY_CODES = array(
+"", "AP", "EU", "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN", "AO", "AQ",
+"AR", "AS", "AT", "AU", "AW", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH",
+"BI", "BJ", "BM", "BN", "BO", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA",
+"CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU",
+"CV", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG",
+"EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", "FX", "GA", "GB",
+"GD", "GE", "GF", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT",
+"GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IN",
+"IO", "IQ", "IR", "IS", "IT", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM",
+"KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS",
+"LT", "LU", "LV", "LY", "MA", "MC", "MD", "MG", "MH", "MK", "ML", "MM", "MN",
+"MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA",
+"NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA",
+"PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY",
+"QA", "RE", "RO", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI",
+"SJ", "SK", "SL", "SM", "SN", "SO", "SR", "ST", "SV", "SY", "SZ", "TC", "TD",
+"TF", "TG", "TH", "TJ", "TK", "TM", "TN", "TO", "TL", "TR", "TT", "TV", "TW",
+"TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN",
+"VU", "WF", "WS", "YE", "YT", "RS", "ZA", "ZM", "ME", "ZW", "A1", "A2", "O1",
+"AX", "GG", "IM", "JE"
+);
+ var $GEOIP_COUNTRY_CODES3 = array(
+"","AP","EU","AND","ARE","AFG","ATG","AIA","ALB","ARM","ANT","AGO","AQ","ARG",
+"ASM","AUT","AUS","ABW","AZE","BIH","BRB","BGD","BEL","BFA","BGR","BHR","BDI",
+"BEN","BMU","BRN","BOL","BRA","BHS","BTN","BV","BWA","BLR","BLZ","CAN","CC",
+"COD","CAF","COG","CHE","CIV","COK","CHL","CMR","CHN","COL","CRI","CUB","CPV",
+"CX","CYP","CZE","DEU","DJI","DNK","DMA","DOM","DZA","ECU","EST","EGY","ESH",
+"ERI","ESP","ETH","FIN","FJI","FLK","FSM","FRO","FRA","FX","GAB","GBR","GRD",
+"GEO","GUF","GHA","GIB","GRL","GMB","GIN","GLP","GNQ","GRC","GS","GTM","GUM",
+"GNB","GUY","HKG","HM","HND","HRV","HTI","HUN","IDN","IRL","ISR","IND","IO",
+"IRQ","IRN","ISL","ITA","JAM","JOR","JPN","KEN","KGZ","KHM","KIR","COM","KNA",
+"PRK","KOR","KWT","CYM","KAZ","LAO","LBN","LCA","LIE","LKA","LBR","LSO","LTU",
+"LUX","LVA","LBY","MAR","MCO","MDA","MDG","MHL","MKD","MLI","MMR","MNG","MAC",
+"MNP","MTQ","MRT","MSR","MLT","MUS","MDV","MWI","MEX","MYS","MOZ","NAM","NCL",
+"NER","NFK","NGA","NIC","NLD","NOR","NPL","NRU","NIU","NZL","OMN","PAN","PER",
+"PYF","PNG","PHL","PAK","POL","SPM","PCN","PRI","PSE","PRT","PLW","PRY","QAT",
+"REU","ROU","RUS","RWA","SAU","SLB","SYC","SDN","SWE","SGP","SHN","SVN","SJM",
+"SVK","SLE","SMR","SEN","SOM","SUR","STP","SLV","SYR","SWZ","TCA","TCD","TF",
+"TGO","THA","TJK","TKL","TLS","TKM","TUN","TON","TUR","TTO","TUV","TWN","TZA",
+"UKR","UGA","UM","USA","URY","UZB","VAT","VCT","VEN","VGB","VIR","VNM","VUT",
+"WLF","WSM","YEM","YT","SRB","ZAF","ZMB","MNE","ZWE","A1","A2","O1",
+"ALA","GGY","IMN","JEY"
+ );
+ var $GEOIP_COUNTRY_NAMES = array(
+"", "Asia/Pacific Region", "Europe", "Andorra", "United Arab Emirates",
+"Afghanistan", "Antigua and Barbuda", "Anguilla", "Albania", "Armenia",
+"Netherlands Antilles", "Angola", "Antarctica", "Argentina", "American Samoa",
+"Austria", "Australia", "Aruba", "Azerbaijan", "Bosnia and Herzegovina",
+"Barbados", "Bangladesh", "Belgium", "Burkina Faso", "Bulgaria", "Bahrain",
+"Burundi", "Benin", "Bermuda", "Brunei Darussalam", "Bolivia", "Brazil",
+"Bahamas", "Bhutan", "Bouvet Island", "Botswana", "Belarus", "Belize",
+"Canada", "Cocos (Keeling) Islands", "Congo, The Democratic Republic of the",
+"Central African Republic", "Congo", "Switzerland", "Cote D'Ivoire", "Cook
+Islands", "Chile", "Cameroon", "China", "Colombia", "Costa Rica", "Cuba", "Cape
+Verde", "Christmas Island", "Cyprus", "Czech Republic", "Germany", "Djibouti",
+"Denmark", "Dominica", "Dominican Republic", "Algeria", "Ecuador", "Estonia",
+"Egypt", "Western Sahara", "Eritrea", "Spain", "Ethiopia", "Finland", "Fiji",
+"Falkland Islands (Malvinas)", "Micronesia, Federated States of", "Faroe
+Islands", "France", "France, Metropolitan", "Gabon", "United Kingdom",
+"Grenada", "Georgia", "French Guiana", "Ghana", "Gibraltar", "Greenland",
+"Gambia", "Guinea", "Guadeloupe", "Equatorial Guinea", "Greece", "South Georgia
+and the South Sandwich Islands", "Guatemala", "Guam", "Guinea-Bissau",
+"Guyana", "Hong Kong", "Heard Island and McDonald Islands", "Honduras",
+"Croatia", "Haiti", "Hungary", "Indonesia", "Ireland", "Israel", "India",
+"British Indian Ocean Territory", "Iraq", "Iran, Islamic Republic of",
+"Iceland", "Italy", "Jamaica", "Jordan", "Japan", "Kenya", "Kyrgyzstan",
+"Cambodia", "Kiribati", "Comoros", "Saint Kitts and Nevis", "Korea, Democratic
+People's Republic of", "Korea, Republic of", "Kuwait", "Cayman Islands",
+"Kazakstan", "Lao People's Democratic Republic", "Lebanon", "Saint Lucia",
+"Liechtenstein", "Sri Lanka", "Liberia", "Lesotho", "Lithuania", "Luxembourg",
+"Latvia", "Libyan Arab Jamahiriya", "Morocco", "Monaco", "Moldova, Republic
+of", "Madagascar", "Marshall Islands", "Macedonia",
+"Mali", "Myanmar", "Mongolia", "Macau", "Northern Mariana Islands",
+"Martinique", "Mauritania", "Montserrat", "Malta", "Mauritius", "Maldives",
+"Malawi", "Mexico", "Malaysia", "Mozambique", "Namibia", "New Caledonia",
+"Niger", "Norfolk Island", "Nigeria", "Nicaragua", "Netherlands", "Norway",
+"Nepal", "Nauru", "Niue", "New Zealand", "Oman", "Panama", "Peru", "French
+Polynesia", "Papua New Guinea", "Philippines", "Pakistan", "Poland", "Saint
+Pierre and Miquelon", "Pitcairn Islands", "Puerto Rico", "Palestinian Territory",
+"Portugal", "Palau", "Paraguay", "Qatar", "Reunion", "Romania",
+"Russian Federation", "Rwanda", "Saudi Arabia", "Solomon Islands",
+"Seychelles", "Sudan", "Sweden", "Singapore", "Saint Helena", "Slovenia",
+"Svalbard and Jan Mayen", "Slovakia", "Sierra Leone", "San Marino", "Senegal",
+"Somalia", "Suriname", "Sao Tome and Principe", "El Salvador", "Syrian Arab
+Republic", "Swaziland", "Turks and Caicos Islands", "Chad", "French Southern
+Territories", "Togo", "Thailand", "Tajikistan", "Tokelau", "Turkmenistan",
+"Tunisia", "Tonga", "Timor-Leste", "Turkey", "Trinidad and Tobago", "Tuvalu",
+"Taiwan", "Tanzania, United Republic of", "Ukraine",
+"Uganda", "United States Minor Outlying Islands", "United States", "Uruguay",
+"Uzbekistan", "Holy See (Vatican City State)", "Saint Vincent and the
+Grenadines", "Venezuela", "Virgin Islands, British", "Virgin Islands, U.S.",
+"Vietnam", "Vanuatu", "Wallis and Futuna", "Samoa", "Yemen", "Mayotte",
+"Serbia", "South Africa", "Zambia", "Montenegro", "Zimbabwe",
+"Anonymous Proxy","Satellite Provider","Other",
+"Aland Islands","Guernsey","Isle of Man","Jersey"
+);
+}
+function geoip_load_shared_mem ($file) {
+
+ $fp = fopen($file, "rb");
+ if (!$fp) {
+ print "error opening $file: $php_errormsg\n";
+ exit;
+ }
+ $s_array = fstat($fp);
+ $size = $s_array['size'];
+ if ($shmid = @shmop_open (GEOIP_SHM_KEY, "w", 0, 0)) {
+ shmop_delete ($shmid);
+ shmop_close ($shmid);
+ }
+ $shmid = shmop_open (GEOIP_SHM_KEY, "c", 0644, $size);
+ shmop_write ($shmid, fread($fp, $size), 0);
+ shmop_close ($shmid);
+}
+
+function _setup_segments($gi){
+ $gi->databaseType = GEOIP_COUNTRY_EDITION;
+ $gi->record_length = STANDARD_RECORD_LENGTH;
+ if ($gi->flags & GEOIP_SHARED_MEMORY) {
+ $offset = @shmop_size ($gi->shmid) - 3;
+ for ($i = 0; $i < STRUCTURE_INFO_MAX_SIZE; $i++) {
+ $delim = @shmop_read ($gi->shmid, $offset, 3);
+ $offset += 3;
+ if ($delim == (chr(255).chr(255).chr(255))) {
+ $gi->databaseType = ord(@shmop_read ($gi->shmid, $offset, 1));
+ $offset++;
+
+ if ($gi->databaseType == GEOIP_REGION_EDITION_REV0){
+ $gi->databaseSegments = GEOIP_STATE_BEGIN_REV0;
+ } else if ($gi->databaseType == GEOIP_REGION_EDITION_REV1){
+ $gi->databaseSegments = GEOIP_STATE_BEGIN_REV1;
+ } else if (($gi->databaseType == GEOIP_CITY_EDITION_REV0)||
+ ($gi->databaseType == GEOIP_CITY_EDITION_REV1)
+ || ($gi->databaseType == GEOIP_ORG_EDITION)
+ || ($gi->databaseType == GEOIP_ISP_EDITION)
+ || ($gi->databaseType == GEOIP_ASNUM_EDITION)){
+ $gi->databaseSegments = 0;
+ $buf = @shmop_read ($gi->shmid, $offset, SEGMENT_RECORD_LENGTH);
+ for ($j = 0;$j < SEGMENT_RECORD_LENGTH;$j++){
+ $gi->databaseSegments += (ord($buf[$j]) << ($j * 8));
+ }
+ if (($gi->databaseType == GEOIP_ORG_EDITION)||
+ ($gi->databaseType == GEOIP_ISP_EDITION)) {
+ $gi->record_length = ORG_RECORD_LENGTH;
+ }
+ }
+ break;
+ } else {
+ $offset -= 4;
+ }
+ }
+ if (($gi->databaseType == GEOIP_COUNTRY_EDITION)||
+ ($gi->databaseType == GEOIP_PROXY_EDITION)||
+ ($gi->databaseType == GEOIP_NETSPEED_EDITION)){
+ $gi->databaseSegments = GEOIP_COUNTRY_BEGIN;
+ }
+ } else {
+ $filepos = ftell($gi->filehandle);
+ fseek($gi->filehandle, -3, SEEK_END);
+ for ($i = 0; $i < STRUCTURE_INFO_MAX_SIZE; $i++) {
+ $delim = fread($gi->filehandle,3);
+ if ($delim == (chr(255).chr(255).chr(255))){
+ $gi->databaseType = ord(fread($gi->filehandle,1));
+ if ($gi->databaseType == GEOIP_REGION_EDITION_REV0){
+ $gi->databaseSegments = GEOIP_STATE_BEGIN_REV0;
+ }
+ else if ($gi->databaseType == GEOIP_REGION_EDITION_REV1){
+ $gi->databaseSegments = GEOIP_STATE_BEGIN_REV1;
+ } else if (($gi->databaseType == GEOIP_CITY_EDITION_REV0) ||
+ ($gi->databaseType == GEOIP_CITY_EDITION_REV1) ||
+ ($gi->databaseType == GEOIP_ORG_EDITION) ||
+ ($gi->databaseType == GEOIP_ISP_EDITION) ||
+ ($gi->databaseType == GEOIP_ASNUM_EDITION)){
+ $gi->databaseSegments = 0;
+ $buf = fread($gi->filehandle,SEGMENT_RECORD_LENGTH);
+ for ($j = 0;$j < SEGMENT_RECORD_LENGTH;$j++){
+ $gi->databaseSegments += (ord($buf[$j]) << ($j * 8));
+ }
+ if ($gi->databaseType == GEOIP_ORG_EDITION) {
+ $gi->record_length = ORG_RECORD_LENGTH;
+ }
+ }
+ break;
+ } else {
+ fseek($gi->filehandle, -4, SEEK_CUR);
+ }
+ }
+ if (($gi->databaseType == GEOIP_COUNTRY_EDITION)||
+ ($gi->databaseType == GEOIP_PROXY_EDITION)||
+ ($gi->databaseType == GEOIP_NETSPEED_EDITION)){
+ $gi->databaseSegments = GEOIP_COUNTRY_BEGIN;
+ }
+ fseek($gi->filehandle,$filepos,SEEK_SET);
+ }
+ return $gi;
+}
+
+function geoip_open($filename, $flags) {
+ $gi = new GeoIP;
+ $gi->flags = $flags;
+ if ($gi->flags & GEOIP_SHARED_MEMORY) {
+ $gi->shmid = @shmop_open (GEOIP_SHM_KEY, "a", 0, 0);
+ } else {
+ $gi->filehandle = fopen($filename,"rb");
+ if ($gi->flags & GEOIP_MEMORY_CACHE) {
+ $s_array = fstat($gi->filehandle);
+ $gi->memory_buffer = fread($gi->filehandle, $s_array[size]);
+ }
+ }
+
+ $gi = _setup_segments($gi);
+ return $gi;
+}
+
+function geoip_close($gi) {
+ if ($gi->flags & GEOIP_SHARED_MEMORY) {
+ return true;
+ }
+
+ return fclose($gi->filehandle);
+}
+
+function geoip_country_id_by_name($gi, $name) {
+ $addr = gethostbyname($name);
+ if (!$addr || $addr == $name) {
+ return false;
+ }
+ return geoip_country_id_by_addr($gi, $addr);
+}
+
+function geoip_country_code_by_name($gi, $name) {
+ $country_id = geoip_country_id_by_name($gi,$name);
+ if ($country_id !== false) {
+ return $gi->GEOIP_COUNTRY_CODES[$country_id];
+ }
+ return false;
+}
+
+function geoip_country_name_by_name($gi, $name) {
+ $country_id = geoip_country_id_by_name($gi,$name);
+ if ($country_id !== false) {
+ return $gi->GEOIP_COUNTRY_NAMES[$country_id];
+ }
+ return false;
+}
+
+function geoip_country_id_by_addr($gi, $addr) {
+ $ipnum = ip2long($addr);
+ return _geoip_seek_country($gi, $ipnum) - GEOIP_COUNTRY_BEGIN;
+}
+
+function geoip_country_code_by_addr($gi, $addr) {
+ if ($gi->databaseType == GEOIP_CITY_EDITION_REV1) {
+ $record = geoip_record_by_addr($gi,$addr);
+ return $record->country_code;
+ } else {
+ $country_id = geoip_country_id_by_addr($gi,$addr);
+ if ($country_id !== false) {
+ return $gi->GEOIP_COUNTRY_CODES[$country_id];
+ }
+ }
+ return false;
+}
+
+function geoip_country_name_by_addr($gi, $addr) {
+ if ($gi->databaseType == GEOIP_CITY_EDITION_REV1) {
+ $record = geoip_record_by_addr($gi,$addr);
+ return $record->country_name;
+ } else {
+ $country_id = geoip_country_id_by_addr($gi,$addr);
+ if ($country_id !== false) {
+ return $gi->GEOIP_COUNTRY_NAMES[$country_id];
+ }
+ }
+ return false;
+}
+
+function _geoip_seek_country($gi, $ipnum) {
+ $offset = 0;
+ for ($depth = 31; $depth >= 0; --$depth) {
+ if ($gi->flags & GEOIP_MEMORY_CACHE) {
+ $buf = substr($gi->memory_buffer,
+ 2 * $gi->record_length * $offset,
+ 2 * $gi->record_length);
+ } elseif ($gi->flags & GEOIP_SHARED_MEMORY) {
+ $buf = @shmop_read ($gi->shmid,
+ 2 * $gi->record_length * $offset,
+ 2 * $gi->record_length );
+ } else {
+ fseek($gi->filehandle, 2 * $gi->record_length * $offset, SEEK_SET) == 0
+ or die("fseek failed");
+ $buf = fread($gi->filehandle, 2 * $gi->record_length);
+ }
+ $x = array(0,0);
+ for ($i = 0; $i < 2; ++$i) {
+ for ($j = 0; $j < $gi->record_length; ++$j) {
+ $x[$i] += ord($buf[$gi->record_length * $i + $j]) << ($j * 8);
+ }
+ }
+ if ($ipnum & (1 << $depth)) {
+ if ($x[1] >= $gi->databaseSegments) {
+ return $x[1];
+ }
+ $offset = $x[1];
+ } else {
+ if ($x[0] >= $gi->databaseSegments) {
+ return $x[0];
+ }
+ $offset = $x[0];
+ }
+ }
+ trigger_error("error traversing database - perhaps it is corrupt?", E_USER_ERROR);
+ return false;
+}
+
+function _get_org($gi,$ipnum){
+ $seek_org = _geoip_seek_country($gi,$ipnum);
+ if ($seek_org == $gi->databaseSegments) {
+ return NULL;
+ }
+ $record_pointer = $seek_org + (2 * $gi->record_length - 1) * $gi->databaseSegments;
+ if ($gi->flags & GEOIP_SHARED_MEMORY) {
+ $org_buf = @shmop_read ($gi->shmid, $record_pointer, MAX_ORG_RECORD_LENGTH);
+ } else {
+ fseek($gi->filehandle, $record_pointer, SEEK_SET);
+ $org_buf = fread($gi->filehandle,MAX_ORG_RECORD_LENGTH);
+ }
+ $org_buf = substr($org_buf, 0, strpos($org_buf, 0));
+ return $org_buf;
+}
+
+function geoip_org_by_addr ($gi,$addr) {
+ if ($addr == NULL) {
+ return 0;
+ }
+ $ipnum = ip2long($addr);
+ return _get_org($gi, $ipnum);
+}
+
+function _get_region($gi,$ipnum){
+ if ($gi->databaseType == GEOIP_REGION_EDITION_REV0){
+ $seek_region = _geoip_seek_country($gi,$ipnum) - GEOIP_STATE_BEGIN_REV0;
+ if ($seek_region >= 1000){
+ $country_code = "US";
+ $region = chr(($seek_region - 1000)/26 + 65) . chr(($seek_region - 1000)%26 + 65);
+ } else {
+ $country_code = $gi->GEOIP_COUNTRY_CODES[$seek_region];
+ $region = "";
+ }
+ return array ($country_code,$region);
+ } else if ($gi->databaseType == GEOIP_REGION_EDITION_REV1) {
+ $seek_region = _geoip_seek_country($gi,$ipnum) - GEOIP_STATE_BEGIN_REV1;
+ //print $seek_region;
+ if ($seek_region < US_OFFSET){
+ $country_code = "";
+ $region = "";
+ } else if ($seek_region < CANADA_OFFSET) {
+ $country_code = "US";
+ $region = chr(($seek_region - US_OFFSET)/26 + 65) . chr(($seek_region - US_OFFSET)%26 + 65);
+ } else if ($seek_region < WORLD_OFFSET) {
+ $country_code = "CA";
+ $region = chr(($seek_region - CANADA_OFFSET)/26 + 65) . chr(($seek_region - CANADA_OFFSET)%26 + 65);
+ } else {
+ $country_code = $gi->GEOIP_COUNTRY_CODES[($seek_region - WORLD_OFFSET) / FIPS_RANGE];
+ $region = "";
+ }
+ return array ($country_code,$region);
+ }
+}
+
+function geoip_region_by_addr ($gi,$addr) {
+ if ($addr == NULL) {
+ return 0;
+ }
+ $ipnum = ip2long($addr);
+ return _get_region($gi, $ipnum);
+}
+
+function getdnsattributes ($l,$ip){
+ $r = new Net_DNS_Resolver();
+ $r->nameservers = array("ws1.maxmind.com");
+ $p = $r->search($l."." . $ip .".s.maxmind.com","TXT","IN");
+ $str = is_object($p->answer[0])?$p->answer[0]->string():'';
+ ereg("\"(.*)\"",$str,$regs);
+ $str = $regs[1];
+ return $str;
+}
+
+function getCCByIP($ip) {
+
+ // path to file needs to be absolute, as fopen() is used to read it
+ $gi = geoip_open(dirname(__FILE__) . "/GeoIP.dat", GEOIP_STANDARD);
+
+ // get the country code for $ip
+ $cc = geoip_country_code_by_addr($gi, $ip);
+ geoip_close($gi);
+
+ return $cc;
+}
+
+?>
diff --git a/pp3/au/run-au-import.php b/pp3/au/run-au-import.php
new file mode 100755
index 0000000..fe40175
--- /dev/null
+++ b/pp3/au/run-au-import.php
@@ -0,0 +1,48 @@
+#!/usr/bin/php
+<?php
+ini_set('memory_limit', '560M');
+
+require_once 'config.php';
+require_once 'lib/Getopt.php';
+require_once 'lib/geoip.inc';
+require_once 'Logger.php';
+require_once 'Db.php';
+require_once 'Runner.php';
+require_once 'Importer.php';
+
+// start logger, use it's static methods ::info($msg), ::error($msg) anywhere to log status
+Logger::init(LOGFILE_PATH . '/au-import-info-' . date('Ymd') . '.log');
+
+// parse cmdline for product which AU log we want to import: netbeans|dlc|vvm .. default to all
+$opts = new Zend_Console_Getopt(array(
+ 'help|h' => 'Show help',
+ 'source|s=s' => 'Which AU log to import, possible valules: dlc, nb, vvm'));
+try {
+ $opts->parse();
+ $source = ($opts->getOption('source')) ? $opts->getOption('source') : 'all';
+ $help = $opts->getOption('help');
+ if ($help) {
+ echo $opts->getUsageMessage();
+ exit(0);
+ }
+} catch (Exception $e) {
+ // some illegal option specified, show help and exit
+ echo $opts->getUsageMessage();
+ exit(1);
+}
+
+// here it happens
+try {
+ // invoke runner
+ Logger::write(Logger::INFO, 'Starting import, log specified: ' . $source);
+ Runner::run($source);
+ Logger::write(Logger::INFO, 'Import finished');
+ exit(0);
+} catch (Exception $e) {
+ // if any exception is thrown, send it in email to the system owner
+ mail(ADMIN_EMAIL, 'AU-IMPORT - exception occured', "Hello,\nthis is to inform you about exception during the AU-IMPORT procedure.\n\n" . date('Y-m-d H:i:s') . "\n\nException:\n" . $e->getMessage() . "\n\n" . $e->getTraceAsString());
+ // also print it
+ echo $e->getMessage();
+ exit(1);
+}
+?>