__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 ', $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 ', $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 ', $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 ', $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 ', $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; * '

word word word

' 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 "\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: */ ?>