Further cleanup for Horde 4. Renamed XML classes to Xml. Some tests still failing...
authorGunnar Wrobel <p@rdus.de>
Sun, 30 Aug 2009 21:17:37 +0000 (23:17 +0200)
committerGunnar Wrobel <p@rdus.de>
Sun, 30 Aug 2009 21:21:23 +0000 (23:21 +0200)
24 files changed:
framework/Kolab_Format/lib/Horde/Kolab/Format.php
framework/Kolab_Format/lib/Horde/Kolab/Format/XML.php [deleted file]
framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Annotation.php [deleted file]
framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Contact.php [deleted file]
framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Distributionlist.php [deleted file]
framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Event.php [deleted file]
framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Hprefs.php [deleted file]
framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Note.php [deleted file]
framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Task.php [deleted file]
framework/Kolab_Format/lib/Horde/Kolab/Format/Xml.php [new file with mode: 0644]
framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Annotation.php [new file with mode: 0644]
framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Contact.php [new file with mode: 0644]
framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Distributionlist.php [new file with mode: 0644]
framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Event.php [new file with mode: 0644]
framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Hprefs.php [new file with mode: 0644]
framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Note.php [new file with mode: 0644]
framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Task.php [new file with mode: 0644]
framework/Kolab_Format/package.xml
framework/Kolab_Format/test/Horde/Kolab/Format/ContactTest.php
framework/Kolab_Format/test/Horde/Kolab/Format/EventTest.php
framework/Kolab_Format/test/Horde/Kolab/Format/MimeAttrTest.php
framework/Kolab_Format/test/Horde/Kolab/Format/PreferencesTest.php
framework/Kolab_Format/test/Horde/Kolab/Format/RecurrenceTest.php
framework/Kolab_Format/test/Horde/Kolab/Format/XmlTest.php

