Page MenuHomePhorge

No OneTemporary

diff --git a/plugins/libkolab/lib/kolab_storage_dav.php b/plugins/libkolab/lib/kolab_storage_dav.php
index 1837ff0a..7dd27da2 100644
--- a/plugins/libkolab/lib/kolab_storage_dav.php
+++ b/plugins/libkolab/lib/kolab_storage_dav.php
@@ -1,547 +1,564 @@
<?php
/**
* Kolab storage class providing access to groupware objects on a *DAV server.
*
* @author Aleksander Machniak <machniak@apheleia-it.ch>
*
* Copyright (C) 2022, Apheleia IT AG <contact@apheleia-it.ch>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_storage_dav
{
const ERROR_DAV_CONN = 1;
const ERROR_CACHE_DB = 2;
const ERROR_NO_PERMISSION = 3;
const ERROR_INVALID_FOLDER = 4;
protected $dav;
protected $url;
/**
* Object constructor
*/
public function __construct($url)
{
$this->url = $url;
$this->setup();
}
/**
* Setup the environment
*/
public function setup()
{
$rcmail = rcube::get_instance();
$this->config = $rcmail->config;
$this->dav = new kolab_dav_client($this->url);
}
/**
* Get a list of storage folders for the given data type
*
* @param string Data type to list folders for (contact,event,task,note)
*
* @return array List of kolab_storage_dav_folder objects
*/
public function get_folders($type)
{
// TODO: This should be cached
$folders = $this->dav->listFolders($this->get_dav_type($type));
if (is_array($folders)) {
foreach ($folders as $idx => $folder) {
// Exclude some special folders
if (in_array('schedule-inbox', $folder['resource_type']) || in_array('schedule-outbox', $folder['resource_type'])) {
unset($folders[$idx]);
continue;
}
$folders[$idx] = new kolab_storage_dav_folder($this->dav, $folder, $type);
}
usort($folders, function ($a, $b) {
return strcoll($a->get_foldername(), $b->get_foldername());
});
}
return $folders ?: [];
}
/**
* Getter for the storage folder for the given type
*
* @param string Data type to list folders for (contact,event,task,note)
*
* @return object kolab_storage_dav_folder The folder object
*/
public function get_default_folder($type)
{
// TODO: Not used
}
/**
* Getter for a specific storage folder
*
* @param string $id Folder to access
* @param string $type Expected folder type
*
* @return ?object kolab_storage_folder The folder object
*/
public function get_folder($id, $type)
{
foreach ($this->get_folders($type) as $folder) {
if ($folder->id == $id) {
return $folder;
}
}
}
/**
* Getter for a single Kolab object, identified by its UID.
* This will search all folders storing objects of the given type.
*
* @param string Object UID
* @param string Object type (contact,event,task,journal,file,note,configuration)
*
* @return array The Kolab object represented as hash array or false if not found
*/
public function get_object($uid, $type)
{
// TODO
return false;
}
/**
* Execute cross-folder searches with the given query.
*
* @param array Pseudo-SQL query as list of filter parameter triplets
* @param string Folder type (contact,event,task,journal,file,note,configuration)
* @param int Expected number of records or limit (for performance reasons)
*
* @return array List of Kolab data objects (each represented as hash array)
*/
public function select($query, $type, $limit = null)
{
$result = [];
foreach ($this->get_folders($type) as $folder) {
if ($limit) {
$folder->set_order_and_limit(null, $limit);
}
foreach ($folder->select($query) as $object) {
$result[] = $object;
}
}
return $result;
}
/**
* Compose an URL to query the free/busy status for the given user
*
* @param string Email address of the user to get free/busy data for
* @param object DateTime Start of the query range (optional)
* @param object DateTime End of the query range (optional)
*
* @return string Fully qualified URL to query free/busy data
*/
public static function get_freebusy_url($email, $start = null, $end = null)
{
return kolab_storage::get_freebusy_url($email, $start, $end);
}
+ /**
+ * Creates folder ID from a DAV folder location and server URI.
+ *
+ * @param string $uri DAV server location
+ * @param string $href Folder location
+ *
+ * @return string Folder ID string
+ */
+ public static function folder_id($uri, $href)
+ {
+ if (($rootPath = parse_url($uri, PHP_URL_PATH)) && strpos($href, $rootPath) === 0) {
+ $href = substr($href, strlen($rootPath));
+ }
+
+ return md5(rtrim($uri, '/') . '/' . trim($href, '/'));
+ }
+
/**
* Deletes a folder
*
* @param string $id Folder ID
* @param string $type Folder type (contact,event,task,journal,file,note,configuration)
*
* @return bool True on success, false on failure
*/
public function folder_delete($id, $type)
{
if ($folder = $this->get_folder($id, $type)) {
return $this->dav->folderDelete($folder->href);
}
return false;
}
/**
* Creates a folder
*
* @param string $name Folder name (UTF7-IMAP)
* @param string $type Folder type
* @param bool $subscribed Sets folder subscription
* @param bool $active Sets folder state (client-side subscription)
*
* @return bool True on success, false on failure
*/
public function folder_create($name, $type = null, $subscribed = false, $active = false)
{
// TODO
}
/**
* Renames DAV folder
*
* @param string $oldname Old folder name (UTF7-IMAP)
* @param string $newname New folder name (UTF7-IMAP)
*
* @return bool True on success, false on failure
*/
public function folder_rename($oldname, $newname)
{
// TODO ??
}
/**
* Update or Create a new folder.
*
* Does additional checks for permissions and folder name restrictions
*
* @param array &$prop Hash array with folder properties and metadata
* - name: Folder name
* - oldname: Old folder name when changed
* - parent: Parent folder to create the new one in
* - type: Folder type to create
* - subscribed: Subscribed flag (IMAP subscription)
* - active: Activation flag (client-side subscription)
*
* @return string|false New folder ID or False on failure
*/
public function folder_update(&$prop)
{
// TODO: Folder hierarchies, active and subscribed state
// sanity checks
if (!isset($prop['name']) || !is_string($prop['name']) || !strlen($prop['name'])) {
self::$last_error = 'cannotbeempty';
return false;
}
else if (strlen($prop['name']) > 256) {
self::$last_error = 'nametoolong';
return false;
}
if (!empty($prop['id'])) {
if ($folder = $this->get_folder($prop['id'], $prop['type'])) {
$result = $this->dav->folderUpdate($folder->href, $folder->get_dav_type(), $prop);
}
else {
$result = false;
}
}
else {
$rcube = rcube::get_instance();
$uid = rtrim(chunk_split(md5($prop['name'] . $rcube->get_user_name() . uniqid('-', true)), 12, '-'), '-');
$type = $this->get_dav_type($prop['type']);
$home = $this->dav->discover($type);
if ($home === false) {
return false;
}
$location = unslashify($home) . '/' . $uid;
$result = $this->dav->folderCreate($location, $type, $prop);
if ($result !== false) {
- $result = md5($this->dav->url . '/' . $location);
+ $result = self::folder_id($this->dav->url, $location);
}
}
return $result;
}
/**
* Getter for human-readable name of a folder
*
* @param string $folder Folder name (UTF7-IMAP)
* @param string $folder_ns Will be set to namespace name of the folder
*
* @return string Name of the folder-object
*/
public static function object_name($folder, &$folder_ns = null)
{
// TODO: Shared folders
$folder_ns = 'personal';
return $folder;
}
/**
* Creates a SELECT field with folders list
*
* @param string $type Folder type
* @param array $attrs SELECT field attributes (e.g. name)
* @param string $current The name of current folder (to skip it)
*
* @return html_select SELECT object
*/
public function folder_selector($type, $attrs, $current = '')
{
// TODO
}
/**
* Returns a list of folder names
*
* @param string Optional root folder
* @param string Optional name pattern
* @param string Data type to list folders for (contact,event,task,journal,file,note,mail,configuration)
* @param bool Enable to return subscribed folders only (null to use configured subscription mode)
* @param array Will be filled with folder-types data
*
* @return array List of folders
*/
public function list_folders($root = '', $mbox = '*', $filter = null, $subscribed = null, &$folderdata = array())
{
// TODO
}
/**
* Search for shared or otherwise not listed groupware folders the user has access
*
* @param string Folder type of folders to search for
* @param string Search string
* @param array Namespace(s) to exclude results from
*
* @return array List of matching kolab_storage_folder objects
*/
public function search_folders($type, $query, $exclude_ns = [])
{
// TODO
return [];
}
/**
* Sort the given list of folders by namespace/name
*
* @param array List of kolab_storage_dav_folder objects
*
* @return array Sorted list of folders
*/
public static function sort_folders($folders)
{
// TODO
return $folders;
}
/**
* Returns folder types indexed by folder name
*
* @param string $prefix Folder prefix (Default '*' for all folders)
*
* @return array|bool List of folders, False on failure
*/
public function folders_typedata($prefix = '*')
{
// TODO: Used by kolab_folders, kolab_activesync, kolab_delegation
return [];
}
/**
* Returns type of a DAV folder
*
* @param string $folder Folder name (UTF7-IMAP)
*
* @return string Folder type
*/
public function folder_type($folder)
{
// TODO: Used by kolab_folders, kolab_activesync, kolab_delegation
return '';
}
/**
* Sets folder content-type.
*
* @param string $folder Folder name
* @param string $type Content type
*
* @return bool True on success, False otherwise
*/
public function set_folder_type($folder, $type = 'mail')
{
// NOP: Used by kolab_folders, kolab_activesync, kolab_delegation
return false;
}
/**
* Check subscription status of this folder
*
* @param string $folder Folder name
* @param bool $temp Include temporary/session subscriptions
*
* @return bool True if subscribed, false if not
*/
public function folder_is_subscribed($folder, $temp = false)
{
// NOP
return true;
}
/**
* Change subscription status of this folder
*
* @param string $folder Folder name
* @param bool $temp Only subscribe temporarily for the current session
*
* @return True on success, false on error
*/
public function folder_subscribe($folder, $temp = false)
{
// NOP
return true;
}
/**
* Change subscription status of this folder
*
* @param string $folder Folder name
* @param bool $temp Only remove temporary subscription
*
* @return True on success, false on error
*/
public function folder_unsubscribe($folder, $temp = false)
{
// NOP
return false;
}
/**
* Check activation status of this folder
*
* @param string $folder Folder name
*
* @return bool True if active, false if not
*/
public function folder_is_active($folder)
{
return true; // TODO
}
/**
* Change activation status of this folder
*
* @param string $folder Folder name
*
* @return True on success, false on error
*/
public function folder_activate($folder)
{
return true; // TODO
}
/**
* Change activation status of this folder
*
* @param string $folder Folder name
*
* @return True on success, false on error
*/
public function folder_deactivate($folder)
{
return false; // TODO
}
/**
* Creates default folder of specified type
* To be run when none of subscribed folders (of specified type) is found
*
* @param string $type Folder type
* @param string $props Folder properties (color, etc)
*
* @return string Folder name
*/
public function create_default_folder($type, $props = [])
{
// TODO: For kolab_addressbook??
return '';
}
/**
* Returns a list of IMAP folders shared by the given user
*
* @param array User entry from LDAP
* @param string Data type to list folders for (contact,event,task,journal,file,note,mail,configuration)
* @param int 1 - subscribed folders only, 0 - all folders, 2 - all non-active
* @param array Will be filled with folder-types data
*
* @return array List of folders
*/
public function list_user_folders($user, $type, $subscribed = 0, &$folderdata = [])
{
// TODO
return [];
}
/**
* Get a list of (virtual) top-level folders from the other users namespace
*
* @param string Data type to list folders for (contact,event,task,journal,file,note,mail,configuration)
* @param bool Enable to return subscribed folders only (null to use configured subscription mode)
*
* @return array List of kolab_storage_folder_user objects
*/
public function get_user_folders($type, $subscribed)
{
// TODO
return [];
}
/**
* Handler for user_delete plugin hooks
*
* Remove all cache data from the local database related to the given user.
*/
public static function delete_user_folders($args)
{
$db = rcmail::get_instance()->get_dbh();
$table = $db->table_name('kolab_folders', true);
$prefix = 'dav://' . urlencode($args['username']) . '@' . $args['host'] . '/%';
$db->query("DELETE FROM $table WHERE `resource` LIKE ?", $prefix);
}
/**
* Get folder METADATA for all supported keys
* Do this in one go for better caching performance
*/
public function folder_metadata($folder)
{
// TODO ?
return [];
}
/**
* Get a folder DAV content type
*/
public static function get_dav_type($type)
{
$types = [
'event' => 'VEVENT',
'task' => 'VTODO',
'contact' => 'VCARD',
];
return $types[$type];
}
}
diff --git a/plugins/libkolab/lib/kolab_storage_dav_folder.php b/plugins/libkolab/lib/kolab_storage_dav_folder.php
index adfa1d88..c1141144 100644
--- a/plugins/libkolab/lib/kolab_storage_dav_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_dav_folder.php
@@ -1,722 +1,722 @@
<?php
/**
* A class representing a DAV folder object.
*
* @author Aleksander Machniak <machniak@apheleia-it.ch>
*
* Copyright (C) 2014-2022, Apheleia IT AG <contact@apheleia-it.ch>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_storage_dav_folder extends kolab_storage_folder
{
public $dav;
public $href;
public $attributes;
/**
* Object constructor
*/
public function __construct($dav, $attributes, $type = '')
{
$this->attributes = $attributes;
$this->href = $this->attributes['href'];
- $this->id = md5($dav->url . '/' . $this->href);
+ $this->id = kolab_storage_dav::folder_id($dav->url, $this->href);
$this->dav = $dav;
$this->valid = true;
list($this->type, $suffix) = strpos($type, '.') ? explode('.', $type) : [$type, ''];
$this->default = $suffix == 'default';
$this->subtype = $this->default ? '' : $suffix;
// Init cache
$this->cache = kolab_storage_dav_cache::factory($this);
}
/**
* Returns the owner of the folder.
*
* @param bool Return a fully qualified owner name (i.e. including domain for shared folders)
*
* @return string The owner of this folder.
*/
public function get_owner($fully_qualified = false)
{
// return cached value
if (isset($this->owner)) {
return $this->owner;
}
$rcube = rcube::get_instance();
$this->owner = $rcube->get_user_name();
$this->valid = true;
// TODO: Support shared folders
return $this->owner;
}
/**
* Get a folder Etag identifier
*/
public function get_ctag()
{
return $this->attributes['ctag'];
}
/**
* Getter for the name of the namespace to which the folder belongs
*
* @return string Name of the namespace (personal, other, shared)
*/
public function get_namespace()
{
// TODO: Support shared folders
return 'personal';
}
/**
* Get the display name value of this folder
*
* @return string Folder name
*/
public function get_name()
{
return kolab_storage_dav::object_name($this->attributes['name']);
}
/**
* Getter for the top-end folder name (not the entire path)
*
* @return string Name of this folder
*/
public function get_foldername()
{
return $this->attributes['name'];
}
public function get_folder_info()
{
return []; // todo ?
}
/**
* Getter for parent folder path
*
* @return string Full path to parent folder
*/
public function get_parent()
{
// TODO
return '';
}
/**
* Compose a unique resource URI for this folder
*/
public function get_resource_uri()
{
if (!empty($this->resource_uri)) {
return $this->resource_uri;
}
// compose fully qualified resource uri for this instance
$host = preg_replace('|^https?://|', 'dav://' . urlencode($this->get_owner(true)) . '@', $this->dav->url);
$path = $this->href[0] == '/' ? $this->href : "/{$this->href}";
$host_path = parse_url($host, PHP_URL_PATH);
if ($host_path && strpos($path, $host_path) === 0) {
$path = substr($path, strlen($host_path));
}
$this->resource_uri = unslashify($host) . $path;
return $this->resource_uri;
}
/**
* Getter for the Cyrus mailbox identifier corresponding to this folder
* (e.g. user/john.doe/Calendar/Personal@example.org)
*
* @return string Mailbox ID
*/
public function get_mailbox_id()
{
// TODO: This is used with Bonnie related features
return '';
}
/**
* Get the color value stored in metadata
*
* @param string Default color value to return if not set
*
* @return mixed Color value from the folder metadata or $default if not set
*/
public function get_color($default = null)
{
return !empty($this->attributes['color']) ? $this->attributes['color'] : $default;
}
/**
* Get ACL information for this folder
*
* @return string Permissions as string
*/
public function get_myrights()
{
// TODO
return '';
}
/**
* Helper method to extract folder UID
*
* @return string Folder's UID
*/
public function get_uid()
{
// TODO ???
return '';
}
/**
* Check activation status of this folder
*
* @return bool True if enabled, false if not
*/
public function is_active()
{
return true; // Unused
}
/**
* Change activation status of this folder
*
* @param bool The desired subscription status: true = active, false = not active
*
* @return bool True on success, false on error
*/
public function activate($active)
{
return true; // Unused
}
/**
* Check subscription status of this folder
*
* @return bool True if subscribed, false if not
*/
public function is_subscribed()
{
return true; // TODO
}
/**
* Change subscription status of this folder
*
* @param bool The desired subscription status: true = subscribed, false = not subscribed
*
* @return True on success, false on error
*/
public function subscribe($subscribed)
{
return true; // TODO
}
/**
* Delete the specified object from this folder.
*
* @param array|string $object The Kolab object to delete or object UID
* @param bool $expunge Should the folder be expunged?
*
* @return bool True if successful, false on error
*/
public function delete($object, $expunge = true)
{
if (!$this->valid) {
return false;
}
$uid = is_array($object) ? $object['uid'] : $object;
$success = $this->dav->delete($this->object_location($uid));
if ($success) {
$this->cache->set($uid, false);
}
return $success;
}
/**
* Delete all objects in a folder.
*
* Note: This method is used by kolab_addressbook plugin only
*
* @return bool True if successful, false on error
*/
public function delete_all()
{
if (!$this->valid) {
return false;
}
// TODO: Maybe just deleting and re-creating a folder would be
// better, but probably might not always work (ACL)
$this->cache->synchronize();
foreach (array_keys($this->cache->folder_index()) as $uid) {
$this->dav->delete($this->object_location($uid));
}
$this->cache->purge();
return true;
}
/**
* Restore a previously deleted object
*
* @param string $uid Object UID
*
* @return mixed Message UID on success, false on error
*/
public function undelete($uid)
{
if (!$this->valid) {
return false;
}
// TODO
return false;
}
/**
* Move a Kolab object message to another IMAP folder
*
* @param string Object UID
* @param string IMAP folder to move object to
*
* @return bool True on success, false on failure
*/
public function move($uid, $target_folder)
{
if (!$this->valid) {
return false;
}
// TODO
return false;
}
/**
* Save an object in this folder.
*
* @param array $object The array that holds the data of the object.
* @param string $type The type of the kolab object.
* @param string $uid The UID of the old object if it existed before
*
* @return mixed False on error or object UID on success
*/
public function save(&$object, $type = null, $uid = null)
{
if (!$this->valid || empty($object)) {
return false;
}
if (!$type) {
$type = $this->type;
}
/*
// copy attachments from old message
$copyfrom = $object['_copyfrom'] ?: $object['_msguid'];
if (!empty($copyfrom) && ($old = $this->cache->get($copyfrom, $type, $object['_mailbox']))) {
foreach ((array)$old['_attachments'] as $key => $att) {
if (!isset($object['_attachments'][$key])) {
$object['_attachments'][$key] = $old['_attachments'][$key];
}
// unset deleted attachment entries
if ($object['_attachments'][$key] == false) {
unset($object['_attachments'][$key]);
}
// load photo.attachment from old Kolab2 format to be directly embedded in xcard block
else if ($type == 'contact' && ($key == 'photo.attachment' || $key == 'kolab-picture.png') && $att['id']) {
if (!isset($object['photo']))
$object['photo'] = $this->get_attachment($copyfrom, $att['id'], $object['_mailbox']);
unset($object['_attachments'][$key]);
}
}
}
// process attachments
if (is_array($object['_attachments'])) {
$numatt = count($object['_attachments']);
foreach ($object['_attachments'] as $key => $attachment) {
// FIXME: kolab_storage and Roundcube attachment hooks use different fields!
if (empty($attachment['content']) && !empty($attachment['data'])) {
$attachment['content'] = $attachment['data'];
unset($attachment['data'], $object['_attachments'][$key]['data']);
}
// make sure size is set, so object saved in cache contains this info
if (!isset($attachment['size'])) {
if (!empty($attachment['content'])) {
if (is_resource($attachment['content'])) {
// this need to be a seekable resource, otherwise
// fstat() failes and we're unable to determine size
// here nor in rcube_imap_generic before IMAP APPEND
$stat = fstat($attachment['content']);
$attachment['size'] = $stat ? $stat['size'] : 0;
}
else {
$attachment['size'] = strlen($attachment['content']);
}
}
else if (!empty($attachment['path'])) {
$attachment['size'] = filesize($attachment['path']);
}
$object['_attachments'][$key] = $attachment;
}
// generate unique keys (used as content-id) for attachments
if (is_numeric($key) && $key < $numatt) {
// derrive content-id from attachment file name
$ext = preg_match('/(\.[a-z0-9]{1,6})$/i', $attachment['name'], $m) ? $m[1] : null;
$basename = preg_replace('/[^a-z0-9_.-]/i', '', basename($attachment['name'], $ext)); // to 7bit ascii
if (!$basename) $basename = 'noname';
$cid = $basename . '.' . microtime(true) . $key . $ext;
$object['_attachments'][$cid] = $attachment;
unset($object['_attachments'][$key]);
}
}
}
*/
$rcmail = rcube::get_instance();
$result = false;
// generate and save object message
if ($content = $this->to_dav($object)) {
$method = $uid ? 'update' : 'create';
$dav_type = $this->get_dav_type();
$result = $this->dav->{$method}($this->object_location($object['uid']), $content, $dav_type);
// Note: $result can be NULL if the request was successful, but ETag wasn't returned
if ($result !== false) {
// insert/update object in the cache
$object['etag'] = $result;
$object['_raw'] = $content;
$this->cache->save($object, $uid);
$result = true;
unset($object['_raw']);
}
}
return $result;
}
/**
* Fetch the object the DAV server and convert to internal format
*
* @param string The object UID to fetch
* @param string The object type expected (use wildcard '*' to accept all types)
* @param string Unused (kept for compat. with the parent class)
*
* @return mixed Hash array representing the Kolab object, a kolab_format instance or false if not found
*/
public function read_object($uid, $type = null, $folder = null)
{
if (!$this->valid) {
return false;
}
$href = $this->object_location($uid);
$objects = $this->dav->getData($this->href, $this->get_dav_type(), [$href]);
if (!is_array($objects) || count($objects) != 1) {
rcube::raise_error([
'code' => 900,
'message' => "Failed to fetch {$href}"
], true);
return false;
}
return $this->from_dav($objects[0]);
}
/**
* Fetch multiple objects from the DAV server and convert to internal format
*
* @param array The object UIDs to fetch
*
* @return mixed Hash array representing the Kolab objects
*/
public function read_objects($uids)
{
if (!$this->valid) {
return false;
}
if (empty($uids)) {
return [];
}
foreach ($uids as $uid) {
$hrefs[] = $this->object_location($uid);
}
$objects = $this->dav->getData($this->href, $this->get_dav_type(), $hrefs);
if (!is_array($objects)) {
rcube::raise_error([
'code' => 900,
'message' => "Failed to fetch {$href}"
], true);
return false;
}
$objects = array_map([$this, 'from_dav'], $objects);
foreach ($uids as $idx => $uid) {
foreach ($objects as $oidx => $object) {
if ($object && $object['uid'] == $uid) {
$uids[$idx] = $object;
unset($objects[$oidx]);
continue 2;
}
}
$uids[$idx] = false;
}
return $uids;
}
/**
* Convert DAV object into PHP array
*
* @param array Object data in kolab_dav_client::fetchData() format
*
* @return array Object properties
*/
public function from_dav($object)
{
if ($this->type == 'event') {
$ical = libcalendaring::get_ical();
$events = $ical->import($object['data']);
if (!count($events) || empty($events[0]['uid'])) {
return false;
}
$result = $events[0];
}
else if ($this->type == 'contact') {
if (stripos($object['data'], 'BEGIN:VCARD') !== 0) {
return false;
}
// vCard properties not supported by rcube_vcard
$map = [
'uid' => 'UID',
'kind' => 'KIND',
'member' => 'MEMBER',
'x-kind' => 'X-ADDRESSBOOKSERVER-KIND',
'x-member' => 'X-ADDRESSBOOKSERVER-MEMBER',
];
// TODO: We should probably use Sabre/Vobject to parse the vCard
$vcard = new rcube_vcard($object['data'], RCUBE_CHARSET, false, $map);
if (!empty($vcard->displayname) || !empty($vcard->surname) || !empty($vcard->firstname) || !empty($vcard->email)) {
$result = $vcard->get_assoc();
// Contact groups
if (!empty($result['x-kind']) && implode($result['x-kind']) == 'group') {
$result['_type'] = 'group';
$members = isset($result['x-member']) ? $result['x-member'] : [];
unset($result['x-kind'], $result['x-member']);
}
else if (!empty($result['kind']) && implode($result['kind']) == 'group') {
$result['_type'] = 'group';
$members = isset($result['member']) ? $result['member'] : [];
unset($result['kind'], $result['member']);
}
if (isset($members)) {
$result['member'] = [];
foreach ($members as $member) {
if (strpos($member, 'urn:uuid:') === 0) {
$result['member'][] = ['uid' => substr($member, 9)];
}
else if (strpos($member, 'mailto:') === 0) {
$member = reset(rcube_mime::decode_address_list(urldecode(substr($member, 7))));
if (!empty($member['mailto'])) {
$result['member'][] = ['email' => $member['mailto'], 'name' => $member['name']];
}
}
}
}
if (!empty($result['uid'])) {
$result['uid'] = preg_replace('/^urn:uuid:/', '', implode($result['uid']));
}
}
else {
return false;
}
}
$result['etag'] = $object['etag'];
$result['href'] = !empty($object['href']) ? $object['href'] : null;
$result['uid'] = !empty($object['uid']) ? $object['uid'] : $result['uid'];
return $result;
}
/**
* Convert Kolab object into DAV format (iCalendar)
*/
public function to_dav($object)
{
$result = '';
if ($this->type == 'event') {
$ical = libcalendaring::get_ical();
if (!empty($object['exceptions'])) {
$object['recurrence']['EXCEPTIONS'] = $object['exceptions'];
}
$result = $ical->export([$object]);
}
else if ($this->type == 'contact') {
// copy values into vcard object
// TODO: We should probably use Sabre/Vobject to create the vCard
// vCard properties not supported by rcube_vcard
$map = ['uid' => 'UID', 'kind' => 'KIND'];
$vcard = new rcube_vcard('', RCUBE_CHARSET, false, $map);
if ((!empty($object['_type']) && $object['_type'] == 'group')
|| (!empty($object['type']) && $object['type'] == 'group')
) {
$object['kind'] = 'group';
}
foreach ($object as $key => $values) {
list($field, $section) = rcube_utils::explode(':', $key);
// avoid casting DateTime objects to array
if (is_object($values) && $values instanceof DateTimeInterface) {
$values = [$values];
}
foreach ((array) $values as $value) {
if (isset($value)) {
$vcard->set($field, $value, $section);
}
}
}
$result = $vcard->export(false);
if (!empty($object['kind']) && $object['kind'] == 'group') {
$members = '';
foreach ((array) $object['member'] as $member) {
$value = null;
if (!empty($member['uid'])) {
$value = 'urn:uuid:' . $member['uid'];
}
else if (!empty($member['email']) && !empty($member['name'])) {
$value = 'mailto:' . urlencode(sprintf('"%s" <%s>', addcslashes($member['name'], '"'), $member['email']));
}
else if (!empty($member['email'])) {
$value = 'mailto:' . $member['email'];
}
if ($value) {
$members .= "MEMBER:{$value}\r\n";
}
}
if ($members) {
$result = preg_replace('/\r\nEND:VCARD/', "\r\n{$members}END:VCARD", $result);
}
/**
Version 4.0 of the vCard format requires Cyrus >= 3.6.0, we'll use Version 3.0 for now
$result = preg_replace('/\r\nVERSION:3\.0\r\n/', "\r\nVERSION:4.0\r\n", $result);
$result = preg_replace('/\r\nN:[^\r]+/', '', $result);
$result = preg_replace('/\r\nUID:([^\r]+)/', "\r\nUID:urn:uuid:\\1", $result);
*/
$result = preg_replace('/\r\nMEMBER:([^\r]+)/', "\r\nX-ADDRESSBOOKSERVER-MEMBER:\\1", $result);
$result = preg_replace('/\r\nKIND:([^\r]+)/', "\r\nX-ADDRESSBOOKSERVER-KIND:\\1", $result);
}
}
if ($result) {
// The content must be UTF-8, otherwise if we try to fetch the object
// from server XML parsing would fail.
$result = rcube_charset::clean($result);
}
return $result;
}
protected function object_location($uid)
{
return unslashify($this->href) . '/' . urlencode($uid) . '.' . $this->get_dav_ext();
}
/**
* Get a folder DAV content type
*/
public function get_dav_type()
{
return kolab_storage_dav::get_dav_type($this->type);
}
/**
* Get a DAV file extension for specified Kolab type
*/
public function get_dav_ext()
{
$types = [
'event' => 'ics',
'task' => 'ics',
'contact' => 'vcf',
];
return $types[$this->type];
}
/**
* Return folder name as string representation of this object
*
* @return string Folder display name
*/
public function __toString()
{
return $this->attributes['name'];
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Jan 18, 8:12 PM (12 h, 7 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
120005
Default Alt Text
(40 KB)

Event Timeline