From: Gunnar Wrobel
Date: Mon, 29 Jun 2009 07:10:05 +0000 (+0200)
Subject: Imported Kolab_Storage
X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=6fb8c610eb0f6b24786bbecc9616f8d5247ec597;p=horde.git
Imported Kolab_Storage
---
diff --git a/framework/Kolab_Storage/doc/Horde/Kolab/Storage/usage.txt b/framework/Kolab_Storage/doc/Horde/Kolab/Storage/usage.txt
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/framework/Kolab_Storage/doc/Horde/Kolab/Storage/usage.txt
@@ -0,0 +1 @@
+
diff --git a/framework/Kolab_Storage/examples/Horde/Kolab/Storage/list.php b/framework/Kolab_Storage/examples/Horde/Kolab/Storage/list.php
new file mode 100644
index 000000000..4b0e238b9
--- /dev/null
+++ b/framework/Kolab_Storage/examples/Horde/Kolab/Storage/list.php
@@ -0,0 +1,11 @@
+listFolders());
diff --git a/framework/Kolab_Storage/lib/Horde/Kolab/Deprecated.php b/framework/Kolab_Storage/lib/Horde/Kolab/Deprecated.php
new file mode 100644
index 000000000..add0e612e
--- /dev/null
+++ b/framework/Kolab_Storage/lib/Horde/Kolab/Deprecated.php
@@ -0,0 +1,878 @@
+
+ * @author Gunnar Wrobel
+ * @author Thomas Jarosch
+ * @package Kolab_Storage
+ */
+class Kolab_Storage_Deprecated extends Kolab_Storage {
+
+ /**
+ * The the folder we currently access.
+ *
+ * @deprecated
+ *
+ * @var Kolab_Folder
+ */
+ var $_folder;
+
+ /**
+ * The the folder data we currently access.
+ *
+ * @deprecated
+ *
+ * @var Kolab_Data
+ */
+ var $_data;
+
+ /**
+ * A copy of the app_consts for the current app
+ *
+ * @deprecated
+ *
+ * @var string
+ */
+ var $_app_consts;
+
+ /**
+ * Version of the data format to load
+ *
+ * @deprecated
+ *
+ * @var int
+ */
+ var $_loader_version;
+
+ /**
+ * The (encoded) name of the IMAP folder that corresponds to the current
+ * share.
+ *
+ * @deprecated
+ *
+ * @var string
+ */
+ var $_share;
+
+ /**
+ * The IMAP connection
+ *
+ * @deprecated
+ *
+ * @var resource
+ */
+ var $_imap;
+
+ /**
+ * Folder object type
+ *
+ * @deprecated
+ *
+ * @var string
+ */
+ var $_object_type;
+
+ /**
+ * The full mime type string of the current Kolab object format we're
+ * dealing with.
+ *
+ * @deprecated
+ *
+ * @var string
+ */
+ var $_mime_type;
+
+ /**
+ * The id of the part with the Kolab attachment.
+ *
+ * @deprecated
+ *
+ * @var int
+ */
+ var $_mime_id;
+
+ /**
+ * Message headers
+ *
+ * @deprecated
+ *
+ * @var MIME_Header
+ */
+ var $_headers;
+
+ /**
+ * The MIME_Message object that contains the currently loaded message. This
+ * is used when updating an object, in order to preserve everything else
+ * within the message that we don't know how to handle.
+ *
+ * @deprecated
+ *
+ * @var MIME_Message
+ */
+ var $_message;
+
+ /**
+ * The IMAP message number of $this->_message.
+ *
+ * @deprecated
+ *
+ * @var integer
+ */
+ var $_msg_no;
+
+ /**
+ * Open the specified share.
+ *
+ * @deprecated
+ *
+ * @param string $share The id of the share
+ * that should be opened.
+ * @param int $loader_version The version of the format
+ * loader
+ *
+ * @return mixed True on success, a PEAR error otherwise
+ */
+ function open($share, $app_consts, $loader_version = 0)
+ {
+ $folder = $this->getShare($share,
+ $app_consts['mime_type_suffix']);
+ if (is_a($folder, 'PEAR_Error')) {
+ return $folder;
+ }
+ $this->_folder = &$folder;
+
+ $data = $this->getData($this->_folder,
+ $app_consts['mime_type_suffix'],
+ $loader_version);
+ if (is_a($data, 'PEAR_Error')) {
+ return $data;
+ }
+ $this->_data = $data;
+
+ $this->_app_consts = &$app_consts;
+ $this->_loader_version = $loader_version;
+
+ // This is only necessary for the old framework.
+ if ($loader_version == 0) {
+ /** We need the DOM library for xml handling (PHP4/5). */
+ require_once 'Horde/DOM.php';
+
+ $session = &Horde_Kolab_Session::singleton();
+ $this->_imap = &$session->getImap();
+
+ $this->_object_type = $app_consts['mime_type_suffix'];
+ $this->_mime_type = 'application/x-vnd.kolab.' . $this->_object_type;
+
+ // Check that the folder exists. For the new framework
+ // this happens in _synchronize()
+ $result = $this->exists();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Handles the horde syntax for default shares
+ *
+ * @deprecated
+ *
+ * @param string $share The share name that should be parsed
+ *
+ * @return string The corrected IMAP folder name.
+ */
+ function parseFolder($share)
+ {
+ global $registry;
+ $const = Kolab::getAppConsts($registry->getApp());
+ $list = &Kolab_List::singleton();
+ return $list->parseShare($share, $const['folder_type']);
+ }
+
+ /**
+ * Selects the type of data we are currently dealing with.
+ *
+ * @deprecated
+ */
+ function setObjectType($type)
+ {
+ if (in_array($type, $this->_app_consts['allowed_types'])) {
+ $data = $this->getData($this->_folder,
+ $type,
+ $this->_loader_version);
+ if (is_a($data, 'PEAR_Error')) {
+ return $data;
+ }
+ $this->_data = $data;
+ } else {
+ return PEAR::raiseError(sprintf(_("Object type %s not allowed for folder type %s!"), $type, $this->_store_type));
+ }
+ }
+
+ /**
+ * Returns a list of all IMAP folders (including their groupware type)
+ * that the current user has acccess to.
+ *
+ * @deprecated
+ *
+ * @return array An array of array($foldername, $foldertype) items (empty
+ * on error).
+ */
+ function listFolders()
+ {
+ $list = &Kolab_List::singleton();
+ $folders = $list->getFolders();
+ if (is_a($folders, 'PEAR_Error')) {
+ return $folders;
+ }
+ $result = array();
+ foreach ($folders as $folder) {
+ $result[] = array($folder->name, $folder->getType());
+ }
+ return $result;
+ }
+
+ /**
+ * Close the current folder.
+ *
+ * @deprecated
+ */
+ function close()
+ {
+ }
+
+ /**
+ *
+ *
+ * @deprecated
+ */
+ function exists()
+ {
+ return $this->_folder->exists();
+ }
+
+ /**
+ *
+ * @deprecated
+ *
+ */
+ function deleteAll()
+ {
+ return $this->_data->deleteAll();
+ }
+
+ /**
+ * Delete the specified message from the current folder
+ *
+ * @deprecated
+ *
+ * @param string $object_uid Id of the message to be deleted.
+ *
+ * @return mixed True is successful, false if the message does not
+ * exist, a PEAR error otherwise.
+ */
+ function delete($object_uid)
+ {
+ return $this->_data->delete($object_uid);
+ }
+
+ /**
+ * Move the specified message from the current folder into a new
+ * folder
+ *
+ * @deprecated
+ *
+ * @param string $object_uid ID of the message to be deleted.
+ * @param string $new_share ID of the target share.
+ *
+ * @return mixed True is successful, false if the object does not
+ * exist, a PEAR error otherwise.
+ */
+ function move($object_uid, $new_share)
+ {
+ return $this->_data->move($object_uid, $new_share);
+ }
+
+ /**
+ * Save an object.
+ *
+ * @deprecated
+ *
+ * @param array $object The array that holds the data object
+ * @param string $old_object_id The id of the object if it existed before
+ *
+ * @return mixed True on success, a PEAR error otherwise
+ */
+ function save($object, $old_object_id = null)
+ {
+ return $this->_data->save($object, $old_object_id);
+ }
+
+ /**
+ * Generate a unique object id
+ *
+ * @deprecated
+ *
+ * @return string The unique id
+ */
+ function generateUID()
+ {
+ return $this->_data->generateUID();
+ }
+
+ /**
+ * Check if the given id exists
+ *
+ * @deprecated
+ *
+ * @param string $uid The object id
+ *
+ * @return boolean True if the id was found, false otherwise
+ */
+ function objectUidExists($uid)
+ {
+ return $this->_data->objectUidExists($uid);
+ }
+
+ /**
+ * Return the specified object
+ *
+ * @deprecated
+ *
+ * @param string $object_id The object id
+ *
+ * @return mixed The object data as array or a PEAR error if the
+ * object is missing from the cache.
+ */
+ function getObject($object_id)
+ {
+ return $this->_data->getObject($object_id);
+ }
+
+ /**
+ * Retrieve all object ids in the current folder
+ *
+ * @deprecated
+ *
+ * @return array The object ids
+ */
+ function getObjectIds()
+ {
+ return $this->_data->getObjectIds();
+ }
+
+ /**
+ * Retrieve all objects in the current folder
+ *
+ * @deprecated
+ *
+ * @return array All object data arrays
+ */
+ function getObjects()
+ {
+ return $this->_data->getObjects();
+ }
+
+ /**
+ * Retrieve all objects in the current folder as an array
+ *
+ * @deprecated
+ *
+ * @return array The object data array
+ */
+ function getObjectArray()
+ {
+ return $this->_data->getObjectArray();
+ }
+
+ /**
+ * List the objects in the current share.
+ *
+ * @deprecated
+ *
+ * @return mixed false if there are no objects, a list of message
+ * ids or a PEAR error.
+ */
+ function listObjects()
+ {
+ if (empty($this->_imap)) {
+ return false;
+ }
+
+ // Select folder
+ $result = $this->_imap->select($this->_folder);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $this->_imap->searchHeaders('X-Kolab-Type', $this->_mime_type);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ return $result;
+ }
+
+ /**
+ * List the objects in the specified folder.
+ *
+ * @deprecated
+ *
+ * @param string $folder The folder to search.
+ *
+ * @return mixed false if there are no objects, a list of message
+ * ids otherwise.
+ */
+ function listObjectsInFolder($folder)
+ {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+
+ // Select mailbox to search in
+ $result = $imap->select($folder);
+ if (is_a($result, 'PEAR_Error')) {
+ return false;
+ }
+
+ $result = $imap->searchHeaders('X-Kolab-Type', $this->_mime_type);
+ if (!isset($result)) {
+ $result = array();
+ }
+ return $result;
+ }
+
+ /**
+ * Find the object with the given UID in the current share.
+ *
+ * @deprecated
+ *
+ * @param string $uid The UID of the object.
+ *
+ * @return mixed false if there is no such object
+ */
+ function findObject($uid)
+ {
+ if (empty($this->_imap)) {
+ return false;
+ }
+
+ if (empty($uid) || $uid == "") {
+ return PEAR::raiseError("Cannot search for an empty uid.");
+ }
+
+ // Select folder
+ $result = $this->_imap->select($this->_folder->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $this->_imap->search("SUBJECT \"$uid\"");
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ if (empty($result)) {
+ return PEAR::raiseError(sprintf(_("No message corresponds to object %s"), $uid));
+ }
+
+ return $result[0];
+ }
+
+ /**
+ * Load the object with the given UID into $this->_xml
+ *
+ * @deprecated
+ *
+ * @param string $uid The UID of the object.
+ * @param boolean $is_msgno Indicate if $uid holds an
+ * IMAP message number
+ *
+ * @return mixed false if there is no such object, a PEAR error if
+ * the object could not be loaded. Otherwise the xml
+ * document will be returned
+ */
+ function loadObject($uid, $is_msgno = false)
+ {
+ if (empty($this->_imap)) {
+ $object = false;
+ return $object;
+ }
+
+ // Select folder
+ $result = $this->_imap->select($this->_folder->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ if ($is_msgno === false) {
+ $uid = $this->findObject($uid);
+ if (is_a($uid, 'PEAR_Error')) {
+ return $uid;
+ }
+ }
+
+ $header = $this->_imap->getMessageHeader($uid);
+ if (is_a($header, 'PEAR_Error')) {
+ return $header;
+ }
+ $this->_headers = MIME_Headers::parseHeaders($header);
+
+ $message_text = $this->_imap->getMessage($uid);
+ if (is_a($message_text, 'PEAR_Error')) {
+ return $message_text;
+ }
+
+ if (is_array($message_text)) {
+ $message_text = array_shift($message_text);
+ }
+
+ $this->_msg_no = $uid;
+ $this->_message = &MIME_Structure::parseTextMIMEMessage($message_text);
+
+ $parts = $this->_message->contentTypeMap();
+ $this->_mime_id = array_search($this->_mime_type, $parts);
+ if ($this->_mime_id !== false) {
+ $part = $this->_message->getPart($this->_mime_id);
+ $text = $part->transferDecode();
+ } else {
+ return PEAR::raiseError(sprintf(_("Horde/Kolab: No object of type %s found in message %s"), $this->_mime_type, $uid));
+ }
+
+ return Horde_DOM_Document::factory(array('xml' => $text));
+ }
+
+ /**
+ * Create the object with UID in the current share
+ *
+ * @deprecated
+ *
+ * @param string $uid The UID of the object.
+ *
+ * @return mixed false if there is no open share, a PEAR error if
+ * the object could not be created. Otherwise the xml
+ * document will be returned
+ */
+ function newObject($uid)
+ {
+ if (empty($this->_imap)) {
+ $object = false;
+ return $object;
+ }
+
+ $this->_msg_no = -1;
+ $this->_message = new MIME_Message();
+
+ $kolab_text = sprintf(_("This is a Kolab Groupware object. To view this object you will need an email client that understands the Kolab Groupware format. For a list of such email clients please visit %s"),
+ 'http://www.kolab.org/kolab2-clients.html');
+ $part = new MIME_Part('text/plain',
+ Horde_String::wrap($kolab_text, 76, "\r\n", NLS::getCharset()),
+ NLS::getCharset());
+ $part->setTransferEncoding('quoted-printable');
+ $this->_message->addPart($part);
+
+ $part = new MIME_Part($this->_mime_type, '', NLS::getCharset());
+ $part->setTransferEncoding('quoted-printable');
+ $this->_message->addPart($part);
+
+ $parts = $this->_message->contentTypeMap();
+ $this->_mime_id = array_search($this->_mime_type, $parts);
+ if ($this->_mime_id === false) {
+ return PEAR::raiseError(sprintf(_("Horde/Kolab: Unable to retrieve MIME ID for the part of type %s"), $this->_mime_type));
+ }
+
+ $headers = new MIME_Headers();
+ $headers->addHeader('From', Auth::getAuth());
+ $headers->addHeader('To', Auth::getAuth());
+ $headers->addHeader('Subject', $uid);
+ $headers->addHeader('User-Agent', 'Horde::Kolab v1.1');
+ $headers->addHeader('Reply-To', '');
+ $headers->addHeader('Date', date('r'));
+ $headers->addHeader('X-Kolab-Type', $this->_mime_type);
+ $headers->addMIMEHeaders($this->_message);
+
+ $this->_headers = $headers->toArray();
+
+ $xml = '' .
+ '<' . $this->_object_type . ' version="1.0">' .
+ '' . $uid . '' .
+ '' .
+ '' .
+ '' . Kolab::encodeDateTime() . '' .
+ 'public' .
+ '' . $this->_object_type . '>';
+
+ return Horde_DOM_Document::factory(array('xml' => $xml));
+ }
+
+ /**
+ * Save the current object.
+ *
+ * @deprecated
+ *
+ * @return mixed false if there is no open share, a PEAR error if
+ * the object could not be saved. True otherwise
+ */
+ function saveObject($xml, $uid)
+ {
+ if (empty($this->_imap)) {
+ return false;
+ }
+
+ // Select folder
+ $result = $this->_imap->select($this->_folder->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $part = new MIME_Part($this->_mime_type, $xml->dump_mem(true),
+ NLS::getCharset());
+ $part->setTransferEncoding('quoted-printable');
+ $this->_message->alterPart($this->_mime_id, $part);
+
+ if ($this->_msg_no != -1) {
+ $this->removeObjects($this->_msg_no, true);
+ }
+
+ $headers = new MIME_Headers();
+ foreach ($this->_headers as $key => $val) {
+ $headers->addHeader($key, $val);
+ }
+
+ $message = Horde_Kolab_IMAP::kolabNewlines($headers->toString() .
+ $this->_message->toString(false));
+
+ $result = $this->_imap->appendMessage($message);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $this->_msg_no = $this->findObject($uid);
+ if (is_a($this->_msg_no, 'PEAR_Error')) {
+ return $this->_msg_no;
+ }
+
+ return true;
+ }
+
+ /**
+ * Move the object with the given UID from the current share into
+ * the specified new share.
+ *
+ * @deprecated
+ *
+ * @param string $uid The UID of the object.
+ * @param boolean $new_share The share the object should be moved to.
+ *
+ * @return mixed false if there is no current share, a PEAR error if
+ * the object could not be moved. True otherwise.
+ */
+ function moveObject($uid, $new_share)
+ {
+ if (empty($this->_imap)) {
+ return false;
+ }
+
+ // No IMAP folder select needed as findObject
+ // does it for us
+
+ $new_share = rawurldecode($new_share);
+ $new_share = $this->parseFolder($new_share);
+ if (is_a($new_share, 'PEAR_Error')) {
+ return $new_share;
+ }
+
+ $msg_no = $this->findObject($uid);
+ if (is_a($msg_no, 'PEAR_Error')) {
+ return $msg_no;
+ }
+
+ $result = $this->_imap->copyMessage($msg_no, $new_share);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $this->_imap->deleteMessages($msg_no);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ return $this->_imap->expunge();
+ }
+
+ /**
+ * Remove the specified objects from the current share.
+ *
+ * @deprecated
+ *
+ * @param string $objects The UIDs (or maessage numbers)
+ * of the objects to be deleted.
+ * @param boolean $is_msgno Indicate if $objects holds
+ * IMAP message numbers
+ *
+ * @return mixed false if there is no IMAP connection, a PEAR
+ * error if the objects could not be removed. True
+ * if the call succeeded.
+ */
+ function removeObjects($objects, $is_msgno = false)
+ {
+ if (empty($this->_imap)) {
+ return false;
+ }
+
+ if (!is_array($objects)) {
+ $objects = array($objects);
+ }
+
+ if ($is_msgno === false) {
+ $new_objects = array();
+
+ foreach ($objects as $object) {
+ $result = $this->findObject($object);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $new_objects[] = $result;
+ }
+
+ $objects = $new_objects;
+ }
+
+ // Select folder
+ $result = $this->_imap->select($this->_folder->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $this->_imap->deleteMessages($objects);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $this->_imap->expunge();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove all objects from the current share.
+ *
+ * @deprecated
+ *
+ * @return mixed false if there is no IMAP connection, a PEAR
+ * error if the objects could not be removed. True
+ * if the call succeeded.
+ */
+ function removeAllObjects()
+ {
+ if (empty($this->_imap)) {
+ return false;
+ }
+
+ // Select folder
+ $result = $this->_imap->select($this->_folder->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $messages = $this->listObjects();
+
+ if ($messages) {
+ $result = $this->_imap->deleteMessages($messages);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the groupware type of the given IMAP folder.
+ *
+ * @deprecated
+ *
+ * @param object $mailbox The mailbox of interest.
+ *
+ * @return mixed A string indicating the groupware type of $mailbox or
+ * boolean "false" on error.
+ */
+ function getMailboxType($mailbox)
+ {
+ return $this->_folder->getType();
+ }
+
+ /**
+ * Converts all newlines (in DOS, MAC & UNIX format) in the specified text
+ * to Kolab (Cyrus) format.
+ *
+ * @deprecated
+ *
+ * @param string $text The text to convert.
+ *
+ * @return string $text with all newlines replaced by KOLAB_NEWLINE.
+ */
+ function kolabNewlines($text)
+ {
+ return preg_replace("/\r\n|\n|\r/s", "\r\n", $text);
+ }
+
+ /**
+ * Find the object using the given criteria in the current share.
+ *
+ * @deprecated
+ *
+ * @param string $criteria The search criteria.
+ *
+ * @return mixed false if no object can be found
+ */
+ function findObjects($criteria)
+ {
+ if (empty($this->_imap)) {
+ return false;
+ }
+
+ return $this->_imap->search($criteria);
+ }
+
+ /**
+ * Return the MIME type of the message we are currently dealing with.
+ *
+ * @deprecated
+ *
+ * @return string The MIME type of the message we are currently
+ * dealing with.
+ */
+ function getMimeType()
+ {
+ return $this->_mime_type;
+ }
+}
+
diff --git a/framework/Kolab_Storage/lib/Horde/Kolab/Storage.php b/framework/Kolab_Storage/lib/Horde/Kolab/Storage.php
new file mode 100644
index 000000000..83f7cc678
--- /dev/null
+++ b/framework/Kolab_Storage/lib/Horde/Kolab/Storage.php
@@ -0,0 +1,146 @@
+
+ * require_once 'Horde/Kolab/Storage.php';
+ * $folder = Kolab_Storage::getFolder('INBOX/Calendar');
+ *
+ *
+ * or (in case you are dealing with share identifications):
+ *
+ *
+ * require_once 'Horde/Kolab/Storage.php';
+ * $folder = Kolab_Storage::getShare(Auth::getAuth(), 'event');
+ *
+ *
+ * To access data in a share (or folder) you need to retrieve the
+ * corresponding data object:
+ *
+ *
+ * require_once 'Horde/Kolab/Storage.php';
+ * $folder = Kolab_Storage::getShareData(Auth::getAuth(), 'event');
+ *
+ *
+ * $Horde: framework/Kolab_Storage/lib/Horde/Kolab/Storage.php,v 1.4 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Gunnar Wrobel
+ * @package Kolab_Storage
+ */
+class Kolab_Storage {
+
+ /**
+ * Return the folder object corresponding to the share of the
+ * specified type (e.g. "contact", "event" etc.).
+ *
+ * @param string $share The id of the share.
+ * @param string $type The share type.
+ *
+ * @return Kolab_Folder|PEAR_Error The folder object representing
+ * the share.
+ */
+ function &getShare($share, $type)
+ {
+ $list = &Kolab_List::singleton();
+ $share = $list->getByShare($share, $type);
+ return $share;
+ }
+
+ /**
+ * Return the folder object.
+ *
+ * @param string $folder The name of the folder.
+ *
+ * @return Kolab_Folder|PEAR_Error The folder object.
+ */
+ function &getFolder($folder)
+ {
+ $list = &Kolab_List::singleton();
+ $share = $list->getFolder($folder);
+ return $share;
+ }
+
+ /**
+ * Return a data object for accessing data in the specified
+ * folder.
+ *
+ * @param Kolab_Folder $folder The folder object.
+ * @param string $data_type The type of data we want to
+ * access in the folder.
+ * @param int $data_format The version of the data format
+ * we want to access in the folder.
+ *
+ * @return Kolab_Data|PEAR_Error The data object.
+ */
+ function &getData(&$folder, $data_type = null, $data_format = 1)
+ {
+ if (empty($data_type)) {
+ $data_type = $folder->getType();
+ }
+ $data = $folder->getData($data_type, $data_format);
+ return $data;
+ }
+
+ /**
+ * Return a data object for accessing data in the specified
+ * share.
+ *
+ * @param string $share The id of the share.
+ * @param string $type The share type.
+ * @param string $data_type The type of data we want to
+ * access in the folder.
+ * @param int $data_format The version of the data format
+ * we want to access in the folder.
+ *
+ * @return Kolab_Data|PEAR_Error The data object.
+ */
+ function &getShareData($share, $type, $data_type = null, $data_format = 1)
+ {
+ $folder = Kolab_Storage::getShare($share, $type);
+ if (is_a($folder, 'PEAR_Error')) {
+ return $folder;
+ }
+ $data = Kolab_Storage::getData($folder, $data_type, $data_format);
+ return $data;
+ }
+
+ /**
+ * Return a data object for accessing data in the specified
+ * folder.
+ *
+ * @param string $folder The name of the folder.
+ * @param string $data_type The type of data we want to
+ * access in the folder.
+ * @param int $data_format The version of the data format
+ * we want to access in the folder.
+ *
+ * @return Kolab_Data|PEAR_Error The data object.
+ */
+ function &getFolderData($folder, $data_type = null, $data_format = 1)
+ {
+ $folder = Kolab_Storage::getFolder($folder);
+ if (is_a($folder, 'PEAR_Error')) {
+ return $folder;
+ }
+ $data = Kolab_Storage::getData($folder, $data_type, $data_format);
+ return $data;
+ }
+}
+
diff --git a/framework/Kolab_Storage/lib/Horde/Kolab/Storage/Cache.php b/framework/Kolab_Storage/lib/Horde/Kolab/Storage/Cache.php
new file mode 100644
index 000000000..24f19f1fb
--- /dev/null
+++ b/framework/Kolab_Storage/lib/Horde/Kolab/Storage/Cache.php
@@ -0,0 +1,286 @@
+
+ * @author Thomas Jarosch
+ * @package Kolab_Storage
+ */
+class Kolab_Cache {
+
+ /**
+ * The version of the cache we loaded.
+ *
+ * @var int
+ */
+ var $_version;
+
+ /**
+ * The internal version of the cache format represented by the
+ * code.
+ *
+ * @var int
+ */
+ var $_base_version = 1;
+
+ /**
+ * The version of the data format provided by the storage handler.
+ *
+ * @var int
+ */
+ var $_data_version;
+
+ /**
+ * The version of the cache format that includes the data version.
+ *
+ * @var int
+ */
+ var $_cache_version = -1;
+
+ /**
+ * A validity marker for a share in the cache. This allows the
+ * storage handler to invalidate the cache for this share.
+ *
+ * @var int
+ */
+ var $validity;
+
+ /**
+ * A nextid marker for a share in the cache. This allows the
+ * storage handler to invalidate the cache for this share.
+ *
+ * @var int
+ */
+ var $nextid;
+
+ /**
+ * The objects of the current share.
+ *
+ * | objects: key is uid (GUID)
+ * | ----------- hashed object data
+ * |----------- uid: object id (GUID)
+ * | |----------- all fields from kolab specification
+ *
+ * @var array
+ */
+ var $objects;
+
+ /**
+ * The uid<->object mapping of the current share.
+ *
+ * | uids Mapping between imap uid and object uids: imap uid -> object uid
+ * Special: A value of "false" means we've seen the uid
+ * but we deciced to ignore it in the future
+ *
+ * @var array
+ */
+ var $uids;
+
+ /**
+ * The unique key for the currently loaded data.
+ *
+ * @var string
+ */
+ var $_key;
+
+ /**
+ * The link to the horde cache.
+ *
+ * @var Horde_Cache
+ */
+ var $_horde_cache;
+
+ /**
+ * Constructor.
+ */
+ function Kolab_Cache()
+ {
+ /**
+ * We explicitly select the file based cache to ensure
+ * that different users have access to the same cache
+ * data. I am not certain this is guaranteed for the other
+ * possible cache drivers.
+ */
+ $this->_horde_cache = &Horde_Cache::singleton('file',
+ array('prefix' => 'kolab_cache',
+ 'dir' => Horde::getTempDir()));
+ }
+
+ /**
+ * Attempts to return a reference to a concrete
+ * Kolab_Cache instance. It will only create a new
+ * instance if no Kolab_Cache instance currently exists.
+ *
+ * This method must be invoked as: $var = &Kolab_Cache::singleton()
+ *
+ * @return Kolab_Cache The concrete Kolab_Cache
+ * reference, or false on error.
+ */
+ function &singleton()
+ {
+ static $kolab_cache;
+
+ if (!isset($kolab_cache)) {
+ $kolab_cache = new Kolab_Cache();
+ }
+
+ return $kolab_cache;
+ }
+
+ /**
+ * Load the cached share data identified by $key.
+ *
+ * @param string $key Access key to the cached data.
+ * @param int $data_version A version identifier provided by
+ * the storage manager.
+ * @param bool $force Force loading the cache.
+ */
+ function load($key, $data_version, $force = false)
+ {
+ if (!$force && $this->_key == $key
+ && $this->_data_version == $data_version) {
+ return;
+ }
+
+ $this->_key = $key;
+ $this->_data_version = $data_version;
+ $this->_cache_version = ($data_version << 8) | $this->_base_version;
+
+ $this->reset();
+
+ $cache = $this->_horde_cache->get($this->_key, 0);
+
+ if (!$cache) {
+ return;
+ }
+
+ $data = unserialize($cache);
+
+ // Delete disc cache if it's from an old version
+ if ($data['version'] != $this->_cache_version) {
+ $this->_horde_cache->expire($this->_key);
+ $this->reset();
+ } else {
+ $this->_version = $data['version'];
+ $this->validity = $data['uidvalidity'];
+ $this->nextid = $data['uidnext'];
+ $this->objects = $data['objects'];
+ $this->uids = $data['uids'];
+ }
+ }
+
+ /**
+ * Load a cached attachment.
+ *
+ * @param string $key Access key to the cached data.
+ *
+ * @return mixed The data of the object.
+ */
+ function loadAttachment($key)
+ {
+ return $this->_horde_cache->get($key, 0);
+ }
+
+ /**
+ * Cache an attachment.
+ *
+ * @param string $key Access key to the cached data.
+ * @param string $data The data to be cached.
+ *
+ * @return boolean True if successfull.
+ */
+ function storeAttachment($key, $data)
+ {
+ return $this->_horde_cache->set($key, $data);
+ }
+
+ /**
+ * Initialize the cache structure.
+ */
+ function reset()
+ {
+ $this->_version = $this->_cache_version;
+ $this->validity = -1;
+ $this->nextid = -1;
+ $this->objects = array();
+ $this->uids = array();
+ }
+
+ /**
+ * Save the share data in the cache.
+ *
+ * @return boolean True on success.
+ */
+ function save()
+ {
+ if (!isset($this->_key)) {
+ return PEAR::raiseError('The cache has not been loaded yet!');
+ }
+
+ $data = array('version' => $this->_version,
+ 'uidvalidity' => $this->validity,
+ 'uidnext' => $this->nextid,
+ 'objects' => $this->objects,
+ 'uids' => $this->uids);
+
+ return $this->_horde_cache->set($this->_key,
+ serialize($data));
+ }
+
+ /**
+ * Store an object in the cache.
+ *
+ * @param int $id The storage ID.
+ * @param string $object_id The object ID.
+ * @param array $object The object data.
+ */
+ function store($id, $object_id, &$object)
+ {
+ $this->uids[$id] = $object_id;
+ $this->objects[$object_id] = $object;
+ }
+
+ /**
+ * Mark the ID as invalid (cannot be correctly parsed).
+ *
+ * @param int $id The ID of the storage item to ignore.
+ */
+ function ignore($id)
+ {
+ $this->uids[$id] = false;
+ }
+
+ /**
+ * Deliberately expire a cache.
+ */
+ function expire()
+ {
+ if (!isset($this->_key)) {
+ return PEAR::raiseError('The cache has not been loaded yet!');
+ }
+
+ $this->_version = -1;
+ $this->save();
+ $this->load($this->_key, $this->_data_version, true);
+ }
+
+}
+
+
diff --git a/framework/Kolab_Storage/lib/Horde/Kolab/Storage/Data.php b/framework/Kolab_Storage/lib/Horde/Kolab/Storage/Data.php
new file mode 100644
index 000000000..a46d4108b
--- /dev/null
+++ b/framework/Kolab_Storage/lib/Horde/Kolab/Storage/Data.php
@@ -0,0 +1,646 @@
+
+ * @author Gunnar Wrobel
+ * @author Thomas Jarosch
+ * @package Kolab_Storage
+ */
+class Kolab_Data {
+
+ /**
+ * The link to the folder object.
+ *
+ * @var Kolab_Folder
+ */
+ var $_folder;
+
+ /**
+ * The folder type.
+ *
+ * @var string
+ */
+ var $_type;
+
+ /**
+ * The object type of the data.
+ *
+ * @var string
+ */
+ var $_object_type;
+
+ /**
+ * The version of the data.
+ *
+ * @var int
+ */
+ var $_data_version;
+
+ /**
+ * The data cache.
+ *
+ * @var Kolab_Cache
+ */
+ var $_cache;
+
+ /**
+ * The Id of this data object in the cache.
+ *
+ * @var string
+ */
+ var $_cache_key;
+
+ /**
+ * An addition to the cache key in case we are operating on
+ * something other than the default type.
+ *
+ * @var string
+ */
+ var $_type_key;
+
+ /**
+ * Do we optimize for cyrus IMAPD?
+ *
+ * @var boolean
+ */
+ var $_cache_cyrus_optimize = true;
+
+ /**
+ * Creates a Kolab Folder Data representation.
+ *
+ * @param string $type Type of the folder.
+ * @param string $object_type Type of the objects we want to read.
+ * @param int $data_version Format version of the object data.
+ */
+ function Kolab_Data($type, $object_type = null, $data_version = 1)
+ {
+ $this->_type = $type;
+ if (!empty($object_type)) {
+ $this->_object_type = $object_type;
+ } else {
+ $this->_object_type = $type;
+ }
+ $this->_data_version = $data_version;
+
+ if ($this->_object_type != $this->_type) {
+ $this->_type_key = '@' . $this->_object_type;
+ } else {
+ $this->_type_key = '';
+ }
+ $this->__wakeup();
+ }
+
+ /**
+ * Initializes the object.
+ */
+ function __wakeup()
+ {
+ $this->_cache = &Kolab_Cache::singleton();
+ }
+
+ /**
+ * Returns the properties that need to be serialized.
+ *
+ * @return array List of serializable properties.
+ */
+ function __sleep()
+ {
+ $properties = get_object_vars($this);
+ unset($properties['_cache'], $properties['_folder']);
+ $properties = array_keys($properties);
+ return $properties;
+ }
+
+ /**
+ * Set the folder handler.
+ *
+ * @param Kolab_Folder $folder The handler for the folder of folders.
+ */
+ function setFolder(&$folder)
+ {
+ $this->_folder = &$folder;
+ $this->_cache_key = $this->_getCacheKey();
+ }
+
+ /**
+ * Return a unique key for the current folder.
+ *
+ * @return string A key that represents the current folder.
+ */
+ function _getCacheKey()
+ {
+ if ($this->_cache_cyrus_optimize) {
+ $search_prefix = 'INBOX/';
+
+ $pos = strpos($this->_folder->name, $search_prefix);
+ if ($pos !== false && $pos == 0) {
+ $key = 'user/' . Auth::getBareAuth() . '/'
+ . substr($this->_folder->name,
+ strlen($search_prefix))
+ . $this->_type_key;
+ } else {
+ $key = $this->_folder->name;
+ }
+ } else {
+ $key = $this->_folder->getOwner() . '/' . $this->_folder->name;
+ }
+ return $key;
+ }
+
+ /**
+ * Delete the specified message from this folder.
+ *
+ * @param string $object_uid Id of the message to be deleted.
+ *
+ * @return boolean|PEAR_Error True is successful, false if the
+ * message does not exist.
+ */
+ function delete($object_uid)
+ {
+ if (!$this->objectUidExists($object_uid)) {
+ return false;
+ }
+
+ // Find the storage ID
+ $id = $this->_getStorageId($object_uid);
+ if ($id === false) {
+ return false;
+ }
+
+ $result = $this->_folder->deleteMessage($id);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $this->_cache->load($this->_cache_key, $this->_data_version);
+
+ unset($this->_cache->objects[$object_uid]);
+ unset($this->_cache->uids[$id]);
+ $this->_cache->save();
+ return true;
+ }
+
+ /**
+ * Delete all messages from the current folder.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function deleteAll()
+ {
+ if (empty($this->_cache->uids)) {
+ return true;
+ }
+ foreach ($this->_cache->uids as $id => $object_uid) {
+ $result = $this->_folder->deleteMessage($id, false);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $this->_cache->load($this->_cache_key, $this->_data_version);
+
+ unset($this->_cache->objects[$object_uid]);
+ unset($this->_cache->uids[$id]);
+ }
+ $this->_cache->save();
+
+ $result = $this->_folder->trigger();
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Failed triggering folder %s!',
+ $this->_folder->name),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+
+ return true;
+ }
+
+ /**
+ * Move the specified message from the current folder into a new
+ * folder.
+ *
+ * @param string $object_uid ID of the message to be deleted.
+ * @param string $new_share ID of the target share.
+ *
+ * @return boolean|PEAR_Error True is successful, false if the
+ * object does not exist.
+ */
+ function move($object_uid, $new_share)
+ {
+ if (!$this->objectUidExists($object_uid)) {
+ return false;
+ }
+
+ // Find the storage ID
+ $id = $this->_getStorageId($object_uid);
+ if ($id === false) {
+ return false;
+ }
+
+ $result = $this->_folder->moveMessageToShare($id, $new_share);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $this->_cache->load($this->_cache_key, $this->_data_version);
+
+ unset($this->_cache->objects[$object_uid]);
+ unset($this->_cache->uids[$id]);
+ $this->_cache->save();
+ return true;
+ }
+
+ /**
+ * Save an object.
+ *
+ * @param array $object The array that holds the data object.
+ * @param string $old_object_id The id of the object if it existed before.
+ *
+ * @return boolean|PEAR_Error True on success.
+ */
+ function save($object, $old_object_id = null)
+ {
+ // update existing kolab object
+ if ($old_object_id != null) {
+ // check if object really exists
+ if (!$this->objectUidExists($old_object_id)) {
+ return PEAR::raiseError(sprintf(_("Old object %s does not exist."),
+ $old_object_id));
+ }
+
+ // get the storage ID
+ $id = $this->_getStorageId($old_object_id);
+ if ($id === false) {
+ return PEAR::raiseError(sprintf(_("Old object %s does not map to a uid."),
+ $old_object_id));
+ }
+
+ $old_object = $this->getObject($old_object_id);
+ } else {
+ $id = null;
+ $old_object = null;
+ }
+
+ $result = $this->_folder->saveObject($object, $this->_data_version,
+ $this->_object_type, $id, $old_object);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $this->synchronize($old_object_id);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ return true;
+ }
+
+ /**
+ * Synchronize the data cache for the current folder.
+ *
+ * @param string $history_ignore Object uid that should not be
+ * updated in the History
+ */
+ function synchronize($history_ignore = null)
+ {
+ $this->_cache->load($this->_cache_key, $this->_data_version);
+
+ $result = $this->_folder->getStatus();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ list($validity, $nextid, $ids) = $result;
+
+ $changes = $this->_folderChanged($validity, $nextid, array_keys($this->_cache->uids), $ids);
+ if ($changes) {
+ $modified = array();
+
+ $recent_uids = array_diff($ids, array_keys($this->_cache->uids));
+
+ $formats = $this->_folder->getFormats();
+
+ $handler = Horde_Kolab_Format::factory('XML', $this->_object_type, $this->_data_version);
+ if (is_a($handler, 'PEAR_Error')) {
+ return $handler;
+ }
+
+ $count = 0;
+ foreach ($recent_uids as $id) {
+
+ if ($this->_type == 'annotation' && $id != 1) {
+ continue;
+ }
+
+ $mime = $this->_folder->parseMessage($id, $handler->getMimeType(), false);
+ if (is_a($mime, 'PEAR_Error')) {
+ Horde::logMessage($mime, __FILE__, __LINE__, PEAR_LOG_WARNING);
+ $text = false;
+ } else {
+ $text = $mime[0];
+ }
+
+ if ($text) {
+ $object = $handler->load($text);
+ if (is_a($object, 'PEAR_Error')) {
+ $this->_cache->ignore($id);
+ $object->addUserInfo('STORAGE ID: ' . $id);
+ Horde::logMessage($object, __FILE__, __LINE__, PEAR_LOG_WARNING);
+ continue;
+ }
+ } else {
+ $object = false;
+ }
+
+ if ($object !== false) {
+ $message = &$mime[2];
+ $handler_type = $handler->getMimeType();
+ foreach ($message->getParts() as $part) {
+ $name = $part->getName();
+ $type = $part->getType();
+ $dp = $part->getDispositionParameter('x-kolab-type');
+ if (!empty($name) && $type != $handler_type
+ || (!empty($dp) && in_array($dp, $formats))) {
+ $object['_attachments'][$name]['type'] = $type;
+ $object['_attachments'][$name]['key'] = $this->_cache_key . '/' . $object['uid'] . ':' . $name;
+ $part->transferDecodeContents();
+ $result = $this->_cache->storeAttachment($object['_attachments'][$name]['key'],
+ $part->getContents());
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Failed storing attachment of object %s: %s',
+ $id, $result->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ $object = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if ($object !== false) {
+ $this->_cache->store($id, $object['uid'], $object);
+ $mod_ts = time();
+ if (is_array($changes) && in_array($object['uid'], $changes)
+ && $object['uid'] != $history_ignore) {
+ $this->_updateHistory($object['uid'], $mod_ts, 'modify');
+ $modified[] = $object['uid'];
+ } else {
+ $this->_updateHistory($object['uid'], $mod_ts, 'add');
+ }
+ } else {
+ $this->_cache->ignore($id);
+ }
+
+ // write out cache once in a while so if the browser times out
+ // we don't have to start from the beginning.
+ if ($count > 500) {
+ $count = 0;
+ $this->_cache->save();
+ }
+ $count++;
+ }
+
+ $this->_cache->save();
+
+ if (is_array($changes)) {
+ $deleted = array_diff($changes, $modified);
+ foreach ($deleted as $deleted_oid) {
+ if ($deleted_oid != $history_ignore) {
+ $this->_updateHistory($deleted_oid, time(), 'delete');
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Update the Horde history in case an element was modified
+ * outside of Horde.
+ *
+ * @param string $object_uid Object uid that should be updated.
+ * @param int $mod_ts Timestamp of the modification.
+ * @param string $action The action that was performed.
+ */
+ function _updateHistory($object_uid, $mod_ts, $action)
+ {
+ global $registry;
+
+ if (!isset($registry)) {
+ return;
+ }
+
+ $app = $registry->getApp();
+ if (empty($app) || is_a($app, 'PEAR_Error')) {
+ /**
+ * Ignore the history if we are not in application
+ * context.
+ */
+ return $app;
+ }
+
+ /* Log the action on this item in the history log. */
+ $history = &Horde_History::singleton();
+
+ $history_id = $app . ':' . $this->_folder->getShareId() . ':' . $object_uid;
+ $history->log($history_id, array('action' => $action, 'ts' => $mod_ts), true);
+ }
+
+
+ /**
+ * Check if the folder has changed and the cache needs to be updated.
+ *
+ * @param string $validity ID validity of the folder.
+ * @param string $nextid next ID for the folder.
+ * @param array $old_ids Old list of IDs in the folder.
+ * @param array $new_ids New list of IDs in the folder.
+ *
+ * @return mixed True or an array of deleted IDs if the
+ * folder changed and false otherwise.
+ */
+ function _folderChanged($validity, $nextid, &$old_ids, &$new_ids)
+ {
+ $changed = false;
+ $reset_done = false;
+
+ // uidvalidity changed?
+ if ($validity != $this->_cache->validity) {
+ $this->_cache->reset();
+ $reset_done = true;
+ }
+
+ // nextid changed?
+ if ($nextid != $this->_cache->nextid) {
+ $changed = true;
+ }
+
+ $this->_cache->validity = $validity;
+ $this->_cache->nextid = $nextid;
+
+ if ($reset_done) {
+ return true;
+ }
+
+ // Speed optimization: if nextid and validity didn't change
+ // and count(old_ids) == count(new_ids), the folder didn't change.
+ if ($changed || count($old_ids) != count ($new_ids)) {
+ // remove deleted messages from cache
+ $delete_ids = array_diff($old_ids, $new_ids);
+ $deleted_oids = array();
+ foreach ($delete_ids as $delete_id) {
+ $object_id = $this->_cache->uids[$delete_id];
+ if ($object_id !== false) {
+ unset($this->_cache->objects[$object_id]);
+ $deleted_oids[] = $object_id;
+ }
+ unset($this->_cache->uids[$delete_id]);
+ }
+ if (!empty($deleted_oids)) {
+ $changed = $deleted_oids;
+ } else {
+ $changed = true;
+ }
+ }
+ return $changed;
+ }
+
+ /**
+ * Return the IMAP ID for the given object ID.
+ *
+ * @param string $object_id The object ID.
+ *
+ * @return int The IMAP ID.
+ */
+ function _getStorageId($object_uid)
+ {
+ $this->_cache->load($this->_cache_key, $this->_data_version);
+
+ $id = array_search($object_uid, $this->_cache->uids);
+ if ($id === false) {
+ return false;
+ }
+
+ return $id;
+ }
+
+ /**
+ * Test if the storage ID exists.
+ *
+ * @param int $uid The storage ID.
+ *
+ * @return boolean True if the ID exists.
+ */
+ function _storageIdExists($uid)
+ {
+ $this->_cache->load($this->_cache_key, $this->_data_version);
+
+ return array_key_exists($uid, $this->_cache->uids);
+ }
+
+ /**
+ * Generate a unique object id.
+ *
+ * @return string The unique id.
+ */
+ function generateUID()
+ {
+ do {
+ $key = md5(uniqid(mt_rand(), true));
+ } while($this->objectUidExists($key));
+
+ return $key;
+ }
+
+ /**
+ * Check if the given id exists.
+ *
+ * @param string $uid The object id.
+ *
+ * @return boolean True if the id was found, false otherwise.
+ */
+ function objectUidExists($uid)
+ {
+ $this->_cache->load($this->_cache_key, $this->_data_version);
+
+ return array_key_exists($uid, $this->_cache->objects);
+ }
+
+ /**
+ * Return the specified object.
+ *
+ * @param string $object_id The object id.
+ *
+ * @return array|PEAR_Error The object data as an array.
+ */
+ function getObject($object_id)
+ {
+ $this->_cache->load($this->_cache_key, $this->_data_version);
+
+ if (!$this->objectUidExists($object_id)) {
+ return PEAR::raiseError(sprintf(_("Kolab cache: Object uid %s does not exist in the cache!"), $object_id));
+ }
+ return $this->_cache->objects[$object_id];
+ }
+
+ /**
+ * Return the specified attachment.
+ *
+ * @param string $attachment_id The attachment id.
+ *
+ * @return string|PEAR_Error The attachment data as a string.
+ */
+ function getAttachment($attachment_id)
+ {
+ return $this->_cache->loadAttachment($attachment_id);
+ }
+
+ /**
+ * Retrieve all object ids in the current folder.
+ *
+ * @return array The object ids.
+ */
+ function getObjectIds()
+ {
+ $this->_cache->load($this->_cache_key, $this->_data_version);
+
+ return array_keys($this->_cache->objects);
+ }
+
+ /**
+ * Retrieve all objects in the current folder.
+ *
+ * @return array All object data arrays.
+ */
+ function getObjects()
+ {
+ $this->_cache->load($this->_cache_key, $this->_data_version);
+
+ return array_values($this->_cache->objects);
+ }
+
+ /**
+ * Retrieve all objects in the current folder as an array.
+ *
+ * @return array The object data array.
+ */
+ function getObjectArray()
+ {
+ $this->_cache->load($this->_cache_key, $this->_data_version);
+
+ return $this->_cache->objects;
+ }
+}
diff --git a/framework/Kolab_Storage/lib/Horde/Kolab/Storage/Folder.php b/framework/Kolab_Storage/lib/Horde/Kolab/Storage/Folder.php
new file mode 100644
index 000000000..a6f6485af
--- /dev/null
+++ b/framework/Kolab_Storage/lib/Horde/Kolab/Storage/Folder.php
@@ -0,0 +1,1655 @@
+
+ * @author Gunnar Wrobel
+ * @author Thomas Jarosch
+ * @package Kolab_Storage
+ */
+class Kolab_Folder {
+
+ /**
+ * The folder name.
+ *
+ * @var string
+ */
+ var $name;
+
+ /**
+ * A new folder name if the folder should be renamed on the next
+ * save.
+ *
+ * @var string
+ */
+ var $new_name;
+
+ /**
+ * The handler for the list of Kolab folders.
+ *
+ * @var Kolab_List
+ */
+ var $_list;
+
+ /**
+ * The type of this folder.
+ *
+ * @var string
+ */
+ var $_type;
+
+ /**
+ * The complete folder type annotation (type + default).
+ *
+ * @var string
+ */
+ var $_type_annotation;
+
+ /**
+ * The owner of this folder.
+ *
+ * @var string
+ */
+ var $_owner;
+
+ /**
+ * The pure folder.
+ *
+ * @var string
+ */
+ var $_subpath;
+
+ /**
+ * Additional Horde folder attributes.
+ *
+ * @var array
+ */
+ var $_attributes;
+
+ /**
+ * Additional Kolab folder attributes.
+ *
+ * @var array
+ */
+ var $_kolab_attributes;
+
+ /**
+ * Is this a default folder?
+ *
+ * @var boolean
+ */
+ var $_default;
+
+ /**
+ * The title of this folder.
+ *
+ * @var string
+ */
+ var $_title;
+
+ /**
+ * The permission handler for the folder.
+ *
+ * @var Horde_Permission_Kolab
+ */
+ var $_perms;
+
+ /**
+ * Links to the data handlers for this folder.
+ *
+ * @var array
+ */
+ var $_data;
+
+ /**
+ * Links to the annotation data handlers for this folder.
+ *
+ * @var array
+ */
+ var $_annotation_data;
+
+ /**
+ * Indicate that the folder data has been modified from the
+ * outside and all Data handlers need to synchronize.
+ *
+ * @var boolean
+ */
+ var $tainted = false;
+
+ /**
+ * Creates a Kolab Folder representation.
+ *
+ * @param string $name Name of the folder
+ */
+ function Kolab_Folder($name = null)
+ {
+ $this->name = $name;
+ $this->__wakeup();
+ }
+
+ /**
+ * Initializes the object.
+ */
+ function __wakeup()
+ {
+ if (!isset($this->_data)) {
+ $this->_data = array();
+ }
+
+ foreach($this->_data as $data) {
+ $data->setFolder($this);
+ }
+
+ if (isset($this->_perms)) {
+ $this->_perms->setFolder($this);
+ }
+ }
+
+ /**
+ * Returns the properties that need to be serialized.
+ *
+ * @return array List of serializable properties.
+ */
+ function __sleep()
+ {
+ $properties = get_object_vars($this);
+ unset($properties['_list']);
+ $properties = array_keys($properties);
+ return $properties;
+ }
+
+ /**
+ * Set the list handler.
+ *
+ * @param Kolab_List $list The handler for the list of folders.
+ */
+ function setList(&$list)
+ {
+ $this->_list = &$list;
+ }
+
+ /**
+ * Set a new name for the folder. The new name will be realized
+ * when saving the folder.
+ *
+ * @param string $name The new folder name
+ */
+ function setName($name)
+ {
+ $name = str_replace(':', '/', $name);
+ if (substr($name, 0, 5) != 'user/' && substr($name, 0, 7) != 'shared.') {
+ $name = 'INBOX/' . $name;
+ }
+ $this->new_name = Horde_String::convertCharset($name, NLS::getCharset(), 'UTF7-IMAP');
+ }
+
+ /**
+ * Set a new IMAP folder name for the folder. The new name will be
+ * realized when saving the folder.
+ *
+ * @param string $name The new folder name.
+ */
+ function setFolder($name)
+ {
+ $this->new_name = $name;
+ }
+
+ /**
+ * Return the share ID of this folder.
+ *
+ * @return string The share ID of this folder.
+ */
+ function getShareId()
+ {
+ $current_user = Auth::getAuth();
+ if ($this->isDefault() && $this->getOwner() == $current_user) {
+ return $current_user;
+ }
+ return rawurlencode($this->name);
+ }
+
+ /**
+ * Saves the folder.
+ *
+ * @param array $attributes An array of folder attributes. You can
+ * set any attribute but there are a few
+ * special ones like 'type', 'default',
+ * 'owner' and 'desc'.
+ *
+ * @return boolean|PEAR_Error True on success.
+ */
+ function save($attributes = null)
+ {
+ if (!isset($this->name)) {
+ /* A new folder needs to be created */
+ if (!isset($this->new_name)) {
+ return PEAR::raiseError(_("Cannot create this folder! The name has not yet been set."));
+ }
+
+ if (isset($attributes['type'])) {
+ $this->_type = $attributes['type'];
+ unset($attributes['type']);
+ } else {
+ $this->_type = 'mail';
+ }
+
+ if (isset($attributes['default'])) {
+ $this->_default = $attributes['default'];
+ unset($attributes['default']);
+ } else {
+ $this->_default = false;
+ }
+
+ $result = $this->_list->create($this);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ $this->name = $this->new_name;
+ $this->new_name = null;
+
+ /* Initialize the new folder to default permissions */
+ if (empty($this->_perms)) {
+ $this->getPermission();
+ }
+
+ } else {
+
+ $type = $this->getType();
+
+ if (isset($attributes['type'])) {
+ if ($attributes['type'] != $type) {
+ Horde::logMessage(sprintf('Cannot modify the type of a folder from %s to %s!',
+ $type, $attributes['type']),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ unset($attributes['type']);
+ }
+
+ if (isset($attributes['default'])) {
+ $this->_default = $attributes['default'];
+ unset($attributes['default']);
+ } else {
+ $this->_default = $this->isDefault();
+ }
+
+ if (isset($this->new_name)
+ && $this->new_name != $this->name) {
+ /** The folder needs to be renamed */
+ $result = $this->_list->rename($this);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ /**
+ * Trigger the old folder on an empty IMAP folder.
+ */
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (!is_a($imap, 'PEAR_Error')) {
+ $result = $imap->create($this->name);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Failed creating dummy folder: %s!',
+ $result->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ $imap->setAnnotation(KOLAB_ANNOT_FOLDER_TYPE,
+ array('value.shared' => $this->_type),
+ $this->name);
+
+ $result = $this->trigger($this->name);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Failed triggering dummy folder: %s!',
+ $result->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ $result = $imap->delete($this->name);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Failed deleting dummy folder: %s!',
+ $result->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ }
+
+ $this->name = $this->new_name;
+ $this->new_name = null;
+ $this->_title = null;
+ $this->_owner = null;
+ }
+ }
+
+ if (isset($attributes['owner'])) {
+ if ($attributes['owner'] != $this->getOwner()) {
+ Horde::logMessage(sprintf('Cannot modify the owner of a folder from %s to %s!',
+ $this->getOwner(), $attributes['owner']),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ unset($attributes['owner']);
+ }
+
+ /** Handle the folder type */
+ $folder_type = $this->_type . ($this->_default ? '.default' : '');
+ if ($this->_type_annotation != $folder_type) {
+ $result = $this->_setAnnotation(KOLAB_ANNOT_FOLDER_TYPE, $folder_type);
+ if (is_a($result, 'PEAR_Error')) {
+ $this->_type = null;
+ $this->_default = false;
+ $this->_type_annotation = null;
+ return $result;
+ }
+ }
+
+ if (!empty($attributes)) {
+ if (!is_array($attributes)) {
+ $attributes = array($attributes);
+ }
+ foreach ($attributes as $key => $value) {
+ if ($key == 'params') {
+ $params = unserialize($value);
+ if (isset($params['xfbaccess'])) {
+ $result = $this->setXfbAccess($params['xfbaccess']);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ if (isset($params['fbrelevance'])) {
+ $result = $this->setFbrelevance($params['fbrelevance']);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ }
+
+ // setAnnotation apparently does not suppoort UTF-8 nor any special characters
+ $store = base64_encode($value);
+ if ($key == 'desc') {
+ $entry = '/comment';
+ } else {
+ $entry = HORDE_ANNOT_SHARE_ATTR . $key;
+ }
+ $result = $this->_setAnnotation($entry, $store);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ $this->_attributes = $attributes;
+ }
+
+ /** Now save the folder permissions */
+ if (isset($this->_perms)) {
+ $result = $this->_perms->save();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+
+ $result = $this->trigger();
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s',
+ $this->name, $result->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+
+ return true;
+ }
+
+ /**
+ * Delete this folder.
+ *
+ * @return boolean|PEAR_Error True if the operation succeeded.
+ */
+ function delete()
+ {
+ $result = $this->_list->remove($this);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the owner of the folder.
+ *
+ * @return string|PEAR_Error The owner of this folder.
+ */
+ function getOwner()
+ {
+ if (!isset($this->_owner)) {
+ if (!isset($this->name) && isset($this->new_name)) {
+ $name = $this->new_name;
+ } else {
+ $name = $this->name;
+ }
+
+ if (!preg_match(";(shared\.|INBOX[/]?|user/([^/]+)[/]?)([^@]*)(@.*)?;", $name, $matches)) {
+ return PEAR::raiseError(sprintf(_("Owner of folder %s cannot be determined."), $name));
+ }
+
+ $this->_subpath = $matches[3];
+
+ if (substr($matches[1], 0, 6) == 'INBOX/') {
+ $this->_owner = Auth::getAuth();
+ } elseif (substr($matches[1], 0, 5) == 'user/') {
+ $domain = strstr(Auth::getAuth(), '@');
+ $user_domain = isset($matches[4]) ? $matches[4] : $domain;
+ $this->_owner = $matches[2] . $user_domain;
+ } elseif ($matches[1] == 'shared.') {
+ $this->_owner = 'anonymous';
+ }
+ }
+ return $this->_owner;
+ }
+
+ /**
+ * Returns the subpath of the folder.
+ *
+ * @param string $name Name of the folder that should be triggered.
+ *
+ * @return string|PEAR_Error The subpath of this folder.
+ */
+ function getSubpath($name = null)
+ {
+ if (!isset($this->_subpath) || isset($name)) {
+ if (!isset($name)) {
+ if (!isset($this->name) && isset($this->new_name)) {
+ $name = $this->new_name;
+ } else {
+ $name = $this->name;
+ }
+ }
+
+ if (!preg_match(";(shared\.|INBOX[/]?|user/([^/]+)[/]?)([^@]*)(@.*)?;", $name, $matches)) {
+ return PEAR::raiseError(sprintf(_("Subpath of folder %s cannot be determined."), $name));
+ }
+
+ $this->_subpath = $matches[3];
+
+ }
+ return $this->_subpath;
+ }
+
+ /**
+ * Returns a readable title for this folder.
+ *
+ * @return string The folder title.
+ */
+ function getTitle()
+ {
+ if (!isset($this->_title) && isset($this->name)) {
+ $title = $this->name;
+ if (substr($title, 0, 6) == 'INBOX/') {
+ $title = substr($title, 6);
+ }
+ $title = str_replace('/', ':', $title);
+ $this->_title = Horde_String::convertCharset($title, 'UTF7-IMAP');
+ }
+ return $this->_title;
+ }
+
+ /**
+ * The type of this folder.
+ *
+ * @return string|PEAR_Error The folder type.
+ */
+ function getType()
+ {
+ if (!isset($this->_type)) {
+ $type_annotation = $this->_getAnnotation(KOLAB_ANNOT_FOLDER_TYPE,
+ $this->name);
+ if (is_a($type_annotation, 'PEAR_Error')) {
+ $this->_default = false;
+ return $type_annotation;
+ } else if (empty($type_annotation)) {
+ $this->_default = false;
+ $this->_type = '';
+ } else {
+ $type = explode('.', $type_annotation);
+ $this->_default = (!empty($type[1]) && $type[1] == 'default');
+ $this->_type = $type[0];
+ }
+ $this->_type_annotation = $type_annotation;
+ }
+ return $this->_type;
+ }
+
+ /**
+ * Is this a default folder?
+ *
+ * @return boolean Boolean that indicates the default status.
+ */
+ function isDefault()
+ {
+ if (!isset($this->_default)) {
+ /* This call also determines default status */
+ $this->getType();
+ }
+ return $this->_default;
+ }
+
+ /**
+ * Returns one of the attributes of the folder, or an empty string
+ * if it isn't defined.
+ *
+ * @param string $attribute The attribute to retrieve.
+ *
+ * @return mixed The value of the attribute, an empty string or an
+ * error.
+ */
+ function getAttribute($attribute)
+ {
+ if (!isset($this->_attributes[$attribute])) {
+ if ($attribute == 'desc') {
+ $entry = '/comment';
+ } else {
+ $entry = HORDE_ANNOT_SHARE_ATTR . $attribute;
+ }
+ $annotation = $this->_getAnnotation($entry, $this->name);
+ if (is_a($annotation, 'PEAR_Error')) {
+ return $annotation;
+ }
+ if (empty($annotation)) {
+ $this->_attributes[$attribute] = '';
+ } else {
+ $this->_attributes[$attribute] = base64_decode($annotation);
+ }
+ }
+ return $this->_attributes[$attribute];
+ }
+
+ /**
+ * Returns one of the Kolab attributes of the folder, or an empty
+ * string if it isn't defined.
+ *
+ * @param string $attribute The attribute to retrieve.
+ *
+ * @return mixed The value of the attribute, an empty string or an
+ * error.
+ */
+ function getKolabAttribute($attribute)
+ {
+ if (!isset($this->_kolab_attributes[$attribute])) {
+ $entry = KOLAB_ANNOT_ROOT . $attribute;
+ $annotation = $this->_getAnnotation($entry, $this->name);
+ if (is_a($annotation, 'PEAR_Error')) {
+ return $annotation;
+ }
+ if (empty($annotation)) {
+ $this->_kolab_attributes[$attribute] = '';
+ } else {
+ $this->_kolab_attributes[$attribute] = $annotation;
+ }
+ }
+ return $this->_kolab_attributes[$attribute];
+ }
+
+
+ /**
+ * Returns whether the folder exists.
+ *
+ * @return boolean|PEAR_Error True if the folder exists.
+ */
+ function exists()
+ {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ $result = $imap->exists($this->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return false;
+ }
+ return $result;
+ }
+
+ /**
+ * Returns whether the folder is accessible.
+ *
+ * @return boolean|PEAR_Error True if the folder can be accessed.
+ */
+ function accessible()
+ {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ $result = $imap->select($this->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return false;
+ }
+ return $result;
+ }
+
+ /**
+ * Retrieve a handler for the data in this folder.
+ *
+ * @param Kolab_List $list The handler for the list of folders.
+ *
+ * @return Kolab_Data|PEAR_Error The data handler.
+ */
+ function &getData($object_type = null, $data_version = 1)
+ {
+ if (empty($object_type)) {
+ $object_type = $this->getType();
+ if (is_a($object_type, 'PEAR_Error')) {
+ return $object_type;
+ }
+ }
+
+ if ($this->tainted) {
+ foreach ($this->_data as $data) {
+ $data->synchronize();
+ }
+ $this->tainted = false;
+ }
+
+ $key = $object_type . '|' . $data_version;
+ if (!isset($this->_data[$key])) {
+ if ($object_type != 'annotation') {
+ $type = $this->getType();
+ } else {
+ $type = 'annotation';
+ }
+ $data = new Kolab_Data($type, $object_type, $data_version);
+ $data->setFolder($this);
+ $data->synchronize();
+ $this->_data[$key] = &$data;
+ }
+ return $this->_data[$key];
+ }
+
+ /**
+ * Delete the specified message from this folder.
+ *
+ * @param string $id IMAP id of the message to be deleted.
+ * @param boolean $trigger Should the folder be triggered?
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function deleteMessage($id, $trigger = true)
+ {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ // Select folder
+ $result = $imap->select($this->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $imap->deleteMessages($id);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $imap->expunge();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ if ($trigger) {
+ $result = $this->trigger();
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s',
+ $this->name, $result->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Move the specified message to the specified folder.
+ *
+ * @param string $id IMAP id of the message to be moved.
+ * @param string $folder Name of the receiving folder.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function moveMessage($id, $folder)
+ {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ // Select folder
+ $result = $imap->select($this->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $imap->moveMessage($id, $folder);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $imap->expunge();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $this->trigger();
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s',
+ $this->name, $result->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+
+ return true;
+ }
+
+ /**
+ * Move the specified message to the specified share.
+ *
+ * @param string $id IMAP id of the message to be moved.
+ * @param string $share Name of the receiving share.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function moveMessageToShare($id, $share)
+ {
+ $folder = $this->_list->getByShare($share, $this->getType());
+ if (is_a($folder, 'PEAR_Error')) {
+ return $folder;
+ }
+ $folder->tainted = true;
+
+ $success = $this->moveMessage($id, $folder->name);
+
+ $result = $this->trigger();
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s',
+ $this->name, $result->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ return $success;
+ }
+
+ /**
+ * Retrieve the supported formats.
+ *
+ * @return array The names of the supported formats.
+ */
+ function getFormats()
+ {
+ global $conf;
+
+ if (empty($conf['kolab']['misc']['formats'])) {
+ $formats = array('XML');
+ } else {
+ $formats = $conf['kolab']['misc']['formats'];
+ }
+ if (!is_array($formats)) {
+ $formats = array($formats);
+ }
+ if (!in_array('XML', $formats)) {
+ $formats[] = 'XML';
+ }
+ return $formats;
+ }
+
+ /**
+ * Save an object in this folder.
+ *
+ * @param array $object The array that holds the data of the object.
+ * @param int $data_version The format handler version.
+ * @param string $object_type The type of the kolab object.
+ * @param string $id The IMAP id of the old object if it
+ * existed before
+ * @param array $old_object The array that holds the current data of the
+ * object.
+ *
+ * @return boolean|PEAR_Error True on success.
+ */
+ function saveObject(&$object, $data_version, $object_type, $id = null,
+ &$old_object = null)
+ {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ // Select folder
+ $result = $imap->select($this->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $new_headers = new Horde_Mime_Headers();
+ $new_headers->setEOL("\r\n");
+
+ $formats = $this->getFormats();
+
+ $handlers = array();
+ foreach ($formats as $type) {
+ $handlers[$type] = &Horde_Kolab_Format::factory($type, $object_type,
+ $data_version);
+ if (is_a($handlers[$type], 'PEAR_Error')) {
+ if ($type == 'XML') {
+ return $handlers[$type];
+ }
+ Horde::logMessage(sprintf('Loading format handler "%s" failed: %s',
+ $type, $handlers[$type]->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ continue;
+ }
+ }
+
+ if ($id != null) {
+ /** Update an existing kolab object */
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ if (!in_array($id, $imap->getUids())) {
+ return PEAR::raiseError(sprintf(_("The message with ID %s does not exist. This probably means that the Kolab object has been modified by somebody else while you were editing it. Your edits have been lost."),
+ $id));
+ }
+
+ /** Parse email and load Kolab format structure */
+ $result = $this->parseMessage($id, $handlers['XML']->getMimeType(),
+ true, $formats);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ list($old_message, $part_ids, $mime_message, $mime_headers) = $result;
+ if (is_a($old_message, 'PEAR_Error')) {
+ return $old_message;
+ }
+
+ if (isset($object['_attachments']) && isset($old_object['_attachments'])) {
+ $attachments = array_keys($object['_attachments']);
+ foreach (array_keys($old_object['_attachments']) as $attachment) {
+ if (!in_array($attachment, $attachments)) {
+ foreach ($mime_message->getParts() as $part) {
+ if ($part->getName() === $attachment) {
+ foreach (array_keys($mime_message->_parts) as $key) {
+ if ($mime_message->_parts[$key]->getMimeId() == $part->getMimeId()) {
+ unset($mime_message->_parts[$key]);
+ break;
+ }
+ }
+ $mime_message->_generateIdMap($mime_message->_parts);
+ }
+ }
+ }
+ }
+ }
+ $object = array_merge($old_object, $object);
+
+ if (isset($attachments)) {
+ foreach ($mime_message->getParts() as $part) {
+ $name = $part->getName();
+ foreach ($attachments as $attachment) {
+ if ($name === $attachment) {
+ $object['_attachments'][$attachment]['id'] = $part->getMimeId();
+ }
+ }
+ }
+ }
+
+ /** Copy email header */
+ if (!empty($mime_headers) && !$mime_headers === false) {
+ foreach ($mime_headers as $header => $value) {
+ $new_headers->addheader($header, $value);
+ }
+ }
+ } else {
+ $mime_message = $this->_prepareNewMessage($new_headers);
+ $mime_part_id = false;
+ }
+
+ if (isset($object['_attachments'])) {
+ $attachments = array_keys($object['_attachments']);
+ foreach ($attachments as $attachment) {
+ $data = $object['_attachments'][$attachment];
+
+ if (!isset($data['content']) && !isset($data['path'])) {
+ /**
+ * There no new content and no new path. Do not rewrite the
+ * attachment.
+ */
+ continue;
+ }
+
+ $part = new Horde_Mime_Part();
+ $part->setType(isset($data['type']) ? $data['type'] : null);
+ $part->setContents(isset($data['content']) ? $data['content'] : file_get_contents($data['path']));
+ $part->setCharset(NLS::getCharset());
+ $part->setTransferEncoding('quoted-printable');
+ $part->setDisposition('attachment');
+ $part->setName($attachment);
+
+ if (!isset($data['id'])) {
+ $mime_message->addPart($part);
+ } else {
+ $mime_message->alterPart($data['id'], $part);
+ }
+ }
+ }
+
+ foreach ($formats as $type) {
+ $new_content = $handlers[$type]->save($object);
+ if (is_a($new_content, 'PEAR_Error')) {
+ return $new_content;
+ }
+
+ /** Update mime part */
+ $part = new Horde_Mime_Part();
+ $part->setType($handlers[$type]->getMimeType());
+ $part->setContents($new_content);
+ $part->setCharset(NLS::getCharset());
+ $part->setTransferEncoding('quoted-printable');
+ $part->setDisposition($handlers[$type]->getDisposition());
+ $part->setDispositionParameter('x-kolab-type', $type);
+ $part->setName($handlers[$type]->getName());
+
+ if (!isset($part_ids) || $part_ids[$type] === false) {
+ $mime_message->addPart($part);
+ } else {
+ $mime_message->alterPart($part_ids[$type], $part);
+ }
+ }
+
+ $session = &Horde_Kolab_Session::singleton();
+
+ // Update email headers
+ $new_headers->addHeader('From', $session->user_mail);
+ $new_headers->addHeader('To', $session->user_mail);
+ $new_headers->addHeader('Date', date('r'));
+ $new_headers->addHeader('X-Kolab-Type', $handlers['XML']->getMimeType());
+ $new_headers->addHeader('Subject', $object['uid']);
+ $new_headers->addHeader('User-Agent', 'Horde::Kolab::Storage v0.2');
+ $mime_message->addMimeHeaders($new_headers);
+
+ $msg = $new_headers->toString() . $mime_message->toCanonicalString(false);
+
+ // delete old email?
+ if ($id != null) {
+ $result = $imap->deleteMessages($id);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+
+ // store new email
+ $result = $imap->appendMessage($msg);
+ if (is_a($result, 'PEAR_Error')) {
+ if ($id != null) {
+ $result = $imap->undeleteMessages($id);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ return $result;
+ }
+
+ // remove deleted object
+ if ($id != null) {
+ $result = $imap->expunge();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+
+ $result = $this->trigger();
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s',
+ $this->name, $result->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+
+ return true;
+ }
+
+ /**
+ * Get an IMAP message and retrieve the Kolab Format object.
+ *
+ * @param int $id The message to retrieve.
+ * @param string $mime_type The mime type of the part to retrieve.
+ * @param boolean $parse_headers Should the heades be Mime parsed?
+ * @param array $formats The list of possible format parts.
+ *
+ * @return array|PEAR_Error An array that list the Kolab XML
+ * object text, the mime ID of the part
+ * with the XML object, the Mime parsed
+ * message and the Mime parsed headers if
+ * requested.
+ */
+ function parseMessage($id, $mime_type, $parse_headers = true,
+ $formats = array('XML'))
+ {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ $raw_headers = $imap->getMessageHeader($id);
+ if (is_a($raw_headers, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Failed retrieving the message with ID %s. Original error: %s."),
+ $id, $raw_headers->getMessage()));
+ }
+
+ $body = $imap->getMessageBody($id);
+ if (is_a($body, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Failed retrieving the message with ID %s. Original error: %s."),
+ $id, $body->getMessage()));
+ }
+
+ $mime_message = Horde_Mime_Part::parseMessage($raw_headers . $body);
+ $parts = $mime_message->contentTypeMap();
+
+ $mime_headers = false;
+ $xml = false;
+
+ // Read in a Kolab event object, if one exists
+ $part_ids['XML'] = array_search($mime_type, $parts);
+ if ($part_ids['XML'] !== false) {
+ if ($parse_headers) {
+ $mime_headers = Horde_Mime_Headers::parseHeaders($raw_headers);
+ $mime_headers->setEOL("\r\n");
+ }
+
+ $part = $mime_message->getPart($part_ids['XML']);
+ $part->transferDecodeContents();
+ $xml = $part->getContents();
+ }
+
+ $alternate_formats = array_diff(array('XML'), $formats);
+ if (!empty($alternate_formats)) {
+ foreach ($alternate_formats as $type) {
+ $part_ids[$type] = false;
+ }
+ foreach ($mime_message->getParts() as $part) {
+ $params = $part->getDispositionParameters();
+ foreach ($alternate_formats as $type) {
+ if (isset($params['x-kolab-format'])
+ && $params['x-kolab-format'] == $type) {
+ $part_ids[$type] = $part->getMimeId();
+ }
+ }
+ }
+ }
+
+ $result = array($xml, $part_ids, $mime_message, $mime_headers);
+ return $result;
+ }
+
+ /**
+ * Prepares a new kolab Groupeware message.
+ *
+ * @return string The Mime message
+ */
+ function _prepareNewMessage()
+ {
+ $mime_message = new Horde_Mime_Part();
+ $mime_message->setName('Kolab Groupware Data');
+ $mime_message->setType('multipart/mixed');
+ $kolab_text = sprintf(_("This is a Kolab Groupware object. To view this object you will need an email client that understands the Kolab Groupware format. For a list of such email clients please visit %s"),
+ 'http://www.kolab.org/kolab2-clients.html');
+ $part = new Horde_Mime_Part();
+ $part->setType('text/plain');
+ $part->setName('Kolab Groupware Information');
+ $part->setContents(Horde_String::wrap($kolab_text, 76, "\r\n", NLS::getCharset()));
+ $part->setCharset(NLS::getCharset());
+
+ $part->setTransferEncoding('quoted-printable');
+ $mime_message->addPart($part);
+ return $mime_message;
+ }
+
+ /**
+ * Report the status of this folder.
+ *
+ * @return array|PEAR_Error An array listing the validity ID, the
+ * next IMAP ID and an array of IMAP IDs.
+ */
+ function getStatus()
+ {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ // Select the folder to update uidnext
+ $result = $imap->select($this->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $status = $imap->status();
+ if (is_a($status, 'PEAR_Error')) {
+ return $status;
+ }
+
+ $uids = $imap->getUids();
+ if (is_a($uids, 'PEAR_Error')) {
+ return $uids;
+ }
+ return array($status['uidvalidity'], $status['uidnext'], $uids);
+ }
+
+ /**
+ * Triggers any required updates after changes within the
+ * folder. This is currently only required for handling free/busy
+ * information with Kolab.
+ *
+ * @param string $name Name of the folder that should be triggered.
+ *
+ * @return boolean|PEAR_Error True if successfull.
+ */
+ function trigger($name = null)
+ {
+ $type = $this->getType();
+ if (is_a($type, 'PEAR_Error')) {
+ return $type;
+ }
+
+ $owner = $this->getOwner();
+ if (is_a($owner, 'PEAR_Error')) {
+ return $owner;
+ }
+
+ $subpath = $this->getSubpath($name);
+ if (is_a($subpath, 'PEAR_Error')) {
+ return $subpath;
+ }
+
+ switch($type) {
+ case 'event':
+ $session = &Horde_Kolab_Session::singleton();
+ $url = sprintf('%s/trigger/%s/%s.pfb',
+ $session->freebusy_server, $owner, $subpath);
+ break;
+ default:
+ return true;
+ }
+
+ $result = $this->triggerUrl($url);
+ if (is_a($result, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Failed triggering folder %s. Error was: %s"),
+ $this->name, $result->getMessage()));
+ }
+ return $result;
+ }
+
+ /**
+ * Triggers a URL.
+ *
+ * @param string $url The URL to be triggered.
+ *
+ * @return boolean|PEAR_Error True if successfull.
+ */
+ function triggerUrl($url)
+ {
+ global $conf;
+
+ if (!empty($conf['kolab']['no_triggering'])) {
+ return true;
+ }
+
+ $options['method'] = 'GET';
+ $options['timeout'] = 5;
+ $options['allowRedirects'] = true;
+
+ if (isset($conf['http']['proxy']) && !empty($conf['http']['proxy']['proxy_host'])) {
+ $options = array_merge($options, $conf['http']['proxy']);
+ }
+
+ require_once 'HTTP/Request.php';
+ $http = new HTTP_Request($url, $options);
+ $http->setBasicAuth(Auth::getAuth(), Auth::getCredential('password'));
+ @$http->sendRequest();
+ if ($http->getResponseCode() != 200) {
+ return PEAR::raiseError(sprintf(_("Unable to trigger URL %s. Response: %s"),
+ $url, $http->getResponseCode()));
+ }
+ return true;
+ }
+
+ /**
+ * Checks to see if a user has a given permission.
+ *
+ * @param string $userid The userid of the user.
+ * @param integer $permission A PERMS_* constant to test for.
+ * @param string $creator The creator of the shared object.
+ *
+ * @return boolean|PEAR_Error Whether or not $userid has $permission.
+ */
+ function hasPermission($userid, $permission, $creator = null)
+ {
+ if ($userid == $this->getOwner()) {
+ return true;
+ }
+
+ $perm = &$this->getPermission();
+ if (is_a($perm, 'PEAR_Error')) {
+ return $perm;
+ }
+ return $perm->hasPermission($userid, $permission, $creator);
+ }
+
+ /**
+ * Returns the permissions from this storage object.
+ *
+ * @return Horde_Permission_Kolab The permissions on the share.
+ */
+ function &getPermission()
+ {
+ if (!isset($this->_perms)) {
+ if ($this->exists()) {
+ // The permissions are unknown but the folder exists
+ // -> discover permissions
+ $perms = null;
+ } else {
+ $perms = array(
+ 'users' => array(
+ Auth::getAuth() => PERMS_SHOW | PERMS_READ |
+ PERMS_EDIT | PERMS_DELETE));
+ }
+ $this->_perms = &new Horde_Permission_Kolab($this, $perms);
+ }
+ return $this->_perms;
+ }
+
+ /**
+ * Sets the permissions on the share.
+ *
+ * @param Horde_Permission_Kolab $perms Permission object to store on the
+ * object.
+ * @param boolean $update Save the updated information?
+ *
+ * @return boolean|PEAR_Error True on success.
+ */
+ function setPermission(&$perms, $update = true)
+ {
+ if (!is_a($perms, 'Horde_Permission')) {
+ return PEAR::raiseError('The permissions for this share must be specified as an instance of the Horde_Permission class!');
+ }
+
+ if (!is_a($perms, 'Horde_Permission_Kolab')) {
+ $this->_perms = &new Horde_Permission_Kolab($this, $perms->data);
+ } else {
+ $this->_perms = &$perms;
+ $this->_perms->setFolder($this);
+ }
+
+ if ($update) {
+ return $this->save();
+ }
+
+ return true;
+ }
+
+ /**
+ * Return the IMAP ACL of this folder.
+ *
+ * @return array|PEAR_Error An array with IMAP ACL.
+ */
+ function getACL()
+ {
+ global $conf;
+
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ if (!empty($conf['kolab']['imap']['no_acl'])) {
+ $acl = array();
+ $acl[Auth::getAuth()] = 'lrid';
+ return $acl;
+ }
+
+ $acl = $imap->getACL($this->name);
+
+ /*
+ * Check if the getPerm comes from the owner in this case we
+ * can use getACL to have all the right of the share Otherwise
+ * we just ask for the right of the current user for a folder
+ */
+ if ($this->getOwner() == Auth::getAuth()) {
+ return $acl;
+ } else {
+ if (!is_a($acl, 'PEAR_Error')) {
+ return $acl;
+ }
+
+ $my_rights = $imap->getMyrights($this->name);
+ if (is_a($my_rights, 'PEAR_Error')) {
+ return $my_rights;
+ }
+
+ $acl = array();
+ $acl[Auth::getAuth()] = $my_rights;
+ return $acl;
+ }
+ }
+
+ /**
+ * Set the IMAP ACL of this folder.
+ *
+ * @param $user The user for whom the ACL should be set.
+ * @param $acl The new ACL value.
+ *
+ * @return boolean|PEAR_Error True on success.
+ */
+ function setACL($user, $acl)
+ {
+ global $conf;
+
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ if (!empty($conf['kolab']['imap']['no_acl'])) {
+ return true;
+ }
+
+ $iresult = $imap->setACL($this->name, $user, $acl);
+ if (is_a($iresult, 'PEAR_Error')) {
+ return $iresult;
+ }
+
+ if (!empty($this->_perms)) {
+ /** Refresh the cache after changing the permissions */
+ $this->_perms->getPerm();
+ }
+
+ $result = $this->trigger();
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s',
+ $this->name, $result->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+
+ return $iresult;
+ }
+
+ /**
+ * Delete the IMAP ACL for a user on this folder.
+ *
+ * @param $user The user for whom the ACL should be deleted.
+ *
+ * @return boolean|PEAR_Error True on success.
+ */
+ function deleteACL($user)
+ {
+ global $conf;
+
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ if (!empty($conf['kolab']['imap']['no_acl'])) {
+ return true;
+ }
+
+ $iresult = $imap->deleteACL($this->name, $user);
+ if (is_a($iresult, 'PEAR_Error')) {
+ return $iresult;
+ }
+
+ $result = $this->trigger();
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s',
+ $this->name, $result->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+
+ return $iresult;
+ }
+
+
+ /**
+ * Get annotation values on IMAP server that do not support
+ * METADATA.
+ *
+ * @return array|PEAR_Error The anotations of this folder.
+ */
+ function _getAnnotationData()
+ {
+ $this->_annotation_data = $this->getData('annotation');
+ }
+
+
+ /**
+ * Get an annotation value of this folder.
+ *
+ * @param $key The key of the annotation to retrieve.
+ *
+ * @return string|PEAR_Error The anotation value.
+ */
+ function _getAnnotation($key)
+ {
+ global $conf;
+
+ if (empty($conf['kolab']['imap']['no_annotations'])) {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+ return $imap->getAnnotation($key, 'value.shared',
+ $this->name);
+ }
+
+ if (!isset($this->_annotation_data)) {
+ $this->_getAnnotationData();
+ }
+ $data = $this->_annotation_data->getObject('KOLAB_FOLDER_CONFIGURATION');
+ if (is_a($data, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Error retrieving annotation data on folder %s: %s',
+ $this->name, $data->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ return '';
+ }
+ if (isset($data[$key])) {
+ return $data[$key];
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Set an annotation value of this folder.
+ *
+ * @param $key The key of the annotation to change.
+ * @param $value The new value.
+ *
+ * @return boolean|PEAR_Error True on success.
+ */
+ function _setAnnotation($key, $value)
+ {
+ if (empty($conf['kolab']['imap']['no_annotations'])) {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+ return $imap->setAnnotation($key,
+ array('value.shared' => $value),
+ $this->name);
+ }
+
+ if (!isset($this->_annotation_data)) {
+ $this->_getAnnotationData();
+ }
+ $data = $this->_annotation_data->getObject('KOLAB_FOLDER_CONFIGURATION');
+ if (is_a($data, 'PEAR_Error')) {
+ Horde::logMessage(sprintf('Error retrieving annotation data on folder %s: %s',
+ $this->name, $data->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ $data = array();
+ $uid = null;
+ } else {
+ $uid = 'KOLAB_FOLDER_CONFIGURATION';
+ }
+ $data[$key] = $value;
+ $data['uid'] = 'KOLAB_FOLDER_CONFIGURATION';
+ return $this->_annotation_data->save($data, $uid);
+ }
+
+
+
+ /**
+ * Get the free/busy relevance for this folder
+ *
+ * @return int Value containing the FB_RELEVANCE.
+ */
+ function getFbrelevance()
+ {
+ $result = $this->getKolabAttribute('incidences-for');
+ if (is_a($result, 'PEAR_Error') || empty($result)) {
+ return KOLAB_FBRELEVANCE_ADMINS;
+ }
+ switch ($result) {
+ case 'admins':
+ return KOLAB_FBRELEVANCE_ADMINS;
+ case 'readers':
+ return KOLAB_FBRELEVANCE_READERS;
+ case 'nobody':
+ return KOLAB_FBRELEVANCE_NOBODY;
+ default:
+ return KOLAB_FBRELEVANCE_ADMINS;
+ }
+ }
+
+ /**
+ * Set the free/busy relevance for this folder
+ *
+ * @param int $relevance Value containing the FB_RELEVANCE
+ *
+ * @return mixed True on success or a PEAR_Error.
+ */
+ function setFbrelevance($relevance)
+ {
+ switch ($relevance) {
+ case KOLAB_FBRELEVANCE_ADMINS:
+ $value = 'admins';
+ break;
+ case KOLAB_FBRELEVANCE_READERS:
+ $value = 'readers';
+ break;
+ case KOLAB_FBRELEVANCE_NOBODY:
+ $value = 'nobody';
+ break;
+ default:
+ $value = 'admins';
+ }
+
+ return $this->_setAnnotation(KOLAB_ANNOT_ROOT . 'incidences-for',
+ $value);
+ }
+
+ /**
+ * Get the extended free/busy access settings for this folder
+ *
+ * @return array Array containing the users with access to the
+ * extended information.
+ */
+ function getXfbaccess()
+ {
+ $result = $this->getKolabAttribute('pxfb-readable-for');
+ if (is_a($result, 'PEAR_Error') || empty($result)) {
+ return array();
+ }
+ return explode(' ', $result);
+ }
+
+ /**
+ * Set the extended free/busy access settings for this folder
+ *
+ * @param array $access Array containing the users with access to the
+ * extended information.
+ *
+ * @return mixed True on success or a PEAR_Error.
+ */
+ function setXfbaccess($access)
+ {
+ $value = join(' ', $access);
+ return $this->_setAnnotation(KOLAB_ANNOT_ROOT . 'pxfb-readable-for',
+ $value);
+ }
+}
diff --git a/framework/Kolab_Storage/lib/Horde/Kolab/Storage/List.php b/framework/Kolab_Storage/lib/Horde/Kolab/Storage/List.php
new file mode 100644
index 000000000..fdceb731a
--- /dev/null
+++ b/framework/Kolab_Storage/lib/Horde/Kolab/Storage/List.php
@@ -0,0 +1,512 @@
+
+ * @package Kolab_Storage
+ */
+class Kolab_List {
+
+ /**
+ * The list of existing folders on this server.
+ *
+ * @var array
+ */
+ var $_list;
+
+ /**
+ * A cache for folder objects (these do not necessarily exist).
+ *
+ * @var array
+ */
+ var $_folders;
+
+ /**
+ * A cache array listing a default folder for each folder type.
+ *
+ * @var array
+ */
+ var $_defaults;
+
+ /**
+ * A cache array listing a the folders for each folder type.
+ *
+ * @var array
+ */
+ var $_types;
+
+ /**
+ * A validity marker.
+ *
+ * @var int
+ */
+ var $validity;
+
+
+ /**
+ * Constructor.
+ */
+ function Kolab_List()
+ {
+ $this->validity = 0;
+ $this->__wakeup();
+ }
+
+ /**
+ * Initializes the object.
+ */
+ function __wakeup()
+ {
+ if (!isset($this->_folders)) {
+ $this->_folders = array();
+ }
+
+ foreach($this->_folders as $folder) {
+ $folder->setList($this);
+ }
+ }
+
+ /**
+ * Attempts to return a reference to a concrete Kolab_Folders_List instance.
+ *
+ * It will only create a new instance if no Kolab_Folders instance currently
+ * exists.
+ *
+ * This method must be invoked as:
+ * $var = &Kolab_Folders_List::singleton();
+ *
+ * @static
+ *
+ * @return Kolab_Folders_List The concrete List reference.
+ */
+ static public function &singleton($destruct = false)
+ {
+ static $list;
+
+ if (!isset($list) &&
+ !empty($GLOBALS['conf']['kolab']['imap']['cache_folders'])) {
+ require_once 'Horde/SessionObjects.php';
+ $session = &Horde_SessionObjects::singleton();
+ $list = $session->query('kolab_folderlist');
+ }
+
+ if (empty($list[Auth::getAuth()]) || $destruct) {
+ $list[Auth::getAuth()] = new Kolab_List();
+ }
+
+ if (!empty($GLOBALS['conf']['kolab']['imap']['cache_folders'])) {
+ register_shutdown_function(array(&$list, 'shutdown'));
+ }
+
+ return $list[Auth::getAuth()];
+ }
+
+ /**
+ * Stores the object in the session cache.
+ */
+ function shutdown()
+ {
+ require_once 'Horde/SessionObjects.php';
+ $session = &Horde_SessionObjects::singleton();
+ $session->overwrite('kolab_folderlist', $this, false);
+ }
+
+ /**
+ * Returns the list of folders visible to the current user.
+ *
+ * @return array|PEAR_Error The list of IMAP folders, represented
+ * as Kolab_Folder objects.
+ */
+ function &listFolders()
+ {
+ if (!isset($this->_list)) {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ // Obtain a list of all folders the current user has access to
+ $this->_list = $imap->getMailboxes();
+ if (is_a($this->_list, 'PEAR_Error')) {
+ return $this->_list;
+ }
+ }
+ return $this->_list;
+ }
+
+ /**
+ * Get several or all Folder objects.
+ *
+ * @param array $folders Several folder names or unset to retrieve
+ * all folders.
+ *
+ * @return array|PEAR_Error An array of Kolab_Folder objects.
+ */
+ function getFolders($folders = null)
+ {
+ if (!isset($folders)) {
+ $folders = $this->listFolders();
+ if (is_a($folders, 'PEAR_Error')) {
+ return $folders;
+ }
+ }
+
+ $result = array();
+ foreach ($folders as $folder) {
+ $result[] = $this->getFolder($folder);
+ }
+ return $result;
+ }
+
+ /**
+ * Get a Folder object.
+ *
+ * @param string $folder The folder name.
+ *
+ * @return Kolab_Folder|PEAR_Error The Kolab folder object.
+ */
+ function getFolder($folder)
+ {
+ if (!isset($this->_folders[$folder])) {
+ $kf = new Kolab_Folder($folder);
+ $kf->setList($this);
+ $this->_folders[$folder] = &$kf;
+ }
+ return $this->_folders[$folder];
+ }
+
+ /**
+ * Get a new Folder object.
+ *
+ * @return Kolab_Folder|PEAR_Error The new Kolab folder object.
+ */
+ function getNewFolder()
+ {
+ $folder = new Kolab_Folder(null);
+ $folder->setList($this);
+ return $folder;
+ }
+
+ /**
+ * Get a Folder object based on a share ID.
+ *
+ * @param string $share The share ID.
+ * @param string $type The type of the share/folder.
+ *
+ * @return Kolab_Folder|PEAR_Error The Kolab folder object.
+ */
+ function getByShare($share, $type)
+ {
+ $folder = $this->parseShare($share, $type);
+ if (is_a($folder, 'PEAR_Error')) {
+ return $folder;
+ }
+ return $this->getFolder($folder);
+ }
+
+ /**
+ * Get a list of folders based on the type.
+ *
+ * @param string $type The type of the share/folder.
+ *
+ * @return Kolab_Folder|PEAR_Error The list of Kolab folder
+ * objects.
+ */
+ function getByType($type)
+ {
+ if (!isset($this->_types)) {
+ $result = $this->initiateCache();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ if (isset($this->_types[$type])) {
+ return $this->getFolders($this->_types[$type]);
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Get the default folder for a certain type.
+ *
+ * @param string $type The type of the share/folder.
+ *
+ * @return mixed The default folder, false if there is no default
+ * and a PEAR_Error in case of an error.
+ */
+ function getDefault($type)
+ {
+ if (!isset($this->_defaults)) {
+ $result = $this->initiateCache();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ if (isset($this->_defaults[Auth::getAuth()][$type])) {
+ return $this->getFolder($this->_defaults[Auth::getAuth()][$type]);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get the default folder for a certain type from a different owner.
+ *
+ * @param string $owner The folder owner.
+ * @param string $type The type of the share/folder.
+ *
+ * @return mixed The default folder, false if there is no default
+ * and a PEAR_Error in case of an error.
+ */
+ function getForeignDefault($owner, $type)
+ {
+ if (!isset($this->_defaults)) {
+ $result = $this->initiateCache();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ if (isset($this->_defaults[$owner][$type])) {
+ return $this->getFolder($this->_defaults[$owner][$type]);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Start the cache for the type specific and the default folders.
+ */
+ function initiateCache()
+ {
+ $folders = $this->getFolders();
+ if (is_a($folders, 'PEAR_Error')) {
+ return $folders;
+ }
+
+ $this->_types = array();
+ $this->_defaults = array();
+
+ foreach ($folders as $folder) {
+ $type = $folder->getType();
+ if (is_a($type, 'PEAR_Error')) {
+ return $type;
+ }
+ $default = $folder->isDefault();
+ if (is_a($default, 'PEAR_Error')) {
+ return $default;
+ }
+ $owner = $folder->getOwner();
+ if (is_a($owner, 'PEAR_Error')) {
+ return $owner;
+ }
+ if (!isset($this->_types[$type])) {
+ $this->_types[$type] = array();
+ }
+ $this->_types[$type][] = $folder->name;
+ if ($default) {
+ $this->_defaults[$owner][$type] = $folder->name;
+ }
+ }
+ }
+
+ /**
+ * Converts the horde syntax for shares to storage identifiers.
+ *
+ * @param string $share The share ID that should be parsed.
+ * @param string $type The type of the share/folder.
+ *
+ * @return string|PEAR_Error The corrected folder name.
+ */
+ function parseShare($share, $type)
+ {
+ // Handle default shares
+ if ($share == Auth::getAuth()) {
+ $result = $this->getDefault($type);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ if (!empty($result)) {
+ return $result->name;
+ }
+ }
+ return rawurldecode($share);
+ }
+
+ /**
+ * Creates a new IMAP folder.
+ *
+ * @param Kolab_Folder $folder The folder that should be created.
+ *
+ * @return boolean|PEAR_Error True on success.
+ */
+ function create(&$folder)
+ {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ $result = $imap->exists($folder->new_name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ if ($result) {
+ return PEAR::raiseError(sprintf(_("Unable to add %s: destination folder already exists"),
+ $folder->new_name));
+ }
+
+ $result = $imap->create($folder->new_name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ $this->updateCache($folder);
+ $this->validity++;
+ return true;
+ }
+
+ /**
+ * Rename an IMAP folder.
+ *
+ * @param Kolab_Folder $folder The folder that should be renamed.
+ *
+ * @return boolean|PEAR_Error True on success.
+ */
+ function rename(&$folder)
+ {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ $result = $imap->exists($folder->new_name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ if ($result) {
+ return PEAR::raiseError(sprintf(_("Unable to rename %s to %s: destination folder already exists"),
+ $folder->name, $folder->new_name));
+ }
+
+ $result = $imap->rename($folder->name, $folder->new_name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $this->updateCache($folder, false);
+ $this->updateCache($folder);
+ $this->validity++;
+ return true;
+ }
+
+ /**
+ * Delete an IMAP folder.
+ *
+ * @param Kolab_Folder $folder The folder that should be deleted.
+ *
+ * @return boolean|PEAR_Error True on success.
+ */
+ function remove(&$folder)
+ {
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ if (is_a($imap, 'PEAR_Error')) {
+ return $imap;
+ }
+
+ $result = $imap->exists($folder->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ if ($result === true) {
+ $result = $imap->delete($folder->name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ $this->updateCache($folder, false);
+ $this->validity++;
+ return true;
+ }
+
+ /**
+ * Update the cache variables.
+ *
+ * @param Kolab_Folder $folder The folder that was changed.
+ * @param boolean $added Has the folder been added or removed?
+ */
+ function updateCache(&$folder, $added = true)
+ {
+ $type = $folder->getType();
+ if (is_a($type, 'PEAR_Error')) {
+ Horde::logMessage(sprintf("Error while updating the Kolab folder list cache: %s.",
+ $type->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+ $default = $folder->isDefault();
+ if (is_a($default, 'PEAR_Error')) {
+ Horde::logMessage(sprintf("Error while updating the Kolab folder list cache: %s.",
+ $default->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+ $owner = $folder->getOwner();
+ if (is_a($owner, 'PEAR_Error')) {
+ Horde::logMessage(sprintf("Error while updating the Kolab folder list cache: %s.",
+ $owner->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ if (!isset($this->_types) || !isset($this->_defaults)) {
+ $this->initiateCache();
+ }
+
+ if ($added) {
+ $this->_folders[$folder->new_name] = &$folder;
+ if (isset($this->_list)) {
+ $this->_list[] = $folder->new_name;
+ }
+ $this->_types[$type][] = $folder->new_name;
+ if ($default) {
+ $this->_defaults[$owner][$type] = $folder->new_name;
+ }
+ } else {
+ unset($this->_folders[$folder->name]);
+ if (isset($this->_list)) {
+ $idx = array_search($folder->name, $this->_list);
+ if ($idx !== false) {
+ unset($this->_list[$idx]);
+ }
+ }
+ if (isset($this->_types[$type])) {
+ $idx = array_search($folder->name, $this->_types[$type]);
+ if ($idx !== false) {
+ unset($this->_types[$type][$idx]);
+ }
+ }
+ if ($default && isset($this->_defaults[$owner][$type])) {
+ unset($this->_defaults[$owner][$type]);
+ }
+ }
+ }
+}
diff --git a/framework/Kolab_Storage/lib/Horde/Kolab/Storage/Perms.php b/framework/Kolab_Storage/lib/Horde/Kolab/Storage/Perms.php
new file mode 100644
index 000000000..63f4920be
--- /dev/null
+++ b/framework/Kolab_Storage/lib/Horde/Kolab/Storage/Perms.php
@@ -0,0 +1,414 @@
+
+ * @package Kolab_Storage
+ */
+class Horde_Permission_Kolab extends Horde_Permission {
+
+ /**
+ * The folder name.
+ *
+ * @var string
+ */
+ var $_folder;
+
+ /**
+ * A cache for the folder acl settings. The cache holds the permissions
+ * in horde compatible format, not in the IMAP permission format.
+ *
+ * @var string
+ */
+ var $data;
+
+ /**
+ * A cache for the raw IMAP folder acl settings.
+ *
+ * @var string
+ */
+ var $acl;
+
+ /**
+ * Constructor.
+ *
+ * @param Kolab_Folder $folder The Kolab Folder
+ these permissions belong to.
+ * @param array $perms A set of initial permissions.
+ */
+ function Horde_Permission_Kolab(&$folder, $perms = null)
+ {
+ $this->setFolder($folder);
+ if (!isset($perms)) {
+ $result = $this->getPerm();
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage(sprintf("Failed parsing permission information. Error was: %s",
+ $result->getMessage()), __FILE__, __LINE__);
+ } else {
+ $perms = $result;
+ }
+ }
+ $this->data = $perms;
+
+ }
+
+ /**
+ * Returns the properties that need to be serialized.
+ *
+ * @return array List of serializable properties.
+ */
+ function __sleep()
+ {
+ $properties = get_object_vars($this);
+ unset($properties['_folder']);
+ $properties = array_keys($properties);
+ return $properties;
+ }
+
+ /**
+ * Sets the folder object for this permission object.
+ *
+ * @param string $folder Kolab Folder object.
+ */
+ function setFolder(&$folder)
+ {
+ $this->_folder = $folder;
+ }
+
+ /**
+ * Gets one of the attributes of the object, or null if it isn't defined.
+ *
+ * @param string $attribute The attribute to get.
+ *
+ * @return mixed The value of the attribute, or null.
+ */
+ function get($attribute)
+ {
+ // This object only handles permissions. So only return these
+ switch ($attribute) {
+ case 'perm':
+ return $this->data;
+ case 'type':
+ return 'matrix';
+ default:
+ // User requested something other than permissions: return null
+ return null;
+ }
+ }
+
+ /**
+ * Gets the current permission of the folder and stores the values in the
+ * cache.
+ *
+ * @return array|PEAR_Error The data array representing the permissions.
+ */
+ function getPerm()
+ {
+ $acl = $this->_folder->getACL();
+ if (is_a($acl, 'PEAR_Error')) {
+ Horde::logMessage($acl, __FILE__, __LINE__);
+ return array();
+ }
+ if (empty($acl)) {
+ return array();
+ }
+ $this->acl = &$acl;
+
+ // Loop through the returned users
+ $data = array();
+ foreach ($acl as $user => $rights) {
+ // Convert the user rights to horde format
+ $result = 0;
+ for ($i = 0, $j = strlen($rights); $i < $j; $i++) {
+ switch ($rights[$i]) {
+ case 'l':
+ $result |= PERMS_SHOW;
+ break;
+ case 'r':
+ $result |= PERMS_READ;
+ break;
+ case 'i':
+ $result |= PERMS_EDIT;
+ break;
+ case 'd':
+ $result |= PERMS_DELETE;
+ break;
+ }
+ }
+
+ // Check for special users
+ $name = '';
+ switch ($user) {
+ case 'anyone':
+ $name = 'default';
+ break;
+ case 'anonymous':
+ $name = 'guest';
+ break;
+ }
+
+ // Did we have a special user?
+ if ($name) {
+ // Store the converted acl in the cache
+ $data[$name] = $result;
+ continue;
+ }
+
+ // Is it a group?
+ if (substr($user, 0, 6) == 'group:') {
+ if (!isset($groups)) {
+ require_once 'Horde/Group.php';
+ $groups = &Group::singleton();
+ }
+ $group_id = $groups->getGroupId(substr($user, 6));
+ if (!is_a($group_id, 'PEAR_Error')) {
+ // Store the converted acl in the cache
+ $data['groups'][$group_id] = $result;
+ }
+
+ continue;
+ }
+
+ // Standard user
+ // Store the converted acl in the cache
+ $data['users'][$user] = $result;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Saves the current permission values from the cache to the IMAP folder.
+ *
+ * @return boolean|PEAR_Error True on success, false if there is
+ * nothing to save.
+ */
+ function save()
+ {
+ if (!isset($this->data)) {
+ return false;
+ }
+
+ // FIXME: If somebody else accessed the folder before us, we will overwrite
+ // the change here.
+ $current = $this->getPerm();
+
+ foreach ($this->data as $user => $user_perms) {
+ if (is_array($user_perms)) {
+ foreach ($user_perms as $userentry => $perms) {
+ if ($user == 'groups') {
+ if (!isset($groups)) {
+ require_once 'Horde/Group.php';
+ $groups = &Group::singleton();
+ }
+ // Convert group id back to name
+ $group_name = $groups->getGroupName($userentry);
+ if (is_a($group_name, 'PEAR_Error')) {
+ return $group_name;
+ }
+ $name = 'group:' . $group_name;
+ } else if ($user == 'users') {
+ $name = $userentry;
+ } else {
+ continue;
+ }
+ $result = $this->savePermission($name, $perms);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ unset($current[$user][$userentry]);
+ }
+ } else {
+ if ($user == 'default') {
+ $name = 'anyone';
+ } else if ($user == 'guest') {
+ $name = 'anonymous';
+ } else {
+ continue;
+ }
+ $result = $this->savePermission($name, $user_perms);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ unset($current[$user]);
+ }
+ }
+
+ // Delete ACLs that have been removed
+ foreach ($current as $user => $user_perms) {
+ if (is_array($user_perms)) {
+ foreach ($user_perms as $userentry => $perms) {
+ if ($user == 'groups') {
+ if (!isset($groups)) {
+ require_once 'Horde/Group.php';
+ $groups = &Group::singleton();
+ }
+ // Convert group id back to name
+ $group_name = $groups->getGroupName($userentry);
+ if (is_a($group_name, 'PEAR_Error')) {
+ return $group_name;
+ }
+ $name = 'group:' . $group_name;
+ } else {
+ $name = $userentry;
+ }
+
+ $result = $this->_folder->deleteACL($name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ } else {
+ if ($user == 'default') {
+ $name = 'anyone';
+ } else if ($user == 'guest') {
+ $name = 'anonymous';
+ } else {
+ continue;
+ }
+ $result = $this->_folder->deleteACL($name);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ }
+
+ // Load the permission from the folder again
+ $this->data = $this->getPerm();
+
+ return true;
+ }
+
+ /**
+ * Saves the specified permission values for the given user on the
+ * IMAP folder.
+ *
+ * @return boolean|PEAR_Error True on success.
+ */
+ function savePermission($user, $perms)
+ {
+ // Convert the horde permission style to IMAP permissions
+ $result = $user == $this->_folder->getOwner() ? 'a' : '';
+ if ($perms & PERMS_SHOW) {
+ $result .= 'l';
+ }
+ if ($perms & PERMS_READ) {
+ $result .= 'r';
+ }
+ if ($perms & PERMS_EDIT) {
+ $result .= 'iswc';
+ }
+ if ($perms & PERMS_DELETE) {
+ $result .= 'd';
+ }
+
+ $result = $this->_folder->setACL($user, $result);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ return true;
+ }
+
+ /**
+ * Finds out what rights the given user has to this object.
+ *
+ * @param string $user The user to check for. Defaults to the current
+ * user.
+ * @param string $creator The user who created the object.
+ *
+ * @return mixed A bitmask of permissions, a permission value, or
+ * an array of permission values the user has,
+ * depending on the permission type and whether the
+ * permission value is ambiguous. False if there is
+ * no such permsission.
+ */
+ function getPermissions($user = null, $creator = null)
+ {
+ if ($user === null) {
+ $user = Auth::getAuth();
+ }
+ // If $creator was specified, check creator permissions.
+ if ($creator !== null) {
+ // If the user is the creator see if there are creator
+ // permissions.
+ if (strlen($user) && $user === $creator &&
+ ($perms = $this->getCreatorPermissions()) !== null) {
+ return $perms;
+ }
+ }
+
+ // Check user-level permissions.
+ $userperms = $this->getUserPermissions();
+ if (isset($userperms[$user])) {
+ return $userperms[$user];
+ }
+
+ // If no user permissions are found, try group permissions.
+ $groupperms = $this->getGroupPermissions();
+ if (!empty($groupperms)) {
+ require_once 'Horde/Group.php';
+ $groups = &Group::singleton();
+
+ $composite_perm = null;
+ foreach ($this->data['groups'] as $group => $perm) {
+ $result = $groups->userIsInGroup($user, $group);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ if ($result) {
+ if ($composite_perm === null) {
+ $composite_perm = 0;
+ }
+ $composite_perm |= $perm;
+ }
+ }
+
+ if ($composite_perm !== null) {
+ return $composite_perm;
+ }
+ }
+
+ // If there are default permissions, return them.
+ if (($perms = $this->getDefaultPermissions()) !== null) {
+ return $perms;
+ }
+
+ // Otherwise, deny all permissions to the object.
+ return false;
+ }
+
+ /**
+ * Finds out if the user has the specified rights to the given object.
+ *
+ * @param string $user The user to check for.
+ * @param integer $perm The permission level that needs to be checked
+ * for.
+ * @param string $creator The creator of the shared object.
+ *
+ * @return boolean True if the user has the specified permissions.
+ */
+ function hasPermission($user, $perm, $creator = null)
+ {
+ return ($this->getPermissions($user, $creator) & $perm);
+ }
+}
diff --git a/framework/Kolab_Storage/lib/Horde/Kolab/Test/Storage.php b/framework/Kolab_Storage/lib/Horde/Kolab/Test/Storage.php
new file mode 100644
index 000000000..28240b940
--- /dev/null
+++ b/framework/Kolab_Storage/lib/Horde/Kolab/Test/Storage.php
@@ -0,0 +1,379 @@
+
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Storage
+ */
+
+/**
+ * We need the unit test framework
+ */
+require_once 'Horde/Kolab/Test/Server.php';
+
+/**
+ * We need the classes to be tested
+ */
+require_once 'Horde/Kolab/Storage/List.php';
+
+/**
+ * Base for PHPUnit scenarios.
+ *
+ * $Horde: framework/Kolab_Storage/lib/Horde/Kolab/Test/Storage.php,v 1.9 2009/06/24 23:39:23 slusarz Exp $
+ *
+ * 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.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Kolab
+ * @package Kolab_Test
+ * @author Gunnar Wrobel
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Storage
+ */
+class Horde_Kolab_Test_Storage extends Horde_Kolab_Test_Server
+{
+ /**
+ * Handle a "given" step.
+ *
+ * @param array &$world Joined "world" of variables.
+ * @param string $action The description of the step.
+ * @param array $arguments Additional arguments to the step.
+ *
+ * @return mixed The outcome of the step.
+ */
+ public function runGiven(&$world, $action, $arguments)
+ {
+ switch($action) {
+ case 'an empty Kolab storage':
+ $world['storage'] = &$this->prepareEmptyKolabStorage();
+ break;
+ case 'a Kolab setup':
+ $result = $this->prepareKolabSetup();
+
+ $world['server'] = &$result['server'];
+ $world['storage'] = &$result['storage'];
+ $world['auth'] = &$result['auth'];
+ break;
+ case 'a populated Kolab setup':
+ $result = $this->prepareBasicSetup();
+
+ $world['server'] = &$result['server'];
+ $world['storage'] = &$result['storage'];
+ $world['auth'] = &$result['auth'];
+ break;
+ default:
+ return parent::runGiven($world, $action, $arguments);
+ }
+ }
+
+ /**
+ * Handle a "when" step.
+ *
+ * @param array &$world Joined "world" of variables.
+ * @param string $action The description of the step.
+ * @param array $arguments Additional arguments to the step.
+ *
+ * @return mixed The outcome of the step.
+ */
+ public function runWhen(&$world, $action, $arguments)
+ {
+ switch($action) {
+ case 'create a Kolab default calendar with name':
+ $folder = $world['storage']->getNewFolder();
+ $folder->setName($arguments[0]);
+ $world['folder_creation'] = $folder->save(array('type' => 'event',
+ 'default' => true));
+ $folder->setACL(Auth::getAuth(), 'alrid');
+ break;
+ case 'allow a group full access to a folder':
+ $folder = $world['storage']->getFolder($arguments[1]);
+ $folder->setACL($arguments[0], 'alrid');
+ break;
+ case 'retrieving the list of shares for the application':
+ require_once 'Horde/Share.php';
+
+ $shares = Horde_Share::singleton($arguments[0], 'kolab');
+
+ $world['list'] = $shares->listShares(Auth::getAuth());
+ break;
+ case 'logging in as a user with a password':
+ $world['login'] = $world['auth']->authenticate($arguments[0],
+ array('password' => $arguments[1]));
+ $world['storage'] = &$this->prepareEmptyKolabStorage();
+ return parent::runWhen($world, $action, $arguments);
+ default:
+ return parent::runWhen($world, $action, $arguments);
+ }
+ }
+
+ /**
+ * Handle a "then" step.
+ *
+ * @param array &$world Joined "world" of variables.
+ * @param string $action The description of the step.
+ * @param array $arguments Additional arguments to the step.
+ *
+ * @return mixed The outcome of the step.
+ */
+ public function runThen(&$world, $action, $arguments)
+ {
+ switch($action) {
+ case 'the creation of the folder was successful':
+ $this->assertNoError($world['folder_creation']);
+ break;
+ case 'the list contains a share named':
+ $this->assertNoError($world['list']);
+ $this->assertContains($arguments[0],
+ array_keys($world['list']));
+ break;
+ default:
+ return parent::runThen($world, $action, $arguments);
+ }
+ }
+
+ /**
+ * Prepare a Kolab server with some basic entries.
+ *
+ * @return Horde_Kolab_Server The empty server.
+ */
+ public function &prepareBasicSetup()
+ {
+ $world = &$this->prepareKolabSetup();
+ $this->prepareUsers($world['server']);
+ return $world;
+ }
+
+ /**
+ * Prepare an empty Kolab storage.
+ *
+ * @return Kolab_List The empty storage.
+ */
+ public function &prepareEmptyKolabStorage()
+ {
+ /** Ensure that IMAP runs in testing mode and is empty */
+ $GLOBALS['KOLAB_TESTING'] = array();
+
+ /** Prepare a Kolab test storage */
+ $storage = Kolab_List::singleton(true);
+ return $storage;
+ }
+
+ /**
+ * Prepare the browser setup.
+ *
+ * @return NULL
+ */
+ public function prepareBrowser()
+ {
+ /** Provide a browser setup */
+ include_once 'Horde/Browser.php';
+ $GLOBALS['browser'] = new Horde_Browser();
+ }
+
+ /**
+ * Prepare the configuration.
+ *
+ * @return NULL
+ */
+ public function prepareConfiguration()
+ {
+ $fh = fopen(HORDE_BASE . '/config/conf.php', 'w');
+ $data = <<applications['horde'] = array(
+ 'fileroot' => dirname(__FILE__) . '/..',
+ 'webroot' => '/',
+ 'initial_page' => 'login.php',
+ 'name' => _("Horde"),
+ 'status' => 'active',
+ 'templates' => dirname(__FILE__) . '/../templates',
+ 'provides' => 'horde',
+);
+EOD;
+ fwrite($fh, "prepareEmptyKolabServer();
+ $world['storage'] = &$this->prepareEmptyKolabStorage();
+ $world['auth'] = &$this->prepareKolabAuthDriver();
+
+ $this->prepareBasicConfiguration();
+
+ if (!defined('HORDE_BASE')) {
+ define('HORDE_BASE', $this->provideHordeBase());
+ }
+
+ if (!file_exists(HORDE_BASE . '/config')) {
+ $result = mkdir(HORDE_BASE . '/config', 0755, true);
+ }
+
+ $this->prepareConfiguration();
+ $this->prepareRegistry();
+ $this->prepareNotification();
+
+ if (!isset($GLOBALS['perms'])) {
+ $GLOBALS['perms'] = &Perms::singleton();
+ }
+
+ /** Provide the horde registry */
+ include_once 'Horde/Registry.php';
+
+ $GLOBALS['registry'] = &Registry::singleton();
+ $GLOBALS['notification'] = &Horde_Notification::singleton();
+
+ $this->prepareFixedConfiguration();
+
+ $this->prepareBrowser();
+
+ /* Make sure the configuration is correct after initializing the registry */
+ $this->prepareBasicConfiguration();
+
+ return $world;
+ }
+
+ /**
+ * Fix the read configuration.
+ *
+ * @return NULL
+ */
+ public function prepareFixedConfiguration()
+ {
+ $GLOBALS['registry']->importConfig('horde');
+ }
+
+ /**
+ * Prepare a basic Kolab configuration.
+ *
+ * @return NULL
+ */
+ public function prepareBasicConfiguration()
+ {
+ /** We need a server name for MIME processing */
+ $_SERVER['SERVER_NAME'] = $this->provideServerName();
+ $_SERVER['SERVER_PORT'] = 80;
+
+ $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+ }
+
+ /**
+ * Create a new folder.
+ *
+ * @param string $name Name of the new folder.
+ * @param string $type Type of the new folder.
+ * @param boolean $default Should the new folder be a default folder?
+ *
+ * @return Kolab_Folder The new folder.
+ */
+ public function &prepareNewFolder(&$storage, $name, $type, $default = false)
+ {
+ $folder = $storage->getNewFolder();
+ $folder->setName($name);
+ $this->assertNoError($folder->save(array('type' => $type,
+ 'default' => $default)));
+ return $folder;
+ }
+
+ function provideServerName() {
+ return 'localhost';
+ }
+
+ function provideHordeBase() {
+ return Horde::getTempDir() . '/test_config';
+ }
+}
diff --git a/framework/Kolab_Storage/package.xml b/framework/Kolab_Storage/package.xml
new file mode 100644
index 000000000..287563739
--- /dev/null
+++ b/framework/Kolab_Storage/package.xml
@@ -0,0 +1,259 @@
+
+
+ Kolab_Storage
+ pear.horde.org
+ A package for handling Kolab data stored on an IMAP server.
+ Storing user data in an IMAP account belonging to the
+ user is one of the Kolab server core concepts. This package provides
+ all the necessary means to deal with this type of data storage
+ effectively.
+
+
+ Gunnar Wrobel
+ wrobel
+ p@rdus.de
+ yes
+
+
+ Thomas Jarosch
+ jarosch
+ thomas.jarosch@intra2net.com
+ yes
+
+
+ Chuck Hagenbuch
+ chuck
+ chuck@horde.org
+ yes
+
+
+ Jan Schneider
+ jan
+ jan@horde.org
+ yes
+
+ 2009-02-24
+
+ 0.4.0
+ 0.1.0
+
+
+ alpha
+ alpha
+
+ LGPL
+
+ * Fixed list driver to prevent overwriting folder data when
+ authenticating twice (relevant for testing).
+ * Allow to supress triggering (relevant for testing).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 4.3.0
+
+
+ 1.4.0b1
+
+
+ Net_IMAP
+ pear.php.net
+ 1.1.0beta2
+
+
+ Mail_mimeDecode
+ pear.php.net
+
+
+ HTTP_Request
+ pear.php.net
+
+
+ Kolab_Format
+ pear.horde.org
+
+
+ Kolab_Server
+ pear.horde.org
+
+
+ Auth
+ pear.horde.org
+ 0.1.1
+
+
+ Horde_Cache
+ pear.horde.org
+
+
+ Group
+ pear.horde.org
+
+
+ Horde_History
+ pear.horde.org
+
+
+ Horde_LDAP
+ pear.horde.org
+
+
+ Perms
+ pear.horde.org
+ 0.1.0
+
+
+ Horde_SessionObjects
+ pear.horde.org
+
+
+ Horde_MIME
+ pear.horde.org
+
+
+ Horde_NLS
+ pear.horde.org
+
+
+ Util
+ pear.horde.org
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2008-12-05
+
+ 0.3.0
+ 0.1.0
+
+
+ alpha
+ alpha
+
+ LGPL
+
+ * Added attachment support.
+ * Fixed triggering folders.
+ * Fixed renaming default folders.
+ * Implemented extended free/busy access concept.
+ * kolab/issue3292 (Sharing default groupware resources does not work
+ in the web client)
+
+
+
+ 2008-09-12
+
+ 0.2.0
+ 0.1.0
+
+
+ alpha
+ alpha
+
+ LGPL
+ * Fixed the Kolab_Storage::getFolder() function.
+ * Added Kolab_List::getForeignDefault() to retrieve the default folders of other
+ users. Also fixes issues with overlapping default folders.
+ * Fixed retrieval of general Kolab annotations.
+ * Correctly determine the owner of the INBOX of another user.
+ * Automatically trigger a folder within the folder handler.
+ * Moved Kolab session handler from Kolab_Storage to Kolab_Server.
+ * Moved the IMAP drivers from Kolab_Storage to Kolab_Server as the
+ IMAP connection must be handled by the Kolab session.
+
+
+
+
+ 0.1.0
+ 0.1.0
+
+
+ alpha
+ alpha
+
+ 2008-09-11
+ LGPL
+
+ * Initial release.
+
+
+
+
diff --git a/framework/Kolab_Storage/test/Horde/Kolab/Storage/AllTests.php b/framework/Kolab_Storage/test/Horde/Kolab/Storage/AllTests.php
new file mode 100644
index 000000000..11b44d8a0
--- /dev/null
+++ b/framework/Kolab_Storage/test/Horde/Kolab/Storage/AllTests.php
@@ -0,0 +1,76 @@
+
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Storage
+ */
+
+/**
+ * Define the main method
+ */
+if (!defined('PHPUnit_MAIN_METHOD')) {
+ define('PHPUnit_MAIN_METHOD', 'Horde_Kolab_Storage_AllTests::main');
+}
+
+require_once 'PHPUnit/Framework/TestSuite.php';
+require_once 'PHPUnit/TextUI/TestRunner.php';
+
+/**
+ * Combine the tests for this package.
+ *
+ * $Horde: framework/Kolab_Storage/test/Horde/Kolab/Storage/AllTests.php,v 1.4 2009/01/06 17:49:28 jan Exp $
+ *
+ * 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.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Kolab
+ * @package Kolab_Storage
+ * @subpackage UnitTests
+ * @author Gunnar Wrobel
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Storage
+ */
+class Horde_Kolab_Storage_AllTests {
+
+ public static function main()
+ {
+ PHPUnit_TextUI_TestRunner::run(self::suite());
+ }
+
+ public static function suite()
+ {
+ $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Horde_Kolab_Storage');
+
+ $basedir = dirname(__FILE__);
+ $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/');
+
+ foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) {
+ if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) {
+ $pathname = $file->getPathname();
+ require $pathname;
+
+ $class = str_replace(DIRECTORY_SEPARATOR, '_',
+ preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname));
+ $suite->addTestSuite('Horde_Kolab_Storage_' . $class);
+ }
+ }
+
+ return $suite;
+ }
+
+}
+
+if (PHPUnit_MAIN_METHOD == 'Horde_Kolab_Storage_AllTests::main') {
+ Horde_Kolab_Storage_AllTests::main();
+}
diff --git a/framework/Kolab_Storage/test/Horde/Kolab/Storage/AttachmentTest.php b/framework/Kolab_Storage/test/Horde/Kolab/Storage/AttachmentTest.php
new file mode 100644
index 000000000..88535be89
--- /dev/null
+++ b/framework/Kolab_Storage/test/Horde/Kolab/Storage/AttachmentTest.php
@@ -0,0 +1,120 @@
+
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Storage
+ */
+
+/**
+ * We need the base class
+ */
+require_once 'Horde/Kolab/Test/Storage.php';
+
+require_once 'Horde.php';
+require_once 'Horde/Kolab/Storage/Data.php';
+require_once 'Horde/Kolab/IMAP.php';
+require_once 'Horde/Kolab/IMAP/test.php';
+
+/**
+ * Test the handling of attachments.
+ *
+ * $Horde: framework/Kolab_Storage/test/Horde/Kolab/Storage/AttachmentTest.php,v 1.4 2009/06/09 23:23:39 slusarz Exp $
+ *
+ * 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.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Kolab
+ * @package Kolab_Storage
+ * @subpackage UnitTests
+ * @author Gunnar Wrobel
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Storage
+ */
+class Horde_Kolab_Storage_AttachmentTest extends Horde_Kolab_Test_Storage
+{
+
+ /**
+ * Test setup.
+ *
+ * @return NULL
+ */
+ public function setUp()
+ {
+ $world = $this->prepareBasicSetup();
+
+ $this->assertTrue($world['auth']->authenticate('wrobel@example.org',
+ array('password' => 'none')));
+
+ $this->prepareNewFolder($world['storage'], 'Contacts', 'contact', true);
+ }
+
+ /**
+ * Test storing attachments.
+ *
+ * @return NULL
+ */
+ public function testCacheAttachmentInFile()
+ {
+ $data = &new Kolab_Data('contact');
+ $folder = &new Kolab_Folder('INBOX/Contacts');
+ $data->setFolder($folder);
+
+ $atc1 = Horde_Util::getTempFile();
+ $fh = fopen($atc1, 'w');
+ fwrite($fh, 'test');
+ fclose($fh);
+
+ $object = array('uid' => '1',
+ 'full-name' => 'User Name',
+ 'email' => 'user@example.org',
+ 'inline-attachment' => array('test.txt'),
+ '_attachments' => array('test.txt'=> array('type' => 'text/plain',
+ 'path' => $atc1,
+ 'name' => 'test.txt')));
+
+ $result = $data->save($object);
+ $this->assertNoError($result);
+ $result = $data->getObject(1);
+ $this->assertNoError($result);
+ $this->assertTrue(isset($result['_attachments']['test.txt']));
+ $this->assertEquals("test\n", $data->getAttachment($result['_attachments']['test.txt']['key']));
+ }
+
+ /**
+ * Test storing attachments.
+ *
+ * @return NULL
+ */
+ public function testCacheAttachmentAsContent()
+ {
+ $data = &new Kolab_Data('contact');
+ $folder = &new Kolab_Folder('INBOX/Contacts');
+ $data->setFolder($folder);
+
+ $object = array('uid' => '1',
+ 'full-name' => 'User Name',
+ 'email' => 'user@example.org',
+ 'inline-attachment' => array('test.txt'),
+ '_attachments' => array('test.txt'=> array('type' => 'text/plain',
+ 'content' => 'test',
+ 'name' => 'test.txt')));
+
+ $result = $data->save($object);
+ $this->assertNoError($result);
+ $result = $data->getObject(1);
+ $this->assertNoError($result);
+ $this->assertTrue(isset($result['_attachments']['test.txt']));
+ $this->assertEquals("test\n", $data->getAttachment($result['_attachments']['test.txt']['key']));
+ }
+}
diff --git a/framework/Kolab_Storage/test/Horde/Kolab/Storage/CacheTest.php b/framework/Kolab_Storage/test/Horde/Kolab/Storage/CacheTest.php
new file mode 100644
index 000000000..d9ecd7d44
--- /dev/null
+++ b/framework/Kolab_Storage/test/Horde/Kolab/Storage/CacheTest.php
@@ -0,0 +1,129 @@
+
+ * @package Kolab_Storage
+ */
+class Horde_Kolab_Storage_CacheTest extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Test cache construction.
+ */
+ public function testConstruct()
+ {
+ $cache = &new Kolab_Cache();
+ $this->assertEquals('Horde_Cache_File', get_class($cache->_horde_cache));
+ }
+
+ /**
+ * Test cleaning the cache.
+ */
+ public function testReset()
+ {
+ $cache = &new Kolab_Cache();
+ $cache->reset();
+ $this->assertEquals(-1, $cache->validity);
+ $this->assertEquals(-1, $cache->nextid);
+ $this->assertTrue(empty($cache->objects));
+ $this->assertTrue(empty($cache->uids));
+ }
+
+ /**
+ * Test storing data.
+ */
+ public function testStore()
+ {
+ $cache = &new Kolab_Cache();
+ $cache->reset();
+ $item = array(1);
+ $cache->store(10, 1, $item);
+ $this->assertTrue(isset($cache->objects[1]));
+ $this->assertTrue(isset($cache->uids[10]));
+ $this->assertEquals(1, $cache->uids[10]);
+ $this->assertSame($item, $cache->objects[1]);
+ }
+
+ /**
+ * Test ignoring objects.
+ */
+ public function testIgnore()
+ {
+ $cache = &new Kolab_Cache();
+ $cache->reset();
+ $cache->ignore(11);
+ $this->assertEquals(false, $cache->uids[11]);
+ }
+
+ /**
+ * Test loading/saving the cache.
+ */
+ public function testLoadSave()
+ {
+ $cache = &new Kolab_Cache();
+ $cache->load('test', 1);
+ $cache->expire();
+ $this->assertEquals(1, $cache->_data_version);
+ $this->assertEquals('test', $cache->_key);
+ $this->assertEquals(-1, $cache->validity);
+ $this->assertEquals(-1, $cache->nextid);
+ $this->assertTrue(empty($cache->objects));
+ $this->assertTrue(empty($cache->uids));
+ $item1 = array(1);
+ $item2 = array(2);
+ $cache->store(10, 1, $item1);
+ $cache->store(12, 2, $item2);
+ $cache->ignore(11);
+ $this->assertTrue(isset($cache->objects[1]));
+ $this->assertTrue(isset($cache->uids[10]));
+ $this->assertEquals(1, $cache->uids[10]);
+ $this->assertEquals($item1, $cache->objects[1]);
+ $cache->save();
+ $this->assertEquals(false, $cache->uids[11]);
+ $cache->ignore(10);
+ $cache->ignore(12);
+ $this->assertEquals(false, $cache->uids[10]);
+ $this->assertEquals(false, $cache->uids[12]);
+ /** Allow us to reload the cache */
+ $cache->_key = null;
+ $cache->load('test', 1);
+ $this->assertEquals((1 << 8) | 1, $cache->_cache_version);
+ $this->assertTrue(isset($cache->objects[1]));
+ $this->assertTrue(isset($cache->uids[10]));
+ $this->assertEquals(1, $cache->uids[10]);
+ $this->assertEquals($item1, $cache->objects[1]);
+ $cache->expire();
+ $this->assertEquals((1 << 8) | 1, $cache->_cache_version);
+ $this->assertEquals(1, $cache->_data_version);
+ $this->assertEquals('test', $cache->_key);
+ $this->assertEquals(-1, $cache->validity);
+ $this->assertEquals(-1, $cache->nextid);
+ $this->assertTrue(empty($cache->objects));
+ $this->assertTrue(empty($cache->uids));
+ }
+
+}
diff --git a/framework/Kolab_Storage/test/Horde/Kolab/Storage/DataTest.php b/framework/Kolab_Storage/test/Horde/Kolab/Storage/DataTest.php
new file mode 100644
index 000000000..c8504c3b4
--- /dev/null
+++ b/framework/Kolab_Storage/test/Horde/Kolab/Storage/DataTest.php
@@ -0,0 +1,324 @@
+
+ * @package Kolab_Storage
+ */
+class Horde_Kolab_Storage_DataTest extends Horde_Kolab_Test_Storage
+{
+
+ /**
+ * Test setup.
+ */
+ public function setUp()
+ {
+ $world = $this->prepareBasicSetup();
+
+ $this->assertTrue($world['auth']->authenticate('wrobel@example.org',
+ array('password' => 'none')));
+
+ $this->prepareNewFolder($world['storage'], 'Contacts', 'contact', true);
+ $this->prepareNewFolder($world['storage'], 'NewContacts', 'contact');
+ }
+
+ /**
+ * Test class constructor.
+ */
+ public function testConstruct()
+ {
+ $data = &new Kolab_Data('test');
+ $this->assertEquals('test', $data->_object_type);
+ }
+
+ /**
+ * Test cache access.
+ */
+ public function testGetCacheKey()
+ {
+ $data = &new Kolab_Data('test');
+ $this->assertTrue($data->_cache_cyrus_optimize);
+ $session = &Horde_Kolab_Session::singleton();
+ $imap = &$session->getImap();
+ $this->assertEquals('Horde_Kolab_IMAP_test', get_class($imap));
+
+ $folder = &new Kolab_Folder('INBOX/Test');
+ $data->setFolder($folder);
+ $this->assertEquals('INBOX/Test', $data->_folder->name);
+ $this->assertEquals('user/wrobel/Test', $data->_getCacheKey());
+ }
+
+ /**
+ * Test object deletion.
+ */
+ public function testDelete()
+ {
+ $data = &new Kolab_Data('contact');
+ $folder = &new Kolab_Folder('INBOX/Contacts');
+ $data->setFolder($folder);
+
+ /**
+ * During testing we want to ensure that we do not access any
+ * old, cached data. The cache gets loaded when calling
+ * getObjectIds and is manually expired afterwards.
+ */
+ $result = $data->getObjectIds();
+ $data->_cache->expire();
+
+ $result = $data->delete('1');
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertFalse($result);
+ $object = array(
+ 'uid' => '1',
+ 'given-name' => 'Gunnar',
+ 'full-name' => 'Gunnar Wrobel',
+ 'email' => 'p@rdus.de'
+ );
+ $result = $data->save($object);
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+ $result = $data->delete('1');
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+ $ids = $data->getObjectIds();
+ if (is_a($ids, 'PEAR_Error')) {
+ $this->assertEquals('', $ids->message);
+ }
+ $this->assertTrue(empty($ids));
+ }
+
+ /**
+ * Test object moving.
+ */
+ public function testMove()
+ {
+ $list = &new Kolab_List();
+ $list->_imap = &new Horde_Kolab_IMAP_test('', 0);
+
+ $data = &new Kolab_Data('contact');
+ $folder = &new Kolab_Folder('INBOX/Contacts');
+ $folder->setList($list);
+ $data->setFolder($folder);
+ /**
+ * During testing we want to ensure that we do not access any
+ * old, cached data. The cache gets loaded when calling
+ * getObjectIds and is manually expired afterwards.
+ */
+ $result = $data->getObjectIds();
+ $data->_cache->expire();
+
+ $data2 = &new Kolab_Data('contact');
+ $folder2 = &new Kolab_Folder('INBOX/NewContacts');
+ $folder2->setList($list);
+ $data2->setFolder($folder2);
+ /**
+ * During testing we want to ensure that we do not access any
+ * old, cached data. The cache gets loaded when calling
+ * getObjectIds and is manually expired afterwards.
+ */
+ $result = $data2->getObjectIds();
+ $data2->_cache->expire();
+
+ $result = $data->move('1', 'INBOX%20NewContacts');
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertFalse($result);
+ $object = array(
+ 'uid' => '1',
+ 'given-name' => 'Gunnar',
+ 'full-name' => 'Gunnar Wrobel',
+ 'email' => 'p@rdus.de'
+ );
+ $result = $data->save($object);
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+
+ $result = $data->move('1', rawurlencode('INBOX/NewContacts'));
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+
+ $ids = $data->getObjectIds();
+ if (is_a($ids, 'PEAR_Error')) {
+ $this->assertEquals('', $ids->message);
+ }
+ $this->assertTrue(empty($ids));
+
+ $data2->synchronize();
+ $ids = $data2->getObjectIds();
+ if (is_a($ids, 'PEAR_Error')) {
+ $this->assertEquals('', $ids->message);
+ }
+ $this->assertEquals(1, count($ids));
+
+ $result = $data2->delete('1');
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+ }
+
+ /**
+ * Test saving data.
+ */
+ public function testSave()
+ {
+ $data = &new Kolab_Data('contact');
+ $folder = &new Kolab_Folder('INBOX/Contacts');
+ $data->setFolder($folder);
+ /**
+ * During testing we want to ensure that we do not access any
+ * old, cached data. The cache gets loaded when calling
+ * getObjectIds and is manually expired afterwards.
+ */
+ $result = $data->getObjectIds();
+ $data->_cache->expire();
+ $object = array(
+ 'uid' => '1',
+ 'given-name' => 'Gunnar',
+ 'full-name' => 'Gunnar Wrobel',
+ 'email' => 'p@rdus.de'
+ );
+
+ $result = $data->save($object, '1000');
+ $this->assertEquals("Old object 1000 does not exist.", $result->message);
+
+ $result = $data->save($object);
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+
+ $id = $data->_getStorageId('1');
+ $this->assertTrue($data->_storageIdExists($id));
+ $this->assertTrue($data->objectUidExists('1'));
+
+ $object = $data->getObject('1');
+ $this->assertEquals('Gunnar', $object['given-name']);
+
+ $objects = $data->getObjects();
+ if (is_a($objects, 'PEAR_Error')) {
+ $this->assertEquals('', $objects->message);
+ }
+ $this->assertEquals(1, count($objects));
+
+ $object = array(
+ 'uid' => '1',
+ 'given-name' => 'Gunnar',
+ 'full-name' => 'Gunnar Wrobel',
+ 'email' => 'p@rdus.de'
+ );
+
+ $result = $data->save($object, '1');
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+ $this->assertNotEquals($id, $data->_getStorageId('1'));
+ $result = $data->delete('1');
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+ $this->assertFalse($data->_getStorageId('1'));
+ $this->assertFalse($data->_storageIdExists($id));
+ $this->assertFalse($data->objectUidExists('1'));
+
+ $object = $data->getObject('1');
+ $this->assertEquals("Kolab cache: Object uid 1 does not exist in the cache!", $object->message);
+
+ $objects = $data->getObjects();
+ if (is_a($objects, 'PEAR_Error')) {
+ $this->assertEquals('', $objects->message);
+ }
+ $this->assertEquals(0, count($objects));
+ }
+
+ /**
+ * Test clearing data in a folder.
+ */
+ public function testObjectDeleteAll()
+ {
+ $data = &new Kolab_Data('contact');
+ $folder = &new Kolab_Folder('INBOX/Contacts');
+ $data->setFolder($folder);
+ /**
+ * During testing we want to ensure that we do not access any
+ * old, cached data. The cache gets loaded when calling
+ * getObjectIds and is manually expired afterwards.
+ */
+ $result = $data->getObjectIds();
+ $data->_cache->expire();
+ $result = $data->deleteAll();
+ $this->assertTrue($result);
+
+ $object = array(
+ 'uid' => '1',
+ 'given-name' => 'Gunnar',
+ 'full-name' => 'Gunnar Wrobel',
+ 'email' => 'p@rdus.de'
+ );
+ $result = $data->save($object);
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+
+ $result = $data->deleteAll();
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+
+ $ids = $data->getObjectIds();
+ if (is_a($ids, 'PEAR_Error')) {
+ $this->assertEquals('', $ids->message);
+ }
+ $this->assertTrue(empty($ids));
+ }
+
+}
diff --git a/framework/Kolab_Storage/test/Horde/Kolab/Storage/FolderTest.php b/framework/Kolab_Storage/test/Horde/Kolab/Storage/FolderTest.php
new file mode 100644
index 000000000..97227c4ee
--- /dev/null
+++ b/framework/Kolab_Storage/test/Horde/Kolab/Storage/FolderTest.php
@@ -0,0 +1,194 @@
+
+ * @package Kolab_Storage
+ */
+class Horde_Kolab_Storage_FolderTest extends Horde_Kolab_Test_Storage
+{
+
+ /**
+ * Test setup.
+ */
+ public function setUp()
+ {
+ $world = $this->prepareBasicSetup();
+
+ $this->assertTrue($world['auth']->authenticate('wrobel@example.org',
+ array('password' => 'none')));
+
+ $this->prepareNewFolder($world['storage'], 'Contacts', 'contact', true);
+ $this->prepareNewFolder($world['storage'], 'NewContacts', 'contact');
+
+ $this->list = &new Kolab_List();
+ }
+
+ /**
+ * Test class creation.
+ */
+ public function testConstruct()
+ {
+ $folder = &new Kolab_Folder('INBOX/Contacts');
+ $this->assertEquals('INBOX/Contacts', $folder->name);
+ $this->assertTrue(is_array($folder->_data));
+ $this->assertTrue(empty($folder->_data));
+ $this->assertTrue(empty($folder->new_name));
+ }
+
+ /**
+ * Test renaming.
+ */
+ public function testSetName()
+ {
+ $folder = &new Kolab_Folder('INBOX/Contacts');
+ $folder->setName('TestAÃÃ');
+ $this->assertEquals(Horde_String::convertCharset('INBOX/TestAÃÃ', NLS::getCharset(), 'UTF7-IMAP'), $folder->new_name);
+ }
+
+ /**
+ * Test saving objects.
+ */
+ public function testSave()
+ {
+ $folder = &new Kolab_Folder();
+ $folder->setList($this->list);
+
+ $result = $folder->save();
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals("Cannot create this folder! The name has not yet been set.", $result->message);
+ }
+ $folder->setName('TestÃÃÃ');
+ $result = $folder->exists();
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertFalse($result);
+ $result = $folder->accessible();
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertFalse($result);
+
+ $result = $folder->save();
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+ $this->assertEquals("wrobel@example.org", $folder->getOwner());
+ $this->assertEquals("TestÃÃÃ", $folder->getTitle());
+ $this->assertEquals("mail", $folder->getType());
+ $this->assertFalse($folder->isDefault());
+ $this->assertTrue($folder->exists());
+ $this->assertTrue($folder->accessible());
+
+ $folder2 = &new Kolab_Folder();
+ $folder2->setList($this->list);
+ $folder2->setName('TestEvents');
+ $attributes = array(
+ 'type' => 'event',
+ 'default' => true,
+ );
+ $result = $folder2->save($attributes);
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+ $this->assertEquals("wrobel@example.org", $folder2->getOwner());
+ $this->assertEquals("TestEvents", $folder2->getTitle());
+ $this->assertEquals("event", $folder2->getType());
+ $this->assertTrue($folder2->isDefault());
+
+ $attributes = array(
+ 'default' => false,
+ 'dummy' =>'test',
+ 'desc' =>'A test folder',
+ );
+ $result = $folder2->save($attributes);
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+ $this->assertEquals('test', $folder2->getAttribute('dummy'));
+ $this->assertEquals('A test folder', $folder2->getAttribute('desc'));
+
+ $folder2->setName('TestEventsNew');
+ $result = $folder2->save($attributes);
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+
+ $result = $folder->delete();
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+ $result = $folder2->delete();
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->message);
+ }
+ $this->assertTrue($result);
+ }
+
+ /**
+ * Test class construction with missing configuration data.
+ */
+ public function testGetImapFailNoServer()
+ {
+ $session = Horde_Kolab_Session::singleton('anonymous', null, true);
+ $imap = $session->getImapParams();
+ $this->assertEquals('localhost', $imap['hostspec']);
+ }
+
+ /**
+ * Test triggering.
+ */
+ public function testTrigger()
+ {
+ $folder = $this->getMock('Kolab_Folder', array('triggerUrl'));
+ $folder->expects($this->once())
+ ->method('triggerUrl')
+ ->with($this->equalTo('https://fb.example.org/freebusy/trigger/wrobel@example.org/Kalender.pfb'));
+
+ $folder->setList(&$this->list);
+ $folder->setName('Kalender');
+ $folder->save(array('type' => 'event'));
+
+ $folder = $this->getMock('Kolab_Folder', array('triggerUrl'));
+ $folder->expects($this->once())
+ ->method('triggerUrl')
+ ->with($this->equalTo('https://fb.example.org/freebusy/trigger/test@example.org/Kalender.pfb'));
+
+ $folder->setList(&$this->list);
+ $folder->setName('user/test/Kalender');
+ $folder->save(array('type' => 'event'));
+
+ }
+}
diff --git a/framework/Kolab_Storage/test/Horde/Kolab/Storage/ListTest.php b/framework/Kolab_Storage/test/Horde/Kolab/Storage/ListTest.php
new file mode 100644
index 000000000..e587db82f
--- /dev/null
+++ b/framework/Kolab_Storage/test/Horde/Kolab/Storage/ListTest.php
@@ -0,0 +1,272 @@
+
+ * @package Kolab_Storage
+ */
+class Horde_Kolab_Storage_ListTest extends Horde_Kolab_Test_Storage
+{
+
+ /**
+ * Test setup.
+ */
+ public function setUp()
+ {
+ $world = $this->prepareBasicSetup();
+
+ $this->assertTrue($world['auth']->authenticate('wrobel@example.org',
+ array('password' => 'none')));
+
+ $this->prepareNewFolder($world['storage'], 'Contacts', 'contact', true);
+ $this->prepareNewFolder($world['storage'], 'Calendar', 'event', true);
+
+ $this->assertTrue($world['auth']->authenticate('test@example.org',
+ array('password' => 'test')));
+
+ $this->prepareNewFolder($world['storage'], 'Contacts', 'contact', true);
+ $this->prepareNewFolder($world['storage'], 'TestContacts', 'contact');
+ $this->prepareNewFolder($world['storage'], 'Calendar', 'event', true);
+ $this->prepareNewFolder($world['storage'], 'TestCalendar', 'event');
+
+ $this->list = &new Kolab_List();
+ }
+
+ /**
+ * Test class construction.
+ */
+ public function testConstruct()
+ {
+ $this->assertEquals(0, $this->list->validity);
+ $this->assertTrue(is_array($this->list->_folders));
+ $this->assertTrue(empty($this->list->_folders));
+ $this->assertTrue(empty($this->list->_defaults));
+ $this->assertTrue(empty($this->list->_types));
+ }
+
+ /**
+ * Test listing folders.
+ */
+ public function testListFolders()
+ {
+ $folders = $this->list->listFolders();
+ $this->assertFalse(is_a($folders, 'PEAR_Error'));
+ $this->assertContains('INBOX/Contacts', $folders);
+ }
+
+ /**
+ * Test folder retrieval.
+ */
+ public function testGetFolders()
+ {
+ $folders = $this->list->getFolders();
+ $this->assertEquals(6, count($folders));
+ $this->assertContains('INBOX/Contacts', array_keys($this->list->_folders));
+ }
+
+ /**
+ * Test retrieving by share ID.
+ */
+ public function testGetByShare()
+ {
+ $folder = $this->list->getByShare('test@example.org', 'event');
+ if (is_a($folder, 'PEAR_Error')) {
+ $this->assertEquals('', $folder->message);
+ }
+ $this->assertEquals('INBOX/Calendar', $folder->name);
+ }
+
+ /**
+ * Test fetching the folder type.
+ */
+ public function testGetByType()
+ {
+ $folders = $this->list->getByType('event');
+ $this->assertEquals(3, count($folders));
+ $names = array();
+ foreach ($folders as $folder) {
+ $names[] = $folder->name;
+ }
+ $this->assertContains('INBOX/Calendar', $names);
+ $this->assertContains('INBOX/TestCalendar', $names);
+ }
+
+ /**
+ * Test retrieving the default folder.
+ */
+ public function testGetDefault()
+ {
+ $folder = $this->list->getDefault('event');
+ $this->assertEquals('INBOX/Calendar', $folder->name);
+ $folder = $this->list->getDefault('contact');
+ $this->assertEquals('INBOX/Contacts', $folder->name);
+ }
+
+ /**
+ * Test foreign folder owner.
+ */
+ public function testGetForeignOwner()
+ {
+ $folder = $this->list->getFolder('user/wrobel');
+ $this->assertEquals('wrobel@example.org', $folder->getOwner());
+ }
+
+ /**
+ * Test retrieving a foreign default folder.
+ */
+ public function testGetForeignDefault()
+ {
+ $folder = $this->list->getForeignDefault('wrobel@example.org', 'event');
+ $this->assertEquals('user/wrobel/Calendar', $folder->name);
+ $this->assertEquals('user%2Fwrobel%2FCalendar', $folder->getShareId());
+ $folder = $this->list->getForeignDefault('wrobel@example.org', 'contact');
+ $this->assertEquals('user/wrobel/Contacts', $folder->name);
+ $this->assertEquals('user%2Fwrobel%2FContacts', $folder->getShareId());
+ }
+
+ /**
+ * Test folder creation.
+ */
+ public function testCreate()
+ {
+ $folder = &new Kolab_Folder(null);
+ $folder->setList($this->list);
+ $folder->setName('Notes');
+ $folder->save(array());
+ $this->assertContains('INBOX/Notes', array_keys($this->list->_folders));
+ $this->assertEquals(1, $this->list->validity);
+ }
+
+ /**
+ * Test cache update.
+ */
+ public function testCacheUpdate()
+ {
+ $this->list = $this->getMock('Kolab_List', array('updateCache'));
+ $this->list->expects($this->once())
+ ->method('updateCache');
+
+ $folder = &new Kolab_Folder(null);
+ $folder->setList($this->list);
+ $folder->setName('Notes');
+ $this->assertEquals('INBOX/Notes', $folder->new_name);
+ $folder->save(array());
+ $type = $folder->getType();
+ if (is_a($type, 'PEAR_Error')) {
+ $this->assertEquals('', $type->getMessage());
+ }
+ $this->assertEquals('mail', $type);
+ $this->assertEquals('INBOX/Notes', $folder->name);
+ }
+
+
+ /**
+ * Test renaming folders.
+ */
+ public function testRename()
+ {
+ $folder = &new Kolab_Folder('INBOX/TestContacts');
+ $folder->setList($this->list);
+ $folder->setName('TestNotes');
+ $folder->save(array());
+ $this->assertNotContains('INBOX/TestContacts', array_keys($this->list->_folders));
+ $this->assertContains('INBOX/TestNotes', array_keys($this->list->_folders));
+ $this->assertEquals(1, $this->list->validity);
+ }
+
+ /**
+ * Test folder removal.
+ */
+ public function testRemove()
+ {
+ $folder = &new Kolab_Folder('INBOX/Calendar');
+ $folder->setList($this->list);
+ $this->assertTrue($folder->exists());
+ $this->assertTrue($folder->isDefault());
+ $folder->delete();
+ $this->assertNotContains('INBOX/Calendar', array_keys($this->list->_folders));
+ $this->assertEquals(1, $this->list->validity);
+ }
+
+ /**
+ * Test the list cache.
+ */
+ public function testCaching()
+ {
+ $GLOBALS['KOLAB_TESTING'] = array();
+ $this->list = &new Kolab_List();
+ $folders = $this->list->getFolders();
+ $this->assertTrue(empty($folders));
+ $folders = $this->list->getByType('event');
+ $this->assertTrue(empty($folders));
+ $default = $this->list->getDefault('event');
+ $this->assertTrue(empty($default));
+ $addfolder = &new Kolab_Folder(null);
+ $addfolder->setName('TestFolder');
+ $addfolder->setList($this->list);
+ $addfolder->save(array('type' => 'event', 'default' => true));
+ $this->assertContains('INBOX/TestFolder', array_keys($this->list->_folders));
+ $this->assertEquals('test@example.org', $addfolder->getOwner());
+ $folders = $this->list->getFolders();
+ $names = array();
+ foreach ($folders as $folder) {
+ $names[] = $folder->name;
+ }
+ $this->assertContains('INBOX/TestFolder', $names);
+ $folders = $this->list->getByType('event');
+ $names = array();
+ foreach ($folders as $folder) {
+ $names[] = $folder->name;
+ }
+ $this->assertContains('INBOX/TestFolder', $names);
+ $default = $this->list->getDefault('event');
+ $this->assertTrue($default !== false);
+ $this->assertEquals('INBOX/TestFolder', $default->name);
+ $addfolder->setName('NewCal');
+ $addfolder->save();
+ $folders = $this->list->getFolders();
+ $names = array();
+ foreach ($folders as $folder) {
+ $names[] = $folder->name;
+ }
+ $this->assertContains('INBOX/NewCal', $names);
+ $folders = $this->list->getByType('event');
+ $names = array();
+ foreach ($folders as $folder) {
+ $names[] = $folder->name;
+ }
+ $this->assertContains('INBOX/NewCal', $names);
+ $default = $this->list->getDefault('event');
+ $this->assertEquals('INBOX/NewCal', $default->name);
+ $addfolder->delete();
+ $folders = $this->list->getFolders();
+ $this->assertTrue(empty($folders));
+ $folders = $this->list->getByType('event');
+ $this->assertTrue(empty($folders));
+ $default = $this->list->getDefault('event');
+ $this->assertTrue(empty($default));
+ }
+}
diff --git a/framework/Kolab_Storage/test/Horde/Kolab/Storage/PermsTest.php b/framework/Kolab_Storage/test/Horde/Kolab/Storage/PermsTest.php
new file mode 100644
index 000000000..6b1787a7c
--- /dev/null
+++ b/framework/Kolab_Storage/test/Horde/Kolab/Storage/PermsTest.php
@@ -0,0 +1,169 @@
+
+ * @package Kolab_Storage
+ */
+class Horde_Kolab_Storage_PermsTest extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Test class construction.
+ */
+ public function testConstruct()
+ {
+ $folder = &new DummyFolder(null);
+ $perms = &new Horde_Permission_Kolab($folder);
+ $this->assertEquals('DummyFolder', get_class($perms->_folder));
+ $this->assertEquals(array(), $perms->data);
+ $perms = &new Horde_Permission_Kolab($folder, array('users' => array(
+ 'wrobel' => PERMS_SHOW | PERMS_READ |
+ PERMS_EDIT | PERMS_DELETE)
+ ));
+ $this->assertTrue(is_array($perms->data));
+ }
+
+ /**
+ * Test retrieving permissions.
+ */
+ public function testGetPerm()
+ {
+ $GLOBALS['conf']['auth']['driver'] = 'auto';
+ $GLOBALS['conf']['group']['driver'] = 'mock';
+
+ $folder = &new DummyFolder(
+ array(
+ 'wrobel' => 'lrid',
+ 'reader' => 'lr',
+ 'viewer' => 'l',
+ 'editor' => 'lre',
+ 'anyone' => 'l',
+ 'anonymous' => '',
+ 'group:editors' => 'lre'
+ )
+ );
+ $perms = &new Horde_Permission_Kolab($folder);
+ $this->assertContains('users', array_keys($perms->data));
+ $this->assertContains('wrobel', array_keys($perms->data['users']));
+ $this->assertContains('reader', array_keys($perms->data['users']));
+ $this->assertContains('groups', array_keys($perms->data));
+ $this->assertContains('default', array_keys($perms->data));
+ $this->assertContains('guest', array_keys($perms->data));
+ }
+
+ /**
+ * Test saving permissions
+ */
+ public function testSave()
+ {
+ $GLOBALS['conf']['auth']['driver'] = 'auto';
+ $GLOBALS['conf']['group']['driver'] = 'mock';
+
+ $folder = &new DummyFolder(
+ array(
+ 'wrobel' => 'lrid',
+ 'reader' => 'lr',
+ 'viewer' => 'l',
+ 'editor' => 'lre',
+ 'anyone' => 'l',
+ 'anonymous' => '',
+ 'group:editors' => 'lre'
+ ),
+ 'wrobel'
+ );
+ $perms = &new Horde_Permission_Kolab($folder);
+ unset($perms->data['guest']);
+ unset($perms->data['default']);
+ unset($perms->data['users']['viewer']);
+ $perms->data['users']['editor'] = PERMS_SHOW | PERMS_READ | PERMS_EDIT | PERMS_DELETE;
+ $perms->data['users']['test'] = PERMS_SHOW | PERMS_READ;
+ $perms->data['groups']['group'] = PERMS_SHOW | PERMS_READ;
+ $perms->save();
+ $this->assertNotContains('anyone', array_keys($folder->acl));
+ $this->assertNotContains('anonymous', array_keys($folder->acl));
+ $this->assertEquals('lr', $folder->acl['test']);
+ $this->assertEquals('lriswcd', $folder->acl['editor']);
+ $this->assertEquals('alriswcd', $folder->acl['wrobel']);
+ }
+
+ /**
+ * Test using Horde permissions.
+ */
+ public function testHordePermissions()
+ {
+ $GLOBALS['conf']['auth']['driver'] = 'auto';
+ $GLOBALS['conf']['group']['driver'] = 'mock';
+
+ $folder = &new DummyFolder(array(), 'wrobel');
+ $hperms = &new Horde_Permission('test');
+ $hperms->addUserPermission('wrobel', PERMS_SHOW, false);
+ $perms = &new Horde_Permission_Kolab($folder, $hperms->data);
+ $perms->save();
+ $this->assertEquals('al', $folder->acl['wrobel']);
+ }
+}
+
+/**
+ * A dummy folder representation to test the Kolab permission handler.
+ *
+ * $Horde: framework/Kolab_Storage/test/Horde/Kolab/Storage/PermsTest.php,v 1.4 2009/01/06 17:49:28 jan Exp $
+ *
+ * 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.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Gunnar Wrobel
+ * @package Kolab_Storage
+ */
+class DummyFolder
+{
+ var $acl;
+ var $_owner;
+ function DummyFolder($acl, $owner = null)
+ {
+ $this->acl = $acl;
+ $this->_owner = $owner;
+ }
+ function getACL()
+ {
+ return $this->acl;
+ }
+ function setACL($user, $acl)
+ {
+ return $this->acl[$user] = $acl;
+ }
+ function deleteACL($user)
+ {
+ unset($this->acl[$user]);
+ }
+ function getOwner()
+ {
+ return $this->_owner;
+ }
+}
+