2022-10-06 15:59:53 +02:00
|
|
|
<?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
|
|
|
|
*/
|
2022-11-29 15:54:43 +01:00
|
|
|
public function __construct($dav, $attributes, $type = '')
|
2022-10-06 15:59:53 +02:00
|
|
|
{
|
|
|
|
$this->attributes = $attributes;
|
|
|
|
|
2022-10-14 16:34:19 +02:00
|
|
|
$this->href = $this->attributes['href'];
|
2022-11-29 15:54:43 +01:00
|
|
|
$this->id = md5($dav->url . '/' . $this->href);
|
2022-10-06 15:59:53 +02:00
|
|
|
$this->dav = $dav;
|
|
|
|
$this->valid = true;
|
|
|
|
|
2022-11-29 15:54:43 +01:00
|
|
|
list($this->type, $suffix) = strpos($type, '.') ? explode('.', $type) : [$type, ''];
|
2022-10-12 13:36:57 +02:00
|
|
|
$this->default = $suffix == 'default';
|
|
|
|
$this->subtype = $this->default ? '' : $suffix;
|
2022-10-06 15:59:53 +02:00
|
|
|
|
|
|
|
// 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'];
|
|
|
|
}
|
|
|
|
|
2022-10-11 15:27:59 +02:00
|
|
|
public function get_folder_info()
|
|
|
|
{
|
|
|
|
return []; // todo ?
|
|
|
|
}
|
|
|
|
|
2022-10-06 15:59:53 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2022-10-16 14:22:59 +02:00
|
|
|
// compose fully qualified resource uri for this instance
|
2022-10-06 15:59:53 +02:00
|
|
|
$host = preg_replace('|^https?://|', 'dav://' . urlencode($this->get_owner(true)) . '@', $this->dav->url);
|
|
|
|
$path = $this->href[0] == '/' ? $this->href : "/{$this->href}";
|
|
|
|
|
2022-10-12 13:36:57 +02:00
|
|
|
$host_path = parse_url($host, PHP_URL_PATH);
|
|
|
|
if ($host_path && strpos($path, $host_path) === 0) {
|
|
|
|
$path = substr($path, strlen($host_path));
|
|
|
|
}
|
|
|
|
|
2022-10-06 15:59:53 +02:00
|
|
|
$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()
|
|
|
|
{
|
2022-11-08 12:34:35 +01:00
|
|
|
return true; // Unused
|
2022-10-06 15:59:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
{
|
2022-11-08 12:34:35 +01:00
|
|
|
return true; // Unused
|
2022-10-06 15:59:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check subscription status of this folder
|
|
|
|
*
|
|
|
|
* @return bool True if subscribed, false if not
|
|
|
|
*/
|
|
|
|
public function is_subscribed()
|
|
|
|
{
|
2022-11-08 12:34:35 +01:00
|
|
|
return true; // TODO
|
2022-10-06 15:59:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
{
|
2022-11-08 12:34:35 +01:00
|
|
|
return true; // TODO
|
2022-10-06 15:59:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
|
2022-10-17 13:49:59 +02:00
|
|
|
$success = $this->dav->delete($this->object_location($uid));
|
2022-10-06 15:59:53 +02:00
|
|
|
|
|
|
|
if ($success) {
|
|
|
|
$this->cache->set($uid, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $success;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-10-17 13:49:59 +02:00
|
|
|
* Delete all objects in a folder.
|
2022-10-06 15:59:53 +02:00
|
|
|
*
|
2022-10-17 13:49:59 +02:00
|
|
|
* Note: This method is used by kolab_addressbook plugin only
|
|
|
|
*
|
|
|
|
* @return bool True if successful, false on error
|
2022-10-06 15:59:53 +02:00
|
|
|
*/
|
|
|
|
public function delete_all()
|
|
|
|
{
|
|
|
|
if (!$this->valid) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-10-17 13:49:59 +02:00
|
|
|
// TODO: Maybe just deleting and re-creating a folder would be
|
|
|
|
// better, but probably might not always work (ACL)
|
2022-10-06 15:59:53 +02:00
|
|
|
|
2022-10-17 13:49:59 +02:00
|
|
|
$this->cache->synchronize();
|
|
|
|
|
|
|
|
foreach (array_keys($this->cache->folder_index()) as $uid) {
|
|
|
|
$this->dav->delete($this->object_location($uid));
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->cache->purge();
|
|
|
|
|
|
|
|
return true;
|
2022-10-06 15:59:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)) {
|
2022-10-14 16:34:19 +02:00
|
|
|
$method = $uid ? 'update' : 'create';
|
|
|
|
$dav_type = $this->get_dav_type();
|
|
|
|
$result = $this->dav->{$method}($this->object_location($object['uid']), $content, $dav_type);
|
2022-10-06 15:59:53 +02:00
|
|
|
|
2022-10-11 15:27:59 +02:00
|
|
|
// Note: $result can be NULL if the request was successful, but ETag wasn't returned
|
2022-10-06 15:59:53 +02:00
|
|
|
if ($result !== false) {
|
|
|
|
// insert/update object in the cache
|
|
|
|
$object['etag'] = $result;
|
2022-11-28 12:28:04 +01:00
|
|
|
$object['_raw'] = $content;
|
2022-10-06 15:59:53 +02:00
|
|
|
$this->cache->save($object, $uid);
|
2022-10-11 15:27:59 +02:00
|
|
|
$result = true;
|
2022-11-28 12:28:04 +01:00
|
|
|
unset($object['_raw']);
|
2022-10-06 15:59:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2022-10-12 13:36:57 +02:00
|
|
|
* @param string Unused (kept for compat. with the parent class)
|
2022-10-06 15:59:53 +02:00
|
|
|
*
|
|
|
|
* @return mixed Hash array representing the Kolab object, a kolab_format instance or false if not found
|
|
|
|
*/
|
2022-10-12 13:36:57 +02:00
|
|
|
public function read_object($uid, $type = null, $folder = null)
|
2022-10-06 15:59:53 +02:00
|
|
|
{
|
|
|
|
if (!$this->valid) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-10-12 13:36:57 +02:00
|
|
|
$href = $this->object_location($uid);
|
2022-10-11 15:27:59 +02:00
|
|
|
$objects = $this->dav->getData($this->href, $this->get_dav_type(), [$href]);
|
2022-10-06 15:59:53 +02:00
|
|
|
|
|
|
|
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]);
|
|
|
|
}
|
|
|
|
|
2022-11-25 14:24:28 +01:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2022-10-06 15:59:53 +02:00
|
|
|
/**
|
|
|
|
* 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];
|
|
|
|
}
|
2022-10-14 16:34:19 +02:00
|
|
|
else if ($this->type == 'contact') {
|
|
|
|
if (stripos($object['data'], 'BEGIN:VCARD') !== 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-11-10 13:38:48 +01:00
|
|
|
// 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);
|
2022-10-14 16:34:19 +02:00
|
|
|
|
|
|
|
if (!empty($vcard->displayname) || !empty($vcard->surname) || !empty($vcard->firstname) || !empty($vcard->email)) {
|
|
|
|
$result = $vcard->get_assoc();
|
2022-11-10 13:38:48 +01:00
|
|
|
|
|
|
|
// 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']));
|
|
|
|
}
|
2022-10-14 16:34:19 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2022-10-06 15:59:53 +02:00
|
|
|
|
|
|
|
$result['etag'] = $object['etag'];
|
2022-11-28 12:28:04 +01:00
|
|
|
$result['href'] = !empty($object['href']) ? $object['href'] : null;
|
|
|
|
$result['uid'] = !empty($object['uid']) ? $object['uid'] : $result['uid'];
|
2022-10-06 15:59:53 +02:00
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert Kolab object into DAV format (iCalendar)
|
|
|
|
*/
|
|
|
|
public function to_dav($object)
|
|
|
|
{
|
|
|
|
$result = '';
|
|
|
|
|
|
|
|
if ($this->type == 'event') {
|
|
|
|
$ical = libcalendaring::get_ical();
|
2022-10-11 15:27:59 +02:00
|
|
|
if (!empty($object['exceptions'])) {
|
|
|
|
$object['recurrence']['EXCEPTIONS'] = $object['exceptions'];
|
|
|
|
}
|
|
|
|
|
2022-10-06 15:59:53 +02:00
|
|
|
$result = $ical->export([$object]);
|
|
|
|
}
|
2022-10-14 16:34:19 +02:00
|
|
|
else if ($this->type == 'contact') {
|
|
|
|
// copy values into vcard object
|
2022-11-10 13:38:48 +01:00
|
|
|
// 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);
|
2022-10-14 16:34:19 +02:00
|
|
|
|
2022-11-10 13:38:48 +01:00
|
|
|
if ((!empty($object['_type']) && $object['_type'] == 'group')
|
|
|
|
|| (!empty($object['type']) && $object['type'] == 'group')
|
|
|
|
) {
|
|
|
|
$object['kind'] = 'group';
|
|
|
|
}
|
2022-10-14 16:34:19 +02:00
|
|
|
|
|
|
|
foreach ($object as $key => $values) {
|
|
|
|
list($field, $section) = rcube_utils::explode(':', $key);
|
|
|
|
|
|
|
|
// avoid casting DateTime objects to array
|
|
|
|
if (is_object($values) && is_a($values, 'DateTime')) {
|
|
|
|
$values = [$values];
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ((array) $values as $value) {
|
|
|
|
if (isset($value)) {
|
|
|
|
$vcard->set($field, $value, $section);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$result = $vcard->export(false);
|
2022-11-10 13:38:48 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2022-10-14 16:34:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2022-10-06 15:59:53 +02:00
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
2022-11-04 12:08:22 +01:00
|
|
|
return kolab_storage_dav::get_dav_type($this->type);
|
2022-10-06 15:59:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*
|
2022-11-10 13:38:48 +01:00
|
|
|
* @return string Folder display name
|
2022-10-06 15:59:53 +02:00
|
|
|
*/
|
|
|
|
public function __toString()
|
|
|
|
{
|
|
|
|
return $this->attributes['name'];
|
|
|
|
}
|
|
|
|
}
|