_new ? true : false); if (isset($newval)) { $this->_new = ($newval ? true : false); } return $oldval; } public 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); } /* * Return a value from the object. */ function get($fname) { $fname = $this->nkey($fname); return (isset($this->_record[$fname]) ? $this->_record[$fname] : null); } /* * Return a non-db-backed value from the object. */ function cget($fname) { $fname = $this->nkey($fname); return (isset($this->_custom[$fname]) ? $this->_custom[$fname] : null); } /* * Catchall for fetching properties. */ public function __get($pname) { $pname = $this->nkey($pname); if (isset($this->_record[$pname])) { return $this->_record[$pname]; } else if (isset($this->_custom[$pname])) { return $this->_custom[$pname]; } else if (isset($this->$pname)) { return $this->$pname; } return null; } /* * Set a cell value for the object. */ function set($fname, $val=null) { if (is_array($fname)) { $result = array(); foreach ($fname as $k => $v) { $oldval = $this->set($k, $v); $result[$k] = $oldval; } return $result; } $fname = $this->nkey($fname); $oldval = $this->get($fname); $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)); return $oldval; } /* * Catchall for fetching properties. */ public function __set($pname, $pval) { $pname = $this->nkey($pname); if (isset($this->_record[$pname])) { $this->_record[$pname] = $pval; } else if (isset($this->_custom[$pname])) { $this->_custom[$pname] = $pval; } else { $this->$pname = $pval; } return $pval; } /* * Clear a cell. */ function reset($key=null) { if (! isset($key)) { $this->_record = null; } else { unset($this->_record[$this->nkey($key)]); } } /* * Alias name that might be clearer */ function clear($key=null) { return $this->reset($key); } /* * Set a non-db-backed cell value 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->cget($fname); $this->_custom[$fname] = $val; return $oldval; } /* * Return a list of the fields in the record (debugging purposes). */ function fields() { return $this->_record; } /* * Return the structure of the current object as a printable * string (á la print_r()) but with the option of avoiding * recursion. */ 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 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); } } /* * Try a class for the database stuff, to partition the db handle. */ class lsDB extends lsdb__baseclass { var $_dbo = 0; var $_dbc = 0; var $_dbh = 0; var $_errno = 0; var $_errstr = ''; var $_db_tablename; var $_id_fieldname; var $_id_display; var $_object_type; var $_sql; var $_db; var $_query; var $_key; var $_checksum; /* * Class constructor. */ public function __construct($host='localhost', $username=null, $pw=null) { $this->open($host, $username, $pw); $this->_dbo =& $this; $sql = 'SHOW TABLES'; $q = $this->query($sql); while ($q && ($row = mysql_fetch_row($q))) { list($table) = $row; $this->_db[] = $table; } foreach ($this->_db as $table) { $sql = "DESCRIBE $table"; $q = $this->query($sql); while ($q && ($row = mysql_fetch_row($q))) { $this->_db[$table][$row[0]] = $row[1]; } } $this->importdb($this->_dbo); } /* * Method (doesn't really need to be a method, but o well) to * add a condition segment to a MySQL WHERE clause. */ protected 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; } protected function id($field, $table, $addl=null) { $this->_id_fieldname = $field; $this->_db_tablename = $table; $this->_id_display = $addl; } public function is_valid_field($table, $field) { if (array_key_exists($table, $this->_dbo->_db) && array_key_exists($field, $this->_dbo->_db[$table])) { return true; } return false; } /* * 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. */ public function errno() { $this->_errno = mysql_errno($this->_dbc); return $this->_errno; } public function errstr() { $this->_errstr = mysql_error($this->_dbc); return $this->_errstr; } public function sql() { return $this->_sql; } /* * Load the latest values from the database. */ public function refresh($key=null) { if (! isset($key)) { $key = $this->_key; } $sql = 'SELECT * FROM ' . $this->_db_tablename . ' WHERE ' . $this->where($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. */ public 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(LSDB_INTERNAL_ERROR); } $tname = $this->_db_tablename; $fname = $this->_id_fieldname; $fval = $this->get($fname); if (! $fval) { $this->message('Object not identified'); return $this->error(LSDB_INTERNAL_ERROR); } $sql = "SELECT COUNT(*) FROM $tname WHERE " . $this->where($fname, $fval); $q = $this->query($sql); 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(LSDB_NOSUCH); } else if ($n > 1) { $this->message("'$ident' is an ambiguous record identifier"); return $this->error(LSDB_AMBIGUOUS); } $sql = "DELETE FROM $tname WHERE " . $this->where($fname, $fval); $subject = 'Deleted ' . strtoupper($this->_db_tablename) . ' record'; $more_id = $this->commit_subject(); $subject .= ($more_id ? " $more_id" : ''); $subject .= ' (' . $this->_id_fieldname . ' ' . $this->get($this->_id_fieldname) . ')'; $message = "\n"; $q = $this->query($sql); if (! $this->errno()) { $message .= "Operation succesful.\n"; mail('Rodent of Unusual Size ', "AcDB: $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 ', "AcDB: 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 * acDB class itself, but inherited). */ protected function importdb($dbo) { while (is_object($dbo) && (! is_a($dbo, 'lsDB'))) { $dbo =& $dbo->_dbo; } $this->_dbo =& $dbo; $this->_dbc = $dbo->_dbc; $this->_dbh = $dbo->_dbh; $this->_errno = $dbo->_errno; $this->_errstr = $dbo->_errstr; } /* * Close the connexion. Hardly necessary, but just for completeness.. */ public 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. */ public function open($host='localhost', $username=null, $pw=null) { if ($this->_dbc != 0) { return mysql_ping($this->_dbc); } if (! isset($username)) { $this->_dbc = mysql_pconnect($host); } else if (! isset($pw)) { $this->_dbc = mysql_pconnect($host, $username); } else { $this->_dbc = mysql_pconnect($host, $username, $pw); } if ($this->_dbc) { $this->_dbh = mysql_select_db($_SERVER['PULSE_DB_NAME']); } else { $this->_errno = -666; $this->_errstr = "unable to connect to database host '$host'"; return; } $this->setknownerrstate(); } /* * Function to issue a query inside the object. */ public function query($sql) { $this->_sql = $sql; $this->_query = mysql_query($sql, $this->_dbc); $this->setknownerrstate(); return $this->_query; } public function release($q=null) { $query = $q; $status = true; if (! isset($q)) { $query = $this->_query; } if (is_resource($query)) { $status = mysql_free_result($query); if ($status && (! isset($q))) { $this->_query = null; } } return $status; } /* * Set our error cells to a known state: either no error, stuckage, * or something from the last db action. */ public 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.. */ public function commit($altkey=null) { if (! $this->isDirty()) { return null; } if ($this->isNew()) { $sql = 'INSERT INTO '; $subject = 'New '; $updating = false; } else { $sql = 'UPDATE '; $subject = 'Updated '; $updating = true; } $subject .= strtoupper($this->_db_tablename) . ' record '; $more_id = $this->commit_subject(); if ($more_id) { $subject .= $more_id; } else { $subject .= $this->get($this->_id_fieldname); if (isset($this->_id_display)) { $subject .= '/' . $this->get($this->_id_display); } } $message = "\n"; $sql .= $this->_db_tablename . ' SET '; $joiner = ''; $dbo = $this->_dbo; 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)); } $abort = $this->precommit(); if ($abort) { $message = "\nCommit aborted by precommit function\n\n $abort\n"; $subject = "lsDB: ABORTED: $subject"; mail('Rodent of Unusual Size ', $subject, $message); return LSDB_INTERNAL_ERROR; } $q = $this->query($sql); if ($this->errno()) { $message = "\nOperation failed:\n" . ' error=[' . $this->errno() . "] '" . $this->errstr() . "'\n" . " SQL='" . $this->_sql . "'\n\n" . $message; $subject = "lsDB: 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; } $subject .= " ($altkey $id)"; } $subject = "lsDB: $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. */ public 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. */ public function precommit() { return null; } public function latest() { $sql = 'SELECT xid FROM loadinfo ORDER BY asof DESC LIMIT 1 '; $q = $this->query($sql); if ($q) { list($xid) = mysql_fetch_row($q); $o_asof = new lsLoadInfo($this->_dbo, $xid); return $o_asof->get('asof'); } return null; } public function summary($list, $from, $until=null) { return new lsMListSummary($this->_dbo, null, $list, $from, $until); } public function mlist($id=null) { return new lsMList($this->_dbo, $id); } public function infotext($id=null) { return new lsInfoText($this->_dbo, $id); } public function select($criteria=null, $order=null) { $o_selection = new lsSelection($this->_dbo, $criteria, $order); return $o_selection; } public function mlists($criteria=null, $order=null, $xids_only=false) { $o_selection = $this->select($criteria, $order); return ($xids_only ? $o_selection->xids() : $o_selection->mlists()); } public function domain($id=null) { return new lsDomain($this->_dbo, $id); } public function domains($criteria=null, $order=null) { $sql = 'SELECT DISTINCT domain FROM list '; if (isset($criteria) && is_array($criteria)) { $first = true; foreach ($criteria as $criterion => $value) { if ($this->is_valid_field('list', $criterion)) { $badwhere = false; $sql .= ($first ? ' WHERE ' : ' AND '); $first = false; if (is_array($value)) { $sql .= $this->where($criterion, $value[0], $value[1]); } else { $sql .= $this->where($criterion, $value); } } } } if (isset($order)) { if (is_string($order)) { $sql .= " ORDER BY $order"; } else if (is_array($order)) { $sql .= ' ORDER BY'; $first = true; foreach ($order as $k => $v) { $sql .= ($first ? '' : ', ') . " $k $v"; $first = false; } } } else { $sql .= ' ORDER BY domain ASC '; } $a_result = array(); $q = $this->query($sql); $lists = array('foo', 'bar'); while ($q && (list($domain) = mysql_fetch_row($q))) { $o_domain = new lsDomain($this->_dbo, $domain); $o_domain->lists = $lists; //preg_grep("/\@$domain\$/", $lists); $a_result[] = $o_domain; } return $a_result; } } class lsLoadInfo extends lsDB { public function __construct($dbo, $id=null) { $this->id('xid', 'loadinfo'); $this->importdb($dbo); if (! isset($id)) { $this->isNew(true); return; } if (is_numeric($id)) { $this->refresh((int)$id); return; } $sql = 'SELECT xid FROM loadinfo WHERE ' . $this->where('asof', $id); $q = $this->query($sql); if ($q && (mysql_num_rows($q) > 0)) { list($id) = mysql_fetch_row($q); $this->refresh((int)$id); return; } } public function mlists($criteria=null, $order=null, $xids_only=false) { $sql = 'SELECT xid FROM list WHERE ' . $this->where('asof', $this->get('asof')); if (isset($criteria) && is_array($criteria)) { foreach ($criteria as $criterion => $value) { if ($this->is_valid_field('list', $criterion)) { $badwhere = false; $sql .= ' AND '; if (is_array($value)) { $sql .= $this->where($criterion, $value[0], $value[1]); } else { $sql .= $this->where($criterion, $value); } } } } if (isset($order)) { if (is_string($order)) { $sql .= " ORDER BY $order"; } else if (is_array($order)) { $sql .= ' ORDER BY'; $first = true; foreach ($order as $k => $v) { $sql .= ($first ? '' : ', ') . " $k $v"; $first = false; } } } else { $sql .= ' ORDER BY domain ASC, list ASC '; } $a_result = array(); $q = $this->query($sql); while ($q && (list($xid) = mysql_fetch_row($q))) { $a_result[] = $xids_only ? $xid : $this->_dbo->mlist($xid); } return $a_result; } } class lsSelection extends lsDB { var $_criteria = ''; var $_order = ''; var $_xids; var $_mlists; public function __construct($dbo, $criteria=null, $order=null) { $this->importdb($dbo); $sql = 'SELECT xid FROM list '; if (isset($criteria)) { if (is_string($criteria)) { $this->_criteria = 'WHERE ' . $criteria; } else if (is_array($criteria)) { $first = true; foreach ($criteria as $criterion => $value) { if ($this->is_valid_field('list', $criterion)) { $badwhere = false; $this->_criteria .= ($first ? ' WHERE ' : ' AND '); $first = false; if (is_array($value)) { $this->_criteria .= $this->where($criterion, $value[0], $value[1]); } else { $this->_criteria .= $this->where($criterion, $value); } } } } } if (isset($order)) { if (is_string($order)) { $this->_order .= " ORDER BY $order"; } else if (is_array($order)) { $this->_order .= ' ORDER BY'; $first = true; foreach ($order as $k => $v) { $this->_order .= ($first ? '' : ', ') . " $k $v"; $first = false; } } } else { $this->_order .= ' ORDER BY asof DESC, domain ASC, list ASC '; } $sql = 'SELECT xid FROM list ' . $this->_criteria . $this->_order; $q = $this->query($sql); while ($q && (list($xid) = mysql_fetch_row($q))) { $this->_xids[] = $xid; $this->_mlists[] = $this->_dbo->mlist($xid); } } public function mlists() { return $this->_mlists; } public function xids() { return $this->_xids; } public function domains($countonly=false) { $dlist = array(); foreach ($this->_mlists as $o_mlist) { $dlist[$o_mlist->get('domain')] = true; } if ($countonly) { return count($dlist); } ksort($dlist); return array_keys($dlist); } public function domain_count() { return $this->domains(true); } public function total_posts() { $posts = 0; foreach ($this->_mlists as $o_mlist) { $n = $o_mlist->get('posts'); if ($n >= 0) { $posts += $n; } } return $posts; } public function mean_posts() { $days = $this->post_days(); if ($days) { return $this->total_posts() / $days; } return 0; } public function post_days() { $days = 0; foreach ($this->_mlists as $o_mlist) { if ($o_mlist->get('posts') >= 0) { $days++; } } return $days; } public function total_subscribers() { $subs = 0; foreach ($this->_mlists as $o_mlist) { $n = $o_mlist->get('count'); if ($n >= 0) { $subs += $n; } } return $subs; } public function total_digest_subscribers() { $subs = 0; foreach ($this->_mlists as $o_mlist) { $n = $o_mlist->get('dcount'); if ($n >= 0) { $subs += $n; } } return $subs; } } class lsMListSummary extends lsMList { public function __construct($dbo, $id=null, $list=null, $from=null, $until=null) { $this->id('xid', 'list_summary'); $this->importdb($dbo); if (! isset($id)) { $this->isNew(true); if (isset($list) && isset($from)) { $this->summarise($list, $from, $until); } return; } if (is_numeric($id)) { $this->refresh((int)$id); return; } } public function summarise($list, $begin, $end=null) { $from = new rlib_Date($begin); $until = new rlib_Date($end); $from = $from->as_dbdate(); $until = $until->as_dbdate(); if ($until <= $from) { return; } print "selecting\n"; $criteria = $this->where('list', $list) . ' AND ' . $this->where('asof', $from, '>=') . ' AND ' . $this->where('asof', $until, '<'); $o_selection = $this->_dbo->select($criteria); $a_mlists = $o_selection->mlists(); $o_last = $a_mlists[count($a_mlists) - 1]; $this->set('list', $list); $this->set('list_xid', $a_mlists[0]->get('xid')); $this->set('interval_start', $from); $this->set('interval_end', $until); $n = count($o_selection->xids()); $subs = $o_selection->total_subscribers(); $this->set('count', $subs); $this->set('mean_count', $subs / $n); $subs = $o_selection->total_digest_subscribers(); $this->set('dcount', $subs); $this->set('mean_dcount', $subs / $n); $this->set('posts', $o_selection->total_posts()); $this->set('mean_posts', $o_selection->mean_posts()); } public function subscriber_count() { return $this->get('count'); } public function digester_count() { return $this->get('dcount'); } public function digest_subscriber_count() { return $this->digester_count(); } public function info($entify=false) { if (($iid = $this->get('info')) == 0) { return null; } $o_info = $this->_dbo->infotext($iid); $info = $o_info->get('info'); if ($entify) { $info = HTMLentities($info); } return $info; } } class lsInfoText extends lsDB { public function __construct($dbo, $id=null) { $this->id('xid', 'info_text'); $this->importdb($dbo); if (! isset($id)) { $this->isNew(true); return; } if (is_numeric($id)) { $this->refresh((int)$id); return; } $sql = 'SELECT xid FROM info_text WHERE ' . $this->where('sum', $id); $q = $this->query($sql); if ($q && (mysql_num_rows($q) > 0)) { list($xid) = mysql_fetch_row($q); $this->refresh((int)$xid); } } } class lsDomain extends lsDB { public $lists = array(); public $name; public function __construct($dbo, $id=null) { $this->importdb($dbo); $this->name = $id; } } class lsMList extends lsDB { public function __construct($dbo, $id=null, $asof=null) { $this->id('xid', 'list'); $this->importdb($dbo); if (! isset($id)) { $this->isNew(true); return; } if (is_numeric($id)) { $this->refresh((int)$id); return; } $sql = 'SELECT xid FROM list WHERE ' . $this->where('list', $id) . (isset($asof) ? ' AND ' . $this->where('asof', $asof) : '') . 'ORDER BY asof DESC LIMIT 1 '; $q = $this->query($sql); if ($q && (mysql_num_rows($q) > 0)) { list($id) = mysql_fetch_row($q); $this->refresh((int)$id); return; } } public function subscriber_count() { return $this->get('count'); } public function digester_count() { return $this->get('dcount'); } public function digest_subscriber_count() { return $this->digester_count(); } public function info($entify=false) { if (($iid = $this->get('info')) == 0) { return null; } $o_info = $this->_dbo->infotext($iid); $info = $o_info->get('info'); if ($entify) { $info = HTMLentities($info); } return $info; } } /* * Local Variables: * mode: C * c-file-style: "bsd" * tab-width: 4 * indent-tabs-mode: nil * End: */ ?>