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);
+}
+?>