Files
server/usr/share/psa-pear/pear/php/Horde/History.php
2026-01-07 20:52:11 +01:00

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;
}
}