1: <?php
2: /**
3: * A SQL based history driver.
4: *
5: * PHP version 5
6: *
7: * @category Horde
8: * @package History
9: * @author Chuck Hagenbuch <chuck@horde.org>
10: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
11: * @link http://pear.horde.org/index.php?package=History
12: */
13:
14: /**
15: * The Horde_History_Sql:: class provides a method of tracking changes in
16: * Horde objects, stored in a SQL table.
17: *
18: * Copyright 2003-2012 Horde LLC (http://www.horde.org/)
19: *
20: * See the enclosed file COPYING for license information (LGPL). If you
21: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
22: *
23: * @category Horde
24: * @package History
25: * @author Chuck Hagenbuch <chuck@horde.org>
26: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
27: * @link http://pear.horde.org/index.php?package=History
28: */
29: class Horde_History_Sql extends Horde_History
30: {
31: /**
32: * Horde_Db_Adapter instance to manage the history.
33: *
34: * @var Horde_Db_Adapter
35: */
36: protected $_db;
37:
38: /**
39: * Constructor.
40: *
41: * @param string $auth The current user.
42: * @param Horde_Db_Adapter $db The database connection.
43: */
44: public function __construct($auth, Horde_Db_Adapter $db)
45: {
46: parent::__construct($auth);
47: $this->_db = $db;
48: }
49:
50: /**
51: * Gets the timestamp of the most recent change to $guid.
52: *
53: * @param string $guid The name of the history entry to retrieve.
54: * @param string $action An action: 'add', 'modify', 'delete', etc.
55: *
56: * @return integer The timestamp, or 0 if no matching entry is found.
57: *
58: * @throws Horde_History_Exception If the input parameters are not of type string.
59: */
60: public function getActionTimestamp($guid, $action)
61: {
62: if (!is_string($guid) || !is_string($action)) {
63: throw new Horde_History_Exception('$guid and $action need to be strings!');
64: }
65:
66: try {
67: $result = $this->_db->selectValue('SELECT MAX(history_ts) FROM horde_histories WHERE history_action = ? AND object_uid = ?', array($action, $guid));
68: } catch (Horde_Db_Exception $e) {
69: return 0;
70: }
71:
72: return (int)$result;
73: }
74:
75: /**
76: * Logs an event to an item's history log. Any other details about the
77: * event are passed in $attributes.
78: *
79: * @param Horde_History_Log $history The history item to add to.
80: * @param array $attributes The hash of name => value
81: * entries that describe this
82: * event.
83: * @param boolean $replaceAction If $attributes['action'] is
84: * already present in the item's
85: * history log, update that entry
86: * instead of creating a new one.
87: *
88: * @throws Horde_History_Exception
89: */
90: protected function _log(Horde_History_Log $history, array $attributes,
91: $replaceAction = false)
92: {
93: /* If we want to replace an entry with the same action, try and find
94: * one. Track whether or not we succeed in $done, so we know whether or
95: * not to add the entry later. */
96: $done = false;
97: if ($replaceAction && !empty($attributes['action'])) {
98: foreach ($history as $entry) {
99: if (!empty($entry['action']) &&
100: $entry['action'] == $attributes['action']) {
101: $values = array(
102: $attributes['ts'],
103: $attributes['who'],
104: isset($attributes['desc']) ? $attributes['desc'] : null
105: );
106:
107: unset($attributes['ts'], $attributes['who'], $attributes['desc'], $attributes['action']);
108:
109: $values[] = $attributes
110: ? serialize($attributes)
111: : null;
112: $values[] = $entry['id'];
113:
114: try {
115: $r = $this->_db->update(
116: 'UPDATE horde_histories SET history_ts = ?,' .
117: ' history_who = ?,' .
118: ' history_desc = ?,' .
119: ' history_extra = ? WHERE history_id = ?', $values
120: );
121: } catch (Horde_Db_Exception $e) {
122: throw new Horde_History_Exception($e);
123: }
124:
125: $done = true;
126: break;
127: }
128: }
129: }
130:
131: /* If we're not replacing by action, or if we didn't find an entry to
132: * replace, insert a new row. */
133: if (!$done) {
134: $values = array(
135: $history->uid,
136: $attributes['ts'],
137: $attributes['who'],
138: isset($attributes['desc']) ? $attributes['desc'] : null,
139: isset($attributes['action']) ? $attributes['action'] : null
140: );
141:
142: unset($attributes['ts'], $attributes['who'], $attributes['desc'], $attributes['action']);
143:
144: $values[] = $attributes
145: ? serialize($attributes)
146: : null;
147:
148: try {
149: $this->_db->insert(
150: 'INSERT INTO horde_histories (object_uid, history_ts, history_who, history_desc, history_action, history_extra)' .
151: ' VALUES (?, ?, ?, ?, ?, ?)', $values
152: );
153: } catch (Horde_Db_Exception $e) {
154: throw new Horde_History_Exception($e);
155: }
156: }
157: }
158:
159: /**
160: * Returns a Horde_History_Log corresponding to the named history entry,
161: * with the data retrieved appropriately.
162: *
163: * @param string $guid The name of the history entry to retrieve.
164: *
165: * @return Horde_History_Log A Horde_History_Log object.
166: *
167: * @throws Horde_History_Exception
168: */
169: public function _getHistory($guid)
170: {
171: $rows = $this->_db->selectAll('SELECT * FROM horde_histories WHERE object_uid = ?', array($guid));
172: return new Horde_History_Log($guid, $rows);
173: }
174:
175: /**
176: * Finds history objects by timestamp, and optionally filter on other
177: * fields as well.
178: *
179: * @param string $cmp The comparison operator (<, >, <=, >=, or =) to
180: * check the timestamps with.
181: * @param integer $ts The timestamp to compare against.
182: * @param array $filters An array of additional (ANDed) criteria.
183: * Each array value should be an array with 3
184: * entries:
185: * - field: the history field being compared (i.e.
186: * 'action').
187: * - op: the operator to compare this field with.
188: * - value: the value to check for (i.e. 'add').
189: * @param string $parent The parent history to start searching at. If
190: * non-empty, will be searched for with a LIKE
191: * '$parent:%' clause.
192: *
193: * @return array An array of history object ids, or an empty array if
194: * none matched the criteria.
195: *
196: * @throws Horde_History_Exception
197: */
198: public function _getByTimestamp($cmp, $ts, array $filters = array(),
199: $parent = null)
200: {
201: /* Build the timestamp test. */
202: $where = array("history_ts $cmp $ts");
203:
204: /* Add additional filters, if there are any. */
205: if ($filters) {
206: foreach ($filters as $filter) {
207: $where[] = 'history_' . $filter['field'] . ' ' . $filter['op'] . ' ' . $this->_db->quote($filter['value']);
208: }
209: }
210:
211: if ($parent) {
212: $where[] = 'object_uid LIKE ' . $this->_db->quote($parent . ':%');
213: }
214:
215: return $this->_db->selectAssoc('SELECT DISTINCT object_uid, history_id FROM horde_histories WHERE ' . implode(' AND ', $where));
216: }
217:
218: /**
219: * Removes one or more history entries by name.
220: *
221: * @param array $names The history entries to remove.
222: *
223: * @throws Horde_History_Exception
224: */
225: public function removeByNames(array $names)
226: {
227: if (!count($names)) {
228: return;
229: }
230:
231: $ids = array();
232: foreach ($names as $name) {
233: $ids[] = $this->_db->quote($name);
234: }
235:
236: $this->_db->delete('DELETE FROM horde_histories WHERE object_uid IN (' . implode(',', $ids) . ')');
237: }
238: }
239: