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