--- /dev/null
+<?php
+/**
+ * The Horde_Notification:: class provides a subject-observer pattern for
+ * raising and showing messages of different types and to different
+ * listeners.
+ *
+ * Copyright 2001-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Jan Schneider <jan@horde.org>
+ * @package Horde_Notification
+ */
+class Horde_Notification
+{
+ /**
+ * Horde_Notification instances.
+ *
+ * @var Horde_Notification
+ */
+ protected $_instances = array();
+
+ /**
+ * Hash containing all attached listener objects.
+ *
+ * @var array
+ */
+ protected $_listeners = array();
+
+ /**
+ * The name of the session variable where we store the messages.
+ *
+ * @var string
+ */
+ protected $_stack;
+
+ /**
+ * A Horde_Alarm instance.
+ *
+ * @var Horde_Alarm
+ */
+ protected $_alarm;
+
+ /**
+ * Returns a reference to the global Notification object, only
+ * creating it if it doesn't already exist.
+ *
+ * This method must be invoked as:
+ * $notification = Horde_Notification::singleton()
+ *
+ * @param string $stack The name of the message stack to use.
+ *
+ * return Notification The Horde Notification instance.
+ */
+ static public function singleton($stack = 'horde_notification_stacks')
+ {
+ if (!isset(self::$_instances[$stack])) {
+ self::$_instances[$stack] = new Horde_Notification($stack);
+ }
+
+ return self::$_instances[$stack];
+ }
+
+ /**
+ * Initialize the notification system, set up any needed session
+ * variables, etc.
+ *
+ * @param string $stack The name of the message stack to use.
+ */
+ protected function __construct($stack)
+ {
+ $this->_stack = $stack;
+
+ /* Make sure the message stack is registered in the session,
+ * and obtain a global-scope reference to it. */
+ if (!isset($_SESSION[$this->_stack])) {
+ $_SESSION[$this->_stack] = array();
+ }
+
+ if (!empty($GLOBALS['conf']['alarms']['driver'])) {
+ $this->_alarm = Horde_Alarm::factory();
+ }
+ }
+
+ /**
+ * Registers a listener with the notification object and includes
+ * the necessary library file dynamically.
+ *
+ * @param string $listener The name of the listener to attach. These names
+ * must be unique; further listeners with the same
+ * name will be ignored.
+ * @param array $params A hash containing any additional configuration or
+ * connection parameters a listener driver might
+ * need.
+ * @param string $class The class name from which the driver was
+ * instantiated if not the default one. If given
+ * you have to include the library file containing
+ * this class yourself. This is useful if you want
+ * the listener driver to be overriden by an
+ * application's implementation.
+ *
+ * @return TODO
+ * @throws Horde_Exception
+ */
+ public function attach($listener, $params = array(), $class = null)
+ {
+ $listener = basename($listener);
+ if (!empty($this->_listeners[$listener])) {
+ return $this->_listeners[$listener];
+ }
+
+ if (is_null($class)) {
+ $class = 'Horde_Notification_Listener_' . ucfirst($listener);
+ }
+
+ if (class_exists($class)) {
+ $this->_listeners[$listener] = new $class($params);
+ if (!isset($_SESSION[$this->_stack][$listener])) {
+ $_SESSION[$this->_stack][$listener] = array();
+ }
+ return $this->_listeners[$listener];
+ }
+
+ throw new Horde_Exception(sprintf('Notification listener %s not found.', $listener));
+ }
+
+ /**
+ * Remove a listener from the notification list.
+ *
+ * @param string $listner The name of the listener to detach.
+ *
+ * @throws Horde_Exception
+ */
+ public function detach($listener)
+ {
+ $listener = basename($listener);
+ if (!isset($this->_listeners[$listener])) {
+ throw new Horde_Exception(sprintf('Notification listener %s not found.', $listener));
+ }
+
+ $list = $this->_listeners[$listener];
+ unset($this->_listeners[$listener], $_SESSION[$this->_stack][$list->getName()]);
+ }
+
+ /**
+ * Add an event to the Horde message stack.
+ *
+ * The event type parameter should begin with 'horde.' unless the
+ * application defines its own Horde_Notification_Listener subclass that
+ * handles additional codes.
+ *
+ * @param mixed $event Horde_Notification_Event object or message string.
+ * @param integer $type The type of message: 'horde.error',
+ * 'horde.warning', 'horde.success', or
+ * 'horde.message'.
+ * @param array $flags Array of optional flags that will be passed to the
+ * registered listeners.
+ */
+ public function push($event, $type = null, $flags = array())
+ {
+ if (!$event instanceof Horde_Notification_Event &&
+ !$event instanceof PEAR_Error) {
+ /* Transparently create a Horde_Notification_Event object and
+ * set the message attribute. */
+ $event = new Horde_Notification_Event($event);
+ }
+
+ if ($event instanceof PEAR_Error) {
+ if (is_null($type)) {
+ $type = 'horde.error';
+ }
+ Horde::logMessage($event, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ } elseif (is_null($type)) {
+ $type = 'horde.message';
+ }
+
+ foreach ($this->_listeners as $listener) {
+ if ($listener->handles($type)) {
+ $_SESSION[$this->_stack][$listener->getName()][] = array(
+ 'class' => get_class($event),
+ 'event' => serialize($event),
+ 'flags' => serialize($flags),
+ 'type' => $type
+ );
+ }
+ }
+ }
+
+ /**
+ * Passes the message stack to all listeners and asks them to
+ * handle their messages.
+ *
+ * @param array $options An array containing display options for the
+ * listeners.
+ */
+ public function notify($options = array())
+ {
+ if (!isset($options['listeners'])) {
+ $options['listeners'] = array_keys($this->_listeners);
+ } elseif (!is_array($options['listeners'])) {
+ $options['listeners'] = array($options['listeners']);
+ }
+
+ if ($this->_alarm && in_array('status', $options['listeners'])) {
+ $this->_alarm->notify(Auth::getAuth());
+ }
+
+ foreach ($options['listeners'] as $listener) {
+ if (isset($this->_listeners[$listener])) {
+ $this->_listeners[$listener]->notify($_SESSION[$this->_stack][$this->_listeners[$listener]->getName()], $options);
+ }
+ }
+ }
+
+ /**
+ * Return the number of notification messages in the stack.
+ *
+ * @author David Ulevitch <davidu@everydns.net>
+ *
+ * @param string $my_listener The name of the listener.
+ *
+ * @return integer The number of messages in the stack.
+ */
+ public function count($my_listener = null)
+ {
+ if (is_null($my_listener)) {
+ $count = 0;
+ foreach ($this->_listeners as $listener) {
+ if (isset($_SESSION[$this->_stack][$listener->getName()])) {
+ $count += count($_SESSION[$this->_stack][$listener->getName()]);
+ }
+ }
+ return $count;
+ } else {
+ return @count($_SESSION[$this->_stack][$this->_listeners[$my_listener]->getName()]);
+ }
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Notification_Event:: class provides a container for passing
+ * messages to Horde_Notification_Listener classes.
+ *
+ * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Hans Lellelid <hans@velum.net>
+ * @package Horde_Notification
+ */
+class Horde_Notification_Event
+{
+ /**
+ * The message being passed.
+ *
+ * @var string
+ */
+ protected $_message = '';
+
+ /**
+ * If passed, sets the message for this event.
+ *
+ * @param string $message The text message for this event.
+ */
+ public function __construct($message = null)
+ {
+ if (!is_null($message)) {
+ $this->setMessage($message);
+ }
+ }
+
+ /**
+ * Sets the text message for this event.
+ *
+ * @param string $message The text message to display.
+ */
+ public function setMessage($message)
+ {
+ $this->_message = $message;
+ }
+
+ /**
+ * Gets the text message for this event.
+ *
+ * @return string The text message to display.
+ */
+ public function getMessage()
+ {
+ return $this->_message;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Notification_Listener:: class provides functionality for
+ * displaying messages from the message stack as a status line.
+ *
+ * Copyright 2001-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Notification
+ */
+class Horde_Notification_Listener
+{
+ /**
+ * Array of message types that this listener handles.
+ *
+ * @var array
+ */
+ protected $_handles = array();
+
+ /**
+ * Does this listener handle a certain type of message?
+ *
+ * @param string $type The message type in question.
+ *
+ * @return boolean Whether this listener handles the type.
+ */
+ public function handles($type)
+ {
+ return isset($this->_handles[$type]);
+ }
+
+ /**
+ * Return a unique identifier for this listener.
+ *
+ * @return string Unique id.
+ */
+ public function getName()
+ {
+ return get_class($this);
+ }
+
+ /**
+ * Outputs the status line, sends emails, pages, etc., if there
+ * are any messages on this listener's message stack.
+ *
+ * @param array &$messageStack The stack of messages.
+ * @param array $options An array of options.
+ */
+ public function notify(&$messageStacks, $options = array())
+ {
+ }
+
+ /**
+ * Processes one message from the message stack.
+ *
+ * @param array $message One message hash from the stack.
+ */
+ public function getMessage($message)
+ {
+ }
+
+ /**
+ * Unserialize an event from the message stack, checking to see if the
+ * appropriate class exists and kludging it into a base Notification_Event
+ * object if not.
+ */
+ public function getEvent($message)
+ {
+ $ob = false;
+ if (class_exists($message['class'])) {
+ $ob = @unserialize($message['event']);
+ } else {
+ $ob = @unserialize($message['event']);
+ if (!is_callable(array($ob, 'getMessage'))) {
+ if (isset($ob->_message)) {
+ $ob = new Horde_Notification_Event($ob->_message);
+ }
+ }
+ }
+
+ /* If we've failed to create a valid Notification_Event object
+ * (or subclass object) so far, return a PEAR_Error. */
+ if (!is_callable(array($ob, 'getMessage'))) {
+ $ob = PEAR::raiseError('Unable to decode message event: ' . $message['event']);
+ }
+
+ /* Specially handle PEAR_Error objects and add userinfo if
+ * it's there. */
+ if (is_callable(array($ob, 'getUserInfo'))) {
+ $userinfo = $ob->getUserInfo();
+ if ($userinfo) {
+ if (is_array($userinfo)) {
+ $userinfo_elts = array();
+ foreach ($userinfo as $userinfo_elt) {
+ if (is_scalar($userinfo_elt)) {
+ $userinfo_elts[] = $userinfo_elt;
+ } elseif (is_object($userinfo_elt)) {
+ if (is_callable(array($userinfo_elt, '__toString'))) {
+ $userinfo_elts[] = $userinfo_elt->__toString();
+ } elseif (is_callable(array($userinfo_elt, 'getMessage'))) {
+ $userinfo_elts[] = $userinfo_elt->getMessage();
+ }
+ }
+ }
+ $userinfo = implode(', ', $userinfo_elts);
+ }
+
+ $ob->_message = $ob->getMessage() . ' : ' . $userinfo;
+ }
+ }
+
+ return $ob;
+ }
+
+ /**
+ * Unserialize an array of event flags from the message stack. If this
+ * event has no flags, or the flags array could not be unserialized, an
+ * empty array is returned.
+ *
+ * @return array An array of flags.
+ */
+ public function getFlags($message)
+ {
+ /* If this message doesn't have any flags, return an empty
+ * array. */
+ if (empty($message['flags'])) {
+ return array();
+ }
+
+ /* Unserialize the flags array from the message. */
+ $flags = @unserialize($message['flags']);
+
+ /* If we couldn't unserialize the flags array, return an empty
+ * array. */
+ if (!is_array($flags)) {
+ return array();
+ }
+
+ return $flags;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Notification_Listener_Audio:: class provides functionality for
+ * inserting embedded audio notifications from the stack into the page.
+ *
+ * Copyright 2005-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Jason M. Felice <jason.m.felice@gmail.com>
+ * @package Horde_Notification
+ */
+class Horde_Notification_Listener_Audio extends Horde_Notification_Listener
+{
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->_handles = array('audio' => '');
+ }
+
+ /**
+ * Outputs the embedded audio code if there are any messages on the
+ * 'audio' message stack.
+ *
+ * @param array &$messageStack The stack of messages.
+ * @param array $options An array of options.
+ */
+ public function notify(&$messageStack, $options = array())
+ {
+ if (count($messageStack)) {
+ while ($message = array_shift($messageStack)) {
+ $this->getMessage($message);
+ }
+ }
+ }
+
+ /**
+ * Outputs one message.
+ *
+ * @param array $message One message hash from the stack.
+ */
+ public function getMessage($message)
+ {
+ $event = $this->getEvent($message);
+ echo '<embed src="', htmlspecialchars($event->getMessage()),
+ '" width="0" height="0" autostart="true" />';
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Notification_Listener_Javascript:: class provides functionality
+ * for inserting javascript code from the message stack into the page.
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Jan Schneider <jan@horde.org>
+ * @package Horde_Notification
+ */
+class Horde_Notification_Listener_Javascript extends Horde_Notification_Listener
+{
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->_handles = array(
+ 'javascript' => '',
+ 'javascript-file' => ''
+ );
+ }
+
+ /**
+ * Outputs the javascript code if there are any messages on the
+ * 'javascript' message stack and if the 'notify_javascript' option is set.
+ *
+ * @param array &$messageStack The stack of messages.
+ * @param array $options An array of options. Options: 'noscript'
+ */
+ public function notify(&$messageStack, $options = array())
+ {
+ if (!count($messageStack)) {
+ return;
+ }
+
+ if (empty($options['noscript'])) {
+ echo '<script type="text/javascript">//<![CDATA[' . "\n";
+ }
+
+ $files = array();
+ while ($message = array_shift($messageStack)) {
+ $event = $this->getEvent($message);
+ if ($message['type'] == 'javascript') {
+ echo $event->getMessage() . "\n";
+ } elseif ($message['type'] == 'javascript-file') {
+ $files[] = $event->getMessage();
+ }
+ }
+
+ if (empty($options['noscript'])) {
+ echo "//]]></script>\n";
+ if (count($files)) {
+ foreach ($files as $file) {
+ echo '<script type="text/javascript" src="' . $file . '"></script>' . "\n";
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Notification_Listener_Mobile:: class provides functionality for
+ * displaying messages from the message stack on mobile devices.
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Notification
+ */
+class Horde_Notification_Listener_Mobile extends Horde_Notification_Listener_Status
+{
+ /**
+ * The Horde_Mobile:: object that status lines should be added to.
+ *
+ * @var Horde_Mobile
+ */
+ protected $_mobile;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->_handles = array(
+ 'horde.error' => _("ERR"),
+ 'horde.success' => _("SUCCESS"),
+ 'horde.warning' => _("WARN"),
+ 'horde.message' => _("MSG")
+ );
+ }
+
+ /**
+ * Associate a Horde_Mobile:: object with the listener.
+ *
+ * @param Horde_Mobile The Horde_Mobile:: object to send status lines to.
+ */
+ public function setMobileObject(&$mobile)
+ {
+ $this->_mobile = &$mobile;
+ }
+
+ /**
+ * Outputs the status line if there are any messages on the 'mobile'
+ * message stack.
+ *
+ * @param array &$messageStack The stack of messages.
+ * @param array $options An array of options. Options: 'nospace'
+ */
+ public function notify(&$messageStack, $options = array())
+ {
+ if (!$this->_mobile) {
+ $p = new Horde_Notification_Listener_Status();
+ return $p->notify($messageStack, $options);
+ }
+
+ if (count($messageStack)) {
+ while ($message = array_shift($messageStack)) {
+ $this->getMessage($message);
+ }
+ $t = &$this->_mobile->add(new Horde_Mobile_text("\n"));
+ $t->set('linebreaks', true);
+ }
+ }
+
+ /**
+ * Outputs one message.
+ *
+ * @param array $message One message hash from the stack.
+ */
+ public function getMessage($message)
+ {
+ if (!$this->_mobile) {
+ $p = new Horde_Notification_Listener_Status();
+ return $p->notify($messageStack, $options);
+ }
+
+ $event = $this->getEvent($message);
+ $this->_mobile->add(new Horde_Mobile_text($this->_handles[$message['type']] . ': ' . strip_tags($event->getMessage())));
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Notification_Listener_Status:: class provides functionality for
+ * displaying messages from the message stack as a status line.
+ *
+ * Copyright 2001-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Jan Schneider <jan@horde.org>
+ * @package Horde_Notification
+ */
+class Horde_Notification_Listener_Status extends Horde_Notification_Listener
+{
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->_handles = array(
+ 'horde.error' => array('alerts/error.png', _("Error")),
+ 'horde.success' => array('alerts/success.png', _("Success")),
+ 'horde.warning' => array('alerts/warning.png', _("Warning")),
+ 'horde.message' => array('alerts/message.png', _("Message")),
+ 'horde.alarm' => array('alerts/alarm.png', _("Alarm"))
+ );
+ }
+
+ /**
+ * Outputs the status line if there are any messages on the 'status'
+ * message stack.
+ *
+ * @param array &$messageStack The stack of messages.
+ * @param array $options An array of options.
+ */
+ public function notify(&$messageStack, $options = array())
+ {
+ if (count($messageStack)) {
+ echo '<ul class="notices">';
+ while ($message = array_shift($messageStack)) {
+ $message = $this->getMessage($message);
+ $message = preg_replace('/^<p class="notice">(.*)<\/p>$/', '<li>$1</li>', $message);
+ echo $message;
+ }
+ echo '</ul>';
+ }
+ }
+
+ /**
+ * Outputs one message.
+ *
+ * @param array $message One message hash from the stack.
+ */
+ public function getMessage($message)
+ {
+ $event = $this->getEvent($message);
+ $text = $event->getMessage();
+
+ if (!in_array('content.raw', $this->getFlags($message))) {
+ $text = htmlspecialchars($text);
+ }
+
+ return '<li>' . Horde::img($this->_handles[$message['type']][0], $this->_handles[$message['type']][1], '', $GLOBALS['registry']->getImageDir('horde')) . $text . '</li>';
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Notification</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde Notification System</summary>
+ <description>The Horde_Notification:: class provides a subject-observer pattern for raising and showing messages of different types and to different listeners.
+ </description>
+ <lead>
+ <name>Jan Schneider</name>
+ <user>jan</user>
+ <email>jan@horde.org</email>
+ <active>yes</active>
+ </lead>
+ <date>2009-06-24</date>
+ <version>
+ <release>0.1.0</release>
+ <api>0.1.0</api>
+ </version>
+ <stability>
+ <release>beta</release>
+ <api>beta</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Initial Horde 4 package.
+ </notes>
+ <contents>
+ <dir name="/">
+ <dir name="lib">
+ <dir name="Horde">
+ <dir name="Notification">
+ <dir name="Listener">
+ <file name="Audio.php" role="php" />
+ <file name="Javascript.php" role="php" />
+ <file name="Mobile.php" role="php" />
+ <file name="Status.php" role="php" />
+ </dir> <!-- /lib/Horde/Notification/Listener -->
+ <file name="Event.php" role="php" />
+ <file name="Listener.php" role="php" />
+ </dir> <!-- /lib/Horde/Notification -->
+ <file baseinstalldir="/Horde" name="Notification.php" role="php" />
+ </dir> <!-- /lib/Horde -->
+ </dir> <!-- /lib -->
+ </dir> <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.0</min>
+ </php>
+ <pearinstaller>
+ <min>1.5.4</min>
+ </pearinstaller>
+ <extension>
+ <name>gettext</name>
+ </extension>
+ </required>
+ <optional>
+ <package>
+ <name>Alarm</name>
+ <channel>pear.horde.org</channel>
+ </package>
+ <package>
+ <name>Auth</name>
+ <channel>pear.horde.org</channel>
+ </package>
+ </optional>
+ </dependencies>
+ <phprelease>
+ <filelist>
+ <install name="lib/Horde/Notification/Listener/Audio.php" as="Horde/Notification/Listener/Audio.php" />
+ <install name="lib/Horde/Notification/Listener/Javascript.php" as="Horde/Notification/Listener/Javascript.php" />
+ <install name="lib/Horde/Notification/Listener/Mobile.php" as="Horde/Notification/Listener/Mobile.php" />
+ <install name="lib/Horde/Notification/Listener/Status.php" as="Horde/Notification/Listener/Status.php" />
+ <install name="lib/Horde/Notification/Event.php" as="Horde/Notification/Event.php" />
+ <install name="lib/Horde/Notification/Listener.php" as="Horde/Notification/Listener.php" />
+ <install name="lib/Horde/Notification.php" as="Horde/Notification.php" />
+ </filelist>
+ </phprelease>
+ <changelog>
+ <release>
+ <date>2006-05-08</date>
+ <time>22:52:05</time>
+ <version>
+ <release>0.0.2</release>
+ <api>0.0.2</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>Converted to package.xml 2.0 for pear.horde.org
+ </notes>
+ </release>
+ <release>
+ <version>
+ <release>0.0.1</release>
+ <api>0.0.1</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <date>2003-07-05</date>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>Initial release as a PEAR package
+ </notes>
+ </release>
+ </changelog>
+</package>