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' . + '_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; + } +} +