1: <?php
2: /**
3: * @package Alarm
4: *
5: * Copyright 2007-2012 Horde LLC (http://www.horde.org/)
6: *
7: * See the enclosed file COPYING for license information (LGPL). If you
8: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
9: */
10:
11: /**
12: * The Horde_Alarm:: class provides an interface to deal with reminders,
13: * alarms and notifications through a standardized API.
14: *
15: * @author Jan Schneider <jan@horde.org>
16: * @package Alarm
17: */
18: abstract class Horde_Alarm
19: {
20: /**
21: * Logger.
22: *
23: * @var Horde_Log_Logger
24: */
25: protected $_logger;
26:
27: /**
28: * Alarm loader callback.
29: *
30: * @var mixed
31: */
32: protected $_loader;
33:
34: /**
35: * Hash containing connection parameters.
36: *
37: * @var array
38: */
39: protected $_params = array(
40: 'ttl' => 300
41: );
42:
43: /**
44: * All registered notification handlers.
45: *
46: * @var array
47: */
48: protected $_handlers = array();
49:
50: /**
51: * Whether handler classes have been dynamically loaded already.
52: *
53: * @var boolean
54: */
55: protected $_handlersLoaded = false;
56:
57: /**
58: * Constructor.
59: *
60: * @param array $params Configuration parameters:
61: * <pre>
62: * 'logger' - (Horde_Log_Logger) A logger instance.
63: * 'ttl' - (integer) Time to live value, in seconds.
64: * </pre>
65: */
66: public function __construct(array $params = array())
67: {
68: if (isset($params['logger'])) {
69: $this->_logger = $params['logger'];
70: unset($params['logger']);
71: }
72: if (isset($params['loader'])) {
73: $this->_loader = $params['loader'];
74: unset($params['loader']);
75: }
76: $this->_params = array_merge($this->_params, $params);
77: }
78:
79: /**
80: * Returns a list of alarms from the backend.
81: *
82: * @param string $user Return alarms for this user, all users if
83: * null, or global alarms if empty.
84: * @param Horde_Date $time The time when the alarms should be active.
85: * Defaults to now.
86: * @param boolean $load Update active alarms from all applications?
87: * @param boolean $preload Preload alarms that go off within the next
88: * ttl time span?
89: *
90: * @return array A list of alarm hashes.
91: * @throws Horde_Alarm_Exception
92: */
93: public function listAlarms($user = null, Horde_Date $time = null,
94: $load = false, $preload = true)
95: {
96: if (empty($time)) {
97: $time = new Horde_Date(time());
98: }
99: if ($load && is_callable($this->_loader)) {
100: call_user_func($this->_loader, $user, $preload);
101: }
102:
103: $alarms = $this->_list($user, $time);
104:
105: foreach (array_keys($alarms) as $alarm) {
106: if (isset($alarms[$alarm]['mail']['body'])) {
107: $alarms[$alarm]['mail']['body'] = $this->_fromDriver($alarms[$alarm]['mail']['body']);
108: }
109: }
110: return $alarms;
111: }
112:
113: /**
114: * Returns a list of alarms from the backend.
115: *
116: * @param Horde_Date $time The time when the alarms should be active.
117: * @param string $user Return alarms for this user, all users if
118: * null, or global alarms if empty.
119: *
120: * @return array A list of alarm hashes.
121: * @throws Horde_Alarm_Exception
122: */
123: abstract protected function _list($user, Horde_Date $time);
124:
125: /**
126: * Returns a list of all global alarms from the backend.
127: *
128: * @return array A list of alarm hashes.
129: * @throws Horde_Alarm_Exception
130: */
131: public function globalAlarms()
132: {
133: $alarms = $this->_global();
134: foreach (array_keys($alarms) as $alarm) {
135: if (isset($alarms[$alarm]['mail']['body'])) {
136: $alarms[$alarm]['mail']['body'] = $this->_fromDriver($alarms[$alarm]['mail']['body']);
137: }
138: }
139: return $alarms;
140: }
141:
142: /**
143: * Returns a list of all global alarms from the backend.
144: *
145: * @return array A list of alarm hashes.
146: */
147: abstract protected function _global();
148:
149: /**
150: * Returns an alarm hash from the backend.
151: *
152: * @param string $id The alarm's unique id.
153: * @param string $user The alarm's user
154: *
155: * @return array An alarm hash. Contains the following:
156: * <pre>
157: * id: Unique alarm id.
158: * user: The alarm's user. Empty if a global alarm.
159: * start: The alarm start as a Horde_Date.
160: * end: The alarm end as a Horde_Date.
161: * methods: The notification methods for this alarm.
162: * params: The paramters for the notification methods.
163: * title: The alarm title.
164: * text: An optional alarm description.
165: * snooze: The snooze time (next time) of the alarm as a Horde_Date.
166: * internal: Holds internally used data.
167: * </pre>
168: * @throws Horde_Alarm_Exception
169: */
170: public function get($id, $user)
171: {
172: $alarm = $this->_get($id, $user);
173:
174: if (isset($alarm['mail']['body'])) {
175: $alarm['mail']['body'] = $this->_fromDriver($alarm['mail']['body']);
176: }
177:
178: return $alarm;
179: }
180:
181: /**
182: * Returns an alarm hash from the backend.
183: *
184: * @param string $id The alarm's unique id.
185: * @param string $user The alarm's user
186: *
187: * @return array An alarm hash.
188: * @throws Horde_Alarm_Exception
189: */
190: abstract protected function _get($id, $user);
191:
192: /**
193: * Stores an alarm hash in the backend.
194: *
195: * The alarm will be added if it doesn't exist, and updated otherwise.
196: *
197: * @param array $alarm An alarm hash. See self::get() for format.
198: * @param boolean $keep Whether to keep the snooze value and notification
199: * status unchanged. If true, the alarm will get
200: * "un-snoozed", and notifications (like mails) are
201: * sent again.
202: *
203: * @throws Horde_Alarm_Exception
204: */
205: public function set(array $alarm, $keep = false)
206: {
207: if (isset($alarm['mail']['body'])) {
208: $alarm['mail']['body'] = $this->_toDriver($alarm['mail']['body']);
209: }
210:
211: if ($this->exists($alarm['id'], isset($alarm['user']) ? $alarm['user'] : '')) {
212: $this->_update($alarm, $keep);
213: if (!$keep) {
214: foreach ($this->_handlers as &$handler) {
215: $handler->reset($alarm);
216: }
217: }
218: } else {
219: $this->_add($alarm);
220: }
221: }
222:
223: /**
224: * Adds an alarm hash to the backend.
225: *
226: * @param array $alarm An alarm hash.
227: *
228: * @throws Horde_Alarm_Exception
229: */
230: abstract protected function _add(array $alarm);
231:
232: /**
233: * Updates an alarm hash in the backend.
234: *
235: * @param array $alarm An alarm hash.
236: * @param boolean $keepsnooze Whether to keep the snooze value unchanged.
237: *
238: * @throws Horde_Alarm_Exception
239: */
240: abstract protected function _update(array $alarm, $keepsnooze = false);
241:
242: /**
243: * Updates internal alarm properties, i.e. properties not determined by
244: * the application setting the alarm.
245: *
246: * @param string $id The alarm's unique id.
247: * @param string $user The alarm's user
248: * @param array $internal A hash with the internal data.
249: *
250: * @throws Horde_Alarm_Exception
251: */
252: abstract public function internal($id, $user, array $internal);
253:
254: /**
255: * Returns whether an alarm with the given id exists already.
256: *
257: * @param string $id The alarm's unique id.
258: * @param string $user The alarm's user
259: *
260: * @return boolean True if the specified alarm exists.
261: */
262: public function exists($id, $user)
263: {
264: try {
265: return $this->_exists($id, $user);
266: } catch (Horde_Alarm_Exception $e) {
267: return false;
268: }
269: }
270:
271: /**
272: * Returns whether an alarm with the given id exists already.
273: *
274: * @param string $id The alarm's unique id.
275: * @param string $user The alarm's user
276: *
277: * @return boolean True if the specified alarm exists.
278: * @throws Horde_Alarm_Exception
279: */
280: abstract protected function _exists($id, $user);
281:
282: /**
283: * Delays (snoozes) an alarm for a certain period.
284: *
285: * @param string $id The alarm's unique id.
286: * @param string $user The notified user.
287: * @param integer $minutes The delay in minutes. A negative value
288: * dismisses the alarm completely.
289: *
290: * @throws Horde_Alarm_Exception
291: */
292: public function snooze($id, $user, $minutes)
293: {
294: if (empty($user)) {
295: throw new Horde_Alarm_Exception('This alarm cannot be snoozed.');
296: }
297:
298: $alarm = $this->get($id, $user);
299:
300: if ($alarm) {
301: if ($minutes > 0) {
302: $alarm['snooze'] = new Horde_Date(time());
303: $alarm['snooze']->min += $minutes;
304: $this->_snooze($id, $user, $alarm['snooze']);
305: return;
306: }
307:
308: $this->_dismiss($id, $user);
309: }
310: }
311:
312: /**
313: * Delays (snoozes) an alarm for a certain period.
314: *
315: * @param string $id The alarm's unique id.
316: * @param string $user The alarm's user
317: * @param Horde_Date $snooze The snooze time.
318: *
319: * @throws Horde_Alarm_Exception
320: */
321: abstract protected function _snooze($id, $user, Horde_Date $snooze);
322:
323: /**
324: * Returns whether an alarm is snoozed.
325: *
326: * @param string $id The alarm's unique id.
327: * @param string $user The alarm's user
328: * @param Horde_Date $time The time when the alarm may be snoozed.
329: * Defaults to now.
330: *
331: * @return boolean True if the alarm is snoozed.
332: *
333: * @throws Horde_Alarm_Exception
334: */
335: public function isSnoozed($id, $user, Horde_Date $time = null)
336: {
337: if (is_null($time)) {
338: $time = new Horde_Date(time());
339: }
340: return (bool)$this->_isSnoozed($id, $user, $time);
341: }
342:
343: /**
344: * Returns whether an alarm is snoozed.
345: *
346: * @param string $id The alarm's unique id.
347: * @param string $user The alarm's user
348: * @param Horde_Date $time The time when the alarm may be snoozed.
349: *
350: * @return boolean True if the alarm is snoozed.
351: * @throws Horde_Alarm_Exception
352: */
353: abstract protected function _isSnoozed($id, $user, Horde_Date $time);
354:
355: /**
356: * Dismisses an alarm.
357: *
358: * @param string $id The alarm's unique id.
359: * @param string $user The alarm's user
360: *
361: * @throws Horde_Alarm_Exception
362: */
363: abstract protected function _dismiss($id, $user);
364:
365: /**
366: * Deletes an alarm from the backend.
367: *
368: * @param string $id The alarm's unique id.
369: * @param string $user The alarm's user. All users' alarms if null.
370: *
371: * @throws Horde_Alarm_Exception
372: */
373: function delete($id, $user = null)
374: {
375: $this->_delete($id, $user);
376: }
377:
378: /**
379: * Deletes an alarm from the backend.
380: *
381: * @param string $id The alarm's unique id.
382: * @param string $user The alarm's user. All users' alarms if null.
383: *
384: * @throws Horde_Alarm_Exception
385: */
386: abstract protected function _delete($id, $user = null);
387:
388: /**
389: * Notifies the user about any active alarms.
390: *
391: * @param string $user Notify this user, all users if null, or guest
392: * users if empty.
393: * @param boolean $load Update active alarms from all applications?
394: * @param boolean $preload Preload alarms that go off within the next
395: * ttl time span?
396: * @param array $exclude Don't notify with these methods.
397: *
398: * @throws Horde_Alarm_Exception
399: */
400: public function notify($user = null, $load = true, $preload = true,
401: array $exclude = array())
402: {
403: try {
404: $alarms = $this->listAlarms($user, null, $load, $preload);
405: } catch (Horde_Alarm_Exception $e) {
406: if ($this->_logger) {
407: $this->_logger->log($e, 'ERR');
408: }
409: throw $e;
410: }
411:
412: if (empty($alarms)) {
413: return;
414: }
415:
416: $handlers = $this->handlers();
417: foreach ($alarms as $alarm) {
418: foreach ($alarm['methods'] as $alarm_method) {
419: if (isset($handlers[$alarm_method]) &&
420: !in_array($alarm_method, $exclude)) {
421: $handlers[$alarm_method]->notify($alarm);
422: }
423: }
424: }
425: }
426:
427: /**
428: * Registers a notification handler.
429: *
430: * @param string $name A handler name.
431: * @param Horde_Alarm_Handler $handler A notification handler.
432: */
433: public function addHandler($name, Horde_Alarm_Handler $handler)
434: {
435: $this->_handlers[$name] = $handler;
436: $handler->alarm = $this;
437: }
438:
439: /**
440: * Returns a list of available notification handlers and parameters.
441: *
442: * The returned list is a hash with method names as the keys and
443: * optionally associated parameters as values. The parameters are hashes
444: * again with parameter names as keys and parameter information as
445: * values. The parameter information is hash with the following keys:
446: * 'desc' contains a parameter description; 'required' specifies whether
447: * this parameter is required.
448: *
449: * @return array List of methods and parameters.
450: */
451: public function handlers()
452: {
453: if (!$this->_handlersLoaded) {
454: foreach (new DirectoryIterator(dirname(__FILE__) . '/Alarm/Handler') as $file) {
455: if (!$file->isFile() || substr($file->getFilename(), -4) != '.php') {
456: continue;
457: }
458: $handler = Horde_String::lower($file->getBasename('.php'));
459: if (isset($this->_handlers[$handler])) {
460: continue;
461: }
462: require_once $file->getPathname();
463: $class = 'Horde_Alarm_Handler_' . $file->getBasename('.php');
464: if (class_exists($class, false)) {
465: $this->addHandler($handler, new $class());
466: }
467: }
468: $this->_handlerLoaded = true;
469: }
470:
471: return $this->_handlers;
472: }
473:
474: /**
475: * Garbage collects old alarms in the backend.
476: *
477: * @param boolean $force Force garbace collection? If false, GC happens
478: * with a 1% chance.
479: *
480: * @throws Horde_Alarm_Exception
481: */
482: public function gc($force = false)
483: {
484: /* A 1% chance we will run garbage collection during a call. */
485: if ($force || rand(0, 99) == 0) {
486: $this->_gc();
487: }
488: }
489:
490: /**
491: * Garbage collects old alarms in the backend.
492: *
493: * @throws Horde_Alarm_Exception
494: */
495: abstract protected function _gc();
496:
497: /**
498: * Attempts to initialize the backend.
499: *
500: * @throws Horde_Alarm_Exception
501: */
502: abstract public function initialize();
503:
504: /**
505: * Converts a value from the driver's charset.
506: *
507: * @param mixed $value Value to convert.
508: *
509: * @return mixed Converted value.
510: */
511: abstract protected function _fromDriver($value);
512:
513: /**
514: * Converts a value to the driver's charset.
515: *
516: * @param mixed $value Value to convert.
517: *
518: * @return mixed Converted value.
519: */
520: abstract protected function _toDriver($value);
521:
522: }
523: