436 lines
14 KiB
PHP
436 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* The Horde_History:: system.
|
|
*
|
|
* PHP version 5
|
|
*
|
|
* @category Horde
|
|
* @package History
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @author Gunnar Wrobel <wrobel@pardus.de>
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @link http://pear.horde.org/index.php?package=History
|
|
*/
|
|
|
|
/**
|
|
* The Horde_History:: class provides a method of tracking changes in Horde
|
|
* objects, stored in a SQL table.
|
|
*
|
|
* Copyright 2003-2016 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (LGPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
|
|
*
|
|
* @category Horde
|
|
* @package History
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @author Gunnar Wrobel <wrobel@pardus.de>
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @link http://pear.horde.org/index.php?package=History
|
|
*/
|
|
abstract class Horde_History
|
|
{
|
|
/**
|
|
* The current user.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_auth;
|
|
|
|
/**
|
|
* Cache driver object.
|
|
*
|
|
* @var Horde_Cache
|
|
*/
|
|
protected $_cache;
|
|
|
|
/**
|
|
* Our log handler.
|
|
*
|
|
* @var Horde_Log_Logger
|
|
*/
|
|
protected $_logger;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param string $auth The current user.
|
|
*/
|
|
public function __construct($auth)
|
|
{
|
|
$this->_auth = $auth;
|
|
}
|
|
|
|
/**
|
|
* Set the log handler.
|
|
*
|
|
* @param Horde_Log_Logger $logger The log handler.
|
|
*
|
|
* @return NULL
|
|
*/
|
|
public function setLogger(Horde_Log_Logger $logger)
|
|
{
|
|
$this->_logger = $logger;
|
|
}
|
|
|
|
/**
|
|
* Set Cache object.
|
|
*
|
|
* @since 2.1.0
|
|
*
|
|
* @param Horde_Cache $cache The cache instance.
|
|
*/
|
|
public function setCache(Horde_Cache $cache)
|
|
{
|
|
$this->_cache = $cache;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @throws Horde_History_Exception
|
|
*/
|
|
public function log($guid, array $attributes = array(),
|
|
$replaceAction = false)
|
|
{
|
|
if (!is_string($guid)) {
|
|
throw new InvalidArgumentException('The guid needs to be a string!');
|
|
}
|
|
|
|
$history = $this->getHistory($guid);
|
|
|
|
if (!isset($attributes['who'])) {
|
|
$attributes['who'] = $this->_auth;
|
|
}
|
|
if (!isset($attributes['ts'])) {
|
|
$attributes['ts'] = time();
|
|
}
|
|
|
|
$this->_log($history, $attributes, $replaceAction);
|
|
|
|
if ($this->_cache) {
|
|
$this->_cache->expire('horde:history:' . $guid);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logs an event to an item's history log. Any other details about the
|
|
* event are passed in $attributes.
|
|
*
|
|
* @param Horde_History_Log $history The history item 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.
|
|
*
|
|
* @throws Horde_History_Exception
|
|
*/
|
|
abstract protected function _log(Horde_History_Log $history,
|
|
array $attributes, $replaceAction = false);
|
|
|
|
/**
|
|
* Returns a Horde_History_Log corresponding to the named history entry,
|
|
* with the data retrieved appropriately.
|
|
*
|
|
* @param string $guid The name of the history entry to retrieve.
|
|
*
|
|
* @return Horde_History_Log A Horde_History_Log object.
|
|
*
|
|
* @throws Horde_History_Exception
|
|
*/
|
|
public function getHistory($guid)
|
|
{
|
|
if (!is_string($guid)) {
|
|
throw new InvalidArgumentException('The guid needs to be a string!');
|
|
}
|
|
|
|
if ($this->_cache &&
|
|
($history = @unserialize($this->_cache->get('horde:history:' . $guid, 0)))) {
|
|
return $history;
|
|
}
|
|
|
|
$history = $this->_getHistory($guid);
|
|
if ($this->_cache) {
|
|
$this->_cache->set('horde:history:' . $guid, serialize($history));
|
|
}
|
|
|
|
return $history;
|
|
}
|
|
|
|
/**
|
|
* Returns a Horde_History_Log corresponding to the named history entry,
|
|
* with the data retrieved appropriately.
|
|
*
|
|
* @todo Make protected.
|
|
*
|
|
* @param string $guid The name of the history entry to retrieve.
|
|
*
|
|
* @return Horde_History_Log A Horde_History_Log object.
|
|
*/
|
|
abstract public function _getHistory($guid);
|
|
|
|
/**
|
|
* 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:
|
|
* - field: the history field being compared (i.e.
|
|
* 'action').
|
|
* - op: the operator to compare this field with.
|
|
* - value: the value to check for (i.e. 'add').
|
|
* @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.
|
|
*
|
|
* @throws Horde_History_Exception
|
|
*/
|
|
public function getByTimestamp($cmp, $ts, array $filters = array(),
|
|
$parent = null)
|
|
{
|
|
if (!is_string($cmp)) {
|
|
throw new InvalidArgumentException('The comparison operator needs to be a string!');
|
|
}
|
|
if (!is_integer($ts)) {
|
|
throw new InvalidArgumentException('The timestamp needs to be an integer!');
|
|
}
|
|
return $this->_getByTimestamp($cmp, $ts, $filters, $parent);
|
|
}
|
|
|
|
/**
|
|
* Return history objects with changes during a modseq interval, and
|
|
* optionally filtered on other fields as well.
|
|
*
|
|
* @param integer $start The (exclusive) start of the modseq range.
|
|
* @param integer $end The (inclusive) end of the modseq range.
|
|
* @param array $filters An array of additional (ANDed) criteria.
|
|
* Each array value should be an array with 3
|
|
* entries:
|
|
* - field: the history field being compared (i.e.
|
|
* 'action').
|
|
* - op: the operator to compare this field with.
|
|
* - value: the value to check for (i.e. 'add').
|
|
* @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.
|
|
*/
|
|
public function getByModSeq($start, $end, $filters = array(), $parent = null)
|
|
{
|
|
if (!is_integer($start) || !is_integer($end)) {
|
|
throw new InvalidArgumentException('The modseq values must be integers!');
|
|
}
|
|
|
|
// Exit early if there is no range.
|
|
if ($start == $end) {
|
|
return array();
|
|
}
|
|
|
|
return $this->_getByModSeq($start, $end, $filters, $parent);
|
|
}
|
|
|
|
/**
|
|
* Finds history objects by timestamp, and optionally filter on other
|
|
* fields as well.
|
|
*
|
|
* @todo Make protected.
|
|
*
|
|
* @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:
|
|
* - field: the history field being compared (i.e.
|
|
* 'action').
|
|
* - op: the operator to compare this field with.
|
|
* - value: the value to check for (i.e. 'add').
|
|
* @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.
|
|
*
|
|
* @throws Horde_History_Exception
|
|
*/
|
|
abstract public function _getByTimestamp($cmp, $ts,
|
|
array $filters = array(),
|
|
$parent = null);
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @throws Horde_History_Exception
|
|
*/
|
|
public function getActionTimestamp($guid, $action)
|
|
{
|
|
if (!is_string($guid) || !is_string($action)) {
|
|
throw new InvalidArgumentException('$guid and $action need to be strings!');
|
|
}
|
|
|
|
try {
|
|
$history = $this->getHistory($guid);
|
|
} catch (Horde_History_Exception $e) {
|
|
return 0;
|
|
}
|
|
|
|
$last = 0;
|
|
|
|
foreach ($history as $entry) {
|
|
if (($entry['action'] == $action) && ($entry['ts'] > $last)) {
|
|
$last = $entry['ts'];
|
|
}
|
|
}
|
|
|
|
return (int)$last;
|
|
}
|
|
|
|
/**
|
|
* Remove one or more history entries by parent.
|
|
*
|
|
* @param string $parent The parent name to remove.
|
|
*
|
|
* @throws Horde_History_Exception
|
|
*/
|
|
public function removeByParent($parent)
|
|
{
|
|
/* Remove entries 100 at a time. */
|
|
$all = array_keys($this->getByTimestamp('>', 0, array(), $parent));
|
|
|
|
while (count($d = array_splice($all, 0, 100)) > 0) {
|
|
$this->removebyNames($d);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes one or more history entries by name.
|
|
*
|
|
* @param array $names The history entries to remove.
|
|
*
|
|
* @throws Horde_History_Exception
|
|
*/
|
|
abstract public function removeByNames(array $names);
|
|
|
|
/**
|
|
* Return the maximum modification sequence. To be overridden in concrete
|
|
* class.
|
|
*
|
|
* @param string $parent Restrict to entries a specific parent.
|
|
*
|
|
* @return integer The modseq
|
|
* @since 2.1.0
|
|
* @todo Make abstract in H6. Need to make this non-abstract for BC.
|
|
*/
|
|
public function getHighestModSeq($parent = null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets the modseq 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 modseq, or 0 if no matching entry is found.
|
|
*
|
|
* @throws Horde_History_Exception
|
|
* @since 2.1.0
|
|
* @todo Make abstract in H6.
|
|
*/
|
|
public function getActionModSeq($guid, $action)
|
|
{
|
|
if (!is_string($guid) || !is_string($action)) {
|
|
throw new InvalidArgumentException('$guid and $action need to be strings!');
|
|
}
|
|
|
|
try {
|
|
$history = $this->getHistory($guid);
|
|
} catch (Horde_History_Exception $e) {
|
|
return 0;
|
|
}
|
|
|
|
$last = 0;
|
|
foreach ($history as $entry) {
|
|
if (($entry['action'] == $action) && ($entry['modseq'] > $last)) {
|
|
$last = $entry['modseq'];
|
|
}
|
|
}
|
|
|
|
return (int)$last;
|
|
}
|
|
|
|
/**
|
|
* Gets the latest entry of $guid
|
|
*
|
|
* @param string $guid The name of the history entry to retrieve.
|
|
* @param boolean $use_ts If false we use the 'modseq' field to determine
|
|
* the latest entry. If true we use the timestamp
|
|
* instead of modseq to determine the latest entry.
|
|
* Note: Only 'modseq' can give a definitive answer.
|
|
*
|
|
* @return array|boolean The latest history entry, or false if $guid does not exist.
|
|
*
|
|
* @throws Horde_History_Exception
|
|
* @since 2.2.0
|
|
*/
|
|
public function getLatestEntry($guid, $use_ts = false)
|
|
{
|
|
$log = $this->getHistory($guid);
|
|
if (!count($log)) {
|
|
return false;
|
|
}
|
|
|
|
$last = array('modseq' => -1, 'ts' => -1);
|
|
if ($use_ts) {
|
|
foreach ($log as $entry) {
|
|
if ($entry['ts'] > $last['ts']) {
|
|
$last = $entry;
|
|
}
|
|
}
|
|
} else {
|
|
foreach ($log as $entry) {
|
|
if ($entry['modseq'] > $last['modseq']) {
|
|
$last = $entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $last;
|
|
}
|
|
|
|
}
|