1: <?php
2: /**
3: * The Horde_Notification:: package provides a subject-observer pattern for
4: * raising and showing messages of different types and to different
5: * listeners.
6: *
7: * Copyright 2001-2012 Horde LLC (http://www.horde.org/)
8: *
9: * See the enclosed file COPYING for license information (LGPL). If you
10: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
11: *
12: * @author Jan Schneider <jan@horde.org>
13: * @category Horde
14: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
15: * @package Notification
16: */
17: class Horde_Notification_Handler
18: {
19: /**
20: * Decorators.
21: *
22: * @var array
23: */
24: protected $_decorators = array();
25:
26: /**
27: * Forces immediate attachment of a notification to a listener.
28: *
29: * @var boolean
30: */
31: protected $_forceAttach = false;
32:
33: /**
34: * Additional handle definitions.
35: *
36: * @var array
37: */
38: protected $_handles = array(
39: 'default' => array(
40: '*' => 'Horde_Notification_Event'
41: )
42: );
43:
44: /**
45: * Hash containing all attached listener objects.
46: *
47: * @var array
48: */
49: protected $_listeners = array();
50:
51: /**
52: * The storage location where we store the messages.
53: *
54: * @var Horde_Notification_Storage
55: */
56: protected $_storage;
57:
58: /**
59: * Initialize the notification system.
60: *
61: * @param Horde_Notification_Storage $storage The storage object to use.
62: */
63: public function __construct(Horde_Notification_Storage_Interface $storage)
64: {
65: $this->_storage = $storage;
66: }
67:
68: /**
69: * Registers a listener with the notification object and includes
70: * the necessary library file dynamically.
71: *
72: * @param string $listener The name of the listener to attach. These
73: * names must be unique; further listeners with
74: * the same name will be ignored.
75: * @param array $params A hash containing any additional configuration
76: * or connection parameters a listener driver
77: * might need.
78: * @param string $class The class name from which the driver was
79: * instantiated if not the default one. If given
80: * you have to include the library file
81: * containing this class yourself. This is useful
82: * if you want the listener driver to be
83: * overriden by an application's implementation
84: *
85: * @return Horde_Notification_Listener The listener object.
86: * @throws Horde_Exception
87: */
88: public function attach($listener, $params = null, $class = null)
89: {
90: if ($ob = $this->getListener($listener)) {
91: return $ob;
92: }
93:
94: if (is_null($class)) {
95: $class = 'Horde_Notification_Listener_' . Horde_String::ucfirst(Horde_String::lower($listener));
96: }
97:
98: if (class_exists($class)) {
99: $this->_listeners[$listener] = new $class($params);
100: if (!$this->_storage->exists($listener)) {
101: $this->_storage->set($listener, array());
102: }
103: $this->_addTypes($listener);
104: return $this->_listeners[$listener];
105: }
106:
107: throw new Horde_Exception(sprintf('Notification listener %s not found.', $class));
108: }
109:
110: /**
111: * Remove a listener from the notification list.
112: *
113: * @param string $listner The name of the listener to detach.
114: *
115: * @throws Horde_Exception
116: */
117: public function detach($listener)
118: {
119: if ($ob = $this->getListener($listener)) {
120: unset($this->_listeners[$ob->getName()]);
121: $this->_storage->clear($ob->getName());
122: } else {
123: throw new Horde_Exception(sprintf('Notification listener %s not found.', $listener));
124: }
125: }
126:
127: /**
128: * Clear any notification events that may exist in a listener.
129: *
130: * @param string $listener The name of the listener to flush. If null,
131: * clears all unattached events.
132: */
133: public function clear($listener = null)
134: {
135: if (is_null($listener)) {
136: $this->_storage->clear('_unattached');
137: } elseif ($ob = $this->getListener($listener)) {
138: $this->_storage->clear($ob->getName());
139: }
140: }
141:
142: /**
143: * Returns the current Listener object for a given listener type.
144: *
145: * @param string $type The listener type.
146: *
147: * @return mixed A Horde_Notification_Listener object, or null if
148: * $type listener is not attached.
149: */
150: public function get($type)
151: {
152: foreach ($this->_listeners as $listener) {
153: if ($listener->handles($type)) {
154: return $listener;
155: }
156: }
157:
158: return null;
159: }
160:
161: /**
162: * Returns a listener object given a listener name.
163: *
164: * @param string $listener The listener name.
165: *
166: * @return mixed Either a Horde_Notification_Listener or null.
167: */
168: public function getListener($listener)
169: {
170: $listener = Horde_String::lower(basename($listener));
171: return empty($this->_listeners[$listener])
172: ? null
173: : $this->_listeners[$listener];
174: }
175:
176: /**
177: * Adds a type handler to a given Listener.
178: * To change the default listener, use the following:
179: * <pre>
180: * $ob->addType('default', '*', $classname);
181: * </pre>
182: *
183: * @param string $listener The listener name.
184: * @param string $type The listener type.
185: * @param string $class The Event class to use.
186: */
187: public function addType($listener, $type, $class)
188: {
189: $this->_handles[$listener][$type] = $class;
190:
191: if (isset($this->_listeners[$listener])) {
192: $this->_addTypes($listener);
193: }
194: }
195:
196: /**
197: * Adds any additional listener types to a given Listener.
198: *
199: * @param string $listener The listener name.
200: */
201: protected function _addTypes($listener)
202: {
203: if (isset($this->_handles[$listener])) {
204: foreach ($this->_handles[$listener] as $type => $class) {
205: $this->_listeners[$listener]->addType($type, $class);
206: }
207: }
208: }
209:
210: /**
211: * Add a decorator.
212: *
213: * @param Horde_Notification_Handler_Decorator_Base $decorator The
214: * Decorator
215: * object.
216: */
217: public function addDecorator(Horde_Notification_Handler_Decorator_Base $decorator)
218: {
219: $this->_decorators[] = $decorator;
220: }
221:
222: /**
223: * Add an event to the Horde message stack.
224: *
225: * @param mixed $event Horde_Notification_Event object or message
226: * string.
227: * @param integer $type The type of message.
228: * @param array $flags Array of optional flags that will be passed to
229: * the registered listeners.
230: * @param array $options Additional options:
231: * <pre>
232: * 'immediate' - (boolean) If true, immediately tries to attach to a
233: * listener. If no listener exists for this type, the
234: * message will be dropped.
235: * DEFAULT: false (message will be attached to available
236: * handler at the time notify() is called).
237: * </pre>
238: */
239: public function push($event, $type = null, array $flags = array(),
240: $options = array())
241: {
242: if ($event instanceof Horde_Notification_Event) {
243: $event->flags = $flags;
244: $event->type = $type;
245: } else {
246: $class = (!is_null($type) && ($listener = $this->get($type)))
247: ? $listener->handles($type)
248: : $this->_handles['default']['*'];
249:
250: /* Transparently create a Horde_Notification_Event object. */
251: $event = new $class($event, $type, $flags);
252: }
253:
254: foreach ($this->_decorators as $decorator) {
255: $decorator->push($event, $options);
256: }
257:
258: if (!$this->_forceAttach && empty($options['immediate'])) {
259: $this->_storage->push('_unattached', $event);
260: } else {
261: if ($listener = $this->get($event->type)) {
262: $this->_storage->push($listener->getName(), $event);
263: }
264: }
265: }
266:
267: /**
268: * Passes the message stack to all listeners and asks them to
269: * handle their messages.
270: *
271: * @param array $options An array containing display options for the
272: * listeners. Any options not contained in this
273: * list will be passed to the listeners.
274: * <pre>
275: * listeners - (array) The list of listeners to notify.
276: * raw - (boolean) If true, does not call the listener's notify()
277: * function.
278: * </pre>
279: */
280: public function notify(array $options = array())
281: {
282: /* Convert the 'listeners' option into the format expected by the
283: * notification handler. */
284: if (!isset($options['listeners'])) {
285: $listeners = array_keys($this->_listeners);
286: } elseif (!is_array($options['listeners'])) {
287: $listeners = array($options['listeners']);
288: } else {
289: $listeners = $options['listeners'];
290: }
291:
292: $events = array();
293: $unattached = $this->_storage->exists('_unattached')
294: ? $this->_storage->get('_unattached')
295: : array();
296:
297: /* Pass the message stack to all listeners and asks them to handle
298: * their messages. */
299: foreach ($listeners as $listener) {
300: $listener = Horde_String::lower($listener);
301:
302: if (isset($this->_listeners[$listener])) {
303: $instance = $this->_listeners[$listener];
304: $name = $instance->getName();
305:
306: foreach (array_keys($unattached) as $val) {
307: if ($unattached[$val] instanceof Horde_Notification_Event
308: && $instance->handles($unattached[$val]->type)) {
309: $this->_storage->push($name, $unattached[$val]);
310: unset($unattached[$val]);
311: }
312: }
313:
314: foreach ($this->_decorators as $decorator) {
315: $this->_forceAttach = true;
316: try {
317: $decorator->notify($this, $instance);
318: } catch (Horde_Notification_Exception $e) {
319: $this->push($e);
320: }
321: $this->_forceAttach = false;
322: }
323:
324: if (!$this->_storage->exists($name)) {
325: continue;
326: }
327:
328: $tmp = $this->_storage->get($name);
329: if (empty($options['raw'])) {
330: $instance->notify($tmp, $options);
331: }
332: $this->_storage->clear($name);
333:
334: $events = array_merge($events, $tmp);
335: }
336: }
337:
338: if (empty($unattached)) {
339: $this->_storage->clear('_unattached');
340: } else {
341: $this->_storage->set('_unattached', $unattached);
342: }
343:
344: return $events;
345: }
346:
347: /**
348: * Return the number of notification messages in the stack.
349: *
350: * @author David Ulevitch <davidu@everydns.net>
351: *
352: * @param string $my_listener The name of the listener.
353: *
354: * @return integer The number of messages in the stack.
355: */
356: public function count($my_listener = null)
357: {
358: $count = 0;
359:
360: if (!is_null($my_listener)) {
361: if ($ob = $this->get($my_listener)) {
362: $count = count($this->_storage->get($ob->getName()));
363:
364: if ($this->_storage->exists('_unattached')) {
365: foreach ($this->_storage->get('_unattached') as $val) {
366: if ($ob->handles($val->type)) {
367: ++$count;
368: }
369: }
370: }
371: }
372: } else {
373: if ($this->_storage->exists('_unattached')) {
374: $count = count($this->_storage->get('_unattached'));
375: }
376:
377: foreach ($this->_listeners as $val) {
378: if ($this->_storage->exists($val->getName())) {
379: $count += count($this->_storage->get($val->getName()));
380: }
381: }
382: }
383:
384: return $count;
385: }
386:
387: }
388: