/horde-3.3.13/lib/Horde/History.php
PHP | 407 lines | 239 code | 41 blank | 127 comment | 46 complexity | 9003cd8773d5bb9aad2d70eb4c5f23f1 MD5 | raw file
Possible License(s): LGPL-2.0
- <?php
- /**
- * The History:: class provides a method of tracking changes in Horde
- * objects, stored in a SQL table.
- *
- * $Horde: framework/History/History.php,v 1.28.2.25 2010/11/08 16:17:28 jan Exp $
- *
- * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @author Chuck Hagenbuch <chuck@horde.org>
- * @since Horde 2.1
- * @package Horde_History
- */
- class Horde_History {
- /**
- * Pointer to a DB instance to manage the history.
- *
- * @var DB
- */
- var $_db;
- /**
- * Handle for the current database connection, used for writing. Defaults
- * to the same handle as $_db if a separate write database is not required.
- *
- * @var DB
- */
- var $_write_db;
- /**
- * Attempts to return a reference to a concrete History instance.
- * It will only create a new instance if no History instance
- * currently exists.
- *
- * This method must be invoked as: $var = &History::singleton()
- *
- * @return Horde_History The concrete History reference, or false on an
- * error.
- */
- function &singleton()
- {
- static $history;
- if (!isset($history)) {
- $history = new Horde_History();
- }
- return $history;
- }
- /**
- * Constructor.
- */
- function Horde_History()
- {
- global $conf;
- if (empty($conf['sql']['phptype']) ||
- $conf['sql']['phptype'] == 'none') {
- $this->_db = $this->_write_db =
- PEAR::raiseError(_("The History system is disabled."));
- return;
- }
- require_once 'DB.php';
- $this->_write_db = &DB::connect($conf['sql']);
- /* Set DB portability options. */
- if (is_a($this->_write_db, 'DB_common')) {
- switch ($this->_write_db->phptype) {
- case 'mssql':
- $this->_write_db->setOption('portability',
- DB_PORTABILITY_LOWERCASE |
- DB_PORTABILITY_ERRORS |
- DB_PORTABILITY_RTRIM);
- break;
- default:
- $this->_write_db->setOption('portability',
- DB_PORTABILITY_LOWERCASE |
- DB_PORTABILITY_ERRORS);
- }
- }
- /* Check if we need to set up the read DB connection
- * seperately. */
- if (!empty($conf['sql']['splitread'])) {
- $params = array_merge($conf['sql'], $conf['sql']['read']);
- $this->_db = &DB::connect($params);
- /* Set DB portability options. */
- if (is_a($this->_db, 'DB_common')) {
- switch ($this->_db->phptype) {
- case 'mssql':
- $this->_db->setOption('portability',
- DB_PORTABILITY_LOWERCASE |
- DB_PORTABILITY_ERRORS |
- DB_PORTABILITY_RTRIM);
- break;
- default:
- $this->_db->setOption('portability',
- DB_PORTABILITY_LOWERCASE |
- DB_PORTABILITY_ERRORS);
- }
- }
- } else {
- /* Default to the same DB handle for reads. */
- $this->_db =& $this->_write_db;
- }
- }
- /**
- * Logs an event to an item's history log. The item must be uniquely
- * identified by $guid. Any other details about the event are passed in
- * $attributes. Standard suggested attributes are:
- *
- * 'who' => The id of the user that performed the action (will be added
- * automatically if not present).
- *
- * 'ts' => Timestamp of the action (this will be added automatically if
- * it is not present).
- *
- * @param string $guid The unique identifier of the entry to
- * add to.
- * @param array $attributes The hash of name => value entries that
- * describe this event.
- * @param boolean $replaceAction If $attributes['action'] is already
- * present in the item's history log,
- * update that entry instead of creating a
- * new one.
- *
- * @return boolean|PEAR_Error True on success, PEAR_Error on failure.
- */
- function log($guid, $attributes = array(), $replaceAction = false)
- {
- if (is_a($this->_write_db, 'PEAR_Error')) {
- return $this->_write_db;
- }
- $history = &$this->getHistory($guid);
- if (!$history || is_a($history, 'PEAR_Error')) {
- return $history;
- }
- if (!isset($attributes['who'])) {
- $attributes['who'] = Auth::getAuth();
- }
- if (!isset($attributes['ts'])) {
- $attributes['ts'] = time();
- }
- /* If we want to replace an entry with the same action, try and find
- * one. Track whether or not we succeed in $done, so we know whether
- * or not to add the entry later. */
- $done = false;
- if ($replaceAction && !empty($attributes['action'])) {
- $count = count($history->data);
- for ($i = 0; $i < $count; $i++) {
- if (!empty($history->data[$i]['action']) &&
- $history->data[$i]['action'] == $attributes['action']) {
- $values = array($attributes['ts'],
- $attributes['who'],
- isset($attributes['desc']) ? $attributes['desc'] : null);
- unset($attributes['ts']);
- unset($attributes['who']);
- unset($attributes['desc']);
- unset($attributes['action']);
- if ($attributes) {
- $values[] = serialize($attributes);
- } else {
- $values[] = null;
- }
- $values[] = $history->data[$i]['id'];
- $query = 'UPDATE horde_histories SET history_ts = ?, history_who = ?, history_desc = ?, history_extra = ? WHERE history_id = ?';
- Horde::logMessage('SQL query by Horde_History::log(): ' . $query . ', values: ' . implode(',', $values), __FILE__, __LINE__, PEAR_LOG_DEBUG);
- $r = $this->_write_db->query($query, $values);
- if (is_a($r, 'PEAR_Error')) {
- Horde::logMessage($r, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $r;
- }
- $done = true;
- break;
- }
- }
- }
- /* If we're not replacing by action, or if we didn't find an entry to
- * replace, insert a new row. */
- if (!$done) {
- $history_id = $this->_write_db->nextId('horde_histories');
- if (is_a($history_id, 'PEAR_Error')) {
- Horde::logMessage($history_id, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $history_id;
- }
- $values = array($history_id,
- $guid,
- $attributes['ts'],
- $attributes['who'],
- isset($attributes['desc']) ? $attributes['desc'] : null,
- isset($attributes['action']) ? $attributes['action'] : null);
- unset($attributes['ts']);
- unset($attributes['who']);
- unset($attributes['desc']);
- unset($attributes['action']);
- if ($attributes) {
- $values[] = serialize($attributes);
- } else {
- $values[] = null;
- }
- $query = 'INSERT INTO horde_histories (history_id, object_uid, history_ts, history_who, history_desc, history_action, history_extra) VALUES (?, ?, ?, ?, ?, ?, ?)';
- Horde::logMessage('SQL query by Horde_History::log(): ' . $query . ', values: ' . implode(',', $values), __FILE__, __LINE__, PEAR_LOG_DEBUG);
- $r = $this->_write_db->query($query, $values);
- if (is_a($r, 'PEAR_Error')) {
- Horde::logMessage($r, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $r;
- }
- }
- return true;
- }
- /**
- * Returns a HistoryObject corresponding to the named history
- * entry, with the data retrieved appropriately. $autocreate has
- * no affect.
- *
- * @param string $guid The name of the history entry to retrieve.
- * @param boolean $autocreate Deprecated.
- */
- function &getHistory($guid, $autocreate = null)
- {
- if (is_a($this->_db, 'PEAR_Error')) {
- $false = false;
- return $false;
- }
- $query = 'SELECT * FROM horde_histories WHERE object_uid = ?';
- Horde::logMessage('SQL query by Horde_History::getHistory(): ' . $query . ', values: ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
- $rows = $this->_db->getAll($query, array($guid), DB_FETCHMODE_ASSOC);
- if (is_a($rows, 'PEAR_Error')) {
- Horde::logMessage($rows, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $rows;
- }
- $history = &new HistoryObject($guid, $rows);
- return $history;
- }
- /**
- * Finds history objects by timestamp, and optionally filter on other
- * fields as well.
- *
- * @param string $cmp The comparison operator (<, >, <=, >=, or =) to
- * check the timestamps with.
- * @param integer $ts The timestamp to compare against.
- * @param array $filters An array of additional (ANDed) criteria.
- * Each array value should be an array with 3
- * entries:
- * <pre>
- * 'op' - the operator to compare this field
- * with.
- * 'field' - the history field being compared
- * (i.e. 'action').
- * 'value' - the value to check for (i.e. 'add').
- * </pre>
- * @param string $parent The parent history to start searching at. If non-empty,
- * will be searched for with a LIKE '$parent:%' clause.
- *
- * @return array An array of history object ids, or an empty array if
- * none matched the criteria.
- */
- function getByTimestamp($cmp, $ts, $filters = array(), $parent = null)
- {
- if (is_a($this->_db, 'PEAR_Error')) {
- return false;
- }
- /* Build the timestamp test. */
- $where = array("history_ts $cmp $ts");
- /* Add additional filters, if there are any. */
- if ($filters) {
- foreach ($filters as $filter) {
- $where[] = 'history_' . $filter['field'] . ' ' . $filter['op'] . ' ' . $this->_db->quote($filter['value']);
- }
- }
- if ($parent) {
- $where[] = 'object_uid LIKE ' . $this->_db->quote($parent . ':%');
- }
- $query = 'SELECT DISTINCT object_uid, history_id FROM horde_histories WHERE ' . implode(' AND ', $where);
- Horde::logMessage('SQL query by Horde_History::getByTimestamp(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
- $r = $this->_db->getAssoc($query);
- if (is_a($r, 'PEAR_Error')) {
- Horde::logMessage($r, __FILE__, __LINE__, PEAR_LOG_ERR);
- }
- return $r;
- }
- /**
- * Gets the timestamp of the most recent change to $guid.
- *
- * @param string $guid The name of the history entry to retrieve.
- * @param string $action An action: 'add', 'modify', 'delete', etc.
- *
- * @return integer The timestamp, or 0 if no matching entry is found.
- */
- function getActionTimestamp($guid, $action)
- {
- /* This implementation still works, but we should be able to
- * get much faster now with a SELECT MAX(history_ts)
- * ... query. */
- $history = &$this->getHistory($guid);
- if (!$history || is_a($history, 'PEAR_Error')) {
- return 0;
- }
- $last = 0;
- if (is_array($history->data)) {
- foreach ($history->data as $entry) {
- if ($entry['action'] == $action && $entry['ts'] > $last) {
- $last = $entry['ts'];
- }
- }
- }
- return (int)$last;
- }
- /**
- * Remove one or more history entries by name.
- *
- * @param array $names The history entries to remove.
- */
- function removeByNames($names)
- {
- if (is_a($this->_write_db, 'PEAR_Error')) {
- return false;
- }
- if (!count($names)) {
- return true;
- }
- $ids = array();
- foreach ($names as $name) {
- $ids[] = $this->_write_db->quote($name);
- }
- $query = 'DELETE FROM horde_histories WHERE object_uid IN (' . implode(',', $ids) . ')';
- Horde::logMessage('SQL query by Horde_History::removeByNames(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
- $r = $this->_write_db->query($query);
- if (is_a($r, 'PEAR_Error')) {
- Horde::logMessage($r, __FILE__, __LINE__, PEAR_LOG_ERR);
- }
- return $r;
- }
- }
- /**
- * Class for presenting History information.
- *
- * @author Chuck Hagenbuch <chuck@horde.org>
- * @since Horde 2.1
- * @package Horde_History
- */
- class HistoryObject {
- var $uid;
- var $data = array();
- function HistoryObject($uid, $data = array())
- {
- $this->uid = $uid;
- if (!$data || is_a($data, 'PEAR_Error')) {
- return;
- }
- foreach ($data as $row) {
- $history = array('action' => $row['history_action'],
- 'desc' => $row['history_desc'],
- 'who' => $row['history_who'],
- 'id' => $row['history_id'],
- 'ts' => $row['history_ts']);
- if ($row['history_extra']) {
- $extra = @unserialize($row['history_extra']);
- if ($extra) {
- $history = array_merge($history, $extra);
- }
- }
- $this->data[] = $history;
- }
- }
- function getData()
- {
- return $this->data;
- }
- }