index 37dc3f4..06e4222 100644 (file)
@@ -54,7 +54,7 @@ abstract class Horde_Kolab_Format
     static public function &factory($format_type = '', $object_type = '',
                                     $params = null)
     {
-        $class = 'Horde_Kolab_Format_' . $format_type;
+        $class = 'Horde_Kolab_Format_' . ucfirst(strtolower($format_type));
         if (class_exists($class)) {
             $driver = call_user_func(array($class, 'factory'), $object_type,
                                      $params);
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/XML.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/XML.php
deleted file mode 100644 (file)
index 269cb9b..0000000
+++ /dev/null
@@ -1,1420 +0,0 @@
-<?php
-/**
- * Implementation of the Kolab XML format.
- *
- * PHP version 5
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- */
-
-/**
- * Kolab XML to array hash converter.
- *
- * For implementing a new format type you will have to inherit this
- * class and provide a _load/_save function.
- *
- * Copyright 2007-2009 Klarälvdalens Datakonsult AB
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- * @since    Horde 3.2
- */
-class Horde_Kolab_Format_XML
-{
-
-    /**
-     * Defines a XML value that should get a default value if missing
-     */
-    const PRODUCT_ID = 'Horde::Kolab';
-
-    /**
-     * Defines a XML value that should get a default value if missing
-     */
-    const VALUE_DEFAULT = 0;
-
-    /**
-     * Defines a XML value that may be missing
-     */
-    const VALUE_MAYBE_MISSING = 1;
-
-    /**
-     * Defines a XML value that may not be missing
-     */
-    const VALUE_NOT_EMPTY = 2;
-
-    /**
-     * Defines a XML value that will be calculated by its own function
-     */
-    const VALUE_CALCULATED = 3;
-
-    /**
-     * Defines a XML value as string type
-     */
-    const TYPE_STRING = 0;
-
-    /**
-     * Defines a XML value as integer type
-     */
-    const TYPE_INTEGER = 1;
-
-    /**
-     * Defines a XML value as boolean type
-     */
-    const TYPE_BOOLEAN = 2;
-
-    /**
-     * Defines a XML value as date type
-     */
-    const TYPE_DATE = 3;
-
-    /**
-     * Defines a XML value as datetime type
-     */
-    const TYPE_DATETIME = 4;
-
-    /**
-     * Defines a XML value as date or datetime type
-     */
-    const TYPE_DATE_OR_DATETIME = 5;
-
-    /**
-     * Defines a XML value as color type
-     */
-    const TYPE_COLOR = 6;
-
-    /**
-     * Defines a XML value as composite value type
-     */
-    const TYPE_COMPOSITE = 7;
-
-    /**
-     * Defines a XML value as array type
-     */
-    const TYPE_MULTIPLE = 8;
-
-    /**
-     * Requested version of the data array to return
-     *
-     * @var int
-     */
-    protected $_version = 1;
-
-    /**
-     * The name of the resulting document.
-     *
-     * @var string
-     */
-    protected $_name = 'kolab.xml';
-
-    /**
-     * The XML document this driver works with.
-     *
-     * @var Horde_DOM_Document
-     *
-     * @todo Make protected (fix the XmlTest for that)
-     */
-    public $_xmldoc = null;
-
-    /**
-     * The name of the root element.
-     *
-     * @var string
-     */
-    protected $_root_name = 'kolab';
-
-    /**
-     * Kolab format version of the root element.
-     *
-     * @var string
-     */
-    protected $_root_version = '1.0';
-
-    /**
-     * Basic fields in any Kolab object
-     *
-     * @var array
-     */
-    protected $_fields_basic;
-
-    /**
-     * Automatically create categories if they are missing?
-     *
-     * @var boolean
-     */
-    protected $_create_categories = true;
-
-    /**
-     * Fields for a simple person
-     *
-     * @var array
-     *
-     * @todo Make protected (fix the XmlTest for that)
-     */
-    public $_fields_simple_person = array(
-        'type'    => self::TYPE_COMPOSITE,
-        'value'   => self::VALUE_MAYBE_MISSING,
-        'array'   => array(
-            'display-name' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => '',
-            ),
-            'smtp-address' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => '',
-            ),
-            'uid' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => '',
-            ),
-        ),
-    );
-
-    /**
-     * Fields for an attendee
-     *
-     * @var array
-     *
-     * @todo Make protected (fix the XmlTest for that)
-     */
-    public $_fields_attendee = array(
-        'type'    => self::TYPE_MULTIPLE,
-        'value'   => self::VALUE_DEFAULT,
-        'default' => array(),
-        'array'   => array(
-            'type'    => self::TYPE_COMPOSITE,
-            'value'   => self::VALUE_MAYBE_MISSING,
-            'array'   => array(
-                'display-name' => array(
-                    'type'    => self::TYPE_STRING,
-                    'value'   => self::VALUE_DEFAULT,
-                    'default' => '',
-                ),
-                'smtp-address' => array(
-                    'type'    => self::TYPE_STRING,
-                    'value'   => self::VALUE_DEFAULT,
-                    'default' => '',
-                ),
-                'status' => array(
-                    'type'    => self::TYPE_STRING,
-                    'value'   => self::VALUE_DEFAULT,
-                    'default' => 'none',
-                ),
-                'request-response' => array(
-                    'type'    => self::TYPE_BOOLEAN,
-                    'value'   => self::VALUE_DEFAULT,
-                    'default' => true,
-                ),
-                'role' => array(
-                    'type'    => self::TYPE_STRING,
-                    'value'   => self::VALUE_DEFAULT,
-                    'default' => 'required',
-                ),
-            ),
-        ),
-    );
-
-    /**
-     * Fields for a recurrence
-     *
-     * @var array
-     */
-    protected $_fields_recurrence = array(
-        // Attribute on root node: cycle
-        // Attribute on root node: type
-        'interval' => array(
-            'type'    => self::TYPE_INTEGER,
-            'value'   => self::VALUE_MAYBE_MISSING,
-        ),
-        'day' => array(
-            'type'    => self::TYPE_MULTIPLE,
-            'value'   => self::VALUE_MAYBE_MISSING,
-            'array'   => array(
-                'type' => self::TYPE_STRING,
-                'value' => self::VALUE_MAYBE_MISSING,
-            ),
-        ),
-        'daynumber' => array(
-            'type'    => self::TYPE_INTEGER,
-            'value'   => self::VALUE_MAYBE_MISSING,
-        ),
-        'month' => array(
-            'type'    => self::TYPE_STRING,
-            'value'   => self::VALUE_MAYBE_MISSING,
-        ),
-        // Attribute on range: type
-        'range' => array(
-            'type'    => self::TYPE_STRING,
-            'value'   => self::VALUE_DEFAULT,
-            'default' => '',
-        ),
-        'exclusion' => array(
-            'type'    => self::TYPE_MULTIPLE,
-            'value'   => self::VALUE_MAYBE_MISSING,
-            'array'   => array(
-                'type' => self::TYPE_STRING,
-                'value' => self::VALUE_MAYBE_MISSING,
-            ),
-        ),
-        'complete' => array(
-            'type'    => self::TYPE_MULTIPLE,
-            'value'   => self::VALUE_MAYBE_MISSING,
-            'array'   => array(
-                'type' => self::TYPE_STRING,
-                'value' => self::VALUE_MAYBE_MISSING,
-            ),
-        ),
-    );
-
-    /**
-     * Constructor
-     *
-     * @param array $params Any additional options
-     */
-    public function __construct($params = null)
-    {
-        if (is_array($params) && isset($params['version'])) {
-            $this->_version = $params['version'];
-        } else {
-            $this->_version = 1;
-        }
-
-        /* Generic fields, in kolab format specification order */
-        $this->_fields_basic = array(
-            'uid' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_NOT_EMPTY,
-            ),
-            'body' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => '',
-            ),
-            'categories' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => '',
-            ),
-            'creation-date' => array(
-                'type'    => self::TYPE_DATETIME,
-                'value'   => self::VALUE_CALCULATED,
-                'load'    => 'CreationDate',
-                'save'    => 'CreationDate',
-            ),
-            'last-modification-date' => array(
-                'type'    => self::TYPE_DATETIME,
-                'value'   => self::VALUE_CALCULATED,
-                'load'    => 'ModificationDate',
-                'save'    => 'ModificationDate',
-            ),
-            'sensitivity' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => 'public',
-            ),
-            'inline-attachment' => array(
-                'type'    => self::TYPE_MULTIPLE,
-                'value'   => self::VALUE_MAYBE_MISSING,
-                'array'   => array(
-                    'type'  => self::TYPE_STRING,
-                    'value' => self::VALUE_MAYBE_MISSING,
-                ),
-            ),
-            'link-attachment' => array(
-                'type'    => self::TYPE_MULTIPLE,
-                'value'   => self::VALUE_MAYBE_MISSING,
-                'array'   => array(
-                    'type'  => self::TYPE_STRING,
-                    'value' => self::VALUE_MAYBE_MISSING,
-                ),
-            ),
-            'product-id' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_CALCULATED,
-                'load'    => 'ProductId',
-                'save'    => 'ProductId',
-            ),
-        );
-    }
-
-    /**
-     * Attempts to return a concrete Horde_Kolab_Format_XML instance.
-     * based on $object_type.
-     *
-     * @param string    $object_type The object type that should be handled.
-     * @param array     $params      Any additional parameters.
-     *
-     * @return Horde_Kolab_Format_XML The newly created concrete
-     *                                Horde_Kolab_Format_XML instance.
-     *
-     * @throws Horde_Exception If the class for the object type could
-     *                         not be loaded.
-     */
-    public function &factory($object_type = '', $params = null)
-    {
-        $object_type = ucfirst(str_replace('-', '', $object_type));
-        $class       = 'Horde_Kolab_Format_XML_' . $object_type;
-
-        if (class_exists($class)) {
-            $driver = &new $class($params);
-        } else {
-            throw new Horde_Exception(sprintf(_("Failed to load Kolab XML driver %s"),
-                                              $object_type));
-        }
-
-        return $driver;
-    }
-
-    /**
-     * Return the name of the resulting document.
-     *
-     * @return string The name that may be used as filename.
-     */
-    public function getName()
-    {
-        return $this->_name;
-    }
-
-    /**
-     * Return the mime type of the resulting document.
-     *
-     * @return string The mime type of the result.
-     */
-    public function getMimeType()
-    {
-        return 'application/x-vnd.kolab.' . $this->_root_name;
-    }
-
-    /**
-     * Return the disposition of the resulting document.
-     *
-     * @return string The disportion of this document.
-     */
-    public function getDisposition()
-    {
-        return 'attachment';
-    }
-
-    /**
-     * Load an object based on the given XML string.
-     *
-     * @todo Check encoding of the returned array. It seems to be ISO-8859-1 at
-     * the moment and UTF-8 would seem more appropriate.
-     *
-     * @param string $xmltext The XML of the message as string.
-     *
-     * @return array The data array representing the object.
-     *
-     * @throws Horde_Exception If parsing the XML data failed.
-     */
-    public function load(&$xmltext)
-    {
-        try {
-            $this->_parseXml($xmltext);
-        } catch (DOMException $e) {
-            /**
-             * If the first call does not return successfully this might mean we
-             * got an attachment with broken encoding. There are some Kolab
-             * client versions in the wild that might have done that. So the
-             * next section starts a second attempt by guessing the encoding and
-             * trying again.
-             */
-            if (strcasecmp(mb_detect_encoding($xmltext,
-                                              'UTF-8, ISO-8859-1'), 'UTF-8') !== 0) {
-                $xmltext = mb_convert_encoding($xmltext, 'UTF-8', 'ISO-8859-1');
-            }
-            $this->_parseXml($xmltext);
-        }
-        if (empty($this->_xmldoc)) {
-            return false;
-        }
-
-        if (!$this->_xmldoc->hasChildNodes()) {
-            throw new Horde_Exception(_("No or unreadable content in Kolab XML object"));
-        }
-
-        // fresh object data
-        $object = array();
-
-        $result = $this->_loadArray($this->_xmldoc->childNodes, $this->_fields_basic);
-        $object = array_merge($object, $result);
-        $this->_loadMultipleCategories($object);
-
-        $result = $this->_load($this->_xmldoc->childNodes);
-        $object = array_merge($object, $result);
-
-        // uid is vital
-        if (!isset($object['uid'])) {
-            throw new Horde_Exception(_("UID not found in Kolab XML object"));
-        }
-
-        return $object;
-    }
-
-    /**
-     * Load the groupware object based on the specifc XML values.
-     *
-     * @param array $children An array of XML nodes.
-     *
-     * @return array The data array representing the object.
-     *
-     * @throws Horde_Exception If parsing the XML data failed.
-     */
-    protected function _load(&$children)
-    {
-        if (!empty($this->_fields_specific)) {
-            return $this->_loadArray($children, $this->_fields_specific);
-        } else {
-            return array();
-        }
-    }
-
-    /**
-     * Load an array with data from the XML nodes.
-     *
-     * @param array $object   The resulting data array.
-     * @param array $children An array of XML nodes.
-     * @param array $fields   The fields to populate in the object array.
-     *
-     * @return boolean True on success.
-     *
-     * @throws Horde_Exception If parsing the XML data failed.
-     */
-    protected function _loadArray(&$children, $fields)
-    {
-        $object = array();
-
-        // basic fields below the root node
-        foreach($fields as $field => $params) {
-            $result = $this->_getXmlData($children, $field, $params);
-            if (isset($result)) {
-                $object[$field] = $result;
-            }
-        }
-        return $object;
-    }
-
-    /**
-     * Get the text content of the named data node among the specified
-     * children.
-     *
-     * @param array  &$children The children to search.
-     * @param string $name      The name of the node to return.
-     * @param array  $params    Parameters for the data conversion.
-     *
-     * @return string The content of the specified node or an empty
-     *                string.
-     *
-     * @throws Horde_Exception If parsing the XML data failed.
-     *
-     * @todo Make protected (fix the XmlTest for that)
-     */
-    public function _getXmlData(&$children, $name, $params)
-    {
-        if ($params['type'] == self::TYPE_MULTIPLE) {
-            $result = array();
-            foreach($children as $child) {
-                if ($child->nodeType == XML_ELEMENT_NODE && $child->tagName == $name) {
-                    $value = $this->_getXmlData(array($child), $name,
-                                                $params['array']);
-                    $result[] = $value;
-                }
-            }
-            return $result;
-        }
-
-        $value   = null;
-        $missing = false;
-
-        // identify the child node
-        $child = $this->_findNode($children, $name);
-
-        // Handle empty values
-        if (!$child) {
-            if ($params['value'] == self::VALUE_MAYBE_MISSING) {
-                // 'MAYBE_MISSING' means we should return null
-                return null;
-            } elseif ($params['value'] == self::VALUE_NOT_EMPTY) {
-                // May not be empty. Return an error
-                throw new Horde_Exception(sprintf(_("Data value for %s is empty in Kolab XML object!"),
-                                                  $name));
-            } elseif ($params['value'] == self::VALUE_DEFAULT) {
-                // Return the default
-                return $params['default'];
-            } elseif ($params['value'] == self::VALUE_CALCULATED) {
-                $missing = true;
-            }
-        }
-
-        // Do we need to calculate the value?
-        if ($params['value'] == self::VALUE_CALCULATED && isset($params['load'])) {
-            if (method_exists($this, '_load' . $params['load'])) {
-                $value = call_user_func(array($this, '_load' . $params['load']),
-                                        $child, $missing);
-            } else {
-                throw new Horde_Exception(sprintf("Kolab XML: Missing function %s!",
-                                                  $params['load']));
-            }
-        } elseif ($params['type'] == self::TYPE_COMPOSITE) {
-            return $this->_loadArray($child->childNodes, $params['array']);
-        } else {
-            return $this->_loadDefault($child, $params);
-        }
-
-        // Nothing specified. Return the value as it is.
-        return $value;
-    }
-
-    /**
-     * Parse the XML string. The root node is returned on success.
-     *
-     * @param string $xmltext The XML of the message as string.
-     *
-     * @return NULL
-     *
-     * @throws Horde_Exception If parsing the XML data failed.
-     *
-     * @todo Make protected (fix the XmlTest for that)
-     */
-    public function _parseXml(&$xmltext)
-    {
-        $this->_xmldoc = new DOMDocument();
-        $this->_xmldoc->preserveWhiteSpace = false;
-        $this->_xmldoc->formatOutput = true;
-        $this->_xmldoc->loadXML($xmltext);
-    }
-
-    /**
-     * Convert the data to a XML string.
-     *
-     * @param array $attributes  The data array representing the note.
-     *
-     * @return string The data as XML string.
-     *
-     * @throws Horde_Exception If converting the data to XML failed.
-     */
-    public function save($object)
-    {
-        $root = $this->_prepareSave();
-
-        $this->_saveMultipleCategories($object);
-        $this->_saveArray($root, $object, $this->_fields_basic);
-        $this->_save($root, $object);
-
-        return $this->_xmldoc->saveXML();
-    }
-
-    /**
-     * Save the specific XML values.
-     *
-     * @param array &$root    The XML document root.
-     * @param array $object   The resulting data array.
-     *
-     * @return boolean True on success.
-     *
-     * @throws Horde_Exception If converting the data to XML failed.
-     */
-    protected function _save(&$root, $object)
-    {
-        if (!empty($this->_fields_specific)) {
-            $this->_saveArray($root, $object, $this->_fields_specific);
-        }
-        return true;
-    }
-
-    /**
-     * Creates a new XML document if necessary.
-     *
-     * @param string $xmltext  The XML of the message as string.
-     *
-     * @return Horde_DOM_Node The root node of the document.
-     *
-     * @todo Make protected (fix the XmlTest for that)
-     */
-    public function &_prepareSave()
-    {
-        if (empty($this->_xmldoc)) {
-            // create new XML
-            $this->_xmldoc = new DOMDocument();
-            $this->_xmldoc->preserveWhiteSpace = false;
-            $this->_xmldoc->formatOutput = true;
-            $root = $this->_xmldoc->createElement($this->_root_name);
-            $root = $this->_xmldoc->appendChild($root);
-            $root->setAttribute('version', $this->_root_version);
-        }
-        return $this->_xmldoc;
-    }
-
-    /**
-     * Save a data array to XML nodes.
-     *
-     * @param array   $root     The XML document root.
-     * @param array   $object   The data array.
-     * @param array   $fields   The fields to write into the XML object.
-     * @param boolean $append   Should the nodes be appended?
-     *
-     * @return boolean True on success.
-     *
-     * @throws Horde_Exception If converting the data to XML failed.
-     */
-    protected function _saveArray($root, $object, $fields, $append = false)
-    {
-        // basic fields below the root node
-        foreach($fields as $field => $params) {
-            $this->_updateNode($root, $object, $field, $params, $append);
-        }
-        return true;
-    }
-
-    /**
-     * Update the specified node.
-     *
-     * @param Horde_DOM_Node $parent_node  The parent node of the node that
-     *                                     should be updated.
-     * @param array          $attributes   The data array that holds all
-     *                                     attribute values.
-     * @param string         $name         The name of the the attribute
-     *                                     to be updated.
-     * @param array          $params       Parameters for saving the node
-     * @param boolean        $append       Should the node be appended?
-     *
-     * @return Horde_DOM_Node The new/updated child node.
-     *
-     * @throws Horde_Exception If converting the data to XML failed.
-     *
-     * @todo Make protected (fix the XmlTest for that)
-     */
-    public function _updateNode($parent_node, $attributes, $name, $params,
-                                   $append = false)
-    {
-        $value   = null;
-        $missing = false;
-
-        // Handle empty values
-        if (!isset($attributes[$name])) {
-            // Do we have information if this may be empty?
-            if ($params['value'] == self::VALUE_DEFAULT) {
-                // Use the default
-                $value = $params['default'];
-            } elseif ($params['value'] == self::VALUE_NOT_EMPTY) {
-                // May not be empty. Return an error
-                throw new Horde_Exception(sprintf(_("Data value for %s is empty in Kolab XML object!"),
-                                                  $name));
-            } elseif ($params['value'] == self::VALUE_MAYBE_MISSING) {
-                /**
-                 * 'MAYBE_MISSING' means we should not create an XML
-                 * node here
-                 */
-                $this->_removeNodes($parent_node, $name);
-                return false;
-            } elseif ($params['value'] == self::VALUE_CALCULATED) {
-                $missing = true;
-            }
-        } else {
-            $value = $attributes[$name];
-        }
-
-        if ($params['value'] == self::VALUE_CALCULATED) {
-            // Calculate the value
-            if (method_exists($this, '_save' . $params['save'])) {
-                return call_user_func(array($this, '_save' . $params['save']),
-                                      $parent_node, $name, $value, $missing);
-            } else {
-                throw new Horde_Exception(sprintf("Kolab XML: Missing function %s!",
-                                                  $params['save']));
-            }
-        } elseif ($params['type'] == self::TYPE_COMPOSITE) {
-            // Possibly remove the old node first
-            if (!$append) {
-                $this->_removeNodes($parent_node, $name);
-            }
-
-            // Create a new complex node
-            $composite_node = $this->_xmldoc->createElement($name);
-            $composite_node = $parent_node->appendChild($composite_node);
-            return $this->_saveArray($composite_node, $value, $params['array']);
-        } elseif ($params['type'] == self::TYPE_MULTIPLE) {
-            // Remove the old nodes first
-            $this->_removeNodes($parent_node, $name);
-
-            // Add the new nodes
-            foreach($value as $add_node) {
-                $this->_saveArray($parent_node,
-                                  array($name => $add_node),
-                                  array($name => $params['array']),
-                                  true);
-            }
-            return true;
-        } else {
-            return $this->_saveDefault($parent_node, $name, $value, $params,
-                                       $append);
-        }
-    }
-
-    /**
-     * Create a text node.
-     *
-     * @param Horde_DOM_Node  $parent   The parent of the new node.
-     * @param string          $name     The name of the child node to create.
-     * @param string          $value    The value of the child node to create.
-     *
-     * @return Horde_DOM_Node The new node.
-     */
-    protected function _createTextNode($parent, $name, $value)
-    {
-        $value = Horde_String::convertCharset($value, Horde_Nls::getCharset(), 'utf-8');
-
-        $node = $this->_xmldoc->createElement($name);
-
-        $node = $parent->appendChild($node);
-
-        // content
-        $text = $this->_xmldoc->createTextNode($value);
-        $text = $node->appendChild($text);
-
-        return $node;
-    }
-
-    /**
-     * Return the named node among a list of nodes.
-     *
-     * @param array  %$nodes The list of nodes.
-     * @param string $name   The name of the node to return.
-     *
-     * @return mixed The named Horde_DOM_Node or false if no node was found.
-     */
-    protected function _findNode(&$nodes, $name)
-    {
-        foreach($nodes as $node) {
-            if ($node->nodeType == XML_ELEMENT_NODE && $node->tagName == $name) {
-                return $node;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Retrieve a named child from a named parent if it has the given
-     * value.
-     *
-     * @param array  $nodes       The list of nodes.
-     * @param string $parent_name The name of the parent node.
-     * @param string $child_name  The name of the child node.
-     * @param string $value       The value of the child node
-     *
-     * @return mixed The specified Horde_DOM_Node or false if no node was found.
-     */
-    protected function _findNodeByChildData($nodes, $parent_name, $child_name,
-                                            $value)
-    {
-        foreach($nodes as $node)
-        {
-            if ($node->nodeType == XML_ELEMENT_NODE && $node->tagName == $parent_name) {
-                $children = $node->childNodes;
-                foreach ($children as $child)
-                    if ($child->nodeType == XML_ELEMENT_NODE
-                        && $child->tagName == $child_name
-                        && $child->textContent == $value) {
-                        return $node;
-                    }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Retrieve the content of a Horde_DOM_Node.
-     *
-     * @param Horde_DOM_Node  $nodes  The node that should be read.
-     *
-     * @return string The content of the node.
-     */
-    protected function _getNodeContent($node)
-    {
-        return Horde_String::convertCharset($node->textContent, 'utf-8');
-    }
-
-
-    /**
-     * Create a new named node on a parent node.
-     *
-     * @param Horde_DOM_Node $parent  The parent node.
-     * @param string         $name    The name of the new child node.
-     *
-     * @return Horde_DOM_Node The new child node.
-     */
-    protected function _createChildNode($parent, $name)
-    {
-        $node = $this->_xmldoc->createElement($name);
-        $node = $parent->appendChild($node);
-
-        return $node;
-    }
-
-    /**
-     * Remove named nodes from a parent node.
-     *
-     * @param Horde_DOM_Node $parent  The parent node.
-     * @param string         $name    The name of the children to be removed.
-     */
-    protected function _removeNodes($parent_node, $name)
-    {
-        while ($old_node = $this->_findNode($parent_node->childNodes, $name)) {
-            $parent_node->removeChild($old_node);
-        }
-    }
-
-    /**
-     * Create a new named node on a parent node if it is not already
-     * present in the given children.
-     *
-     * @param Horde_DOM_Node $parent    The parent node.
-     * @param array          $children  The children that might already
-     *                                  contain the node.
-     * @param string         $name      The name of the new child node.
-     *
-     * @return Horde_DOM_Node The new or already existing child node.
-     */
-    protected function _createOrFindChildNode($parent, $children, $name)
-    {
-        // look for existing node
-        $old_node = $this->_findNode($children, $name);
-        if ($old_node !== false) {
-            return $old_node;
-        }
-
-        // create new parent node
-        return $this->_createChildNode($parent, $name);
-    }
-
-    /**
-     * Load the different XML types.
-     *
-     * @param string $node    The node to load the data from
-     * @param array  $params  Parameters for loading the value
-     *
-     * @return string The loaded value.
-     *
-     * @throws Horde_Exception If converting the data from XML failed.
-     */
-    protected function _loadDefault($node, $params)
-    {
-        $content = $this->_getNodeContent($node);
-
-        switch($params['type']) {
-        case self::TYPE_DATE:
-            return Horde_Kolab_Format_Date::decodeDate($content);
-
-        case self::TYPE_DATETIME:
-            return Horde_Kolab_Format_Date::decodeDateTime($content);
-
-        case self::TYPE_DATE_OR_DATETIME:
-            return Horde_Kolab_Format_Date::decodeDateOrDateTime($content);
-
-        case self::TYPE_INTEGER:
-            return (int) $content;
-
-        case self::TYPE_BOOLEAN:
-            return (bool) $content;
-
-        default:
-            // Strings and colors are returned as they are
-            return $content;
-        }
-    }
-
-    /**
-     * Save a data array as a XML node attached to the given parent node.
-     *
-     * @param Horde_DOM_Node $parent_node The parent node to attach
-     *                                    the child to
-     * @param string         $name        The name of the node
-     * @param mixed          $value       The value to store
-     * @param boolean        $missing     Has the value been missing?
-     * @param boolean        $append      Should the node be appended?
-     *
-     * @return Horde_DOM_Node The new child node.
-     *
-     * @throws Horde_Exception If converting the data to XML failed.
-     */
-    protected function _saveDefault($parent_node, $name, $value, $params,
-                                    $append = false)
-    {
-        if (!$append) {
-            $this->_removeNodes($parent_node, $name);
-        }
-
-        switch ($params['type']) {
-        case self::TYPE_DATE:
-            $value = Horde_Kolab_Format_Date::encodeDate($value);
-            break;
-
-        case self::TYPE_DATETIME:
-        case self::TYPE_DATE_OR_DATETIME:
-            $value = Horde_Kolab_Format_Date::encodeDateTime($value);
-            break;
-
-        case self::TYPE_INTEGER:
-            $value = (string) $value;
-            break;
-
-        case self::TYPE_BOOLEAN:
-            if ($value) {
-                $value = 'true';
-            } else {
-                $value = 'false';
-            }
-
-            break;
-        }
-
-        // create the node
-        return $this->_createTextNode($parent_node, $name, $value);
-    }
-
-    /**
-     * Handle loading of categories. Preserve multiple categories in a hidden
-     * object field. Optionally creates categories unknown to the Horde user.
-     *
-     * @param array $object Array of strings, containing the 'categories' field.
-     *
-     * @return NULL
-     */
-    protected function _loadMultipleCategories(&$object)
-    {
-        global $prefs;
-
-        if (empty($object['categories'])) {
-            return;
-        }
-
-        // Create horde category if needed
-        @include_once 'Horde/Prefs/CategoryManager.php';
-        if ($this->_create_categories
-            && class_exists('Prefs_CategoryManager')
-            && isset($prefs) && is_a($prefs, 'Prefs')) {
-            $cManager         = new Prefs_CategoryManager();
-            $horde_categories = $cManager->get();
-        } else {
-            $cManager         = null;
-            $horde_categories = null;
-        }
-
-        $kolab_categories = explode (',', $object['categories']);
-
-        $primary_category = '';
-        foreach ($kolab_categories as $kolab_category) {
-            $kolab_category = trim($kolab_category);
-
-            $valid_category = true;
-            if ($cManager && 
-                array_search($kolab_category, $horde_categories) === false) {
-                // Unknown category -> Add
-                if ($cManager->add($kolab_category) === false) {
-                    // categories might be locked
-                    $valid_category = false;
-                }
-            }
-
-            // First valid category becomes primary category
-            if ($valid_category && empty($primary_category)) {
-                $primary_category = $kolab_category;
-            }
-        }
-
-        // Backup multiple categories
-        if (count($kolab_categories) > 1) {
-            $object['_categories_all']     = $object['categories'];
-            $object['_categories_primary'] = $primary_category;
-        }
-        // Make default category visible to Horde
-        $object['categories'] = $primary_category;
-    }
-
-    /**
-     * Preserve multiple categories on save if "categories" didn't change.
-     * The name "categories" currently refers to one primary category.
-     *
-     * @param array  $object Array of strings, containing the 'categories' field.
-     *
-     * @return NULL
-     */
-    protected function _saveMultipleCategories(&$object)
-    {
-        // Check for multiple categories.
-        if (!isset($object['_categories_all'])
-            || !isset($object['_categories_primary'])
-            || !isset($object['categories']))
-        {
-            return;
-        }
-
-        // Preserve multiple categories if "categories" didn't change
-        if ($object['_categories_primary'] == $object['categories']) {
-            $object['categories'] = $object['_categories_all'];
-        }
-    }
-
-    /**
-     * Load the object creation date.
-     *
-     * @param Horde_DOM_Node  $node    The original node if set.
-     * @param boolean         $missing Has the node been missing?
-     *
-     * @return string The creation date.
-     *
-     * @throws Horde_Exception If converting the data from XML failed.
-     */
-    protected function _loadCreationDate($node, $missing)
-    {
-        if ($missing) {
-            // Be gentle and accept a missing creation date.
-            return time();
-        }
-        return $this->_loadDefault($node,
-                                   array('type' => self::TYPE_DATETIME));
-    }
-
-    /**
-     * Save the object creation date.
-     *
-     * @param Horde_DOM_Node $parent_node The parent node to attach the child
-     *                                    to.
-     * @param string         $name        The name of the node.
-     * @param mixed          $value       The value to store.
-     * @param boolean        $missing     Has the value been missing?
-     *
-     * @return Horde_DOM_Node The new child node.
-     */
-    protected function _saveCreationDate($parent_node, $name, $value, $missing)
-    {
-        // Only create the creation date if it has not been set before
-        if ($missing) {
-            $value = time();
-        }
-        return $this->_saveDefault($parent_node,
-                                   $name,
-                                   $value,
-                                   array('type' => self::TYPE_DATETIME));
-    }
-
-    /**
-     * Load the object modification date.
-     *
-     * @param Horde_DOM_Node  $node    The original node if set.
-     * @param boolean         $missing Has the node been missing?
-     *
-     * @return string The last modification date.
-     */
-    protected function _loadModificationDate($node, $missing)
-    {
-        if ($missing) {
-            // Be gentle and accept a missing modification date.
-            return time();
-        }
-        return $this->_loadDefault($node,
-                                   array('type' => self::TYPE_DATETIME));
-    }
-
-    /**
-     * Save the object modification date.
-     *
-     * @param Horde_DOM_Node $parent_node The parent node to attach
-     *                                    the child to.
-     * @param string         $name        The name of the node.
-     * @param mixed          $value       The value to store.
-     * @param boolean        $missing     Has the value been missing?
-     *
-     * @return Horde_DOM_Node The new child node.
-     */
-    protected function _saveModificationDate($parent_node, $name, $value, $missing)
-    {
-        // Always store now as modification date
-        return $this->_saveDefault($parent_node,
-                                   $name,
-                                   time(),
-                                   array('type' => self::TYPE_DATETIME));
-    }
-
-    /**
-     * Load the name of the last client that modified this object
-     *
-     * @param Horde_DOM_Node  $node    The original node if set.
-     * @param boolean         $missing Has the node been missing?
-     *
-     * @return string The last modification date.
-     */
-    protected function _loadProductId($node, $missing)
-    {
-        if ($missing) {
-            // Be gentle and accept a missing product id
-            return '';
-        }
-        return $this->_getNodeContent($node);
-    }
-
-    /**
-     * Save the name of the last client that modified this object.
-     *
-     * @param Horde_DOM_Node $parent_node The parent node to attach
-     *                                    the child to.
-     * @param string         $name        The name of the node.
-     * @param mixed          $value       The value to store.
-     * @param boolean        $missing     Has the value been missing?
-     *
-     * @return Horde_DOM_Node The new child node.
-     */
-    protected function _saveProductId($parent_node, $name, $value, $missing)
-    {
-        // Always store now as modification date
-        return $this->_saveDefault($parent_node,
-                                   $name,
-                                   self::PRODUCT_ID,
-                                   array('type' => self::TYPE_STRING));
-    }
-
-    /**
-     * Load recurrence information.
-     *
-     * @param Horde_DOM_Node  $node    The original node if set.
-     * @param boolean         $missing Has the node been missing?
-     *
-     * @return array The recurrence information.
-     *
-     * @throws Horde_Exception If converting the data from XML failed.
-     */
-    protected function _loadRecurrence($node, $missing)
-    {
-        if ($missing) {
-            return null;
-        }
-
-        // Collect all child nodes
-        $children = $node->child_nodes();
-
-        $recurrence = $this->_loadArray($children, $this->_fields_recurrence);
-
-        // Get the cycle type (must be present)
-        $recurrence['cycle'] = $node->get_attribute('cycle');
-        // Get the sub type (may be present)
-        $recurrence['type'] = $node->get_attribute('type');
-
-        // Exclusions.
-        if (isset($recurrence['exclusion'])) {
-            $exceptions = array();
-            foreach($recurrence['exclusion'] as $exclusion) {
-                if (!empty($exclusion)) {
-                    list($year, $month, $mday) = sscanf($exclusion, '%04d-%02d-%02d');
-
-                    $exceptions[] = sprintf('%04d%02d%02d', $year, $month, $mday);
-                }
-            }
-            $recurrence['exceptions'] = $exceptions;
-        }
-
-        // Completed dates.
-        if (isset($recurrence['complete'])) {
-            $completions = array();
-            foreach($recurrence['complete'] as $complete) {
-                if (!empty($complete)) {
-                    list($year, $month, $mday) = sscanf($complete, '%04d-%02d-%02d');
-
-                    $completions[] = sprintf('%04d%02d%02d', $year, $month, $mday);
-                }
-            }
-            $recurrence['completions'] = $completions;
-        }
-
-        // Range is special
-        foreach($children as $child) {
-            if ($child->tagname == 'range') {
-                $recurrence['range-type'] = $child->get_attribute('type');
-            }
-        }
-
-        if (isset($recurrence['range']) && isset($recurrence['range-type'])
-            && $recurrence['range-type'] == 'date') {
-            $recurrence['range'] = Horde_Kolab_Format_Date::decodeDate($recurrence['range']);
-        }
-
-        // Sanity check
-        $valid = $this->_validateRecurrence($recurrence);
-
-        return $recurrence;
-    }
-
-    /**
-     * Validate recurrence hash information.
-     *
-     * @param array  $recurrence  Recurrence hash loaded from XML.
-     *
-     * @return boolean True on success.
-     *
-     * @throws Horde_Exception If the recurrence data is invalid.
-     */
-    protected function _validateRecurrence(&$recurrence)
-    {
-        if (!isset($recurrence['cycle'])) {
-              throw new Horde_Exception('recurrence tag error: cycle attribute missing');
-        }
-
-        if (!isset($recurrence['interval'])) {
-              throw new Horde_Exception('recurrence tag error: interval tag missing');
-        }
-        $interval = $recurrence['interval'];
-        if ($interval < 0) {
-            throw new Horde_Exception('recurrence tag error: interval cannot be below zero: '
-                                      . $interval);
-        }
-
-        if ($recurrence['cycle'] == 'weekly') {
-            // Check for <day>
-            if (!isset($recurrence['day']) || count($recurrence['day']) == 0) {
-                throw new Horde_Exception('recurrence tag error: day tag missing for weekly recurrence');
-            }
-        }
-
-        // The code below is only for monthly or yearly recurrences
-        if ($recurrence['cycle'] != 'monthly'
-            && $recurrence['cycle'] != 'yearly')
-            return true;
-
-        if (!isset($recurrence['type'])) {
-            throw new Horde_Exception('recurrence tag error: type attribute missing');
-        }
-
-        if (!isset($recurrence['daynumber'])) {
-            throw new Horde_Exception('recurrence tag error: daynumber tag missing');
-        }
-        $daynumber = $recurrence['daynumber'];
-        if ($daynumber < 0) {
-            throw new Horde_Exception('recurrence tag error: daynumber cannot be below zero: '
-                                      . $daynumber);
-        }
-
-        if ($recurrence['type'] == 'daynumber') {
-            if ($recurrence['cycle'] == 'yearly' && $daynumber > 366) {
-                throw new Horde_Exception('recurrence tag error: daynumber cannot be larger than 366 for yearly recurrences: ' . $daynumber);
-            } else if ($recurrence['cycle'] == 'monthly' && $daynumber > 31) {
-                throw new Horde_Exception('recurrence tag error: daynumber cannot be larger than 31 for monthly recurrences: ' . $daynumber);
-            }
-        } else if ($recurrence['type'] == 'weekday') {
-            // daynumber is the week of the month
-            if ($daynumber > 5) {
-                throw new Horde_Exception('recurrence tag error: daynumber cannot be larger than 5 for type weekday: ' . $daynumber);
-            }
-
-            // Check for <day>
-            if (!isset($recurrence['day']) || count($recurrence['day']) == 0) {
-                throw new Horde_Exception('recurrence tag error: day tag missing for type weekday');
-            }
-        }
-
-        if (($recurrence['type'] == 'monthday' || $recurrence['type'] == 'yearday')
-            && $recurrence['cycle'] == 'monthly')
-        {
-            throw new Horde_Exception('recurrence tag error: type monthday/yearday is only allowed for yearly recurrences');
-        }
-
-        if ($recurrence['cycle'] == 'yearly') {
-            if ($recurrence['type'] == 'monthday') {
-                // daynumber and month
-                if (!isset($recurrence['month'])) {
-                    throw new Horde_Exception('recurrence tag error: month tag missing for type monthday');
-                }
-                if ($daynumber > 31) {
-                    throw new Horde_Exception('recurrence tag error: daynumber cannot be larger than 31 for type monthday: ' . $daynumber);
-                }
-            } else if ($recurrence['type'] == 'yearday') {
-                if ($daynumber > 366) {
-                    throw new Horde_Exception('recurrence tag error: daynumber cannot be larger than 366 for type yearday: ' . $daynumber);
-                }
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Save recurrence information.
-     *
-     * @param Horde_DOM_Node $parent_node The parent node to attach
-     *                                    the child to.
-     * @param string         $name        The name of the node.
-     * @param mixed          $value       The value to store.
-     * @param boolean        $missing     Has the value been missing?
-     *
-     * @return Horde_DOM_Node The new child node.
-     */
-    protected function _saveRecurrence($parent_node, $name, $value, $missing)
-    {
-        $this->_removeNodes($parent_node, $name);
-
-        if (empty($value)) {
-            return false;
-        }
-
-        // Exclusions.
-        if (isset($value['exceptions'])) {
-            $exclusions = array();
-            foreach($value['exceptions'] as $exclusion) {
-                if (!empty($exclusion)) {
-                    list($year, $month, $mday) = sscanf($exclusion, '%04d%02d%02d');
-                    $exclusions[]              = "$year-$month-$mday";
-                }
-            }
-            $value['exclusion'] = $exclusions;
-        }
-
-        // Completed dates.
-        if (isset($value['completions'])) {
-            $completions = array();
-            foreach($value['completions'] as $complete) {
-                if (!empty($complete)) {
-                    list($year, $month, $mday) = sscanf($complete, '%04d%02d%02d');
-                    $completions[]             = "$year-$month-$mday";
-                }
-            }
-            $value['complete'] = $completions;
-        }
-
-        if (isset($value['range'])
-            && isset($value['range-type']) && $value['range-type'] == 'date') {
-            $value['range'] = Horde_Kolab_Format_Date::encodeDate($value['range']);
-        }
-
-        $r_node = $this->_xmldoc->createElement($name);
-        $r_node = $parent_node->appendChild($r_node);
-
-        // Save normal fields
-        $this->_saveArray($r_node, $value, $this->_fields_recurrence);
-
-        // Add attributes
-        $r_node->setAttribute('cycle', $value['cycle']);
-        if (isset($value['type'])) {
-            $r_node->setAttribute('type', $value['type']);
-        }
-
-        $child = $this->_findNode($r_node->childNodes, 'range');
-        if ($child) {
-            $child->setAttribute('type', $value['range-type']);
-        }
-
-        return $r_node;
-    }
-}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Annotation.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Annotation.php
deleted file mode 100644 (file)
index 61a14b5..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-<?php
-/**
- * Implementation for IMAP folder annotations in the Kolab XML format.
- *
- * PHP version 5
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- */
-
-/**
- * Kolab XML handler for IMAP folder annotations.
- *
- * Copyright 2008-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.gnu.org/licenses/old-licenses/lgpl-2.1.html.
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- * @since    Horde 3.2
- */
-class Horde_Kolab_Format_XML_Annotation extends Horde_Kolab_Format_XML
-{
-    /**
-     * Specific data fields for the prefs object
-     *
-     * @var Kolab
-     */
-    protected $_fields_specific;
-
-    /**
-     * Constructor
-     */
-    public function __construct()
-    {
-        $this->_root_name = 'annotations';
-
-        /**
-         * Specific preferences fields, in kolab format specification order
-         */
-        $this->_fields_specific = array(
-            'annotation' => array(
-                'type'    => self::TYPE_MULTIPLE,
-                'value'   => self::VALUE_MAYBE_MISSING,
-                'array'   => array(
-                    'type' => self::TYPE_STRING,
-                    'value' => self::VALUE_MAYBE_MISSING,
-                ),
-            ),
-        );
-
-        parent::__construct();
-    }
-
-    /**
-     * Load the groupware object based on the specifc XML values.
-     *
-     * @param array &$children An array of XML nodes.
-     *
-     * @return array Array with the object data
-     *
-     * @throws Horde_Exception If parsing the XML data failed.
-     */
-    protected function _load(&$children)
-    {
-        $object = $this->_loadArray($children, $this->_fields_specific);
-
-        $result = array();
-        foreach ($object['annotation'] as $annotation) {
-            list($key, $value)           = split('#', $annotation, 2);
-            $result[base64_decode($key)] = base64_decode($value);
-        }
-
-        return $result;
-    }
-
-    /**
-     * Save the specific XML values.
-     *
-     * @param array $root   The XML document root.
-     * @param array $object The resulting data array.
-     *
-     * @return boolean True on success.
-     *
-     * @throws Horde_Exception If converting the data to XML failed.
-     */
-    protected function _save($root, $object)
-    {
-        $annotations = array();
-        foreach ($object as $key => $value) {
-            if ($key != 'uid') {
-                $annotations['annotation'][] = base64_encode($key) .
-                    '#' . base64_encode($value);
-            }
-        }
-
-        return $this->_saveArray($root, $annotations, $this->_fields_specific);
-    }
-}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Contact.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Contact.php
deleted file mode 100644 (file)
index 44f4783..0000000
+++ /dev/null
@@ -1,525 +0,0 @@
-<?php
-/**
- * Implementation for contacts in the Kolab XML format.
- *
- * PHP version 5
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- */
-
-/**
- * Kolab XML handler for contact groupware objects
- *
- * Copyright 2007-2009 Klarälvdalens Datakonsult AB
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- * @since    Horde 3.2
- */
-class Horde_Kolab_Format_XML_Contact extends Horde_Kolab_Format_XML
-{
-    /**
-     * Specific data fields for the contact object
-     *
-     * @var array
-     */
-    protected $_fields_specific;
-
-    /**
-     * Structure of the name field
-     *
-     * @var array
-     */
-    protected $_fields_name = array(
-        'given-name' => array (
-            'type'    => self::TYPE_STRING,
-            'value'   => self::VALUE_MAYBE_MISSING,
-        ),
-        'middle-names' => array (
-            'type'    => self::TYPE_STRING,
-            'value'   => self::VALUE_MAYBE_MISSING,
-        ),
-        'last-name' => array (
-            'type'    => self::TYPE_STRING,
-            'value'   => self::VALUE_MAYBE_MISSING,
-        ),
-        'full-name' => array (
-            'type'    => self::TYPE_STRING,
-            'value'   => self::VALUE_MAYBE_MISSING,
-        ),
-        'initials' => array (
-            'type'    => self::TYPE_STRING,
-            'value'   => self::VALUE_MAYBE_MISSING,
-        ),
-        'prefix' => array (
-            'type'    => self::TYPE_STRING,
-            'value'   => self::VALUE_MAYBE_MISSING,
-        ),
-        'suffix' => array (
-            'type'    => self::TYPE_STRING,
-            'value'   => self::VALUE_MAYBE_MISSING,
-        )
-    );
-
-    /**
-     * Structure of an address field
-     *
-     * @var array
-     */
-    protected $_fields_address = array(
-        'type'    => self::TYPE_COMPOSITE,
-        'value'   => self::VALUE_MAYBE_MISSING,
-        'array'   => array(
-            'type' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => 'home',
-            ),
-            'street' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'locality' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'region' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'postal-code' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'country' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-        )
-    );
-
-    /**
-     * Structure of a phone field
-     *
-     * @var array
-     */
-    protected $_fields_phone = array(
-        'type'    => self::TYPE_COMPOSITE,
-        'value'   => self::VALUE_MAYBE_MISSING,
-        'array'   => array(
-            'type' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => '',
-            ),
-            'number' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-        ),
-    );
-
-    /**
-     * Address types
-     *
-     * @var array
-     */
-    protected $_address_types = array(
-        'business',
-        'home',
-        'other',
-    );
-
-    /**
-     * Phone types
-     *
-     * @var array
-     */
-    protected $_phone_types = array(
-        'business1',
-        'business2',
-        'businessfax',
-        'callback',
-        'car',
-        'company',
-        'home1',
-        'home2',
-        'homefax',
-        'isdn',
-        'mobile',
-        'pager',
-        'primary',
-        'radio',
-        'telex',
-        'ttytdd',
-        'assistant',
-        'other',
-    );
-
-    /**
-     * Constructor
-     */
-    public function __construct()
-    {
-        $this->_root_name = "contact";
-
-        /** Specific task fields, in kolab format specification order
-         */
-        $this->_fields_specific = array(
-            'name' => array (
-                'type'    => self::TYPE_COMPOSITE,
-                'value'   => self::VALUE_MAYBE_MISSING,
-                'array'   => $this->_fields_name,
-            ),
-            'free-busy-url' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'organization' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'web-page' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'im-address' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'department' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'office-location' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'profession' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'job-title' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'manager-name' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'assistant' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'nick-name' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'spouse-name' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'birthday' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'anniversary' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'picture' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'children' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'gender' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'language' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'address' => array(
-                'type'    => self::TYPE_MULTIPLE,
-                'value'   => self::VALUE_MAYBE_MISSING,
-                'array'   => $this->_fields_address,
-            ),
-            'email' => array (
-                'type'    => self::TYPE_MULTIPLE,
-                'value'   => self::VALUE_MAYBE_MISSING,
-                'array'   => $this->_fields_simple_person,
-            ),
-            'phone' => array(
-                'type'    => self::TYPE_MULTIPLE,
-                'value'   => self::VALUE_MAYBE_MISSING,
-                'array'   => $this->_fields_phone,
-            ),
-            'preferred-address' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'latitude' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'longitude' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            // Horde specific fields
-            'pgp-publickey' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            // Support for broken clients
-            'website' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'im-adress' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-        );
-
-        parent::__construct();
-    }
-
-    /**
-     * Load the groupware object based on the specifc XML values.
-     *
-     * @param array &$children An array of XML nodes.
-     *
-     * @return array Array with the object data.
-     *
-     * @throws Horde_Exception If parsing the XML data failed.
-     */
-    protected function _load(&$children)
-    {
-        $object = $this->_loadArray($children, $this->_fields_specific);
-
-        // Handle name fields
-        if (isset($object['name'])) {
-            $object = array_merge($object['name'], $object);
-            unset($object['name']);
-        }
-
-        // Handle email fields
-        $emails = array();
-        if (isset($object['email'])) {
-            foreach ($object['email'] as $email) {
-                $smtp_address = $email['smtp-address'];
-                if (!empty($smtp_address)) {
-                    $emails[] = $smtp_address;
-                }
-            }
-        }
-        $object['emails'] = implode(', ', $emails);
-
-        // Handle phone fields
-        if (isset($object['phone'])) {
-            foreach ($object['phone'] as $phone) {
-                if (isset($phone['number']) &&
-                    in_array($phone['type'], $this->_phone_types)) {
-                    $object["phone-" . $phone['type']] = $phone['number'];
-                }
-            }
-        }
-
-        // Handle address fields
-        if (isset($object['address'])) {
-            foreach ($object['address'] as $address) {
-                if (in_array($address['type'], $this->_address_types)) {
-                    foreach ($address as $name => $value) {
-                        $object["addr-" . $address['type'] . "-" . $name] = $value;
-                    }
-                }
-            }
-        }
-
-        // Handle gender field
-        if (isset($object['gender'])) {
-            $gender = $object['gender'];
-
-            if ($gender == "female") {
-                $object['gender'] = 1;
-            } else if ($gender == "male") {
-                $object['gender'] = 0;
-            } else {
-                // unspecified gender
-                unset($object['gender']);
-            }
-        }
-
-        // Compatibility with broken clients
-        $broken_fields = array("website" => "web-page",
-                               "im-adress" => "im-address");
-        foreach ($broken_fields as $broken_field => $real_field) {
-            if (!empty($object[$broken_field]) && empty($object[$real_field])) {
-                $object[$real_field] = $object[$broken_field];
-            }
-            unset($object[$broken_field]);
-        }
-
-        $object['__type'] = 'Object';
-
-        return $object;
-    }
-
-    /**
-     * Save the  specifc XML values.
-     *
-     * @param array $root   The XML document root.
-     * @param array $object The resulting data array.
-     *
-     * @return boolean True on success.
-     *
-     * @throws Horde_Exception If converting the data to XML failed.
-     */
-    protected function _save($root, $object)
-    {
-        // Handle name fields
-        $name = array();
-        foreach (array_keys($this->_fields_name) as $key) {
-            if (isset($object[$key])) {
-                $name[$key] = $object[$key];
-                unset($object[$key]);
-            }
-        }
-        $object['name'] = $name;
-
-        // Handle email fields
-        if (!isset($object['emails'])) {
-            $emails = array();
-        } else {
-            $emails = explode(',', $object['emails']);
-        }
-
-        if (isset($object['email']) && 
-            !in_array($object['email'], $emails)) {
-            $emails[] = $object['email'];
-        }
-
-        $object['email'] = array();
-
-        foreach ($emails as $email) {
-            $email = trim($email);
-            if (!empty($email)) {
-                $new_email = array('display-name' => $object['name']['full-name'],
-                                   'smtp-address' => $email);
-
-                $object['email'][] = $new_email;
-            }
-        }
-
-        // Handle phone fields
-        if (!isset($object['phone'])) {
-            $object['phone'] = array();
-        }
-        foreach ($this->_phone_types as $type) {
-            $key = 'phone-' . $type;
-            if (array_key_exists($key, $object)) {
-                $new_phone = array('type'   => $type,
-                                   'number' => $object[$key]);
-
-                // Update existing phone entry of this type
-                $updated = false;
-                foreach ($object['phone'] as $index => $phone) {
-                    if ($phone['type'] == $type) {
-                        $object['phone'][$index] = $new_phone;
-
-                        $updated = true;
-                        break;
-                    }
-                }
-                if (!$updated) {
-                    $object['phone'][] = $new_phone;
-                }
-            }
-        }
-
-        // Phone cleanup: remove empty numbers
-        foreach ($object['phone'] as $index => $phone) {
-            if (empty($phone['number'])) {
-                unset($object['phone'][$index]);
-            }
-        }
-
-        // Handle address fields
-        if (!isset($object['address'])) {
-            $object['address'] = array();
-        }
-
-        foreach ($this->_address_types as $type) {
-            $basekey     = 'addr-' . $type . '-';
-            $new_address = array('type'   => $type);
-            foreach (array_keys($this->_fields_address['array']) as $subkey) {
-                $key = $basekey . $subkey;
-                if (array_key_exists($key, $object)) {
-                    $new_address[$subkey] = $object[$key];
-                }
-            }
-
-            // Update existing address entry of this type
-            $updated = false;
-            foreach ($object['address'] as $index => $address) {
-                if ($address['type'] == $type) {
-                    $object['address'][$index] = $new_address;
-
-                    $updated = true;
-                }
-            }
-            if (!$updated) {
-                $object['address'][] = $new_address;
-            }
-        }
-
-        // Address cleanup: remove empty addresses
-        foreach ($object['address'] as $index => $address) {
-            $all_empty = true;
-            foreach ($address as $name => $value) {
-                if (!empty($value) && $name != "type") {
-                    $all_empty = false;
-                    break;
-                }
-            }
-
-            if ($all_empty) {
-                unset($object['address'][$index]);
-            }
-        }
-
-        // Handle gender field
-        if (isset($object['gender'])) {
-            $gender = $object['gender'];
-
-            if ($gender == "0") {
-                $object['gender'] = "male";
-            } else if ($gender == "1") {
-                $object['gender'] = "female";
-            } else {
-                // unspecified gender
-                unset($object['gender']);
-            }
-        }
-
-        // Do the actual saving
-        return $this->_saveArray($root, $object, $this->_fields_specific);
-    }
-}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Distributionlist.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Distributionlist.php
deleted file mode 100644 (file)
index 0e78468..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-<?php
-/**
- * Implementation for distributionlists in the Kolab XML format.
- *
- * PHP version 5
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- */
-
-/**
- * Kolab XML handler for distributionlist groupware objects
- *
- * Copyright 2007-2009 Klarälvdalens Datakonsult AB
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- * @since    Horde 3.2
- */
-class Horde_Kolab_Format_XML_Distributionlist extends Horde_Kolab_Format_XML
-{
-    /**
-     * Specific data fields for the contact object
-     *
-     * @var array
-     */
-    protected $_fields_specific;
-
-    /**
-     * Constructor
-     */
-    public function __construct()
-    {
-        $this->_root_name = "distribution-list";
-
-        /** Specific task fields, in kolab format specification order
-         */
-        $this->_fields_specific = array(
-                'display-name' => array(
-                    'type'    => self::TYPE_STRING,
-                    'value'   => self::VALUE_NOT_EMPTY
-                ),
-                'member' => array(
-                    'type'    => self::TYPE_MULTIPLE,
-                    'value'   => self::VALUE_MAYBE_MISSING,
-                    'array'   => $this->_fields_simple_person,
-                )
-            );
-
-        parent::__construct();
-    }
-
-    /**
-     * Load the groupware object based on the specifc XML values.
-     *
-     * @param array &$children An array of XML nodes.
-     *
-     * @return array Array with data.
-     *
-     * @throws Horde_Exception If parsing the XML data failed.
-     */
-    protected function _load(&$children)
-    {
-        $object = $this->_loadArray($children, $this->_fields_specific);
-
-        // Map the display-name of a kolab dist list to horde's lastname attribute
-        if (isset($object['display-name'])) {
-            $object['last-name'] = $object['display-name'];
-            unset($object['display-name']);
-        }
-
-        /**
-         * The mapping from $object['member'] as stored in XML back to
-         * Turba_Objects (contacts) must be performed in the
-         * Kolab_IMAP storage driver as we need access to the search
-         * facilities of the kolab storage driver.
-         */
-        $object['__type'] = 'Group';
-        return $object;
-    }
-
-    /**
-     * Save the  specifc XML values.
-     *
-     * @param array $root   The XML document root.
-     * @param array $object The resulting data array.
-     *
-     * @return boolean True on success.
-     *
-     * @throws Horde_Exception If converting the data to XML failed.
-     */
-    protected function _save($root, $object)
-    {
-        // Map the display-name of a kolab dist list to horde's lastname attribute
-        if (isset($object['last-name'])) {
-            $object['display-name'] = $object['last-name'];
-            unset($object['last-name']);
-        }
-
-        return $this->_saveArray($root, $object, $this->_fields_specific);
-    }
-}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Event.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Event.php
deleted file mode 100644 (file)
index 63b5df7..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-<?php
-/**
- * Implementation for events in the Kolab XML format.
- *
- * PHP version 5
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- */
-
-/**
- * Kolab XML handler for event groupware objects.
- *
- * Copyright 2007-2009 Klarälvdalens Datakonsult AB
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- * @since    Horde 3.2
- */
-class Horde_Kolab_Format_XML_Event extends Horde_Kolab_Format_XML
-{
-    /**
-     * Specific data fields for the contact object
-     *
-     * @var array
-     */
-    protected $_fields_specific;
-
-    /**
-     * Constructor
-     */
-    public function __construct()
-    {
-        $this->_root_name = 'event';
-
-        /** Specific event fields, in kolab format specification order
-         */
-        $this->_fields_specific = array(
-            'summary' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'location' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'organizer' => $this->_fields_simple_person,
-            'start-date' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_NOT_EMPTY,
-            ),
-            'alarm' => array(
-                'type'    => self::TYPE_INTEGER,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'recurrence' => array(
-                'type'    => self::TYPE_COMPOSITE,
-                'value'   => self::VALUE_CALCULATED,
-                'load'    => 'Recurrence',
-                'save'    => 'Recurrence',
-            ),
-            'attendee' => $this->_fields_attendee,
-            'show-time-as' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'color-label' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'end-date' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_NOT_EMPTY,
-            ),
-        );
-
-        parent::__construct();
-    }
-
-    /**
-     * Load event XML values and translate start/end date.
-     *
-     * @param array &$children An array of XML nodes.
-     *
-     * @return array Array with the object data.
-     *
-     * @throws Horde_Exception If parsing the XML data failed.
-     */
-    protected function _load(&$children)
-    {
-        $object = parent::_load($children);
-
-        // Translate start/end date including full day events
-        if (strlen($object['start-date']) == 10) {
-            $object['start-date'] = Horde_Kolab_Format_Date::decodeDate($object['start-date']);
-            $object['end-date']   = Horde_Kolab_Format_Date::decodeDate($object['end-date']) + 24*60*60;
-        } else {
-            $object['start-date'] = Horde_Kolab_Format_Date::decodeDateTime($object['start-date']);
-            $object['end-date']   = Horde_Kolab_Format_Date::decodeDateTime($object['end-date']);
-        }
-
-        return $object;
-    }
-
-    /**
-     * Save event XML values and translate start/end date.
-     *
-     * @param array $root   The XML document root.
-     * @param array $object The resulting data array.
-     *
-     * @return boolean True on success.
-     *
-     * @throws Horde_Exception If converting the data to XML failed.
-     */
-    protected function _save($root, $object)
-    {
-        // Translate start/end date including full day events
-        if (!empty($object['_is_all_day'])) {
-            $object['start-date'] = Horde_Kolab_Format_Date::encodeDate($object['start-date']);
-            $object['end-date']   = Horde_Kolab_Format_Date::encodeDate($object['end-date'] - 24*60*60);
-        } else {
-            $object['start-date'] = Horde_Kolab_Format_Date::encodeDateTime($object['start-date']);
-            $object['end-date']   = Horde_Kolab_Format_Date::encodeDateTime($object['end-date']);
-        }
-
-        return parent::_save($root, $object);
-    }
-}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Hprefs.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Hprefs.php
deleted file mode 100644 (file)
index 8f63c71..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-<?php
-/**
- * Implementation for horde user preferences in the Kolab XML format.
- *
- * PHP version 5
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- */
-
-/**
- * Kolab XML handler for client preferences.
- *
- * Copyright 2007-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.gnu.org/licenses/old-licenses/lgpl-2.1.html.
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- * @since    Horde 3.2
- */
-class Horde_Kolab_Format_XML_Hprefs extends Horde_Kolab_Format_XML
-{
-    /**
-     * Specific data fields for the prefs object
-     *
-     * @var Kolab
-     */
-    protected $_fields_specific;
-
-    /**
-     * Automatically create categories if they are missing?
-     *
-     * @var boolean
-     */
-    protected $_create_categories = false;
-
-    /**
-     * Constructor
-     */
-    public function __construct()
-    {
-        $this->_root_name = 'h-prefs';
-
-        /** Specific preferences fields, in kolab format specification order
-         */
-        $this->_fields_specific = array(
-            'application' => array (
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'pref' => array(
-                'type'    => self::TYPE_MULTIPLE,
-                'value'   => self::VALUE_MAYBE_MISSING,
-                'array'   => array(
-                    'type' => self::TYPE_STRING,
-                    'value' => self::VALUE_MAYBE_MISSING,
-                ),
-            ),
-        );
-
-        parent::__construct();
-    }
-
-    /**
-     * Load an object based on the given XML string.
-     *
-     * @param string &$xmltext The XML of the message as string.
-     *
-     * @return array The data array representing the object.
-     *
-     * @throws Horde_Exception If parsing the XML data failed.
-     */
-    public function load(&$xmltext)
-    {
-        $object = parent::load($xmltext);
-
-        if (empty($object['application'])) {
-            if (!empty($object['categories'])) {
-                $object['application'] = $object['categories'];
-                unset($object['categories']);
-            } else {
-                throw new Horde_Exception('Preferences XML object is missing an application setting.');
-            }
-        }
-
-        return $object;
-    }
-
-    /**
-     * Convert the data to a XML string.
-     *
-     * @param array $object The data array representing the note.
-     *
-     * @return string The data as XML string.
-     *
-     * @throws Horde_Exception If converting the data to XML failed.
-     */
-    public function save($object)
-    {
-        if (empty($object['application'])) {
-            if (!empty($object['categories'])) {
-                $object['application'] = $object['categories'];
-                unset($object['categories']);
-            } else {
-                throw new Horde_Exception('Preferences XML object is missing an application setting.');
-            }
-        }
-
-        return parent::save($object);
-    }
-}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Note.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Note.php
deleted file mode 100644 (file)
index c15bb64..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-<?php
-/**
- * Implementation for notes in the Kolab XML format.
- *
- * PHP version 5
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- */
-
-/**
- * Kolab XML handler for note groupware objects.
- *
- * Copyright 2007-2009 Klarälvdalens Datakonsult AB
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- * @since    Horde 3.2
- */
-class Horde_Kolab_Format_XML_Note extends Horde_Kolab_Format_XML
-{
-    /**
-     * Specific data fields for the note object
-     *
-     * @var Kolab
-     */
-    protected $_fields_specific;
-
-    /**
-     * Constructor
-     */
-    public function __construct()
-    {
-        $this->_root_name = 'note';
-
-        /** Specific note fields, in kolab format specification order
-         */
-        $this->_fields_specific = array(
-            'summary' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => '',
-            ),
-            'background-color' => array(
-                'type'    => self::TYPE_COLOR,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => '#000000',
-            ),
-            'foreground-color' => array(
-                'type'    => self::TYPE_COLOR,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => '#ffff00',
-            ),
-        );
-
-        parent::__construct();
-    }
-
-    /**
-     * Load the groupware object based on the specifc XML values.
-     *
-     * @param array &$children An array of XML nodes.
-     *
-     * @return array Array with the object data
-     *
-     * @throws Horde_Exception If parsing the XML data failed.
-     */
-    protected function _load(&$children)
-    {
-        $object = $this->_loadArray($children, $this->_fields_specific);
-
-        $object['desc'] = $object['summary'];
-        unset($object['summary']);
-
-        return $object;
-    }
-
-    /**
-     * Save the specific XML values.
-     *
-     * @param array $root   The XML document root.
-     * @param array $object The resulting data array.
-     *
-     * @return boolean True on success.
-     *
-     * @throws Horde_Exception If converting the data to XML failed.
-     */
-    protected function _save($root, $object)
-    {
-        $object['summary'] = $object['desc'];
-        unset($object['desc']);
-
-        return $this->_saveArray($root, $object, $this->_fields_specific);
-    }
-}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Task.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/XML/Task.php
deleted file mode 100644 (file)
index 48bb5bf..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-<?php
-/**
- * Implementation for tasks in the Kolab XML format.
- *
- * PHP version 5
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- */
-
-/**
- * Kolab XML handler for task groupware objects.
- *
- * Copyright 2007-2009 Klarälvdalens Datakonsult AB
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
- *
- * @category Kolab
- * @package  Kolab_Format
- * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author   Gunnar Wrobel <wrobel@pardus.de>
- * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
- * @link     http://pear.horde.org/index.php?package=Kolab_Server
- * @since    Horde 3.2
- */
-class Horde_Kolab_Format_XML_Task extends Horde_Kolab_Format_XML
-{
-    /**
-     * Specific data fields for the note object
-     *
-     * @var array
-     */
-    protected $_fields_specific;
-
-    /**
-     * Constructor
-     */
-    public function __construct()
-    {
-        $this->_root_name = 'task';
-
-        /** Specific task fields, in kolab format specification order
-         */
-        $this->_fields_specific = array(
-            'summary' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => '',
-            ),
-            'location' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => '',
-            ),
-            'creator'   => $this->_fields_simple_person,
-            'organizer' => $this->_fields_simple_person,
-            'start-date' => array(
-                'type'    => self::TYPE_DATE_OR_DATETIME,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'alarm' => array(
-                'type'    => self::TYPE_INTEGER,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'recurrence' => array(
-                'type'    => self::TYPE_COMPOSITE,
-                'value'   => self::VALUE_CALCULATED,
-                'load'    => 'Recurrence',
-                'save'    => 'Recurrence',
-            ),
-            'attendee' => $this->_fields_attendee,
-            'priority' => array(
-                'type'    => self::TYPE_INTEGER,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => 3,
-            ),
-            'completed' => array(
-                'type'    => self::TYPE_INTEGER,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => 0,
-            ),
-            'status' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_DEFAULT,
-                'default' => 'not-started',
-            ),
-            'due-date' => array(
-                'type'    => self::TYPE_DATE_OR_DATETIME,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'parent' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            // These are not part of the Kolab specification but it is
-            // ok if the client supports additional entries
-            'estimate' => array(
-                'type'    => self::TYPE_STRING,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-            'completed_date' => array(
-                'type'    => self::TYPE_DATE_OR_DATETIME,
-                'value'   => self::VALUE_MAYBE_MISSING,
-            ),
-        );
-
-        parent::__construct();
-    }
-
-    /**
-     * Load the groupware object based on the specifc XML values.
-     *
-     * @param array &$children An array of XML nodes.
-     *
-     * @return array Array with data.
-     *
-     * @throws Horde_Exception If parsing the XML data failed.
-     */
-    protected function _load(&$children)
-    {
-        $object = $this->_loadArray($children, $this->_fields_specific);
-
-        $object['name'] = $object['summary'];
-        unset($object['summary']);
-
-        if (empty($object['completed-date'])) {
-            $object['completed-date'] = null;
-        }
-
-        if (empty($object['alarm'])) {
-            $object['alarm'] = null;
-        }
-
-        if (isset($object['due-date'])) {
-            $object['due'] = $object['due-date'];
-            unset($object['due-date']);
-        } else {
-            $object['due'] = null;
-        }
-
-        if (isset($object['start-date'])) {
-            $object['start'] = $object['start-date'];
-            unset($object['start-date']);
-        } else {
-            $object['start'] = null;
-        }
-
-        if (!isset($object['estimate'])) {
-            $object['estimate'] = null;
-        } else {
-            $object['estimate'] = (float) $object['estimate'];
-        }
-
-        if (!isset($object['parent'])) {
-            $object['parent'] = null;
-        }
-
-        $object['completed'] = (bool) Kolab::percentageToBoolean($object['completed']);
-
-        if (isset($object['organizer']) && isset($object['organizer']['smtp-address'])) {
-            $object['assignee'] = $object['organizer']['smtp-address'];
-        }
-
-        return $object;
-    }
-
-    /**
-     * Save the specific XML values.
-     *
-     * @param array $root   The XML document root.
-     * @param array $object The resulting data array.
-     *
-     * @return boolean True on success.
-     *
-     * @throws Horde_Exception If converting the data to XML failed.
-     */
-    protected function _save($root, $object)
-    {
-        $object['summary'] = $object['name'];
-        unset($object['name']);
-
-        $object['due-date'] = $object['due'];
-        unset($object['due']);
-
-        $object['start-date'] = $object['start'];
-        unset($object['start']);
-
-        $object['estimate'] = number_format($object['estimate'], 2);
-
-        $object['completed'] = Kolab::BooleanToPercentage($object['completed']);
-
-        return $this->_saveArray($root, $object, $this->_fields_specific);
-    }
-}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml.php
new file mode 100644 (file)
index 0000000..9650af8
--- /dev/null
@@ -0,0 +1,1421 @@
+<?php
+/**
+ * Implementation of the Kolab XML format.
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * Kolab XML to array hash converter.
+ *
+ * For implementing a new format type you will have to inherit this
+ * class and provide a _load/_save function.
+ *
+ * Copyright 2007-2009 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ * @since    Horde 3.2
+ */
+class Horde_Kolab_Format_Xml
+{
+
+    /**
+     * Defines a XML value that should get a default value if missing
+     */
+    const PRODUCT_ID = 'Horde::Kolab';
+
+    /**
+     * Defines a XML value that should get a default value if missing
+     */
+    const VALUE_DEFAULT = 0;
+
+    /**
+     * Defines a XML value that may be missing
+     */
+    const VALUE_MAYBE_MISSING = 1;
+
+    /**
+     * Defines a XML value that may not be missing
+     */
+    const VALUE_NOT_EMPTY = 2;
+
+    /**
+     * Defines a XML value that will be calculated by its own function
+     */
+    const VALUE_CALCULATED = 3;
+
+    /**
+     * Defines a XML value as string type
+     */
+    const TYPE_STRING = 0;
+
+    /**
+     * Defines a XML value as integer type
+     */
+    const TYPE_INTEGER = 1;
+
+    /**
+     * Defines a XML value as boolean type
+     */
+    const TYPE_BOOLEAN = 2;
+
+    /**
+     * Defines a XML value as date type
+     */
+    const TYPE_DATE = 3;
+
+    /**
+     * Defines a XML value as datetime type
+     */
+    const TYPE_DATETIME = 4;
+
+    /**
+     * Defines a XML value as date or datetime type
+     */
+    const TYPE_DATE_OR_DATETIME = 5;
+
+    /**
+     * Defines a XML value as color type
+     */
+    const TYPE_COLOR = 6;
+
+    /**
+     * Defines a XML value as composite value type
+     */
+    const TYPE_COMPOSITE = 7;
+
+    /**
+     * Defines a XML value as array type
+     */
+    const TYPE_MULTIPLE = 8;
+
+    /**
+     * Requested version of the data array to return
+     *
+     * @var int
+     */
+    protected $_version = 1;
+
+    /**
+     * The name of the resulting document.
+     *
+     * @var string
+     */
+    protected $_name = 'kolab.xml';
+
+    /**
+     * The XML document this driver works with.
+     *
+     * @var Horde_DOM_Document
+     *
+     * @todo Make protected (fix the XmlTest for that)
+     */
+    public $_xmldoc = null;
+
+    /**
+     * The name of the root element.
+     *
+     * @var string
+     */
+    protected $_root_name = 'kolab';
+
+    /**
+     * Kolab format version of the root element.
+     *
+     * @var string
+     */
+    protected $_root_version = '1.0';
+
+    /**
+     * Basic fields in any Kolab object
+     *
+     * @var array
+     */
+    protected $_fields_basic;
+
+    /**
+     * Automatically create categories if they are missing?
+     *
+     * @var boolean
+     */
+    protected $_create_categories = true;
+
+    /**
+     * Fields for a simple person
+     *
+     * @var array
+     *
+     * @todo Make protected (fix the XmlTest for that)
+     */
+    public $_fields_simple_person = array(
+        'type'    => self::TYPE_COMPOSITE,
+        'value'   => self::VALUE_MAYBE_MISSING,
+        'array'   => array(
+            'display-name' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => '',
+            ),
+            'smtp-address' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => '',
+            ),
+            'uid' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => '',
+            ),
+        ),
+    );
+
+    /**
+     * Fields for an attendee
+     *
+     * @var array
+     *
+     * @todo Make protected (fix the XmlTest for that)
+     */
+    public $_fields_attendee = array(
+        'type'    => self::TYPE_MULTIPLE,
+        'value'   => self::VALUE_DEFAULT,
+        'default' => array(),
+        'array'   => array(
+            'type'    => self::TYPE_COMPOSITE,
+            'value'   => self::VALUE_MAYBE_MISSING,
+            'array'   => array(
+                'display-name' => array(
+                    'type'    => self::TYPE_STRING,
+                    'value'   => self::VALUE_DEFAULT,
+                    'default' => '',
+                ),
+                'smtp-address' => array(
+                    'type'    => self::TYPE_STRING,
+                    'value'   => self::VALUE_DEFAULT,
+                    'default' => '',
+                ),
+                'status' => array(
+                    'type'    => self::TYPE_STRING,
+                    'value'   => self::VALUE_DEFAULT,
+                    'default' => 'none',
+                ),
+                'request-response' => array(
+                    'type'    => self::TYPE_BOOLEAN,
+                    'value'   => self::VALUE_DEFAULT,
+                    'default' => true,
+                ),
+                'role' => array(
+                    'type'    => self::TYPE_STRING,
+                    'value'   => self::VALUE_DEFAULT,
+                    'default' => 'required',
+                ),
+            ),
+        ),
+    );
+
+    /**
+     * Fields for a recurrence
+     *
+     * @var array
+     */
+    protected $_fields_recurrence = array(
+        // Attribute on root node: cycle
+        // Attribute on root node: type
+        'interval' => array(
+            'type'    => self::TYPE_INTEGER,
+            'value'   => self::VALUE_MAYBE_MISSING,
+        ),
+        'day' => array(
+            'type'    => self::TYPE_MULTIPLE,
+            'value'   => self::VALUE_MAYBE_MISSING,
+            'array'   => array(
+                'type' => self::TYPE_STRING,
+                'value' => self::VALUE_MAYBE_MISSING,
+            ),
+        ),
+        'daynumber' => array(
+            'type'    => self::TYPE_INTEGER,
+            'value'   => self::VALUE_MAYBE_MISSING,
+        ),
+        'month' => array(
+            'type'    => self::TYPE_STRING,
+            'value'   => self::VALUE_MAYBE_MISSING,
+        ),
+        // Attribute on range: type
+        'range' => array(
+            'type'    => self::TYPE_STRING,
+            'value'   => self::VALUE_DEFAULT,
+            'default' => '',
+        ),
+        'exclusion' => array(
+            'type'    => self::TYPE_MULTIPLE,
+            'value'   => self::VALUE_MAYBE_MISSING,
+            'array'   => array(
+                'type' => self::TYPE_STRING,
+                'value' => self::VALUE_MAYBE_MISSING,
+            ),
+        ),
+        'complete' => array(
+            'type'    => self::TYPE_MULTIPLE,
+            'value'   => self::VALUE_MAYBE_MISSING,
+            'array'   => array(
+                'type' => self::TYPE_STRING,
+                'value' => self::VALUE_MAYBE_MISSING,
+            ),
+        ),
+    );
+
+    /**
+     * Constructor
+     *
+     * @param array $params Any additional options
+     */
+    public function __construct($params = null)
+    {
+        if (is_array($params) && isset($params['version'])) {
+            $this->_version = $params['version'];
+        } else {
+            $this->_version = 1;
+        }
+
+        /* Generic fields, in kolab format specification order */
+        $this->_fields_basic = array(
+            'uid' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_NOT_EMPTY,
+            ),
+            'body' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => '',
+            ),
+            'categories' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => '',
+            ),
+            'creation-date' => array(
+                'type'    => self::TYPE_DATETIME,
+                'value'   => self::VALUE_CALCULATED,
+                'load'    => 'CreationDate',
+                'save'    => 'CreationDate',
+            ),
+            'last-modification-date' => array(
+                'type'    => self::TYPE_DATETIME,
+                'value'   => self::VALUE_CALCULATED,
+                'load'    => 'ModificationDate',
+                'save'    => 'ModificationDate',
+            ),
+            'sensitivity' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => 'public',
+            ),
+            'inline-attachment' => array(
+                'type'    => self::TYPE_MULTIPLE,
+                'value'   => self::VALUE_MAYBE_MISSING,
+                'array'   => array(
+                    'type'  => self::TYPE_STRING,
+                    'value' => self::VALUE_MAYBE_MISSING,
+                ),
+            ),
+            'link-attachment' => array(
+                'type'    => self::TYPE_MULTIPLE,
+                'value'   => self::VALUE_MAYBE_MISSING,
+                'array'   => array(
+                    'type'  => self::TYPE_STRING,
+                    'value' => self::VALUE_MAYBE_MISSING,
+                ),
+            ),
+            'product-id' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_CALCULATED,
+                'load'    => 'ProductId',
+                'save'    => 'ProductId',
+            ),
+        );
+    }
+
+    /**
+     * Attempts to return a concrete Horde_Kolab_Format_Xml instance.
+     * based on $object_type.
+     *
+     * @param string    $object_type The object type that should be handled.
+     * @param array     $params      Any additional parameters.
+     *
+     * @return Horde_Kolab_Format_Xml The newly created concrete
+     *                                Horde_Kolab_Format_Xml instance.
+     *
+     * @throws Horde_Exception If the class for the object type could
+     *                         not be loaded.
+     */
+    public function &factory($object_type = '', $params = null)
+    {
+        $object_type = ucfirst(str_replace('-', '', $object_type));
+        $class       = 'Horde_Kolab_Format_Xml_' . $object_type;
+
+        if (class_exists($class)) {
+            $driver = &new $class($params);
+        } else {
+            throw new Horde_Exception(sprintf(_("Failed to load Kolab XML driver %s"),
+                                              $object_type));
+        }
+
+        return $driver;
+    }
+
+    /**
+     * Return the name of the resulting document.
+     *
+     * @return string The name that may be used as filename.
+     */
+    public function getName()
+    {
+        return $this->_name;
+    }
+
+    /**
+     * Return the mime type of the resulting document.
+     *
+     * @return string The mime type of the result.
+     */
+    public function getMimeType()
+    {
+        return 'application/x-vnd.kolab.' . $this->_root_name;
+    }
+
+    /**
+     * Return the disposition of the resulting document.
+     *
+     * @return string The disportion of this document.
+     */
+    public function getDisposition()
+    {
+        return 'attachment';
+    }
+
+    /**
+     * Load an object based on the given XML string.
+     *
+     * @todo Check encoding of the returned array. It seems to be ISO-8859-1 at
+     * the moment and UTF-8 would seem more appropriate.
+     *
+     * @param string $xmltext The XML of the message as string.
+     *
+     * @return array The data array representing the object.
+     *
+     * @throws Horde_Exception If parsing the XML data failed.
+     */
+    public function load(&$xmltext)
+    {
+        try {
+            $this->_parseXml($xmltext);
+        } catch (DOMException $e) {
+            /**
+             * If the first call does not return successfully this might mean we
+             * got an attachment with broken encoding. There are some Kolab
+             * client versions in the wild that might have done that. So the
+             * next section starts a second attempt by guessing the encoding and
+             * trying again.
+             */
+            if (strcasecmp(mb_detect_encoding($xmltext,
+                                              'UTF-8, ISO-8859-1'), 'UTF-8') !== 0) {
+                $xmltext = mb_convert_encoding($xmltext, 'UTF-8', 'ISO-8859-1');
+            }
+            $this->_parseXml($xmltext);
+        }
+        if (empty($this->_xmldoc)) {
+            return false;
+        }
+
+        if (!$this->_xmldoc->documentElement->hasChildNodes()) {
+            throw new Horde_Exception(_("No or unreadable content in Kolab XML object"));
+        }
+
+        // fresh object data
+        $object = array();
+
+        $result = $this->_loadArray($this->_xmldoc->documentElement->childNodes, $this->_fields_basic);
+        $object = array_merge($object, $result);
+        $this->_loadMultipleCategories($object);
+
+        $result = $this->_load($this->_xmldoc->documentElement->childNodes);
+        $object = array_merge($object, $result);
+
+        // uid is vital
+        if (!isset($object['uid'])) {
+            throw new Horde_Exception(_("UID not found in Kolab XML object"));
+        }
+
+        return $object;
+    }
+
+    /**
+     * Load the groupware object based on the specifc XML values.
+     *
+     * @param array $children An array of XML nodes.
+     *
+     * @return array The data array representing the object.
+     *
+     * @throws Horde_Exception If parsing the XML data failed.
+     */
+    protected function _load(&$children)
+    {
+        if (!empty($this->_fields_specific)) {
+            return $this->_loadArray($children, $this->_fields_specific);
+        } else {
+            return array();
+        }
+    }
+
+    /**
+     * Load an array with data from the XML nodes.
+     *
+     * @param array $object   The resulting data array.
+     * @param array $children An array of XML nodes.
+     * @param array $fields   The fields to populate in the object array.
+     *
+     * @return boolean True on success.
+     *
+     * @throws Horde_Exception If parsing the XML data failed.
+     */
+    protected function _loadArray(&$children, $fields)
+    {
+        $object = array();
+
+        // basic fields below the root node
+        foreach($fields as $field => $params) {
+            $result = $this->_getXmlData($children, $field, $params);
+            if (isset($result)) {
+                $object[$field] = $result;
+            }
+        }
+        return $object;
+    }
+
+    /**
+     * Get the text content of the named data node among the specified
+     * children.
+     *
+     * @param array  &$children The children to search.
+     * @param string $name      The name of the node to return.
+     * @param array  $params    Parameters for the data conversion.
+     *
+     * @return string The content of the specified node or an empty
+     *                string.
+     *
+     * @throws Horde_Exception If parsing the XML data failed.
+     *
+     * @todo Make protected (fix the XmlTest for that)
+     */
+    public function _getXmlData(&$children, $name, $params)
+    {
+        if ($params['type'] == self::TYPE_MULTIPLE) {
+            $result = array();
+            foreach($children as $child) {
+                if ($child->nodeType == XML_ELEMENT_NODE && $child->tagName == $name) {
+                    $child_a = array($child);
+                    $value = $this->_getXmlData($child_a, $name,
+                                                $params['array']);
+                    $result[] = $value;
+                }
+            }
+            return $result;
+        }
+
+        $value   = null;
+        $missing = false;
+
+        // identify the child node
+        $child = $this->_findNode($children, $name);
+
+        // Handle empty values
+        if (!$child) {
+            if ($params['value'] == self::VALUE_MAYBE_MISSING) {
+                // 'MAYBE_MISSING' means we should return null
+                return null;
+            } elseif ($params['value'] == self::VALUE_NOT_EMPTY) {
+                // May not be empty. Return an error
+                throw new Horde_Exception(sprintf(_("Data value for %s is empty in Kolab XML object!"),
+                                                  $name));
+            } elseif ($params['value'] == self::VALUE_DEFAULT) {
+                // Return the default
+                return $params['default'];
+            } elseif ($params['value'] == self::VALUE_CALCULATED) {
+                $missing = true;
+            }
+        }
+
+        // Do we need to calculate the value?
+        if ($params['value'] == self::VALUE_CALCULATED && isset($params['load'])) {
+            if (method_exists($this, '_load' . $params['load'])) {
+                $value = call_user_func(array($this, '_load' . $params['load']),
+                                        $child, $missing);
+            } else {
+                throw new Horde_Exception(sprintf("Kolab XML: Missing function %s!",
+                                                  $params['load']));
+            }
+        } elseif ($params['type'] == self::TYPE_COMPOSITE) {
+            return $this->_loadArray($child->childNodes, $params['array']);
+        } else {
+            return $this->_loadDefault($child, $params);
+        }
+
+        // Nothing specified. Return the value as it is.
+        return $value;
+    }
+
+    /**
+     * Parse the XML string. The root node is returned on success.
+     *
+     * @param string $xmltext The XML of the message as string.
+     *
+     * @return NULL
+     *
+     * @throws Horde_Exception If parsing the XML data failed.
+     *
+     * @todo Make protected (fix the XmlTest for that)
+     */
+    public function _parseXml(&$xmltext)
+    {
+        $this->_xmldoc = new DOMDocument();
+        $this->_xmldoc->preserveWhiteSpace = false;
+        $this->_xmldoc->formatOutput = true;
+        $this->_xmldoc->loadXML($xmltext);
+    }
+
+    /**
+     * Convert the data to a XML string.
+     *
+     * @param array $attributes  The data array representing the note.
+     *
+     * @return string The data as XML string.
+     *
+     * @throws Horde_Exception If converting the data to XML failed.
+     */
+    public function save($object)
+    {
+        $root = $this->_prepareSave();
+
+        $this->_saveMultipleCategories($object);
+        $this->_saveArray($root, $object, $this->_fields_basic);
+        $this->_save($root, $object);
+
+        return $this->_xmldoc->saveXML();
+    }
+
+    /**
+     * Save the specific XML values.
+     *
+     * @param array &$root    The XML document root.
+     * @param array $object   The resulting data array.
+     *
+     * @return boolean True on success.
+     *
+     * @throws Horde_Exception If converting the data to XML failed.
+     */
+    protected function _save(&$root, $object)
+    {
+        if (!empty($this->_fields_specific)) {
+            $this->_saveArray($root, $object, $this->_fields_specific);
+        }
+        return true;
+    }
+
+    /**
+     * Creates a new XML document if necessary.
+     *
+     * @param string $xmltext  The XML of the message as string.
+     *
+     * @return Horde_DOM_Node The root node of the document.
+     *
+     * @todo Make protected (fix the XmlTest for that)
+     */
+    public function &_prepareSave()
+    {
+        if (empty($this->_xmldoc)) {
+            // create new XML
+            $this->_xmldoc = new DOMDocument();
+            $this->_xmldoc->preserveWhiteSpace = false;
+            $this->_xmldoc->formatOutput = true;
+            $root = $this->_xmldoc->createElement($this->_root_name);
+            $this->_xmldoc->appendChild($root);
+            $root->setAttribute('version', $this->_root_version);
+        }
+        return $root;
+    }
+
+    /**
+     * Save a data array to XML nodes.
+     *
+     * @param array   $root     The XML document root.
+     * @param array   $object   The data array.
+     * @param array   $fields   The fields to write into the XML object.
+     * @param boolean $append   Should the nodes be appended?
+     *
+     * @return boolean True on success.
+     *
+     * @throws Horde_Exception If converting the data to XML failed.
+     */
+    protected function _saveArray($root, $object, $fields, $append = false)
+    {
+        // basic fields below the root node
+        foreach($fields as $field => $params) {
+            $this->_updateNode($root, $object, $field, $params, $append);
+        }
+        return true;
+    }
+
+    /**
+     * Update the specified node.
+     *
+     * @param Horde_DOM_Node $parent_node  The parent node of the node that
+     *                                     should be updated.
+     * @param array          $attributes   The data array that holds all
+     *                                     attribute values.
+     * @param string         $name         The name of the the attribute
+     *                                     to be updated.
+     * @param array          $params       Parameters for saving the node
+     * @param boolean        $append       Should the node be appended?
+     *
+     * @return Horde_DOM_Node The new/updated child node.
+     *
+     * @throws Horde_Exception If converting the data to XML failed.
+     *
+     * @todo Make protected (fix the XmlTest for that)
+     */
+    public function _updateNode($parent_node, $attributes, $name, $params,
+                                   $append = false)
+    {
+        $value   = null;
+        $missing = false;
+
+        // Handle empty values
+        if (!isset($attributes[$name])) {
+            // Do we have information if this may be empty?
+            if ($params['value'] == self::VALUE_DEFAULT) {
+                // Use the default
+                $value = $params['default'];
+            } elseif ($params['value'] == self::VALUE_NOT_EMPTY) {
+                // May not be empty. Return an error
+                throw new Horde_Exception(sprintf(_("Data value for %s is empty in Kolab XML object!"),
+                                                  $name));
+            } elseif ($params['value'] == self::VALUE_MAYBE_MISSING) {
+                /**
+                 * 'MAYBE_MISSING' means we should not create an XML
+                 * node here
+                 */
+                $this->_removeNodes($parent_node, $name);
+                return false;
+            } elseif ($params['value'] == self::VALUE_CALCULATED) {
+                $missing = true;
+            }
+        } else {
+            $value = $attributes[$name];
+        }
+
+        if ($params['value'] == self::VALUE_CALCULATED) {
+            // Calculate the value
+            if (method_exists($this, '_save' . $params['save'])) {
+                return call_user_func(array($this, '_save' . $params['save']),
+                                      $parent_node, $name, $value, $missing);
+            } else {
+                throw new Horde_Exception(sprintf("Kolab XML: Missing function %s!",
+                                                  $params['save']));
+            }
+        } elseif ($params['type'] == self::TYPE_COMPOSITE) {
+            // Possibly remove the old node first
+            if (!$append) {
+                $this->_removeNodes($parent_node, $name);
+            }
+
+            // Create a new complex node
+            $composite_node = $this->_xmldoc->createElement($name);
+            $composite_node = $parent_node->appendChild($composite_node);
+            return $this->_saveArray($composite_node, $value, $params['array']);
+        } elseif ($params['type'] == self::TYPE_MULTIPLE) {
+            // Remove the old nodes first
+            $this->_removeNodes($parent_node, $name);
+
+            // Add the new nodes
+            foreach($value as $add_node) {
+                $this->_saveArray($parent_node,
+                                  array($name => $add_node),
+                                  array($name => $params['array']),
+                                  true);
+            }
+            return true;
+        } else {
+            return $this->_saveDefault($parent_node, $name, $value, $params,
+                                       $append);
+        }
+    }
+
+    /**
+     * Create a text node.
+     *
+     * @param Horde_DOM_Node  $parent   The parent of the new node.
+     * @param string          $name     The name of the child node to create.
+     * @param string          $value    The value of the child node to create.
+     *
+     * @return Horde_DOM_Node The new node.
+     */
+    protected function _createTextNode($parent, $name, $value)
+    {
+        $value = Horde_String::convertCharset($value, Horde_Nls::getCharset(), 'utf-8');
+
+        $node = $this->_xmldoc->createElement($name);
+
+        $node = $parent->appendChild($node);
+
+        // content
+        $text = $this->_xmldoc->createTextNode($value);
+        $text = $node->appendChild($text);
+
+        return $node;
+    }
+
+    /**
+     * Return the named node among a list of nodes.
+     *
+     * @param array  %$nodes The list of nodes.
+     * @param string $name   The name of the node to return.
+     *
+     * @return mixed The named Horde_DOM_Node or false if no node was found.
+     */
+    protected function _findNode(&$nodes, $name)
+    {
+        foreach($nodes as $node) {
+            if ($node->nodeType == XML_ELEMENT_NODE && $node->tagName == $name) {
+                return $node;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Retrieve a named child from a named parent if it has the given
+     * value.
+     *
+     * @param array  $nodes       The list of nodes.
+     * @param string $parent_name The name of the parent node.
+     * @param string $child_name  The name of the child node.
+     * @param string $value       The value of the child node
+     *
+     * @return mixed The specified Horde_DOM_Node or false if no node was found.
+     */
+    protected function _findNodeByChildData($nodes, $parent_name, $child_name,
+                                            $value)
+    {
+        foreach($nodes as $node)
+        {
+            if ($node->nodeType == XML_ELEMENT_NODE && $node->tagName == $parent_name) {
+                $children = $node->childNodes;
+                foreach ($children as $child)
+                    if ($child->nodeType == XML_ELEMENT_NODE
+                        && $child->tagName == $child_name
+                        && $child->textContent == $value) {
+                        return $node;
+                    }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Retrieve the content of a Horde_DOM_Node.
+     *
+     * @param Horde_DOM_Node  $nodes  The node that should be read.
+     *
+     * @return string The content of the node.
+     */
+    protected function _getNodeContent($node)
+    {
+        return Horde_String::convertCharset($node->textContent, 'utf-8');
+    }
+
+
+    /**
+     * Create a new named node on a parent node.
+     *
+     * @param Horde_DOM_Node $parent  The parent node.
+     * @param string         $name    The name of the new child node.
+     *
+     * @return Horde_DOM_Node The new child node.
+     */
+    protected function _createChildNode($parent, $name)
+    {
+        $node = $this->_xmldoc->createElement($name);
+        $node = $parent->appendChild($node);
+
+        return $node;
+    }
+
+    /**
+     * Remove named nodes from a parent node.
+     *
+     * @param Horde_DOM_Node $parent  The parent node.
+     * @param string         $name    The name of the children to be removed.
+     */
+    protected function _removeNodes($parent_node, $name)
+    {
+        while ($old_node = $this->_findNode($parent_node->childNodes, $name)) {
+            $parent_node->removeChild($old_node);
+        }
+    }
+
+    /**
+     * Create a new named node on a parent node if it is not already
+     * present in the given children.
+     *
+     * @param Horde_DOM_Node $parent    The parent node.
+     * @param array          $children  The children that might already
+     *                                  contain the node.
+     * @param string         $name      The name of the new child node.
+     *
+     * @return Horde_DOM_Node The new or already existing child node.
+     */
+    protected function _createOrFindChildNode($parent, $children, $name)
+    {
+        // look for existing node
+        $old_node = $this->_findNode($children, $name);
+        if ($old_node !== false) {
+            return $old_node;
+        }
+
+        // create new parent node
+        return $this->_createChildNode($parent, $name);
+    }
+
+    /**
+     * Load the different XML types.
+     *
+     * @param string $node    The node to load the data from
+     * @param array  $params  Parameters for loading the value
+     *
+     * @return string The loaded value.
+     *
+     * @throws Horde_Exception If converting the data from XML failed.
+     */
+    protected function _loadDefault($node, $params)
+    {
+        $content = $this->_getNodeContent($node);
+
+        switch($params['type']) {
+        case self::TYPE_DATE:
+            return Horde_Kolab_Format_Date::decodeDate($content);
+
+        case self::TYPE_DATETIME:
+            return Horde_Kolab_Format_Date::decodeDateTime($content);
+
+        case self::TYPE_DATE_OR_DATETIME:
+            return Horde_Kolab_Format_Date::decodeDateOrDateTime($content);
+
+        case self::TYPE_INTEGER:
+            return (int) $content;
+
+        case self::TYPE_BOOLEAN:
+            return (bool) $content;
+
+        default:
+            // Strings and colors are returned as they are
+            return $content;
+        }
+    }
+
+    /**
+     * Save a data array as a XML node attached to the given parent node.
+     *
+     * @param Horde_DOM_Node $parent_node The parent node to attach
+     *                                    the child to
+     * @param string         $name        The name of the node
+     * @param mixed          $value       The value to store
+     * @param boolean        $missing     Has the value been missing?
+     * @param boolean        $append      Should the node be appended?
+     *
+     * @return Horde_DOM_Node The new child node.
+     *
+     * @throws Horde_Exception If converting the data to XML failed.
+     */
+    protected function _saveDefault($parent_node, $name, $value, $params,
+                                    $append = false)
+    {
+        if (!$append) {
+            $this->_removeNodes($parent_node, $name);
+        }
+
+        switch ($params['type']) {
+        case self::TYPE_DATE:
+            $value = Horde_Kolab_Format_Date::encodeDate($value);
+            break;
+
+        case self::TYPE_DATETIME:
+        case self::TYPE_DATE_OR_DATETIME:
+            $value = Horde_Kolab_Format_Date::encodeDateTime($value);
+            break;
+
+        case self::TYPE_INTEGER:
+            $value = (string) $value;
+            break;
+
+        case self::TYPE_BOOLEAN:
+            if ($value) {
+                $value = 'true';
+            } else {
+                $value = 'false';
+            }
+
+            break;
+        }
+
+        // create the node
+        return $this->_createTextNode($parent_node, $name, $value);
+    }
+
+    /**
+     * Handle loading of categories. Preserve multiple categories in a hidden
+     * object field. Optionally creates categories unknown to the Horde user.
+     *
+     * @param array $object Array of strings, containing the 'categories' field.
+     *
+     * @return NULL
+     */
+    protected function _loadMultipleCategories(&$object)
+    {
+        global $prefs;
+
+        if (empty($object['categories'])) {
+            return;
+        }
+
+        // Create horde category if needed
+        @include_once 'Horde/Prefs/CategoryManager.php';
+        if ($this->_create_categories
+            && class_exists('Prefs_CategoryManager')
+            && isset($prefs) && is_a($prefs, 'Prefs')) {
+            $cManager         = new Prefs_CategoryManager();
+            $horde_categories = $cManager->get();
+        } else {
+            $cManager         = null;
+            $horde_categories = null;
+        }
+
+        $kolab_categories = explode (',', $object['categories']);
+
+        $primary_category = '';
+        foreach ($kolab_categories as $kolab_category) {
+            $kolab_category = trim($kolab_category);
+
+            $valid_category = true;
+            if ($cManager && 
+                array_search($kolab_category, $horde_categories) === false) {
+                // Unknown category -> Add
+                if ($cManager->add($kolab_category) === false) {
+                    // categories might be locked
+                    $valid_category = false;
+                }
+            }
+
+            // First valid category becomes primary category
+            if ($valid_category && empty($primary_category)) {
+                $primary_category = $kolab_category;
+            }
+        }
+
+        // Backup multiple categories
+        if (count($kolab_categories) > 1) {
+            $object['_categories_all']     = $object['categories'];
+            $object['_categories_primary'] = $primary_category;
+        }
+        // Make default category visible to Horde
+        $object['categories'] = $primary_category;
+    }
+
+    /**
+     * Preserve multiple categories on save if "categories" didn't change.
+     * The name "categories" currently refers to one primary category.
+     *
+     * @param array  $object Array of strings, containing the 'categories' field.
+     *
+     * @return NULL
+     */
+    protected function _saveMultipleCategories(&$object)
+    {
+        // Check for multiple categories.
+        if (!isset($object['_categories_all'])
+            || !isset($object['_categories_primary'])
+            || !isset($object['categories']))
+        {
+            return;
+        }
+
+        // Preserve multiple categories if "categories" didn't change
+        if ($object['_categories_primary'] == $object['categories']) {
+            $object['categories'] = $object['_categories_all'];
+        }
+    }
+
+    /**
+     * Load the object creation date.
+     *
+     * @param Horde_DOM_Node  $node    The original node if set.
+     * @param boolean         $missing Has the node been missing?
+     *
+     * @return string The creation date.
+     *
+     * @throws Horde_Exception If converting the data from XML failed.
+     */
+    protected function _loadCreationDate($node, $missing)
+    {
+        if ($missing) {
+            // Be gentle and accept a missing creation date.
+            return time();
+        }
+        return $this->_loadDefault($node,
+                                   array('type' => self::TYPE_DATETIME));
+    }
+
+    /**
+     * Save the object creation date.
+     *
+     * @param Horde_DOM_Node $parent_node The parent node to attach the child
+     *                                    to.
+     * @param string         $name        The name of the node.
+     * @param mixed          $value       The value to store.
+     * @param boolean        $missing     Has the value been missing?
+     *
+     * @return Horde_DOM_Node The new child node.
+     */
+    protected function _saveCreationDate($parent_node, $name, $value, $missing)
+    {
+        // Only create the creation date if it has not been set before
+        if ($missing) {
+            $value = time();
+        }
+        return $this->_saveDefault($parent_node,
+                                   $name,
+                                   $value,
+                                   array('type' => self::TYPE_DATETIME));
+    }
+
+    /**
+     * Load the object modification date.
+     *
+     * @param Horde_DOM_Node  $node    The original node if set.
+     * @param boolean         $missing Has the node been missing?
+     *
+     * @return string The last modification date.
+     */
+    protected function _loadModificationDate($node, $missing)
+    {
+        if ($missing) {
+            // Be gentle and accept a missing modification date.
+            return time();
+        }
+        return $this->_loadDefault($node,
+                                   array('type' => self::TYPE_DATETIME));
+    }
+
+    /**
+     * Save the object modification date.
+     *
+     * @param Horde_DOM_Node $parent_node The parent node to attach
+     *                                    the child to.
+     * @param string         $name        The name of the node.
+     * @param mixed          $value       The value to store.
+     * @param boolean        $missing     Has the value been missing?
+     *
+     * @return Horde_DOM_Node The new child node.
+     */
+    protected function _saveModificationDate($parent_node, $name, $value, $missing)
+    {
+        // Always store now as modification date
+        return $this->_saveDefault($parent_node,
+                                   $name,
+                                   time(),
+                                   array('type' => self::TYPE_DATETIME));
+    }
+
+    /**
+     * Load the name of the last client that modified this object
+     *
+     * @param Horde_DOM_Node  $node    The original node if set.
+     * @param boolean         $missing Has the node been missing?
+     *
+     * @return string The last modification date.
+     */
+    protected function _loadProductId($node, $missing)
+    {
+        if ($missing) {
+            // Be gentle and accept a missing product id
+            return '';
+        }
+        return $this->_getNodeContent($node);
+    }
+
+    /**
+     * Save the name of the last client that modified this object.
+     *
+     * @param Horde_DOM_Node $parent_node The parent node to attach
+     *                                    the child to.
+     * @param string         $name        The name of the node.
+     * @param mixed          $value       The value to store.
+     * @param boolean        $missing     Has the value been missing?
+     *
+     * @return Horde_DOM_Node The new child node.
+     */
+    protected function _saveProductId($parent_node, $name, $value, $missing)
+    {
+        // Always store now as modification date
+        return $this->_saveDefault($parent_node,
+                                   $name,
+                                   self::PRODUCT_ID,
+                                   array('type' => self::TYPE_STRING));
+    }
+
+    /**
+     * Load recurrence information.
+     *
+     * @param Horde_DOM_Node  $node    The original node if set.
+     * @param boolean         $missing Has the node been missing?
+     *
+     * @return array The recurrence information.
+     *
+     * @throws Horde_Exception If converting the data from XML failed.
+     */
+    protected function _loadRecurrence($node, $missing)
+    {
+        if ($missing) {
+            return null;
+        }
+
+        // Collect all child nodes
+        $children = $node->childNodes;
+
+        $recurrence = $this->_loadArray($children, $this->_fields_recurrence);
+
+        // Get the cycle type (must be present)
+        $recurrence['cycle'] = $node->getAttribute('cycle');
+        // Get the sub type (may be present)
+        $recurrence['type'] = $node->getAttribute('type');
+
+        // Exclusions.
+        if (isset($recurrence['exclusion'])) {
+            $exceptions = array();
+            foreach($recurrence['exclusion'] as $exclusion) {
+                if (!empty($exclusion)) {
+                    list($year, $month, $mday) = sscanf($exclusion, '%04d-%02d-%02d');
+
+                    $exceptions[] = sprintf('%04d%02d%02d', $year, $month, $mday);
+                }
+            }
+            $recurrence['exceptions'] = $exceptions;
+        }
+
+        // Completed dates.
+        if (isset($recurrence['complete'])) {
+            $completions = array();
+            foreach($recurrence['complete'] as $complete) {
+                if (!empty($complete)) {
+                    list($year, $month, $mday) = sscanf($complete, '%04d-%02d-%02d');
+
+                    $completions[] = sprintf('%04d%02d%02d', $year, $month, $mday);
+                }
+            }
+            $recurrence['completions'] = $completions;
+        }
+
+        // Range is special
+        foreach($children as $child) {
+            if ($child->tagname == 'range') {
+                $recurrence['range-type'] = $child->get_attribute('type');
+            }
+        }
+
+        if (isset($recurrence['range']) && isset($recurrence['range-type'])
+            && $recurrence['range-type'] == 'date') {
+            $recurrence['range'] = Horde_Kolab_Format_Date::decodeDate($recurrence['range']);
+        }
+
+        // Sanity check
+        $valid = $this->_validateRecurrence($recurrence);
+
+        return $recurrence;
+    }
+
+    /**
+     * Validate recurrence hash information.
+     *
+     * @param array  $recurrence  Recurrence hash loaded from XML.
+     *
+     * @return boolean True on success.
+     *
+     * @throws Horde_Exception If the recurrence data is invalid.
+     */
+    protected function _validateRecurrence(&$recurrence)
+    {
+        if (!isset($recurrence['cycle'])) {
+              throw new Horde_Exception('recurrence tag error: cycle attribute missing');
+        }
+
+        if (!isset($recurrence['interval'])) {
+              throw new Horde_Exception('recurrence tag error: interval tag missing');
+        }
+        $interval = $recurrence['interval'];
+        if ($interval < 0) {
+            throw new Horde_Exception('recurrence tag error: interval cannot be below zero: '
+                                      . $interval);
+        }
+
+        if ($recurrence['cycle'] == 'weekly') {
+            // Check for <day>
+            if (!isset($recurrence['day']) || count($recurrence['day']) == 0) {
+                throw new Horde_Exception('recurrence tag error: day tag missing for weekly recurrence');
+            }
+        }
+
+        // The code below is only for monthly or yearly recurrences
+        if ($recurrence['cycle'] != 'monthly'
+            && $recurrence['cycle'] != 'yearly')
+            return true;
+
+        if (!isset($recurrence['type'])) {
+            throw new Horde_Exception('recurrence tag error: type attribute missing');
+        }
+
+        if (!isset($recurrence['daynumber'])) {
+            throw new Horde_Exception('recurrence tag error: daynumber tag missing');
+        }
+        $daynumber = $recurrence['daynumber'];
+        if ($daynumber < 0) {
+            throw new Horde_Exception('recurrence tag error: daynumber cannot be below zero: '
+                                      . $daynumber);
+        }
+
+        if ($recurrence['type'] == 'daynumber') {
+            if ($recurrence['cycle'] == 'yearly' && $daynumber > 366) {
+                throw new Horde_Exception('recurrence tag error: daynumber cannot be larger than 366 for yearly recurrences: ' . $daynumber);
+            } else if ($recurrence['cycle'] == 'monthly' && $daynumber > 31) {
+                throw new Horde_Exception('recurrence tag error: daynumber cannot be larger than 31 for monthly recurrences: ' . $daynumber);
+            }
+        } else if ($recurrence['type'] == 'weekday') {
+            // daynumber is the week of the month
+            if ($daynumber > 5) {
+                throw new Horde_Exception('recurrence tag error: daynumber cannot be larger than 5 for type weekday: ' . $daynumber);
+            }
+
+            // Check for <day>
+            if (!isset($recurrence['day']) || count($recurrence['day']) == 0) {
+                throw new Horde_Exception('recurrence tag error: day tag missing for type weekday');
+            }
+        }
+
+        if (($recurrence['type'] == 'monthday' || $recurrence['type'] == 'yearday')
+            && $recurrence['cycle'] == 'monthly')
+        {
+            throw new Horde_Exception('recurrence tag error: type monthday/yearday is only allowed for yearly recurrences');
+        }
+
+        if ($recurrence['cycle'] == 'yearly') {
+            if ($recurrence['type'] == 'monthday') {
+                // daynumber and month
+                if (!isset($recurrence['month'])) {
+                    throw new Horde_Exception('recurrence tag error: month tag missing for type monthday');
+                }
+                if ($daynumber > 31) {
+                    throw new Horde_Exception('recurrence tag error: daynumber cannot be larger than 31 for type monthday: ' . $daynumber);
+                }
+            } else if ($recurrence['type'] == 'yearday') {
+                if ($daynumber > 366) {
+                    throw new Horde_Exception('recurrence tag error: daynumber cannot be larger than 366 for type yearday: ' . $daynumber);
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Save recurrence information.
+     *
+     * @param Horde_DOM_Node $parent_node The parent node to attach
+     *                                    the child to.
+     * @param string         $name        The name of the node.
+     * @param mixed          $value       The value to store.
+     * @param boolean        $missing     Has the value been missing?
+     *
+     * @return Horde_DOM_Node The new child node.
+     */
+    protected function _saveRecurrence($parent_node, $name, $value, $missing)
+    {
+        $this->_removeNodes($parent_node, $name);
+
+        if (empty($value)) {
+            return false;
+        }
+
+        // Exclusions.
+        if (isset($value['exceptions'])) {
+            $exclusions = array();
+            foreach($value['exceptions'] as $exclusion) {
+                if (!empty($exclusion)) {
+                    list($year, $month, $mday) = sscanf($exclusion, '%04d%02d%02d');
+                    $exclusions[]              = "$year-$month-$mday";
+                }
+            }
+            $value['exclusion'] = $exclusions;
+        }
+
+        // Completed dates.
+        if (isset($value['completions'])) {
+            $completions = array();
+            foreach($value['completions'] as $complete) {
+                if (!empty($complete)) {
+                    list($year, $month, $mday) = sscanf($complete, '%04d%02d%02d');
+                    $completions[]             = "$year-$month-$mday";
+                }
+            }
+            $value['complete'] = $completions;
+        }
+
+        if (isset($value['range'])
+            && isset($value['range-type']) && $value['range-type'] == 'date') {
+            $value['range'] = Horde_Kolab_Format_Date::encodeDate($value['range']);
+        }
+
+        $r_node = $this->_xmldoc->createElement($name);
+        $r_node = $parent_node->appendChild($r_node);
+
+        // Save normal fields
+        $this->_saveArray($r_node, $value, $this->_fields_recurrence);
+
+        // Add attributes
+        $r_node->setAttribute('cycle', $value['cycle']);
+        if (isset($value['type'])) {
+            $r_node->setAttribute('type', $value['type']);
+        }
+
+        $child = $this->_findNode($r_node->childNodes, 'range');
+        if ($child) {
+            $child->setAttribute('type', $value['range-type']);
+        }
+
+        return $r_node;
+    }
+}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Annotation.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Annotation.php
new file mode 100644 (file)
index 0000000..7604cff
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+/**
+ * Implementation for IMAP folder annotations in the Kolab XML format.
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * Kolab XML handler for IMAP folder annotations.
+ *
+ * Copyright 2008-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.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ * @since    Horde 3.2
+ */
+class Horde_Kolab_Format_Xml_Annotation extends Horde_Kolab_Format_Xml
+{
+    /**
+     * Specific data fields for the prefs object
+     *
+     * @var Kolab
+     */
+    protected $_fields_specific;
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->_root_name = 'annotations';
+
+        /**
+         * Specific preferences fields, in kolab format specification order
+         */
+        $this->_fields_specific = array(
+            'annotation' => array(
+                'type'    => self::TYPE_MULTIPLE,
+                'value'   => self::VALUE_MAYBE_MISSING,
+                'array'   => array(
+                    'type' => self::TYPE_STRING,
+                    'value' => self::VALUE_MAYBE_MISSING,
+                ),
+            ),
+        );
+
+        parent::__construct();
+    }
+
+    /**
+     * Load the groupware object based on the specifc XML values.
+     *
+     * @param array &$children An array of XML nodes.
+     *
+     * @return array Array with the object data
+     *
+     * @throws Horde_Exception If parsing the XML data failed.
+     */
+    protected function _load(&$children)
+    {
+        $object = $this->_loadArray($children, $this->_fields_specific);
+
+        $result = array();
+        foreach ($object['annotation'] as $annotation) {
+            list($key, $value)           = split('#', $annotation, 2);
+            $result[base64_decode($key)] = base64_decode($value);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Save the specific XML values.
+     *
+     * @param array $root   The XML document root.
+     * @param array $object The resulting data array.
+     *
+     * @return boolean True on success.
+     *
+     * @throws Horde_Exception If converting the data to XML failed.
+     */
+    protected function _save($root, $object)
+    {
+        $annotations = array();
+        foreach ($object as $key => $value) {
+            if ($key != 'uid') {
+                $annotations['annotation'][] = base64_encode($key) .
+                    '#' . base64_encode($value);
+            }
+        }
+
+        return $this->_saveArray($root, $annotations, $this->_fields_specific);
+    }
+}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Contact.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Contact.php
new file mode 100644 (file)
index 0000000..ef66f2f
--- /dev/null
@@ -0,0 +1,525 @@
+<?php
+/**
+ * Implementation for contacts in the Kolab XML format.
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * Kolab XML handler for contact groupware objects
+ *
+ * Copyright 2007-2009 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ * @since    Horde 3.2
+ */
+class Horde_Kolab_Format_Xml_Contact extends Horde_Kolab_Format_Xml
+{
+    /**
+     * Specific data fields for the contact object
+     *
+     * @var array
+     */
+    protected $_fields_specific;
+
+    /**
+     * Structure of the name field
+     *
+     * @var array
+     */
+    protected $_fields_name = array(
+        'given-name' => array (
+            'type'    => self::TYPE_STRING,
+            'value'   => self::VALUE_MAYBE_MISSING,
+        ),
+        'middle-names' => array (
+            'type'    => self::TYPE_STRING,
+            'value'   => self::VALUE_MAYBE_MISSING,
+        ),
+        'last-name' => array (
+            'type'    => self::TYPE_STRING,
+            'value'   => self::VALUE_MAYBE_MISSING,
+        ),
+        'full-name' => array (
+            'type'    => self::TYPE_STRING,
+            'value'   => self::VALUE_MAYBE_MISSING,
+        ),
+        'initials' => array (
+            'type'    => self::TYPE_STRING,
+            'value'   => self::VALUE_MAYBE_MISSING,
+        ),
+        'prefix' => array (
+            'type'    => self::TYPE_STRING,
+            'value'   => self::VALUE_MAYBE_MISSING,
+        ),
+        'suffix' => array (
+            'type'    => self::TYPE_STRING,
+            'value'   => self::VALUE_MAYBE_MISSING,
+        )
+    );
+
+    /**
+     * Structure of an address field
+     *
+     * @var array
+     */
+    protected $_fields_address = array(
+        'type'    => self::TYPE_COMPOSITE,
+        'value'   => self::VALUE_MAYBE_MISSING,
+        'array'   => array(
+            'type' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => 'home',
+            ),
+            'street' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'locality' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'region' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'postal-code' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'country' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+        )
+    );
+
+    /**
+     * Structure of a phone field
+     *
+     * @var array
+     */
+    protected $_fields_phone = array(
+        'type'    => self::TYPE_COMPOSITE,
+        'value'   => self::VALUE_MAYBE_MISSING,
+        'array'   => array(
+            'type' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => '',
+            ),
+            'number' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+        ),
+    );
+
+    /**
+     * Address types
+     *
+     * @var array
+     */
+    protected $_address_types = array(
+        'business',
+        'home',
+        'other',
+    );
+
+    /**
+     * Phone types
+     *
+     * @var array
+     */
+    protected $_phone_types = array(
+        'business1',
+        'business2',
+        'businessfax',
+        'callback',
+        'car',
+        'company',
+        'home1',
+        'home2',
+        'homefax',
+        'isdn',
+        'mobile',
+        'pager',
+        'primary',
+        'radio',
+        'telex',
+        'ttytdd',
+        'assistant',
+        'other',
+    );
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->_root_name = "contact";
+
+        /** Specific task fields, in kolab format specification order
+         */
+        $this->_fields_specific = array(
+            'name' => array (
+                'type'    => self::TYPE_COMPOSITE,
+                'value'   => self::VALUE_MAYBE_MISSING,
+                'array'   => $this->_fields_name,
+            ),
+            'free-busy-url' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'organization' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'web-page' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'im-address' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'department' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'office-location' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'profession' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'job-title' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'manager-name' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'assistant' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'nick-name' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'spouse-name' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'birthday' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'anniversary' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'picture' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'children' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'gender' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'language' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'address' => array(
+                'type'    => self::TYPE_MULTIPLE,
+                'value'   => self::VALUE_MAYBE_MISSING,
+                'array'   => $this->_fields_address,
+            ),
+            'email' => array (
+                'type'    => self::TYPE_MULTIPLE,
+                'value'   => self::VALUE_MAYBE_MISSING,
+                'array'   => $this->_fields_simple_person,
+            ),
+            'phone' => array(
+                'type'    => self::TYPE_MULTIPLE,
+                'value'   => self::VALUE_MAYBE_MISSING,
+                'array'   => $this->_fields_phone,
+            ),
+            'preferred-address' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'latitude' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'longitude' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            // Horde specific fields
+            'pgp-publickey' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            // Support for broken clients
+            'website' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'im-adress' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+        );
+
+        parent::__construct();
+    }
+
+    /**
+     * Load the groupware object based on the specifc XML values.
+     *
+     * @param array &$children An array of XML nodes.
+     *
+     * @return array Array with the object data.
+     *
+     * @throws Horde_Exception If parsing the XML data failed.
+     */
+    protected function _load(&$children)
+    {
+        $object = $this->_loadArray($children, $this->_fields_specific);
+
+        // Handle name fields
+        if (isset($object['name'])) {
+            $object = array_merge($object['name'], $object);
+            unset($object['name']);
+        }
+
+        // Handle email fields
+        $emails = array();
+        if (isset($object['email'])) {
+            foreach ($object['email'] as $email) {
+                $smtp_address = $email['smtp-address'];
+                if (!empty($smtp_address)) {
+                    $emails[] = $smtp_address;
+                }
+            }
+        }
+        $object['emails'] = implode(', ', $emails);
+
+        // Handle phone fields
+        if (isset($object['phone'])) {
+            foreach ($object['phone'] as $phone) {
+                if (isset($phone['number']) &&
+                    in_array($phone['type'], $this->_phone_types)) {
+                    $object["phone-" . $phone['type']] = $phone['number'];
+                }
+            }
+        }
+
+        // Handle address fields
+        if (isset($object['address'])) {
+            foreach ($object['address'] as $address) {
+                if (in_array($address['type'], $this->_address_types)) {
+                    foreach ($address as $name => $value) {
+                        $object["addr-" . $address['type'] . "-" . $name] = $value;
+                    }
+                }
+            }
+        }
+
+        // Handle gender field
+        if (isset($object['gender'])) {
+            $gender = $object['gender'];
+
+            if ($gender == "female") {
+                $object['gender'] = 1;
+            } else if ($gender == "male") {
+                $object['gender'] = 0;
+            } else {
+                // unspecified gender
+                unset($object['gender']);
+            }
+        }
+
+        // Compatibility with broken clients
+        $broken_fields = array("website" => "web-page",
+                               "im-adress" => "im-address");
+        foreach ($broken_fields as $broken_field => $real_field) {
+            if (!empty($object[$broken_field]) && empty($object[$real_field])) {
+                $object[$real_field] = $object[$broken_field];
+            }
+            unset($object[$broken_field]);
+        }
+
+        $object['__type'] = 'Object';
+
+        return $object;
+    }
+
+    /**
+     * Save the  specifc XML values.
+     *
+     * @param array $root   The XML document root.
+     * @param array $object The resulting data array.
+     *
+     * @return boolean True on success.
+     *
+     * @throws Horde_Exception If converting the data to XML failed.
+     */
+    protected function _save($root, $object)
+    {
+        // Handle name fields
+        $name = array();
+        foreach (array_keys($this->_fields_name) as $key) {
+            if (isset($object[$key])) {
+                $name[$key] = $object[$key];
+                unset($object[$key]);
+            }
+        }
+        $object['name'] = $name;
+
+        // Handle email fields
+        if (!isset($object['emails'])) {
+            $emails = array();
+        } else {
+            $emails = explode(',', $object['emails']);
+        }
+
+        if (isset($object['email']) && 
+            !in_array($object['email'], $emails)) {
+            $emails[] = $object['email'];
+        }
+
+        $object['email'] = array();
+
+        foreach ($emails as $email) {
+            $email = trim($email);
+            if (!empty($email)) {
+                $new_email = array('display-name' => $object['name']['full-name'],
+                                   'smtp-address' => $email);
+
+                $object['email'][] = $new_email;
+            }
+        }
+
+        // Handle phone fields
+        if (!isset($object['phone'])) {
+            $object['phone'] = array();
+        }
+        foreach ($this->_phone_types as $type) {
+            $key = 'phone-' . $type;
+            if (array_key_exists($key, $object)) {
+                $new_phone = array('type'   => $type,
+                                   'number' => $object[$key]);
+
+                // Update existing phone entry of this type
+                $updated = false;
+                foreach ($object['phone'] as $index => $phone) {
+                    if ($phone['type'] == $type) {
+                        $object['phone'][$index] = $new_phone;
+
+                        $updated = true;
+                        break;
+                    }
+                }
+                if (!$updated) {
+                    $object['phone'][] = $new_phone;
+                }
+            }
+        }
+
+        // Phone cleanup: remove empty numbers
+        foreach ($object['phone'] as $index => $phone) {
+            if (empty($phone['number'])) {
+                unset($object['phone'][$index]);
+            }
+        }
+
+        // Handle address fields
+        if (!isset($object['address'])) {
+            $object['address'] = array();
+        }
+
+        foreach ($this->_address_types as $type) {
+            $basekey     = 'addr-' . $type . '-';
+            $new_address = array('type'   => $type);
+            foreach (array_keys($this->_fields_address['array']) as $subkey) {
+                $key = $basekey . $subkey;
+                if (array_key_exists($key, $object)) {
+                    $new_address[$subkey] = $object[$key];
+                }
+            }
+
+            // Update existing address entry of this type
+            $updated = false;
+            foreach ($object['address'] as $index => $address) {
+                if ($address['type'] == $type) {
+                    $object['address'][$index] = $new_address;
+
+                    $updated = true;
+                }
+            }
+            if (!$updated) {
+                $object['address'][] = $new_address;
+            }
+        }
+
+        // Address cleanup: remove empty addresses
+        foreach ($object['address'] as $index => $address) {
+            $all_empty = true;
+            foreach ($address as $name => $value) {
+                if (!empty($value) && $name != "type") {
+                    $all_empty = false;
+                    break;
+                }
+            }
+
+            if ($all_empty) {
+                unset($object['address'][$index]);
+            }
+        }
+
+        // Handle gender field
+        if (isset($object['gender'])) {
+            $gender = $object['gender'];
+
+            if ($gender == "0") {
+                $object['gender'] = "male";
+            } else if ($gender == "1") {
+                $object['gender'] = "female";
+            } else {
+                // unspecified gender
+                unset($object['gender']);
+            }
+        }
+
+        // Do the actual saving
+        return $this->_saveArray($root, $object, $this->_fields_specific);
+    }
+}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Distributionlist.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Distributionlist.php
new file mode 100644 (file)
index 0000000..e471953
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Implementation for distributionlists in the Kolab XML format.
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * Kolab XML handler for distributionlist groupware objects
+ *
+ * Copyright 2007-2009 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ * @since    Horde 3.2
+ */
+class Horde_Kolab_Format_Xml_Distributionlist extends Horde_Kolab_Format_Xml
+{
+    /**
+     * Specific data fields for the contact object
+     *
+     * @var array
+     */
+    protected $_fields_specific;
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->_root_name = "distribution-list";
+
+        /** Specific task fields, in kolab format specification order
+         */
+        $this->_fields_specific = array(
+                'display-name' => array(
+                    'type'    => self::TYPE_STRING,
+                    'value'   => self::VALUE_NOT_EMPTY
+                ),
+                'member' => array(
+                    'type'    => self::TYPE_MULTIPLE,
+                    'value'   => self::VALUE_MAYBE_MISSING,
+                    'array'   => $this->_fields_simple_person,
+                )
+            );
+
+        parent::__construct();
+    }
+
+    /**
+     * Load the groupware object based on the specifc XML values.
+     *
+     * @param array &$children An array of XML nodes.
+     *
+     * @return array Array with data.
+     *
+     * @throws Horde_Exception If parsing the XML data failed.
+     */
+    protected function _load(&$children)
+    {
+        $object = $this->_loadArray($children, $this->_fields_specific);
+
+        // Map the display-name of a kolab dist list to horde's lastname attribute
+        if (isset($object['display-name'])) {
+            $object['last-name'] = $object['display-name'];
+            unset($object['display-name']);
+        }
+
+        /**
+         * The mapping from $object['member'] as stored in XML back to
+         * Turba_Objects (contacts) must be performed in the
+         * Kolab_IMAP storage driver as we need access to the search
+         * facilities of the kolab storage driver.
+         */
+        $object['__type'] = 'Group';
+        return $object;
+    }
+
+    /**
+     * Save the  specifc XML values.
+     *
+     * @param array $root   The XML document root.
+     * @param array $object The resulting data array.
+     *
+     * @return boolean True on success.
+     *
+     * @throws Horde_Exception If converting the data to XML failed.
+     */
+    protected function _save($root, $object)
+    {
+        // Map the display-name of a kolab dist list to horde's lastname attribute
+        if (isset($object['last-name'])) {
+            $object['display-name'] = $object['last-name'];
+            unset($object['last-name']);
+        }
+
+        return $this->_saveArray($root, $object, $this->_fields_specific);
+    }
+}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Event.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Event.php
new file mode 100644 (file)
index 0000000..9544ae0
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+/**
+ * Implementation for events in the Kolab XML format.
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * Kolab XML handler for event groupware objects.
+ *
+ * Copyright 2007-2009 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ * @since    Horde 3.2
+ */
+class Horde_Kolab_Format_Xml_Event extends Horde_Kolab_Format_Xml
+{
+    /**
+     * Specific data fields for the contact object
+     *
+     * @var array
+     */
+    protected $_fields_specific;
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->_root_name = 'event';
+
+        /** Specific event fields, in kolab format specification order
+         */
+        $this->_fields_specific = array(
+            'summary' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'location' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'organizer' => $this->_fields_simple_person,
+            'start-date' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_NOT_EMPTY,
+            ),
+            'alarm' => array(
+                'type'    => self::TYPE_INTEGER,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'recurrence' => array(
+                'type'    => self::TYPE_COMPOSITE,
+                'value'   => self::VALUE_CALCULATED,
+                'load'    => 'Recurrence',
+                'save'    => 'Recurrence',
+            ),
+            'attendee' => $this->_fields_attendee,
+            'show-time-as' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'color-label' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'end-date' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_NOT_EMPTY,
+            ),
+        );
+
+        parent::__construct();
+    }
+
+    /**
+     * Load event XML values and translate start/end date.
+     *
+     * @param array &$children An array of XML nodes.
+     *
+     * @return array Array with the object data.
+     *
+     * @throws Horde_Exception If parsing the XML data failed.
+     */
+    protected function _load(&$children)
+    {
+        $object = parent::_load($children);
+
+        // Translate start/end date including full day events
+        if (strlen($object['start-date']) == 10) {
+            $object['start-date'] = Horde_Kolab_Format_Date::decodeDate($object['start-date']);
+            $object['end-date']   = Horde_Kolab_Format_Date::decodeDate($object['end-date']) + 24*60*60;
+        } else {
+            $object['start-date'] = Horde_Kolab_Format_Date::decodeDateTime($object['start-date']);
+            $object['end-date']   = Horde_Kolab_Format_Date::decodeDateTime($object['end-date']);
+        }
+
+        return $object;
+    }
+
+    /**
+     * Save event XML values and translate start/end date.
+     *
+     * @param array $root   The XML document root.
+     * @param array $object The resulting data array.
+     *
+     * @return boolean True on success.
+     *
+     * @throws Horde_Exception If converting the data to XML failed.
+     */
+    protected function _save($root, $object)
+    {
+        // Translate start/end date including full day events
+        if (!empty($object['_is_all_day'])) {
+            $object['start-date'] = Horde_Kolab_Format_Date::encodeDate($object['start-date']);
+            $object['end-date']   = Horde_Kolab_Format_Date::encodeDate($object['end-date'] - 24*60*60);
+        } else {
+            $object['start-date'] = Horde_Kolab_Format_Date::encodeDateTime($object['start-date']);
+            $object['end-date']   = Horde_Kolab_Format_Date::encodeDateTime($object['end-date']);
+        }
+
+        return parent::_save($root, $object);
+    }
+}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Hprefs.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Hprefs.php
new file mode 100644 (file)
index 0000000..b7a8eeb
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+/**
+ * Implementation for horde user preferences in the Kolab XML format.
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * Kolab XML handler for client preferences.
+ *
+ * Copyright 2007-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.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ * @since    Horde 3.2
+ */
+class Horde_Kolab_Format_Xml_Hprefs extends Horde_Kolab_Format_Xml
+{
+    /**
+     * Specific data fields for the prefs object
+     *
+     * @var Kolab
+     */
+    protected $_fields_specific;
+
+    /**
+     * Automatically create categories if they are missing?
+     *
+     * @var boolean
+     */
+    protected $_create_categories = false;
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->_root_name = 'h-prefs';
+
+        /** Specific preferences fields, in kolab format specification order
+         */
+        $this->_fields_specific = array(
+            'application' => array (
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'pref' => array(
+                'type'    => self::TYPE_MULTIPLE,
+                'value'   => self::VALUE_MAYBE_MISSING,
+                'array'   => array(
+                    'type' => self::TYPE_STRING,
+                    'value' => self::VALUE_MAYBE_MISSING,
+                ),
+            ),
+        );
+
+        parent::__construct();
+    }
+
+    /**
+     * Load an object based on the given XML string.
+     *
+     * @param string &$xmltext The XML of the message as string.
+     *
+     * @return array The data array representing the object.
+     *
+     * @throws Horde_Exception If parsing the XML data failed.
+     */
+    public function load(&$xmltext)
+    {
+        $object = parent::load($xmltext);
+
+        if (empty($object['application'])) {
+            if (!empty($object['categories'])) {
+                $object['application'] = $object['categories'];
+                unset($object['categories']);
+            } else {
+                throw new Horde_Exception('Preferences XML object is missing an application setting.');
+            }
+        }
+
+        return $object;
+    }
+
+    /**
+     * Convert the data to a XML string.
+     *
+     * @param array $object The data array representing the note.
+     *
+     * @return string The data as XML string.
+     *
+     * @throws Horde_Exception If converting the data to XML failed.
+     */
+    public function save($object)
+    {
+        if (empty($object['application'])) {
+            if (!empty($object['categories'])) {
+                $object['application'] = $object['categories'];
+                unset($object['categories']);
+            } else {
+                throw new Horde_Exception('Preferences XML object is missing an application setting.');
+            }
+        }
+
+        return parent::save($object);
+    }
+}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Note.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Note.php
new file mode 100644 (file)
index 0000000..bd12d21
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+/**
+ * Implementation for notes in the Kolab XML format.
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * Kolab XML handler for note groupware objects.
+ *
+ * Copyright 2007-2009 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ * @since    Horde 3.2
+ */
+class Horde_Kolab_Format_Xml_Note extends Horde_Kolab_Format_Xml
+{
+    /**
+     * Specific data fields for the note object
+     *
+     * @var Kolab
+     */
+    protected $_fields_specific;
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->_root_name = 'note';
+
+        /** Specific note fields, in kolab format specification order
+         */
+        $this->_fields_specific = array(
+            'summary' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => '',
+            ),
+            'background-color' => array(
+                'type'    => self::TYPE_COLOR,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => '#000000',
+            ),
+            'foreground-color' => array(
+                'type'    => self::TYPE_COLOR,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => '#ffff00',
+            ),
+        );
+
+        parent::__construct();
+    }
+
+    /**
+     * Load the groupware object based on the specifc XML values.
+     *
+     * @param array &$children An array of XML nodes.
+     *
+     * @return array Array with the object data
+     *
+     * @throws Horde_Exception If parsing the XML data failed.
+     */
+    protected function _load(&$children)
+    {
+        $object = $this->_loadArray($children, $this->_fields_specific);
+
+        $object['desc'] = $object['summary'];
+        unset($object['summary']);
+
+        return $object;
+    }
+
+    /**
+     * Save the specific XML values.
+     *
+     * @param array $root   The XML document root.
+     * @param array $object The resulting data array.
+     *
+     * @return boolean True on success.
+     *
+     * @throws Horde_Exception If converting the data to XML failed.
+     */
+    protected function _save($root, $object)
+    {
+        $object['summary'] = $object['desc'];
+        unset($object['desc']);
+
+        return $this->_saveArray($root, $object, $this->_fields_specific);
+    }
+}
diff --git a/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Task.php b/framework/Kolab_Format/lib/Horde/Kolab/Format/Xml/Task.php
new file mode 100644 (file)
index 0000000..5d37389
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+/**
+ * Implementation for tasks in the Kolab XML format.
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * Kolab XML handler for task groupware objects.
+ *
+ * Copyright 2007-2009 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @category Kolab
+ * @package  Kolab_Format
+ * @author   Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ * @since    Horde 3.2
+ */
+class Horde_Kolab_Format_Xml_Task extends Horde_Kolab_Format_Xml
+{
+    /**
+     * Specific data fields for the note object
+     *
+     * @var array
+     */
+    protected $_fields_specific;
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->_root_name = 'task';
+
+        /** Specific task fields, in kolab format specification order
+         */
+        $this->_fields_specific = array(
+            'summary' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => '',
+            ),
+            'location' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => '',
+            ),
+            'creator'   => $this->_fields_simple_person,
+            'organizer' => $this->_fields_simple_person,
+            'start-date' => array(
+                'type'    => self::TYPE_DATE_OR_DATETIME,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'alarm' => array(
+                'type'    => self::TYPE_INTEGER,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'recurrence' => array(
+                'type'    => self::TYPE_COMPOSITE,
+                'value'   => self::VALUE_CALCULATED,
+                'load'    => 'Recurrence',
+                'save'    => 'Recurrence',
+            ),
+            'attendee' => $this->_fields_attendee,
+            'priority' => array(
+                'type'    => self::TYPE_INTEGER,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => 3,
+            ),
+            'completed' => array(
+                'type'    => self::TYPE_INTEGER,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => 0,
+            ),
+            'status' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_DEFAULT,
+                'default' => 'not-started',
+            ),
+            'due-date' => array(
+                'type'    => self::TYPE_DATE_OR_DATETIME,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'parent' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            // These are not part of the Kolab specification but it is
+            // ok if the client supports additional entries
+            'estimate' => array(
+                'type'    => self::TYPE_STRING,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+            'completed_date' => array(
+                'type'    => self::TYPE_DATE_OR_DATETIME,
+                'value'   => self::VALUE_MAYBE_MISSING,
+            ),
+        );
+
+        parent::__construct();
+    }
+
+    /**
+     * Load the groupware object based on the specifc XML values.
+     *
+     * @param array &$children An array of XML nodes.
+     *
+     * @return array Array with data.
+     *
+     * @throws Horde_Exception If parsing the XML data failed.
+     */
+    protected function _load(&$children)
+    {
+        $object = $this->_loadArray($children, $this->_fields_specific);
+
+        $object['name'] = $object['summary'];
+        unset($object['summary']);
+
+        if (empty($object['completed-date'])) {
+            $object['completed-date'] = null;
+        }
+
+        if (empty($object['alarm'])) {
+            $object['alarm'] = null;
+        }
+
+        if (isset($object['due-date'])) {
+            $object['due'] = $object['due-date'];
+            unset($object['due-date']);
+        } else {
+            $object['due'] = null;
+        }
+
+        if (isset($object['start-date'])) {
+            $object['start'] = $object['start-date'];
+            unset($object['start-date']);
+        } else {
+            $object['start'] = null;
+        }
+
+        if (!isset($object['estimate'])) {
+            $object['estimate'] = null;
+        } else {
+            $object['estimate'] = (float) $object['estimate'];
+        }
+
+        if (!isset($object['parent'])) {
+            $object['parent'] = null;
+        }
+
+        $object['completed'] = (bool) Kolab::percentageToBoolean($object['completed']);
+
+        if (isset($object['organizer']) && isset($object['organizer']['smtp-address'])) {
+            $object['assignee'] = $object['organizer']['smtp-address'];
+        }
+
+        return $object;
+    }
+
+    /**
+     * Save the specific XML values.
+     *
+     * @param array $root   The XML document root.
+     * @param array $object The resulting data array.
+     *
+     * @return boolean True on success.
+     *
+     * @throws Horde_Exception If converting the data to XML failed.
+     */
+    protected function _save($root, $object)
+    {
+        $object['summary'] = $object['name'];
+        unset($object['name']);
+
+        $object['due-date'] = $object['due'];
+        unset($object['due']);
+
+        $object['start-date'] = $object['start'];
+        unset($object['start']);
+
+        $object['estimate'] = number_format($object['estimate'], 2);
+
+        $object['completed'] = Kolab::BooleanToPercentage($object['completed']);
+
+        return $this->_saveArray($root, $object, $this->_fields_specific);
+    }
+}
index bd98fce..df33af7 100644 (file)
@@ -34,7 +34,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
  </lead>
  <date>2009-04-02</date>
  <version>
-  <release>1.0.1</release>
+  <release>1.0.2</release>
   <api>1.0.0</api>
  </version>
  <stability>
@@ -43,14 +43,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
  </stability>
  <license uri="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">LGPL</license>
  <notes>
-   * Handle parsing errors within the DOM XML extension correctly
-     kolab/issue3520 (calendar with certain entries does not display in web client)
-     https://www.intevation.de/roundup/kolab/issue3520
-     kolab/issue3525 (free/busy regeneration aborts for unparsable events)
-     https://www.intevation.de/roundup/kolab/issue3525
-   * Accept ISO-8859-1 encoding even if advertised as UTF-8
-     kolab/issue3528 (Events with broken encoding should work)
-     https://www.intevation.de/roundup/kolab/issue3528
+   * Converted to Horde4/PHP5
+   * Removed Horde_DOM dependency.
  </notes>
  <contents>
   <dir name="/">
@@ -80,8 +74,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
       <file name="Format.php" role="php" />
       <dir name="Format">
        <file name="Date.php" role="php" />
-       <file name="XML.php" role="php" />
-       <dir name="XML">
+       <file name="Xml.php" role="php" />
+       <dir name="Xml">
         <file name="Annotation.php" role="php" />
         <file name="Contact.php" role="php" />
         <file name="Distributionlist.php" role="php" />
@@ -89,7 +83,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
         <file name="Hprefs.php" role="php" />
         <file name="Note.php" role="php" />
         <file name="Task.php" role="php" />
-       </dir> <!-- /lib/Horde/Kolab/Format/XML -->
+       </dir> <!-- /lib/Horde/Kolab/Format/Xml -->
       </dir> <!-- /lib/Horde/Kolab/Format -->
      </dir> <!-- /lib/Horde/Kolab -->
     </dir> <!-- /lib/Horde -->
@@ -127,12 +121,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
     <min>1.4.0b1</min>
    </pearinstaller>
    <package>
-    <name>Horde_DOM</name>
-    <channel>pear.horde.org</channel>
-    <min>0.1.0</min>
-   </package>
-   <package>
-    <name>Horde_NLS</name>
+    <name>Nls</name>
     <channel>pear.horde.org</channel>
    </package>
    <package>
@@ -146,7 +135,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
     <channel>pear.horde.org</channel>
    </package>
    <package>
-    <name>Horde_Date</name>
+    <name>Date</name>
     <channel>pear.horde.org</channel>
    </package>
   </optional>
@@ -158,14 +147,14 @@ http://pear.php.net/dtd/package-2.0.xsd">
    <install name="examples/Horde/Kolab/Format/new_type.php" as="Horde/Kolab/Format/new_type.php" />
    <install name="lib/Horde/Kolab/Format.php" as="Horde/Kolab/Format.php" />
    <install name="lib/Horde/Kolab/Format/Date.php" as="Horde/Kolab/Format/Date.php" />
-   <install name="lib/Horde/Kolab/Format/XML.php" as="Horde/Kolab/Format/XML.php" />
-   <install name="lib/Horde/Kolab/Format/XML/Annotation.php" as="Horde/Kolab/Format/XML/Annotation.php" />
-   <install name="lib/Horde/Kolab/Format/XML/Contact.php" as="Horde/Kolab/Format/XML/Contact.php" />
-   <install name="lib/Horde/Kolab/Format/XML/Distributionlist.php" as="Horde/Kolab/Format/XML/Distributionlist.php" />
-   <install name="lib/Horde/Kolab/Format/XML/Event.php" as="Horde/Kolab/Format/XML/Event.php" />
-   <install name="lib/Horde/Kolab/Format/XML/Hprefs.php" as="Horde/Kolab/Format/XML/Hprefs.php" />
-   <install name="lib/Horde/Kolab/Format/XML/Note.php" as="Horde/Kolab/Format/XML/Note.php" />
-   <install name="lib/Horde/Kolab/Format/XML/Task.php" as="Horde/Kolab/Format/XML/Task.php" />
+   <install name="lib/Horde/Kolab/Format/Xml.php" as="Horde/Kolab/Format/Xml.php" />
+   <install name="lib/Horde/Kolab/Format/Xml/Annotation.php" as="Horde/Kolab/Format/Xml/Annotation.php" />
+   <install name="lib/Horde/Kolab/Format/Xml/Contact.php" as="Horde/Kolab/Format/Xml/Contact.php" />
+   <install name="lib/Horde/Kolab/Format/Xml/Distributionlist.php" as="Horde/Kolab/Format/Xml/Distributionlist.php" />
+   <install name="lib/Horde/Kolab/Format/Xml/Event.php" as="Horde/Kolab/Format/Xml/Event.php" />
+   <install name="lib/Horde/Kolab/Format/Xml/Hprefs.php" as="Horde/Kolab/Format/Xml/Hprefs.php" />
+   <install name="lib/Horde/Kolab/Format/Xml/Note.php" as="Horde/Kolab/Format/Xml/Note.php" />
+   <install name="lib/Horde/Kolab/Format/Xml/Task.php" as="Horde/Kolab/Format/Xml/Task.php" />
    <install name="test/Horde/Kolab/Format/AllTests.php" as="Horde/Kolab/Format/AllTests.php" />
    <install name="test/Horde/Kolab/Format/ContactTest.php" as="Horde/Kolab/Format/ContactTest.php" />
    <install name="test/Horde/Kolab/Format/PreferencesTest.php" as="Horde/Kolab/Format/PreferencesTest.php" />
@@ -182,6 +171,28 @@ http://pear.php.net/dtd/package-2.0.xsd">
  </phprelease>
  <changelog>
   <release>
+   <date>2009-04-02</date>
+   <version>
+    <release>1.0.1</release>
+    <api>1.0.0</api>
+   </version>
+   <stability>
+    <release>stable</release>
+    <api>stable</api>
+   </stability>
+   <license uri="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">LGPL</license>
+   <notes>
+     * Handle parsing errors within the DOM XML extension correctly
+       kolab/issue3520 (calendar with certain entries does not display in web client)
+       https://www.intevation.de/roundup/kolab/issue3520
+       kolab/issue3525 (free/busy regeneration aborts for unparsable events)
+       https://www.intevation.de/roundup/kolab/issue3525
+     * Accept ISO-8859-1 encoding even if advertised as UTF-8
+       kolab/issue3528 (Events with broken encoding should work)
+       https://www.intevation.de/roundup/kolab/issue3528
+   </notes>
+  </release>
+  <release>
    <date>2008-12-12</date>
    <version>
     <release>1.0.0</release>
index 3648013..5cd8567 100644 (file)
@@ -8,14 +8,10 @@
  */
 
 /**
- *  We need the unit test framework 
+ * The Autoloader allows us to omit "require/include" statements.
  */
-require_once 'PHPUnit/Framework.php';
+require_once 'Horde/Autoloader.php';
 
-require_once 'Horde/Nls.php';
-require_once 'Horde/Kolab/Format.php';
-require_once 'Horde/Kolab/Format/XML.php';
-require_once 'Horde/Kolab/Format/XML/Contact.php';
 
 class DummyRegistry {
     function get()
@@ -24,7 +20,7 @@ class DummyRegistry {
     }
 }
 
-class Horde_Kolab_Format_XML_contact_dummy extends Horde_Kolab_Format_XML_contact
+class Horde_Kolab_Format_Xml_Contact_dummy extends Horde_Kolab_Format_Xml_Contact
 {
     function _saveCreationDate($parent_node, $name, $value, $missing)
     {
@@ -77,7 +73,7 @@ class Horde_Kolab_Format_ContactTest extends PHPUnit_Framework_TestCase
      */
     public function testSingleEmail()
     {
-        $contact = &new Horde_Kolab_Format_XML_contact_dummy();
+        $contact = &new Horde_Kolab_Format_Xml_contact_dummy();
         $object = array('uid' => '1',
                         'full-name' => 'User Name',
                         'email' => 'user@example.org');
@@ -91,7 +87,7 @@ class Horde_Kolab_Format_ContactTest extends PHPUnit_Framework_TestCase
      */
     public function testPGP()
     {
-        $contact = &new Horde_Kolab_Format_XML_contact_dummy();
+        $contact = &new Horde_Kolab_Format_Xml_contact_dummy();
         $object = array('uid' => '1',
                         'full-name' => 'User Name',
                         'pgp-publickey' => 'PGP Test Key',
@@ -108,7 +104,7 @@ class Horde_Kolab_Format_ContactTest extends PHPUnit_Framework_TestCase
     {
         global $prefs;
 
-        $contact = &new Horde_Kolab_Format_XML_contact();
+        $contact = &new Horde_Kolab_Format_Xml_contact();
         $xml = file_get_contents(dirname(__FILE__) . '/fixtures/contact_category.xml');
         $object = $contact->load($xml);
         $this->assertContains('Test', $object['categories']);
@@ -134,7 +130,7 @@ class Horde_Kolab_Format_ContactTest extends PHPUnit_Framework_TestCase
             /* Monkey patch to allw the value to be set. */
             $prefs->_prefs['categories'] = array('v' => '');
             
-            $contact = &new Horde_Kolab_Format_XML_contact();
+            $contact = &new Horde_Kolab_Format_Xml_contact();
             $xml = file_get_contents(dirname(__FILE__) . '/fixtures/contact_category.xml');
 
             $object = $contact->load($xml);
index a8fc5c4..bec51f6 100644 (file)
@@ -8,12 +8,9 @@
  */
 
 /**
- *  We need the unit test framework 
+ * The Autoloader allows us to omit "require/include" statements.
  */
-require_once 'PHPUnit/Framework.php';
-
-require_once 'Horde/Nls.php';
-require_once 'Horde/Kolab/Format.php';
+require_once 'Horde/Autoloader.php';
 
 /**
  * Test event handling.
index f2d5535..99733e3 100644 (file)
  */
 
 /**
- *  We need the unit test framework 
+ * The Autoloader allows us to omit "require/include" statements.
  */
-require_once 'PHPUnit/Framework.php';
-
-require_once 'Horde/Nls.php';
-require_once 'Horde/Kolab/Format.php';
+require_once 'Horde/Autoloader.php';
 
 /**
  * Test Kolab Format MIME attributes
index fc50a53..b752810 100644 (file)
@@ -8,17 +8,13 @@
  */
 
 /**
- *  We need the unit test framework 
+ * The Autoloader allows us to omit "require/include" statements.
  */
-require_once 'PHPUnit/Framework.php';
+require_once 'Horde/Autoloader.php';
 
-require_once 'Horde/Nls.php';
-require_once 'Horde/Kolab/Format.php';
-require_once 'Horde/Kolab/Format/XML.php';
-require_once 'Horde/Kolab/Format/XML/Hprefs.php';
 
 
-class Horde_Kolab_Format_XML_hprefs_dummy extends Horde_Kolab_Format_XML_hprefs
+class Horde_Kolab_Format_Xml_Hprefs_dummy extends Horde_Kolab_Format_Xml_Hprefs
 {
     function _saveCreationDate($parent_node, $name, $value, $missing)
     {
@@ -71,7 +67,7 @@ class Horde_Kolab_Format_PreferencesTest extends PHPUnit_Framework_TestCase
      */
     public function testConversionFromOld()
     {
-        $preferences = &new Horde_Kolab_Format_XML_hprefs_dummy();
+        $preferences = &new Horde_Kolab_Format_Xml_hprefs_dummy();
 
         $xml = file_get_contents(dirname(__FILE__) . '/fixtures/preferences_read_old.xml');
         $object = $preferences->load($xml);
index 0f81ba0..cefd966 100644 (file)
@@ -8,12 +8,9 @@
  */
 
 /**
- *  We need the unit test framework 
+ * The Autoloader allows us to omit "require/include" statements.
  */
-require_once 'PHPUnit/Framework.php';
-
-require_once 'Horde/Nls.php';
-require_once 'Horde/Kolab/Format.php';
+require_once 'Horde/Autoloader.php';
 
 /**
  * Test recurrence handling
index b5f23e7..380c8ac 100644 (file)
@@ -8,13 +8,9 @@
  */
 
 /**
- *  We need the unit test framework 
+ * The Autoloader allows us to omit "require/include" statements.
  */
-require_once 'PHPUnit/Framework.php';
-
-require_once 'Horde/Nls.php';
-require_once 'Horde/Kolab/Format.php';
-require_once 'Horde/Kolab/Format/XML.php';
+require_once 'Horde/Autoloader.php';
 
 /**
  * Test the XML format.
@@ -71,8 +67,7 @@ class Horde_Kolab_Format_XmlTest extends PHPUnit_Framework_TestCase
     public function testAdd()
     {
         $xml = &new Horde_Kolab_Format_XML();
-        $xml->_prepareSave();
-        $root = $xml->_xmldoc;
+        $root = $xml->_prepareSave();
         $base = $xml->_xmldoc->saveXML();
 
         // A missing attribute should cause no change if it
@@ -120,9 +115,8 @@ class Horde_Kolab_Format_XmlTest extends PHPUnit_Framework_TestCase
      */
     public function testNodeOps()
     {
-        $dxml = new Horde_Kolab_Format_XML_dummy();
-        $dxml->_prepareSave();
-        $droot = $dxml->_xmldoc;
+        $dxml = new Horde_Kolab_Format_Xml_dummy();
+        $droot = $dxml->_prepareSave();
 
         // Test calculated nodes
         $dxml->_updateNode($droot,
@@ -137,9 +131,8 @@ class Horde_Kolab_Format_XmlTest extends PHPUnit_Framework_TestCase
                                 'save' => 'Value', 'type' => 0));
         $this->assertEquals("<?xml version=\"1.0\"?>\n<kolab version=\"1.0\">\n  <empty2>empty2: , missing</empty2>\n  <present1>present1: present1</present1>\n</kolab>\n",  $dxml->_xmldoc->saveXML());
 
-        $xml = &new Horde_Kolab_Format_XML();
-        $xml->_prepareSave();
-        $root = $xml->_xmldoc;
+        $xml = &new Horde_Kolab_Format_Xml();
+        $root = $xml->_prepareSave();
         $xml->_updateNode($root,
                          array(),
                          'empty1',
@@ -229,7 +222,7 @@ class Horde_Kolab_Format_XmlTest extends PHPUnit_Framework_TestCase
     public function testReleod()
     {
         // Save an object and reload it
-        $xml = new Horde_Kolab_Format_XML();
+        $xml = new Horde_Kolab_Format_Xml();
         $result = $xml->save(array('uid'=>'test',
                                    'body' => 'body',
                                    'dummy' => 'hello',
@@ -251,9 +244,8 @@ class Horde_Kolab_Format_XmlTest extends PHPUnit_Framework_TestCase
     public function testComplex()
     {
         // Continue with complex values
-        $xml = new Horde_Kolab_Format_XML();
-        $xml->_prepareSave();
-        $root = $xml->_xmldoc;
+        $xml = new Horde_Kolab_Format_Xml();
+        $root = $xml->_prepareSave();
 
         // Test saving a composite value
         $xml->_updateNode($root,
@@ -267,7 +259,7 @@ class Horde_Kolab_Format_XmlTest extends PHPUnit_Framework_TestCase
                          'attendee1', $xml->_fields_attendee);
         $this->assertEquals("<?xml version=\"1.0\"?>\n<kolab version=\"1.0\">\n  <composite1>\n    <display-name>test</display-name>\n    <smtp-address>test@example.com</smtp-address>\n    <uid></uid>\n  </composite1>\n  <attendee1>\n    <display-name>test</display-name>\n    <smtp-address></smtp-address>\n    <status>none</status>\n    <request-response>true</request-response>\n    <role>required</role>\n  </attendee1>\n  <attendee1>\n    <display-name></display-name>\n    <smtp-address>test@example.com</smtp-address>\n    <status>none</status>\n    <request-response>true</request-response>\n    <role>required</role>\n  </attendee1>\n</kolab>\n",  $xml->_xmldoc->saveXML());
 
-        $children = $root->child_nodes();
+        $children = $root->childNodes;
 
         // Load a composite value
         $data = $xml->_getXmlData($children,
@@ -301,7 +293,7 @@ class Horde_Kolab_Format_XmlTest extends PHPUnit_Framework_TestCase
  * @author  Gunnar Wrobel <wrobel@pardus.de>
  * @package Kolab_Format
  */
-class Horde_Kolab_Format_XML_dummy extends Horde_Kolab_Format_XML
+class Horde_Kolab_Format_Xml_dummy extends Horde_Kolab_Format_Xml
 {
     function _saveValue($node, $name, $value, $missing)
     {