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 '<').
+ *
+ * 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)] = '<';
+ }
+ else if (chr($i) == '>') {
+ $new_table[chr($i)] = '>';
+ }
+ else if (chr($i) == '&') {
+ $new_table[chr($i)] = '&';
+ }
+ 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('&', "\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", '&', $k);
+ if (isset($v)) {
+ $v = str_replace("\001", '&', $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