You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@labs.apache.org by co...@apache.org on 2007/01/20 22:21:18 UTC

svn commit: r498170 [2/2] - in /labs/pulse/report-web: ./ liststats-classes.php mlists.php util-classes.php

Added: labs/pulse/report-web/util-classes.php
URL: http://svn.apache.org/viewvc/labs/pulse/report-web/util-classes.php?view=auto&rev=498170
==============================================================================
--- labs/pulse/report-web/util-classes.php (added)
+++ labs/pulse/report-web/util-classes.php Sat Jan 20 13:21:18 2007
@@ -0,0 +1,2273 @@
+<?php
+define('RLIB_DB_SUCCESS', 200);
+define('RLIB_DB_NOSUCH', -404);
+define('RLIB_DB_AMBIGUOUS', -300);
+define('RLIB_DB_UNCHANGED', -304);
+define('RLIB_DB_INTERNAL_ERROR', -500);
+define('RLIB_DB_CURRENT', -1024);
+
+define('RLIB_THIS_URI', -1025);
+
+define('RLIB_FLAG_DB_DEBUG_SQL', 'RLIB_DEBUG_DB_TRACK_SQL');
+
+if (defined('RLIB_TRAP_METHODS')) {
+    class rlib__originclass {
+        var $__trapfields;
+        /*
+         * Catch-all method for handling record field manipulations.
+         * For fields declared in the object's $this->__trapfields array,
+         * this permits $object_t->x() and $object_t->x(newval),
+         * corresponding to $object_t->get('x') and
+         * $object_t->set('x', newval).
+         */
+        function __call($mname, $margs=null) {
+            $valid = true;
+            $ovars = get_object_vars($this);
+            /*
+             * Two ways to fail: this doesn't match a field in this class,
+             * or else it does but the call has the wrong syntax.  We can't
+             * tell the latter until getting past the former, though.
+             */
+            $fname = $this->nkey($mname);
+            if (! in_array($fname, $this->__trapfields)) {
+                $valid = false;
+            }
+            /*
+             * Okey, the method name matches the name of a field in this
+             * class' list of fields to trap.  Turn this call into a get()
+             * or set().  We'll either have no arguments (a get) or one
+             * argument (set).
+             */
+            if ($valid) {
+                if ((! isset($margs))
+                    || (! is_array($margs))
+                    || (count($margs) == 0)) {
+                    return $this->get($mname);
+                }
+                else if (count($margs) == 1) {
+                    return $this->set($mname, $margs[0]);
+                }
+            }
+            $arglist = '';
+            if (is_array($margs) && count($margs)) {
+                $arglist = '[' . implode('],[', $margs) . ']';
+            }
+            trigger_error('Unknown method or bad syntax in call: '
+                          . get_class($this) . "::$mname($arglist)",
+                          E_USER_ERROR);
+            exit(1);
+        }
+    }
+}
+else {
+    class rlib__originclass {
+        var $__trapfields;
+    }
+}
+
+/*
+ * Base class for extending.
+ */
+class rlib__baseclass extends rlib__originclass {
+
+    var $_record;
+    var $_changed;
+    var $___trace = array();
+    var $_custom;
+    var $_flags;
+    var $_booleans;
+    var $_new = false;
+    var $_dirty = false;
+    var $_error;
+    var $_message;
+    var $_errno = 0;
+    var $_errstr = '';
+
+    function numerise_booleans() {
+        if (isset($this->_booleans) && $this->_booleans) {
+            foreach ($this->_booleans as $field) {
+                $val = $this->get($field);
+                $this->set($field, $val ? 1 : 0);
+            }
+        }
+    }
+
+    /*
+     * Self-explanatory, I hope.
+     */
+    function isNew($newsetting=null) {
+        $old = ($this->_new ? true : false);
+        if (isset($newsetting)) {
+            $this->_new = ($newsetting ? true : false);
+        }
+        return $old;
+    }
+
+    function isDirty($newval=null) {
+        $oldval = ($this->_dirty ? true : false);
+        if (isset($newval)) {
+            $this->_dirty = ($newval ? true : false);
+        }
+        return $oldval;
+    }
+
+    /*
+     * Normalise a key name from possible mixed case to whatever we use.
+     */
+    function nkey($keyname) {
+        return strtolower($keyname);
+    }
+
+    function __trace($msg=null) {
+        if (! $this->raised('RLIB_TRACE')) {
+            return;
+        }
+        if (! isset($this->___trace)) {
+            $this->___trace = array();
+        }
+        if (isset($msg)) {
+            $this->___trace[] = $msg;
+        }
+        return $this->___trace;
+    }
+
+    /*
+     * Fetch a particular field value.
+     */
+    function get($fname) {
+        $fname = $this->nkey($fname);
+        return (isset($this->_record[$fname]) ? $this->_record[$fname] : null);
+    }
+
+    /*
+     * Return a value from the object's list of custom fields.
+     */
+    function cget($fname) {
+        $fname = $this->nkey($fname);
+        return (isset($this->_custom[$fname]) ? $this->_custom[$fname] : null);
+    }
+
+    /*
+     * Now set one.
+     */
+    function set($fname, $val=null) {
+        $this->__trace('[Entering set()]');
+/*## for how-did-this-get-overwritten checking
+ *
+        $bt = debug_backtrace();
+        if (is_array($bt)) {
+            $i = count($bt);
+            foreach ($bt as $btline) {
+                if (is_array($btline)) {
+                    $i--;
+                    foreach ($btline as $k => $v) {
+                        $this->__trace("[set backtrace] [$i] [$k] [$v]");
+                    }
+                }
+            }
+        }
+ */
+        if (is_array($fname)) {
+            $this->__trace('[Array processing begins]');
+            $result = array();
+            foreach ($fname as $k => $v) {
+                $oldval = $this->set($k, $v);
+                $result[$k] = $oldval;
+            }
+            $this->__trace('[Array processing ends]');
+            $this->__trace('[Exiting set()]');
+            return $result;
+        }
+        $fname = $this->nkey($fname);
+        $msg = "[$fname]: ";
+        $oldval = $this->get($fname);
+        if (isset($oldval)) {
+            $msg .= "old=[$oldval]";
+        }
+        $msg .= "new=[$val]";
+        $this->__trace($msg);
+        $this->_record[$fname] = $val;
+        if ($val != $oldval) {
+            if (! is_array($this->_changed)) {
+                $this->_changed = array();
+            }
+            if (! array_key_exists($fname, $this->_changed)) {
+                $this->_changed[$fname] = $oldval;
+            }
+        }
+        $this->isDirty($this->isDirty() || ($val != $oldval));
+        $this->__trace('[Exiting set()]');
+        return $oldval;
+    }
+
+    /*
+     * Set a custom key/value pair for the object.
+     */
+    function cset($fname, $val=null) {
+        if (is_array($fname)) {
+            $result = array();
+            foreach ($fname as $k => $v) {
+                $oldval = $this->cset($k, $v);
+                $result[$k] = $oldval;
+            }
+            return $result;
+        }
+        $fname = $this->nkey($fname);
+        $oldval = $this->get($fname);
+        $this->_custom[$fname] = $val;
+        return $oldval;
+    }
+
+    function has_changed($what) {
+        $result = false;
+        if (is_array($what)) {
+            foreach ($what as $field) {
+                $result = $result || $this->has_changed($field);
+                if ($result) {
+                    break;
+                }
+            }
+        }
+        else if (is_string($what)) {
+            $what = $this->nkey($what);
+            $result = array_key_exists($what, $this->_changed);
+        }
+        return $result;
+    }
+
+    /*
+     * Clear a cell.
+     */
+    function reset($key=null) {
+        if (! isset($key)) {
+            $this->_record = null;
+        }
+        else {
+            unset($this->_record[$this->nkey($key)]);
+        }
+    }
+
+    /*
+     * Clear a cell in the custom array -- or clear all of it.
+     */
+    function creset($key=null) {
+        if (! isset($key)) {
+            $this->_custom = null;
+        }
+        else {
+            unset($this->_custom[$this->nkey($key)]);
+        }
+    }
+
+    /*
+     * Function to simplify $object->get('foo') to $object->foo()
+     * codings.
+     */
+    function frobfield($fname, $newval=null) {
+        if (isset($newval)) {
+            $oldval = $this->set($fname, $newval);
+        }
+        else {
+            $oldval = $this->get($fname);
+        }
+        return $oldval;
+    }
+
+    /*
+     * Return a list of the fields in the record (debugging purposes).
+     */
+    function fields() {
+        return $this->_record;
+    }
+
+    function as_string($include_objects=false) {
+        $output = print_r($this, true);
+        if (! $include_objects) {
+            $lines = preg_split('/[\r\n]/', $output);
+            $composite = array();
+            $skipping = false;
+            $start_next = false;
+            foreach ($lines as $line) {
+                if (! $skipping) {
+                    if ($start_next) {
+                        $stop_on = preg_replace('/\(/', ')', $line);
+                        $start_next = false;
+                        $skipping = true;
+                        continue;
+                    }
+                    $composite[] = $line;
+                    if (preg_match('/^(\s*\[).*\s+Object\s*$/', $line)) {
+                        $start_next = true;
+                    }
+                }
+                else {
+                    if ($line != $stop_on) {
+                        continue;
+                    }
+                    $skipping = false;
+                }
+            }
+            $output = implode("\n", $composite);
+        }
+        return $output;
+    }
+    
+    /*
+     * Set an arbitrary object value.
+     */
+    function _set(&$cell, $newval=null) {
+        $rval = $cell;
+        if (isset($newval)) {
+            $cell = $newval;
+        }
+        return $rval;
+    }
+
+    /*
+     * Per-object error codes.
+     */
+    function errno($newno=null) {
+        if (isset($newno)) {
+            $this->_errno = (int)$newno;
+        }
+        return $this->_errno;
+    }
+
+    function errstr($newstr=null) {
+        if (isset($newstr)) {
+            $this->_errstr = $newstr;
+        }
+        return $this->_errstr;
+    }
+
+    function clear_error() {
+        $this->error(0);
+        $this->message('');
+    }
+
+    function error($newval=null) {
+        return $this->_set($this->_error, $newval);
+    }
+
+    function message($newval=null) {
+        return $this->_set($this->_message, $newval);
+    }
+
+    /*
+     * Flag manipulations.
+     */
+    function enflag($what, $setting=true) {
+        if (is_string($what)) {
+            $this->_flags[$this->nkey($what)] = $setting;
+        }
+        else if (is_array($what)) {
+            foreach ($what as $k => $v) {
+                if (is_integer($k)) {
+                    $this->raise($v);
+                }
+                else {
+                    $this->raise($k, $v);
+                }
+            }
+        }
+        return;
+    }
+
+    function raise($what) {
+        $this->enflag($what, true);
+    }
+    function lower($what) {
+        $this->enflag($what, false);
+    }
+    function raised($what, $match='and') {
+        $match = strtolower($match);
+        $result = false;
+        if (is_string($what)) {
+            $what = $this->nkey($what);
+            if (isset($this->_flags[$what])) {
+                return $this->_flags[$what];
+            }
+            else {
+                return false;
+            }
+        }
+        else if (is_array($what)) {
+            $result = ($match == 'and') ? true : false;
+            foreach ($what as $v) {
+                $v = $this->nkey($v);
+                $on = $this->raised($v);
+                /*
+                 * If we're matching on all being true ('and') then
+                 * fail as soon as we find a false value.
+                 */
+                if ((! $on) && ($match == 'and')) {
+                    $result = false;
+                    break;
+                }
+                if ($on) {
+                    $result = true;
+                    break;
+                }
+            }
+        }
+        return $result;
+    }
+
+}
+
+/*
+ * Let the includer specify whether our classes include a __call()
+ * by default or not.*
+ */
+if (defined('RLIB_TRAP_METHODS')) {
+    class rlib__dborigin extends rlib__baseclass {
+        /*
+         * Catch-all method for handling db record field manipulations.
+         * For a table 't' with field 'x', this permits $object_t->x()
+         * and $object_t->x(newval), corresponding to $object_t->get('x')
+         * and $object_t->set('x', newval).
+         */
+        function __call($mname, $margs=null) {
+            $valid = true;
+            $ovars = get_object_vars($this);
+            /*
+             * Two ways to fail: this doesn't match a field in this class,
+             * or else it does but the call has the wrong syntax.  We can't
+             * tell the latter until getting past the former, though.
+             */
+            $fname = $this->nkey($mname);
+            if ((! in_array($fname, $ovars))
+                || (! isset($this->_db_tablename))
+                || (! $this->is_valid_field($fname))) {
+                $valid = false;
+            }
+            /*
+             * Okey, the method name matches the name of a field in this
+             * class' database record.  Turn this call into a get() or set().
+             * We'll either have no arguements (a ge) or one argument (set).
+             */
+            if ($valid) {
+                if ((! isset($margs))
+                    || (! is_array($margs))
+                    || (count($margs) == 0)) {
+                    return $this->get($mname);
+                }
+                else if (count($margs) == 1) {
+                    return $this->set($mname, $margs[0]);
+                }
+            }
+            $arglist = '';
+            if (is_array($margs) && count($margs)) {
+                $arglist = '[' . implode('],[', $margs) . ']';
+            }
+            trigger_error('Unknown method or bad syntax in call: '
+                          . get_class($this) . "::$mname($arglist)",
+                          E_USER_ERROR);
+            exit(1);
+        }
+    }
+}
+else {
+    class rlib__dborigin extends rlib__baseclass {
+    }
+}
+
+class rlib__dbbaseclass extends rlib__dborigin {
+
+    var $_dbname = 'DB';
+    var $_dbo = 0;
+    var $_dbc = 0;
+    var $_dbh = 0;
+    var $_semaphore;
+    var $_db_tablename;
+    var $_id_fieldname;
+    var $_id_display;
+    var $_object_type;
+    var $_sql;
+    var $_dbopstack = array();
+    var $_db;
+    var $_key;
+    var $_checksum;
+
+    /*
+     * Load all the table structures for field verification.
+     */
+    function load_table_info() {
+        $sql = 'SHOW TABLES';
+        $q = $this->query($sql);
+        while ($q && ($row = mysql_fetch_row($q))) {
+            list($this->_db[]) = $row;
+        }
+        if ($this->errno()) {
+            return $this->errno();
+        }
+        foreach ($this->_db as $table) {
+            $sql = "DESCRIBE $table";
+            $q = $this->query($sql);
+            while ($q && ($row = mysql_fetch_row($q))) {
+                $this->_db[$table][$row[0]] = array($row[1], $row[4]);
+            }
+        }
+    }
+
+    /*
+     * This should be overloaded with a function that returns an
+     * object that logs activity.
+     */
+    function logger() {
+        return null;
+    }
+
+/*### rename! ###*/
+    function id($field, $table, $addl=null) {
+        $this->_id_fieldname = $field;
+        $this->_db_tablename = $table;
+        $this->_id_display = $addl;
+    }
+
+    function is_valid_field($field, $table=null) {
+        if (! isset($table)) {
+            $table = $this->_db_tablename;
+        }
+        if (is_array($this->_dbo->_db)
+            && array_key_exists($table, $this->_dbo->_db)
+            && array_key_exists($field, $this->_dbo->_db[$table])) {
+            return true;
+        }
+        return false;
+    }
+
+    function get_field_properties($field, $table=null) {
+        if (! isset($table)) {
+            $table = $this->_db_tablename;
+        }
+        if (is_array($this->_dbo->_db)
+            && array_key_exists($table, $this->_dbo->_db)
+            && array_key_exists($field, $this->_dbo->_db[$table])) {
+            return $this->_dbo->_db[$table][$field];
+        }
+        return null;
+    }
+
+    function get_default($field, $table=null) {
+        $prop = $this->get_field_properties($field, $table);
+        if (! isset($prop[1])) {
+            return null;
+        }
+        $default = $prop[1];
+        if ($default == 'NULL') {
+            $default = null;
+        }
+        return $default;
+    }
+
+    function set_default($field) {
+        if ($this->is_valid_field($field)) {
+            $val = $this->set($field, $this->get_default($field));
+        }
+        else {
+            $val = null;
+        }
+        return $val;
+    }
+
+    /*
+     * Function to create an SQL condition clause.
+     * 1. If passed a string, it returns it enclosed in parentheses.
+     * 2. If passed an array, it is treated as a set of key-value pairs.
+     *    Each key is a fieldname used as the LHS of an expression;
+     *    the value is used to construct the RHS.  The fieldname is
+     *    validated against the fields listed for the table in the
+     *    database object.  (The table is determined from the $table
+     *    argument, or from $this->_db_tablename.)
+     *    a. field => scalar
+     *       (field = 'scalar')
+     *    b. field => array(op, 'val1')
+     *       (field op 'val1')
+     *    c. field => array('in set', 'val1', 'val2')
+     *       (FIND_INSET(field, 'val1,val2'))
+     *    d. field => array('not in set', 'val1', 'val2')
+     *       (NOT FIND_INSET(field, 'val1,val2'))
+     *    e. field => array('between', 'val1', 'val2')
+     *       (field BETWEEN 'val1' AND 'val2')
+     *
+     *    Multiple entries in the array are joined with ' AND '.
+     *
+     * TODO:
+     * o allow for ' OR ' joining
+     * o allow for multiple conditions on a single field
+     * o allow for sub-expressions
+     * o allow non-field LHS and field RHS
+     * o allow for non-quoting of RHS in (a) and (b) (e.g.,
+     *   field => '@(xid + 1)'
+     *   (field = xid + 1)
+     */
+    function criteriate($criteria=null, $default=null, $table=null) {
+        $expr = '';
+        if (! isset($criteria)) {
+//            print "|empty criteria\n";
+            return '';
+        }
+        $joiner = '';
+        if (is_string($criteria)) {
+//            print "|simple-string criteria\n";
+            $expr .= "($criteria)";
+            $joiner = ' AND ';
+        }
+        else if (is_array($criteria)) {
+//            print "|array criteria\n";
+            foreach ($criteria as $field => $value) {
+                if (! $this->is_valid_field($field, $table)) {
+//                    print "|'$field' invalid for '$table'\n";
+                    continue;
+                }
+                if ((! isset($expr)) || (! $expr)) {
+                    $expr = ' WHERE ';
+                }
+                if (is_array($value)) {
+                    $op = array_shift($value);
+                    if (preg_match('/set$/i', $op)) {
+                        $element = "FIND_IN_SET($field, '"
+                            . AddSlashes(implode(',', $value))
+                            . "')";
+                        if (! preg_match('/^in/i', $op)) {
+                            $element = "NOT $element";
+                        }
+                        $expr .= "$joiner ($element)";
+                    }
+                    else if (preg_match('/between/i', $op)) {
+                        $expr .= "$joiner ($field BETWEEN '"
+                            . AddSlashes($value[0])
+                            . "' AND '"
+                            . AddSlashes($value[1]) . "')";
+                    }
+                    else {
+                        $value = implode(',', $value);
+                        $expr .= "$joiner " . $this->where($field, $value, $op);
+                    }
+                }
+                else {
+                    $expr .= "$joiner " . $this->where($field, $value);
+                }
+                $joiner = ' AND ';
+            }
+        }
+        return $expr;
+    }
+
+    function orderate($order=null, $default=null) {
+        $expr = '';
+        if (! isset($order)) {
+            if (isset($default)) {
+                if (! preg_match('/^\s*ORDER\s+BY\b/i', $default)) {
+                    $default = "ORDER BY $default";
+                }
+                $expr = " $default";
+            }
+        }
+        else if (is_string($order)) {
+            if (! preg_match('/^\s*ORDER\s+BY\b/i', $order)) {
+                $order = "ORDER BY $order";
+            }
+            $expr = " $order";
+        }
+        else if (is_array($order)) {
+            $expr = ' ORDER BY';
+            $first = true;
+            foreach ($order as $k => $v) {
+                $expr .= ($first ? '' : ', ')
+                    . " $k $v";
+                $first = false;
+            }
+        }
+        return $expr;
+    }
+
+    /*
+     * The only errors we care about, really, are MySQL ones.
+     * The query() method updates the error cells directly, but
+     * calls such as mysql_fetch_row() aren't methods.  So we
+     * need to copy them from MySQL into our object before
+     * returning them.
+     */
+    function errno() {
+        $this->_errno = mysql_errno($this->_dbc);
+        return $this->_errno;
+    }
+
+    function errstr() {
+        $this->_errstr = mysql_error($this->_dbc);
+        return $this->_errstr;
+    }
+
+    function sql() {
+        return $this->_sql;
+    }
+
+    /*
+     * Load the latest values from the database.
+     */
+    function refresh($key=null, $altfield=null) {
+        if (! isset($key)) {
+            $key = $this->_key;
+        }
+        $sql = 'SELECT * FROM ' . $this->_db_tablename
+            . ' WHERE '
+            . $this->where(isset($altfield) ? $altfield : $this->_id_fieldname,
+                           $key);
+        $q = $this->query($sql);
+        if ($q && (mysql_num_rows($q) == 1)) {
+            $this->isNew(false);
+            $this->isDirty(false);
+            $this->_record = mysql_fetch_assoc($q);
+            $this->_key = $this->get($this->_id_fieldname);
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     * Delete the current record from the database.
+     * A return of 0 means the item was deleted from the database and
+     * the object pointer set to null; -1 means we don't know what to
+     * do (bad object constructor or maybe not a deletable thing);
+     * -2 means there's no single record to delete (either doesn't
+     * exist or ambiguous).  Anything else is a MySQL error number.
+     */
+    function delete() {
+        $this->clear_error();
+        if ((! isset($this->_db_tablename))
+            || (! isset($this->_id_fieldname))
+            || (! $this->get($this->_id_fieldname))) {
+            $this->message('Internal object error; ID fields not set');
+            return $this->error(RLIB_DB_INTERNAL_ERROR);
+        }
+        $tname = $this->_db_tablename;
+        $fname = $this->_id_fieldname;
+        $fval = $this->get($fname);
+        if (! $fval) {
+            $this->message('Object not identified');
+            return $this->error(RLIB_DB_INTERNAL_ERROR);
+        }
+
+        $sql = "SELECT COUNT(*) FROM $tname WHERE "
+            . $this->where($fname, $fval);
+        $q = $this->query($sql);
+        /* ### do log
+         */
+        if ($this->errno()) {
+            $this->message($this->errstr());
+            return $this->error($this->errno());
+        }
+        list($n) = mysql_fetch_row($q);
+        if ($n < 1) {
+            $this->message('No such record in the database');
+            return $this->error(RLIB_DB_NOSUCH);
+        }
+        else if ($n > 1) {
+            $this->message("'$ident' is an ambiguous record identifier");
+            return $this->error(RLIB_DB_AMBIGUOUS);
+        }
+
+        $sql = "DELETE FROM $tname WHERE "
+            . $this->where($fname, $fval);
+        $subject = 'Deleted '
+            . strtoupper($this->_db_tablename)
+            . ' record';
+        $more_id = $this->commit_subject();
+        if ($more_id) {
+            $subject .= " $more_id";
+        }
+        else {
+            if ($name = $this->get('name')) {
+                $subject .= " '$name'";
+            }
+        }
+        $subject .= ' ('
+            . $this->_id_fieldname
+            . ' '
+            . $this->get($this->_id_fieldname)
+            . ')';
+        $message = "\n";
+        $q = $this->query($sql);
+        if (! $this->errno()) {
+            if (! $this->cget('nolog')) {
+                $message .= "Record succesfully deleted.\n";
+                if ($o_log = $this->_dbo->logger()) {
+                    $o_log->set('subject', $subject);
+                    $o_log->set('description', $message);
+                    $o_log->set('action', 'DELETE');
+                    $o_log->set('type', strtoupper($this->_db_tablename));
+                    $o_log->commit();
+                    mail('Rodent of Unusual Size <Ke...@Golux.Com>',
+                         $this->_dbo->_dbname . ": $subject",
+                         $message);
+                }
+            }
+            return 0;
+        }
+        $message .= "Operation failed:\n\n"
+            . "    SQL='" . $this->_sql . "'\n"
+            . "  errno=" . $this->errno() . "\n"
+            . "  error='" . $this->errstr() . "'\n";
+        mail('Rodent of Unusual Size <Ke...@Golux.Com>',
+             $this->_dbo->_dbname . ": DB FAILURE: $subject",
+             $message);
+        $this->message($this->errstr());
+        return $this->error($this->errno());
+    }
+
+    /*
+     * Copy the database bits to the current object (not used in the
+     * DB class itself, but inherited).
+     */
+    function importdb($dbo) {
+        while (is_object($dbo)
+               && ((! isset($dbo->_dbname))
+                   || (! $dbo->_dbname)
+                   || (! is_a($dbo, $dbo->_dbname)))) {
+            $dbo =& $dbo->_dbo;
+        }
+        $this->_dbo =& $dbo;
+        $this->_dbc = $dbo->_dbc;
+        $this->_dbh = $dbo->_dbh;
+        $this->_dbname = $dbo->_dbname;
+        $this->_errno = $dbo->_errno;
+        $this->_errstr = $dbo->_errstr;
+    }
+
+    /*
+     * Close the connexion.  Hardly necessary, but just for completeness..
+     */
+    function close() {
+        if ($this->_dbc != 0) {
+            mysql_close($this->_dbc);
+            $this->_dbc = 0;
+        }
+        $this->_dbh = 0;
+        return true;
+    }
+
+    /*
+     * Access the database server and open the database.
+     */
+    function open($host='localhost', $db='unknown', $user=null, $pw=null) {
+        if ($this->_dbc != 0) {
+            return mysql_ping($this->_dbc);
+        }
+        $this->_dbc = mysql_pconnect($host, $user, $pw);
+        if ($this->_dbc) {
+            $this->_dbh = mysql_select_db($db);
+        }
+        else {
+            $this->_errno = -666;
+            $this->_errstr = "unable to connect to database host '$host'";
+            return;
+        }
+        $this->setknownerrstate();
+    }
+
+    function lock() {
+        return;
+    }
+    function unlock() {
+        return;
+    }
+
+    function dbtrace_enable() {
+        return $this->_dbo->raise(RLIB_FLAG_DB_DEBUG_SQL);
+    }
+    function dbtrace_disable() {
+        return $this->_dbo->lower(RLIB_FLAG_DB_DEBUG_SQL);
+    }
+    function dbtrace() {
+        return $this->_dbo->_dbopstack;
+    }
+    function dbtrace_log($otype=null, $sql='', $errno=0, $errstr='') {
+        if (! isset($otype)) {
+            $otype = get_class($this);
+        }
+        $this->_dbo->_dbopstack[] = array('sql' => $sql,
+                                          'object' => $otype,
+                                          'errno'  => $errno,
+                                          'errstr' => $errstr);
+    }
+
+    /*
+     * Function to issue a query inside the object.
+     */
+    function query($sql) {
+        $this->_sql = $sql;
+        $this->lock();
+        $qr = mysql_query($sql, $this->_dbc);
+        $this->unlock();
+        if ($this->_dbo->raised(RLIB_FLAG_DB_DEBUG_SQL)) {
+            $this->dbtrace_log(null, $sql, $this->errno(), $this->errstr());
+        }
+        $this->setknownerrstate();
+        return $qr;
+    }
+
+    function release($q) {
+        if ($q) {
+            mysql_free_result($q);
+        }
+    }
+        
+    /*
+     * Set our error cells to a known state: either no error, stuckage,
+     * or something from the last db action.
+     */
+    function setknownerrstate() {
+        if ((! isset($this->_dbc)) || (! $this->_dbc)) {
+            $this->_errno = -666;
+            $this->_errstr = 'no database connexion active';
+            return;
+        }
+        if ($merr = mysql_errno($this->_dbc)) {
+            $this->_errno = $merr;
+            $this->_errstr = mysql_error($this->_dbc);
+        }
+        else {
+            $this->_errno = 0;
+            $this->_errstr = '';
+        }
+        return $this->_errno;
+    }
+
+    /*
+     * Commit function..
+     */
+    function commit($altkey=null, $do_pre=true, $do_post=true) {
+        if (! $this->isDirty()) {
+            return null;
+        }
+        if ($this->isNew()) {
+            $sql = 'INSERT INTO ';
+            $subject = 'Added new ';
+            $updating = false;
+        }
+        else {
+            $sql = 'UPDATE ';
+            $subject = 'Updated ';
+            $updating = true;
+        }
+        $message = "\n";
+        $sql .= $this->_db_tablename . ' SET ';
+        $joiner = '';
+        $dbo = $this->_dbo;
+        if ($do_pre) {
+            $abort = $this->precommit();
+            if ($abort) {
+                $message = "\nCommit aborted by precommit function\n\n"
+                    . "$abort\n";
+                $subject = $this->_dbo->_dbname . ": ABORTED: $subject";
+                mail('Rodent of Unusual Size <Ke...@Golux.Com>',
+                     $subject,
+                     $message);
+                return RLIB_DB_INTERNAL_ERROR;
+            }
+        }
+        foreach ($this->_record as $k => $v) {
+            if (! isset($dbo->_db[$this->_db_tablename][$k])) {
+                continue;
+            }
+            $sql .= $joiner
+                . " $k = '"
+                . AddSlashes($v)
+                . "'";
+            $joiner = ',';
+            if ($updating
+                && is_array($this->_changed)
+                && array_key_exists($k, $this->_changed)) {
+                $last = $this->_changed[$k];
+                if (! preg_match('/[\r\n]\s*$/', $last)) {
+                    $last .= "\n";
+                }
+                $message .= "  [$k]:\n    old: $last    new: $v";
+            }
+            else {
+                $message .= "  [$k]:\n         $v";
+            }
+            if (! preg_match('/[\r\n]\s*$/', $v)) {
+                $message .= "\n";
+            }
+        }
+        if (! $this->isNew()) {
+            $sql .= ' WHERE '
+                . $this->where($this->_id_fieldname,
+                               $this->get($this->_id_fieldname));
+        }
+        $q = $this->query($sql);
+        if ($this->errno()) {
+            $message = "\nOperation failed:\n"
+                . '  error=[' . $this->errno() . "] '"
+                . $this->errstr() . "'\n"
+                . "    SQL='" . $this->_sql . "'\n\n"
+                . $message;
+            $subject = $this->_dbo->_dbname . ": DB FAILURE: $subject";
+            mail('Rodent of Unusual Size <Ke...@Golux.Com>',
+                 $subject,
+                 $message);
+            return false;
+        }
+        $this->isDirty(false);
+        if ($this->isNew()) {
+            $this->isNew(false);
+            if ((! isset($altkey)) || $altkey) {
+                $id = mysql_insert_id($this->_dbc);
+                if (! isset($altkey)) {
+                    $altkey = $this->_id_fieldname;
+                }
+                $this->_record[$altkey] = $id;
+            }
+        }
+        if ($do_post) {
+            $this->postcommit();
+        }
+        if (! $this->cget('nolog')) {
+            $subject .= strtoupper($this->_db_tablename) . ' ';
+            $more_id = $this->commit_subject();
+            if ($more_id) {
+                $subject .= $more_id;
+            }
+            else {
+                if ($name = $this->get('name')) {
+                    $subject .= "'$name' ";
+                }
+                $subject .= '('
+                    . $altkey
+                    . ' '
+                    . $this->get($altkey)
+                    . ')';
+            }
+            if (method_exists($this->_dbo, 'log')) {
+                if ($o_log = $this->_dbo->logger()) {
+                    $o_log->set('subject', $subject);
+                    $o_log->set('message', $message);
+                    $o_log->set('action', ($updating ? 'UPDATE' : 'INSERT'));
+                    $o_log->set('type', strtoupper($this->_db_tablename));
+                    $o_log->commit();
+                    $subject = $this->_dbo->_dbname . ": $subject";
+                    mail('Rodent of Unusual Size <Ke...@Golux.Com>',
+                         $subject,
+                         $message);
+                }
+            }
+        }
+        return $this->_record[$this->_id_fieldname];
+    }
+
+    /*
+     * Method for generating info for the commit message subject line.
+     * Other classes get to supercede it.
+     */
+    function commit_subject() {
+        return null;
+    }
+
+    /*
+     * Method for doing any just-in-time things or checks before committing. 
+     * Other classes get to supercede it.  If it returns null, the
+     * commit proceeds, otherwise it's aborted and the return value is
+     * treated as the reason.
+     */
+    function precommit() {
+        return null;
+    }
+
+    /*
+     * Method for handling anything needing to be done after a record
+     * is committed, such as conditionally altering fields.
+     * Other classes get to supercede it.  The return value is meaningless,
+     * since the record has already been committed.
+     */
+    function postcommit() {
+        return null;
+    }
+
+    /*
+     * Method (doesn't really need to be a method, but o well) to
+     * add a condition segment to a MySQL WHERE clause.
+     */
+    function where($field, $value, $comp='=', $verbatim=false) {
+        $clause = '';
+        if (isset($value)) {
+            if ($verbatim) {
+                $clause = "($field $comp $value) ";
+            }
+            else {
+                $clause = "($field $comp '" . AddSlashes($value) . "') ";
+            }
+        }
+        return $clause;
+    }
+
+}
+
+/*
+ * Class for manipulating strings (primarily for XML).
+ *
+ * Either specify the string at object creation, or use $o->parse(string)
+ *
+ */
+define('XML_ENTITIES', 2);
+
+class rlib_String {
+
+    var $_base;
+    var $_xltable;
+    var $_xmltable;
+    var $_encoding;
+    var $_tokens;
+
+    /*
+     * Constructor.  Takes the basic string, the encoding (only UTF-8
+     * and ISO-8859-1 are currently supported), and the entity encoding
+     * table (such as to turn '<' into '&lt;').
+     *
+     * The encoding table can either be an actual character->entity
+     * table of strings, or the special constants HTML_ENTITIES (defined
+     * by PHP) or XML_ENTITIES (defined by this library).
+     *
+     */
+    function rlib_String($base=null, $encoding=null, $table=null) {
+        return $this->parse($base, $encoding, $table);
+    }
+
+    /*
+     * Real constructor-type function, for loading a new string.
+     */
+    function parse($base=null, $encoding=null, $table=null) {
+        if (isset($base)) {
+            $this->_base = $base;
+        }
+        else {
+            return null;
+        }
+        /*
+         * If we weren't given an explicit encoding, try to determine
+         * it.
+         */
+        if (! isset($encoding)) {
+            $encoding = $this->is_UTF8() ? 'UTF-8' : 'ISO-8859-1';
+        }
+        $this->_encoding = strtoupper($encoding);
+        /*
+         * By default, no entity encoding table.
+         */
+        if (isset($table)) {
+            $this->install_table($table);
+        }
+    }
+
+    /*
+     * Try to return a safe truncation of the base string, 'safe'
+     * meaning no tags nor entities broken in the middle.  This
+     * doesn't protect against elements being broken, though;
+     * '<p>word word word</p>' might have the closing tag
+     * truncated off.
+     */
+    function truncate($maxlength, $encode=true, $ellipsis=null) {
+        /*
+         * Encode any entities if so directed.
+         */
+        if ($encode) {
+            $result = $this->encode();
+        }
+        else {
+            $result = $this->_base;
+        }
+        /*
+         * If it's already shorter, it doesn't need to be truncated.
+         */
+        if (strlen($result) <= $maxlength) {
+            return $result;
+        }
+        /*
+         * If we're supposed to add some indication of the truncation,
+         * subtract its length since it's not mutable.
+         */
+        if (isset($ellipsis) && is_string($ellipsis)) {
+            $maxlength -= strlen($ellipsis);
+            if ($maxlength < 1) {
+                return null;
+            }
+        }
+        /*
+         * If there are any atomic sequences, they should have been
+         * set by one of the other routines.
+         */
+        $tokens = $this->_tokens;
+        if (! isset($tokens)) {
+            $this->_tokens = array();
+        }
+        if ($this->is_UTF8()) {
+            /*
+             * Add the atomic byte-patterns so we don't break the
+             * encoding.
+             */
+            $tokens[] = '[\xC2-\xDF][\x80-\xBF]';
+            $tokens[] = '\xE0[\xA0-\xBF][\x80-\xBF]';
+            $tokens[] = '[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}';
+            $tokens[] = '\xED[\x80-\x9F][\x80-\xBF]';
+            $tokens[] = '\xF0[\x90-\xBF][\x80-\xBF]{2}';
+            $tokens[] = '[\xF1-\xF3][\x80-\xBF]{3}';
+            $tokens[] = '\xF4[\x80-\x8F][\x80-\xBF]{2}';
+        }
+        $atoms = '(\s+|[\r\n]+' . (count($tokens) ? '|' : '')
+            . implode('|', $tokens) . ')';
+        $pattern = '/^(.*)' . $atoms . '(.*)?$/xs';
+        /*
+         * Loop around removing atoms/words from the end until we are
+         * shorter than the specified length.
+         */
+        while (true) {
+            if (! preg_match($pattern, $result, $bits)) {
+                return null;
+            }
+            $result = $bits[1];
+            /*
+             * If the separator atom doesn't push us over the edge,
+             * include it again.
+             */
+            if ((strlen($result) + strlen($bits[2])) <= $maxlength) {
+                $result .= $bits[2];
+            }
+            if (strlen($result) <= $maxlength) {
+                break;
+            }
+        }
+        if ($ellipsis) {
+            $result .= $ellipsis;
+        }
+        return $result;
+    }
+
+    /*
+     * UTF-8 has a very rigourous definition that is amenable to regex
+     * parsing.  See if the base string matches.
+     */
+    function is_UTF8() {
+        if (! isset($this->_base)) {
+            return false;
+        }
+        $valid = '/^(?:'
+            // . '  [\09\0A\0D\x20-\x7E]'               // ASCII
+            . '  [\00-\x7F]'                         // ASCII
+            . '| [\xC2-\xDF][\x80-\xBF]'             // non-overlong 2-byte
+            . '|  \xE0[\xA0-\xBF][\x80-\xBF]'        // excluding overlongs
+            . '| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'  // straight 3-byte
+            . '|  \xED[\x80-\x9F][\x80-\xBF]'        // excluding surrogates
+            . '|  \xF0[\x90-\xBF][\x80-\xBF]{2}'     // planes 1-3
+            . '| [\xF1-\xF3][\x80-\xBF]{3}'          // planes 4-15
+            . '|  \xF4[\x80-\x8F][\x80-\xBF]{2}'     // plane 16
+            . ')*$/x';
+        return preg_match($valid, $this->_base);
+    }
+
+    /*
+     * Change the string to a different encoding.  Currently only
+     * UTF-8 and ISO-8859-1 are supported (partially because PHP
+     * has built-ins for doing the work).
+     */
+    function recode($encoding) {
+        $encoding = strtoupper($encoding);
+        if (! preg_match('/ISO-8859-1|UTF-8/i', $encoding)) {
+            return false;
+        }
+        if (! preg_match("/$encoding/i", $this->_encoding)) {
+            if (preg_match('/^UTF-8$/i', $encoding)) {
+                $this->_base = utf8_encode($this->_base);
+            }
+            else if (preg_match('/^ISO-8859-1$/i', $encoding)) {
+                $this->_base = utf8_decode($this->_base);
+            }
+        }
+        $this->_encoding = $encoding;
+        return true;
+    }
+
+    function as_String() {
+        return $this->_base;
+    }
+    
+    function xlate($xltable) {
+        if ((! isset($this->_base))
+            || (! is_array($xltable))) {
+            return $this->_base;
+        }
+        $output = '';
+        for ($i = 0; $i < strlen($this->_base); $i++) {
+            $chr = substr($this->_base, $i, 1);
+            $output .= (isset($xltable[$chr])
+                        ? $xltable[$chr]
+                        : $chr);
+        }
+        return $output;
+    }
+
+    /*
+     * Encode using the entity table.  Passing a true argument will
+     * cause the result to become the base string.
+     */
+    function encode($inplace=false) {
+        $table = $this->_xltable;
+        $result = $this->xlate($table);
+        if ($inplace) {
+            $this->parse($result);
+        }
+        return $result;
+    }
+
+    /*
+     * Decode using the entity table.  Passing a true argument will
+     * cause the result to become the base string.
+     */
+    function decode($inplace=false) {
+        $table = array_flip($this->_xltable);
+        $result = $this->xlate($table);
+        if ($inplace) {
+            $this->parse($result);
+        }
+        return $result;
+    }
+
+    function install_table($new_table) {
+        if ($new_table == HTML_ENTITIES) {
+            $new_table = get_html_translation_table(HTML_ENTITIES);
+            $this->_tokens = array('<[^>]*>',
+                                   '&[^;]*;');
+        }
+        else if ($new_table == XML_ENTITIES) {
+            $this->_tokens = array('<[^>]*>',
+                                   '&[^;]*;');
+            if (isset($this->_xmltable)) {
+                $new_table = $this->_xmltable;
+            }
+            else {
+                $new_table = array();
+                for ($i = 0; $i <= 0xFF; $i++) {
+                    if (($i == 0x09)
+                        || ($i == 0x0A)
+                        || ($i == 0x0D)
+                        || (($i >= 0x20)
+                            && ($i <= 0x7F))) {
+                        if (chr($i) == '<') {
+                            $new_table[chr($i)] = '&lt;';
+                        }
+                        else if (chr($i) == '>') {
+                            $new_table[chr($i)] = '&gt;';
+                        }
+                        else if (chr($i) == '&') {
+                            $new_table[chr($i)] = '&amp;';
+                        }
+                        continue;
+                    }
+                    if (($i < 0x7F)
+                        || (($i >= 0x7F) && ($i <= 0x84))
+                        || (($i >= 0x86) && ($i <= 0x9f))) {
+                        $new_table[chr($i)] = '*';
+                    }
+                    else {
+                        $new_table[chr($i)] = sprintf('&#x%02x;', $i);
+                    }
+                }
+                $this->_xmltable = $new_table;
+            }
+        }
+        if (! is_array($new_table)) {
+            $new_table = null;
+        }
+        $this->_xltable = $new_table;
+    }
+}
+
+/*
+ * Class for manipulating dates.
+ */
+class rlib_Date extends rlib__baseclass {
+
+    var $_time;
+    var $_offset;
+    var $_fraction = 0.0;
+    var $_decomposed;
+        
+    function rlib_Date($when=null) {
+        $tod = gettimeofday();
+        $this->_custom = array();
+        $this->_offset = - $tod['minuteswest'];
+        $this->parse($when);
+    }
+
+    function parse($when=null) {
+        $fraction = null;
+        if (! isset($when)) {
+            $when = time();
+        }
+        if (is_int($when)) {
+            $this->_time = $when;
+        }
+        else {
+            $time = $this->parse_ICal($when);
+            if (! isset($time)) {
+                $time = $this->parse_ISO8601($when);
+            }
+            $fmt_date = '(\d{4})[-:](\d{2})[-:](\d{2})';
+            $fmt_time = '(\d{2}):(\d{2}):(\d{2})';
+            $fmt_offset = '([-+])(\d{2}):(\d{2})';
+            $fmt = '/^' . $fmt_date . '$/';
+            if ((! isset($time)) && preg_match($fmt, $when, $bits)
+                && ($bits[1] > 1900)) {
+                /*
+                 * Database date format: yyyy-mm-dd
+                 */
+                $time = mktime(0, 0, 0,
+                               (int)$bits[2], (int)$bits[3], (int)$bits[1]);
+            }
+            $fmt = '/^' . $fmt_date . ' ' . $fmt_time . '$/';
+            if ((! isset($time)) && preg_match($fmt, $when, $bits)
+                && ($bits[1] > 1900)) {
+                /*
+                 * Database date format: yyyy-mm-dd hh:mm:ss
+                 */
+                $time = mktime((int)$bits[4], (int)$bits[5], (int)$bits[6],
+                               (int)$bits[2], (int)$bits[3], (int)$bits[1]);
+            }
+            $fmt_date = '(\d{4})(\d{2})(\d{2})';
+            $fmt_time = '(\d{2})(\d{2})(\d{2})';
+            $fmt = '/^' . $fmt_date . $fmt_time . '$/';
+            if ((! isset($time)) && preg_match($fmt, $when, $bits)
+                && ($bits[1] > 1900)) {
+                /*
+                 * Database timestamp format: yyyymmddhhmmss
+                 */
+                $time = mktime((int)$bits[4], (int)$bits[5], (int)$bits[6],
+                               (int)$bits[2], (int)$bits[3], (int)$bits[1]);
+            }
+            if (! isset($time)) {
+                $time = strtotime($when);
+            }
+            if (is_array($time)) {
+                $fraction = $time[1];
+                $time = $time[0];
+            }
+            if ($time >= 0) {
+                $this->_time = $time;
+            }
+            if (isset($fraction)) {
+                $this->_fraction = $fraction;
+            }
+        }
+        $this->_decomposed = getdate($this->_time);
+    }
+
+    function parse_ICal($when) {
+        /*
+         * iCalendar (RFC 2445) format:
+         *
+         * yyyymmddThhmm
+         * yyyymmddThhmmss
+         * yyyymmddThhmmZ
+         * yyyymmddThhmmssZ
+         * yyyymmddThhmm{+,-}hhmm
+         * yyyymmddThhmmss{+,-}hhmm
+         */
+        $hour = $minute = $second = $day = $month = $year = 0;
+        $fmt_date = '(\d{4})(\d{2})(\d{2})';
+        $fmt_time = '(\d{2})(\d{2})';
+        $fmt = '/^' . $fmt_date . 'T' . $fmt_time . '/';
+        if (! preg_match($fmt, $when, $bits)) {
+            /*
+             * If the date doesn't match the required part of the format,
+             * don't bother looking any further.
+             */
+            return null;
+        }
+        $year = $bits[1];
+        $month = $bits[2];
+        $day = $bits[3];
+        $hour = $bits[4];
+        $minute = $bits[5];
+        $fmt_base = '/^\d{8}T\d{4}';
+        /*
+         * Find out which (if any) suffix pattern matches:
+         *
+         * $
+         * Z$
+         * [+-]\d{4}$
+         * ss$
+         * ssZ$
+         * ss[+-]\d{4}$
+         */
+        if (preg_match($fmt_base . '(\d{2})?$/', $when, $bits)) {
+            /*
+             * Local time.
+             */
+            $offset = $this->_offset;
+            $seconds = (isset($bits[1]) && $bits[1] ? $bits[1] : 0);
+        }
+        else if (preg_match($fmt_base . '(\d{2})?Z$/', $when, $bits)) {
+            /*
+             * UTC (Zulu) time.
+             */
+            $offset = 0;
+            $seconds = (isset($bits[1]) && $bits[1] ? $bits[1] : 0);
+        }
+        else if (preg_match($fmt_base . '(\d{2})?([-+])(\d{2})(\d{2})$/',
+                            $when, $bits)) {
+            /*
+             * Specific offset.
+             */
+            $offset = ($bits[3] * 60) + $bits[4];
+            if ($bits[2] == '-') {
+                if ($offset == 0) {
+                    /*
+                     * '-0000' is specifically disallowed.
+                     */
+                    return null;
+                }
+                $offset = - $offset;
+            }
+        }
+        else {
+            /*
+             * Didn't match any of our formats..
+             */
+            return null;
+        }
+        $time = mktime($hour, $minute, $second, $month, $day, $year);
+        $offset -= $this->_offset;
+        $time += ($offset * 60);
+        return array($time, 0);
+    }
+
+    function parse_ISO8601($when) {
+        /*
+         * ISO 8601 formats:
+         *
+         * Date:
+         *  yyyy-mm[-dd]
+         *  yyyymm[dd]
+         *  yymmdd
+         *  yyyy
+         *
+         * Week[day]:
+         *  yyyy[-]Www
+         *  yyyy-Www-d
+         *  yyyyWwwd
+         *  yyWww
+         *  yyyy[-]jjj
+         *
+         * Time
+         *  hh[:mm[:ss[.f+]]][[-+]hh[:mm]]
+         *  hh[mm[ss[.f+]]][[-+]hh[mm]]
+         *
+         * Date + time:
+         *  yyyy[-]mm[-]dd[T ]hh[[:]mm[[:]ss
+         *  date[ T]time
+         */
+        $hour = $minute = $second = $day = $month = $year = 0;
+        $offset = $fraction = 0;
+        /*
+         * Date formats:
+         *  [yy]yy[:]mm[:]dd{T, }
+         */
+        $fmt1 = '/^(\d{2})?(\d{2})(\d{2})(\d{2})[T ]/';
+        $fmt2 = '/^(\d{2})?(\d{2})-(\d{2})-(\d{2})[T ]/';
+        
+        if (preg_match($fmt1, $when, $bits)
+            || preg_match($fmt2, $when, $bits)) {
+            $month = (int)$bits[3];
+            $day = (int)$bits[4];
+            $year = (int)$bits[2];
+            if ($bits[1]) {
+                $year += (int)$bits[1] * 100;
+            }
+            else {
+                $year += 2000;
+            }
+        }
+        else {
+            /*
+             * Didn't start with a date we understood.
+             */
+            return null;
+        }
+        /*
+         * Since the date is variable (those optional hyphens), just
+         * strip off the actual value so all that's left is the time.
+         */
+        $when = preg_replace('/^' . $bits[0] . '/', '', $when);
+        /*
+         * Now let's take care of any timezone offset.
+         */
+        if (preg_match('/Z$/', $when)) {
+            $offset = 0;
+            $when = substr($when, 0, strlen($when) - 1);
+        }
+        else if (preg_match('/([-+])(\d{2})(\d{2})$/', $when, $bits)) {
+            $offset = ((int)$bits[2] * 60) + (int)$bits[3];
+            if ($bits[1] == '-') {
+                $offset *= -1;
+            }
+            $when = preg_replace('/' . $bits[0] . '$/', '', $when);
+        }
+        else {
+            /*
+             * No UTC offset, so assume local zone.
+             */
+            $offset = $this->_offset;
+        }
+        /*
+         * What we have now is just the time.  It can look like any of
+         * these:
+         *  hh | hhmm  | hhmmss   | hhmmss.f+
+         *     | hh:mm | hh:mm:ss | hh:mm:ss.f+
+         */
+        if (preg_match('/^(\d{2})$/', $when, $bits)) {
+            /*
+             * Hour only.
+             */
+            $hour = (int)$bits[1];
+        }
+        else if (preg_match('/^(\d{2})[:]?(\d{2})$/', $when, $bits)) {
+            /*
+             * Hour and minute.
+             */
+            $hour = (int)$bits[1];
+            $minute = (int)$bits[2];
+        }
+        else if (preg_match('/^(\d{2}):(\d{2}):(\d{2})(\.\d+)?$/',
+                            $when, $bits)
+                 || preg_match('/^(\d{2})(\d{2})(\d{2})(\.\d+)?$/',
+                               $when, $bits)) {
+            /*
+             * Full time, including optional fractional second.
+             */
+            $hour = (int)$bits[1];
+            $minute = (int)$bits[2];
+            $second = (int)$bits[3];
+            if (isset($bits[4]) && $bits[4]) {
+                $fraction = (float)$bits[4];
+            }
+        }
+        else {
+            /*
+             * Nope, don't get this format.
+             */
+            return null;
+        }
+        $time = mktime($hour, $minute, $second, $month, $day, $year);
+        $offset -= $this->_offset;
+        $time += ($offset * 60);
+        return array($time, $fraction);
+    }
+
+    function year() {
+        return (int) $this->_decomposed['year'];
+    }
+    function month() {
+        return (int) $this->_decomposed['mon'];
+    }
+    function day() {
+        return (int) $this->_decomposed['mday'];
+    }
+    function wday() {
+        return (int) $this->_decomposed['wday'];
+    }
+    function jday() {
+        return (int) $this->_decomposed['yday'];
+    }
+    function jdn() {
+        return (int) UnixToJD($this->_time);
+    }
+
+    /*
+     * Round up a fractional second to the specified precision, and make
+     * any other necessary adjustments (such as to UTC).
+     */
+    function adjust($places) {
+        if (isset($this->_time)) {
+            $time = $this->_time;
+        }
+        else {
+            $time = 0;
+        }
+        $offset = $this->_offset;
+        $fraction = $this->_fraction;
+        if ($this->raised('utc') && $offset) {
+            $time -= ($offset * 60);
+            $offset = 0;
+        }
+        $fraction = (float)sprintf("%.${places}f", $fraction);
+        if ($fraction >= 1.0) {
+            $time++;
+            $fraction = 0.0;
+        }
+        return array($time, $fraction, $offset);
+    }
+
+    /*
+     * Format as an ISO 8601 date string. (yyyy-mm-ddThh:mm:ss[.f+][+-]hh:mm)
+     */
+    function as_ISO8601() {
+        list($time, $fraction, $offset) = $this->adjust(5);
+        $h = abs(floor($offset / 60));
+        $m = abs($offset) - ($h * 60);
+        $result = strftime('%Y-%m-%dT%H:%M:%S', $time);
+        if (isset($fraction) && $fraction) {
+            $fraction = sprintf('%.5f', $fraction);
+            $fraction = preg_replace('/^\d+(\.\d+)$/', '$1', $fraction);
+            $result .= $fraction;
+        }
+        if (($offset == 0) && $this->raised('Z')) {
+            $result .= 'Z';
+        }
+        else {
+            $result = sprintf('%s%s%02d:%02d',
+                              $result,
+                              ($offset < 0 ? '-' : '+'),
+                              $h,
+                              $m);
+        }
+        return $result;
+    }
+
+    /*
+     * Format as an RFC 2445 (ICalendar) date/time string.
+     * (yyyymmddThh:mm:ss[+-]hh:mm)
+     */
+    function as_ICal($dateonly=false) {
+        list($time, $fraction, $offset) = $this->adjust(0);
+        $h = abs(floor($offset / 60));
+        $m = abs($offset) - ($h * 60);
+        $result = strftime('%Y%m%d', $time);
+        if ($dateonly) {
+            return $result;
+        }
+        $result .= strftime('T%H%M%S', $time);
+        if (($offset == 0) && $this->raised('z')) {
+            $result .= 'Z';
+        }
+        else {
+            $result = sprintf('%s%s%02d%02d',
+                              $result,
+                              ($this->_offset < 0 ? '-' : '+'),
+                              $h,
+                              $m);
+        }
+        return $result;
+    }
+
+    /*
+     * Return the Unixish seconds-since-the-epoch count.
+     */
+    function as_localtime() {
+        list($time) = $this->adjust(0);
+        return $time;
+    }
+
+    /*
+     * Year-major date (yyyy-mm-dd)
+     */
+    function as_dbdate() {
+        list($time) = $this->adjust(0);
+        return strftime('%Y-%m-%d', $time);
+    }
+
+    /*
+     * Sortable date/time (yyyy-mm-dd hh:mm:ss)
+     */
+    function as_dbdatetime() {
+        list($time) = $this->adjust(0);
+        return strftime('%Y-%m-%d %H:%M:%S', $time);
+    }
+
+    /*
+     * Time only (hh:mm:ss)
+     */
+    function as_dbtime() {
+        list($time) = $this->adjust(0);
+        return strftime('%H:%M:%S', $time);
+    }
+
+    /*
+     * Time as an 'integer' string, as used by MySQL for timestamps
+     * (yyyymmddhhmmss)
+     */
+    function as_dbtimestamp() {
+        list($time) = $this->adjust(0);
+        return strftime('%Y%m%d%H%M%S', $time);
+    }
+
+    function as_arbitrary($fmt) {
+        return strftime($fmt, $this->_time);
+    }
+}
+
+/*
+ * Class for parsing, constructing, and otherwise manipulating URI
+ * strings.
+ */
+class rlib_URI extends rlib__baseclass {
+
+    var $_args;
+    var $_getargs;
+    var $_postargs;
+    var $_method;
+    var $_defaulturi;
+    var $_defaultobj;
+
+    /*
+     * Constructor.
+     */
+    function rlib_URI($uri=null, $flags=null) {
+        $this->_args = array();
+        $this->_getargs = array();
+        $this->_postargs = array();
+        $this->delimiter('&');
+        $this->decode($uri, $flags);
+    }
+
+    function args() {
+        return $this->_args;
+    }
+
+    function arg($param, $newval=null) {
+        if (isset($this->_args[$param])) {
+            $oldval = $this->_args[$param];
+        }
+        else {
+            $oldval = null;
+        }
+        if (isset($newval)) {
+            $this->_args[$param] = $newval;
+        }
+        return $oldval;
+    }
+
+    function args2qstring() {
+        $qstring = '';
+        if (! is_array($this->_args)) {
+            return;
+        }
+        foreach ($this->_args as $k => $v) {
+            $qstring .= (($qstring ? $this->delimiter() : '')
+                         . URLencode($k)
+                         . '='
+                         . URLencode($v));
+        }
+        $this->qstring($qstring);
+    }
+
+    /*
+     * Copy any POST arguments to the array originally populated from
+     * the GET arguments in the URL.  $merge=true will cause them to
+     * be merged, with POST values overriding any GET values with the
+     * same name.
+     */
+    function post2get($merge=false) {
+        if (! $merge) {
+            $this->_args = $this->_postargs;
+        }
+        else {
+            $this->_args = array_merge($this->_args, $this->_postargs);
+        }
+    }
+
+    function delimiter($char=null) {
+        $old = $this->flag('delimiter');
+        if (isset($char)) {
+            $this->flags(array('delimiter' => substr($char, 0, 1)));
+        }
+        return $old;
+    }
+    
+    /*
+     * Set processing options.  Takes an array of key/value pairs.
+     */
+    function flags($flags=null) {
+        if ((! isset($flags)) || (! is_array($flags))) {
+            return;
+        }
+        foreach ($flags as $k => $v) {
+            $this->cset($k, $v);
+        }
+    }
+
+    function flag($k) {
+        return $this->cget($k);
+    }
+
+    function decode($uri=null, $flags=null) {
+        if (isset($flags)) {
+            $this->creset(null);
+            $this->flags($flags);
+        }
+        if ($this->raised('debug')) {
+            print "parsing $uri\n";
+        }
+        if (! $this->flag('inplace')) {
+            $this->reset(null);
+        }
+        if (isset($uri)) {
+            if (($uri == 'default') || ($uri == RLIB_THIS_URI)) {
+                $this->_method = (isset($_SERVER['REQUEST_METHOD'])
+                                  ? $_SERVER['REQUEST_METHOD']
+                                  : '');
+                $host = (isset($_SERVER['HTTP_HOST'])
+                         ? $_SERVER['HTTP_HOST']
+                         : '');
+                $port = (isset($_SERVER['SERVER_PORT'])
+                         ? $_SERVER['SERVER_PORT']
+                         : '');
+                $turi = (isset($_SERVER['REQUEST_URI'])
+                         ? $_SERVER['REQUEST_URI']
+                         : '');
+                $uri = 'http://'
+                    . preg_replace('/:\d+$/', '', $host)
+                    . ((! $port) || ($port == '80')
+                       ? ''
+                       : ":$port")
+                    . $turi;
+                if (isset($_SERVER['PATH_INFO'])) {
+                    $this->set('path_info', $_SERVER['PATH_INFO']);
+                }
+                $ctype = (isset($_SERVER['HTTP_CONTENT_TYPE'])
+                          ? $_SERVER['HTTP_CONTENT_TYPE']
+                          : '');
+                if (($this->_method == 'POST')
+                    && ($ctype == 'application/x-www-form-urlencoded')) {
+                    $this->_postargs = array();
+                    foreach ($_POST as $k => $v) {
+                        $this->_postargs[$k] = $v;
+                    }
+                }
+            }
+            /*
+             * Regex from RFC 2396, modified somewhat to separate out the port.
+             *
+             * [0] => http://www.ics.uci.edu:339/pub/ietf/uri/foo?bar#Related
+             * [1] => http:
+             * [2] => http
+             * [3] => //www.ics.uci.edu:339
+             * [4] => www.ics.uci.edu:339
+             * [5] => www.ics.uci.edu
+             * [6] => 339
+             * [7] => /pub/ietf/uri/foo
+             * [8] => ?bar
+             * [9] => bar
+             * [10] => #Related
+             * [11] => Related
+             */
+            $x = '^(([^:/?#]+):)?(//(([^/:_?#]*)(?::([0-9]+))?))?'
+                . '([^?#]*)(\?([^#]*))?(#(.*))?';
+            $y = preg_match('§' . $x . '§', $uri, $bits);
+            $this->set('raw', $bits[0]);
+            if (isset($bits[2])) {
+                $this->set('scheme', $bits[2]);
+            }
+            if (isset($bits[5])) {
+                $this->set('host', $bits[5]);
+            }
+            if (isset($bits[6])) {
+                $this->set('port', $bits[6]);
+            }
+            $path = $bits[7];
+            if ($path) {
+                $this->set('path', $path, false);
+            }
+            if (isset($bits[9])) {
+                $this->set('qstring', $bits[9]);
+                $this->splitargs();
+            }
+            if (isset($bits[10])) {
+                $this->set('fragment', $bits[10]);
+            }
+
+            /*
+             * Now look for our own stuff.
+             */
+            $this->normalise_path(false);
+        }
+    }
+
+    function method() {
+        return $this->_method;
+    }
+    function scheme($newval=null) {
+        return $this->frobfield('scheme', $newval);
+    }
+    function net_path($newval=null) {
+        return $this->frobfield('net_path', $newval);
+    }
+    function netpath($newval=null) {
+        return $this->net_path($newval);
+    }
+    function port($newval=null) {
+        return $this->frobfield('port', $newval);
+    }
+    function path($newval=null) {
+        return $this->frobfield('path', $newval);
+    }
+    function pathto($newval=null) {
+        return $this->frobfield('pathto', $newval);
+    }
+    function name($newval=null) {
+        return $this->frobfield('name', $newval);
+    }
+    function path_info($newval=null) {
+        return $this->frobfield('path_info', $newval);
+    }
+    function qstring($newval=null) {
+        return $this->frobfield('qstring', $newval);
+    }
+    function fragment($newval=null) {
+        return $this->frobfield('fragment', $newval);
+    }
+
+    function encode($flags=null) {
+        if ($this->raised('debug')) {
+            print "<!-- encode entry:\n" . $this->as_string() . "-->\n";
+        }
+        $this->flags($flags);
+        $defaults = false;
+        $result = '';
+        if (isset($this->_defaulturi)) {
+            $o_default = $this->_defaultobj;
+            $defaults = true;
+        }
+        if (($scheme = $this->scheme())
+            || ($defaults && ($scheme = $o_default->scheme()))) {
+            $result .= "$scheme:";
+        }
+        if (($net_path = $this->net_path())
+            || ($defaults && ($net_path = $o_default->net_path()))) {
+            $result .= $net_path;
+        }
+        if (($port = $this->port())
+            || ($defaults && ($port = $o_default->port()))) {
+            $result .= ":$port";
+        }
+        if (($pathto = $this->pathto())
+            || ($defaults && ($pathto = $o_default->pathto()))) {
+            $result .= $pathto;
+        }
+        if (($name = $this->name())
+            || ($defaults && ($name = $o_default->name()))) {
+            $result .= $name;
+        }
+        if (($pi = $this->path_info())
+            || ($defaults && ($pi = $o_default->path_info()))) {
+            $path .= $pi;
+        }
+        
+        if (($qstring = $this->qstring())
+            || ($defaults && ($qstring = $o_default->qstring()))) {
+            $result .= "?$qstring";
+        }
+        if (($frag = $this->fragment())
+            || ($defaults && ($frag = $o_default->fragment()))) {
+            $result .= $frag;
+        }
+        return $result;
+    }
+
+    function set_default($uri=null) {
+        $this->_defaulturi = $uri;
+        if (! isset($uri)) {
+            $this->_defaultobj = null;
+        }
+        else {
+            $this->_defaultobj = new rlib_URI($this->_defaulturi);
+        }
+    }
+
+    function parse($uri=null, $flags=null) {
+        return $this->decode($uri, $flags);
+    }
+
+    /*
+     * Deal with the path as distinct components.  Splitpath() and joinpath()
+     * should be called appropriately any time the path, pathto, name, or
+     * path-info elements are changed.
+     */
+    function splitargs() {
+        $this->_getargs = array();
+        if ($qs = $this->qstring()) {
+            $qs = str_replace('&amp;', "\001", $qs);
+            $a_qs = preg_split('/[&;]/', $qs);
+            foreach ($a_qs as $arg) {
+                if (strstr($arg, '=')) {
+                    list($k, $v) = explode('=', $arg, 2);
+                }
+                else {
+                    $k = $arg;
+                    $v = null;
+                }
+                $k = str_replace("\001", '&amp;', $k);
+                if (isset($v)) {
+                    $v = str_replace("\001", '&amp;', $v);
+                }
+                $this->_getargs[$k] = $v;
+            }
+        }
+        $this->_args = $this->_getargs;
+    }
+
+    function joinargs() {
+        $a_qs = array();
+        foreach ($this->_args as $k => $v) {
+            if (isset($v)) {
+                $a_qs[] = "$k=$v";
+            }
+            else {
+                $a_qs[] = $k;
+            }
+        }
+        $this->qstring(implode($this->delimiter(), $a_qs));
+    }
+
+    function set_arg($argname, $argval=null) {
+        $this->_args[$argname] = $argval;
+        $this->joinargs();
+    }
+
+    function unset_arg($argname) {
+        $oldval = null;
+        if (isset($this->_args[$argname])) {
+            $oldval = $this->_args[$argname];
+            unset($this->_args[$argname]);
+        }
+        return $oldval;
+    }
+
+    function normalise_path($join=false) {
+        if ($join) {
+            /*
+             * The components have been updated piecemeal, and we need
+             * to construct the full path (now stale) from them.
+             */
+            $path = '';
+            $fillin = (isset($this->_defaultobj)
+                       && is_a($this->_defaultobj, 'rlib_URI'));
+            $o_def = $this->_defaultobj;
+            if (($pathto = $this->pathto())
+                || ($fillin && ($pathto = $o_def->pathto()))) {
+                $path .= $pathto;
+            }
+            if (($name = $this->name())
+                || ($fillin && ($name = $o_def->name()))) {
+                $path .= ($path ? '' : '/') . $name;
+            }
+            $this->set('path', $path, false);
+        }
+        else {
+            /*
+             * The path element needs to be broken into its components.
+             */
+            $path = $this->path();
+            $pi = $this->path_info();
+            $re = '§' . '(.*/)?([^/]*)';
+            if ($pi && preg_match('§\Q' . $pi . '\E$§', $path)) {
+                $re .= '\Q' . $pi . '\E';
+            }
+            $re .= '$§';
+            preg_match($re, $path, $pbits);
+            $this->set('pathto', $pbits[1], false);
+            $this->set('name', $pbits[2], false);
+        }
+    }
+
+    function get($fname) {
+        if (is_string($fname) && preg_match('/^net_path$/i', $fname)) {
+            if ($val = $this->get('host')) {
+                $val = "//$val";
+            }
+            return $val;
+        }
+        return parent::get($fname);
+    }
+        
+            
+    /*
+     * We have some special handling needs for set()..
+     */
+    function set($fname, $val=null, $recurse=true) {
+        if (is_string($fname) && preg_match('/^net_path$/i', $fname)) {
+            if (is_string($val) && (substr($val, 0, 2) == '//')) {
+                $val = preg_replace(':^//:', '', $val);
+            }
+            $this->set('host', $val);
+        }
+        else if (is_string($fname)
+            && $recurse
+            && preg_match('/^qstring$/i', $fname)) {
+            $this->set('qstring', $val, false);
+            $this->splitargs();
+        }
+        else if (is_string($fname)
+            && $recurse
+            && preg_match('/^(?:pathto|name|path_info|path)$/i', $fname)) {
+            /*
+             * If we're being called normally, and the field is one of the
+             * ones used to construct the path segment (i.e., pathto,
+             * name, and/or path-info), treat it specially in order to
+             * build the path segment field appropriately.
+             */
+            $sname = $this->nkey($fname);
+            if ($sname == 'path_info') {
+                if (substr($val, 0, 1) != '/') {
+                    $val = '/' . $val;
+                }
+                $oldval = $this->set($fname, $val, false);
+            }
+            else if ($sname == 'pathto') {
+                if (substr($val, 0, 1) != '/') {
+                    $val = '/' . $val;
+                }
+                if (substr($val, -1, 1) != '/') {
+                    $val .= '/';
+                }
+                $oldval = $this->set($fname, $val, false);
+                $this->normalise_path(true);
+            }
+            else if ($sname == 'name') {
+                $oldval = $this->set($fname, $val, false);
+                $this->normalise_path(true);
+            }
+            else if ($sname == 'path') {
+                /*
+                 * If it's the path segment field itself, and it doesn't
+                 * begin with a path separator, treat it as a relative
+                 * value by storing is as the name field instead.
+                 */
+                $oldval = $this->get($fname);
+                if (substr($val, 0, 1) != '/') {
+                    $this->set('name', $val, false);
+                    $this->normalise_path(true);
+                }
+                else {
+                    $this->set('pathto', '', false);
+                    $this->set('name', '', false);
+                    $this->set($fname, $val, false);
+                    $this->normalise_path(false);
+                }
+            }
+            return $oldval;
+        }
+        return parent::set($fname, $val);
+    }
+}
+
+
+/*
+ * Local Variables:
+ * mode: C
+ * c-file-style: "bsd"
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
+?>

Propchange: labs/pulse/report-web/util-classes.php
------------------------------------------------------------------------------
    svn:executable = *



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@labs.apache.org
For additional commands, e-mail: commits-help@labs.apache.org