CalDAV driver

This commit is contained in:
Aleksander Machniak 2022-10-11 15:27:59 +02:00
parent af5461eb76
commit 5c6a7a2d6f
16 changed files with 377 additions and 52 deletions

View file

@ -3658,9 +3658,10 @@ function rcube_calendar_ui(settings)
});
// register dbl-click handler to open calendar edit dialog
$(rcmail.gui_objects.calendarslist).on('dblclick', ':not(.virtual) > .calname', function(e){
$(rcmail.gui_objects.calendarslist).on('dblclick', ':not(.virtual) > .calname', function(e) {
var id = $(this).closest('li').attr('id').replace(/^rcmlical/, '');
me.calendar_edit_dialog(me.calendars[id]);
if (me.calendars[id] && me.calendars[id].driver != 'caldav')
me.calendar_edit_dialog(me.calendars[id]);
});
// Make Elastic checkboxes pretty

View file

@ -25,7 +25,7 @@
+-------------------------------------------------------------------------+
*/
// backend type (database, kolab)
// backend type (database, kolab, caldav)
$config['calendar_driver'] = "database";
// default calendar view (agendaDay, agendaWeek, month)

View file

@ -158,7 +158,7 @@ class caldav_calendar extends kolab_storage_dav_folder
// directly access storage object
if (empty($this->events[$id]) && $master_id == $id && ($record = $this->storage->get_object($id))) {
$this->events[$id] = $this->_to_driver_event($record, true);
$this->events[$id] = $record = $this->_to_driver_event($record, true);
}
// maybe a recurring instance is requested
@ -166,10 +166,10 @@ class caldav_calendar extends kolab_storage_dav_folder
$instance_id = substr($id, strlen($master_id) + 1);
if ($record = $this->storage->get_object($master_id)) {
$master = $this->_to_driver_event($record);
$master = $record = $this->_to_driver_event($record);
}
if ($master) {
if (!empty($master)) {
// check for match in top-level exceptions (aka loose single occurrences)
if (!empty($master['_formatobj']) && ($instance = $master['_formatobj']->get_instance($instance_id))) {
$this->events[$id] = $this->_to_driver_event($instance, false, true, $master);
@ -695,7 +695,7 @@ class caldav_calendar extends kolab_storage_dav_folder
if ($exception) {
// copy data from exception
colab_driver::merge_exception_data($rec_event, $exception);
caldav_driver::merge_exception_data($rec_event, $exception);
}
$rec_event['id'] = $rec_id;
@ -776,7 +776,7 @@ class caldav_calendar extends kolab_storage_dav_folder
// TODO: Drop dependency on libkolabxml?
$event_xml = new kolab_format_event();
$event_xml->set($record);
$event['_formatobj'] = $event_xml;
$record['_formatobj'] = $event_xml;
return $record;
}

View file

@ -184,6 +184,9 @@ class caldav_driver extends kolab_driver
'active' => $cal->is_active(),
'owner' => $cal->get_owner(),
'removable' => !$cal->default,
// extras to hide some elements in the UI
'subscriptions' => false,
'driver' => 'caldav',
];
if (!$is_user) {
@ -274,7 +277,7 @@ class caldav_driver extends kolab_driver
{
$this->_read_calendars();
// create calendar object if necesary
// create calendar object if necessary
if (empty($this->calendars[$id])) {
if (in_array($id, [self::INVITATIONS_CALENDAR_PENDING, self::INVITATIONS_CALENDAR_DECLINED])) {
return new caldav_invitation_calendar($id, $this->cal);

View file

@ -381,15 +381,17 @@ class calendar_ui
);
}
$content .= html::tag('input', [
'type' => 'checkbox',
'name' => '_cal[]',
'value' => $id,
'checked' => !empty($prop['active']),
'aria-labelledby' => $label_id
])
. html::span('actions', $actions)
. html::span(['class' => 'handle', 'style' => "background-color: #$color"], ' ');
if (!isset($prop['subscriptions']) || $prop['subscriptions'] !== false) {
$content .= html::tag('input', [
'type' => 'checkbox',
'name' => '_cal[]',
'value' => $id,
'checked' => !empty($prop['active']),
'aria-labelledby' => $label_id
])
. html::span('actions', $actions)
. html::span(['class' => 'handle', 'style' => "background-color: #$color"], ' ');
}
}
$content = html::div(join(' ', $classes), $content);

View file

@ -141,9 +141,11 @@
<div id="calendaractions-menu" class="popupmenu">
<h3 id="aria-label-calendaroptions" class="voice"><roundcube:label name="calendar.calendaractions" /></h3>
<ul class="menu listing" role="menu" aria-labelledby="aria-label-calendaroptions">
<roundcube:if condition="env:calendar_driver != 'caldav'" />
<roundcube:button type="link-menuitem" command="calendar-create" label="calendar.addcalendar" class="create disabled" classAct="create active" />
<roundcube:button type="link-menuitem" command="calendar-edit" label="calendar.editcalendar" class="edit disabled" classAct="edit active" />
<roundcube:button type="link-menuitem" command="calendar-delete" label="calendar.deletecalendar" class="delete disabled" classAct="delete active" />
<roundcube:endif />
<roundcube:if condition="env:calendar_driver == 'kolab'" />
<roundcube:button type="link-menuitem" command="calendar-remove" label="calendar.removelist" class="remove disabled" classAct="remove active" />
<roundcube:endif />

View file

@ -48,13 +48,14 @@ class kolab_addressbook extends rcube_plugin
*/
public function init()
{
require_once(dirname(__FILE__) . '/lib/rcube_kolab_contacts.php');
$this->rc = rcube::get_instance();
// load required plugin
$this->require_plugin('libkolab');
$driver = $this->rc->config->get('kolab_addressbook_driver') ?: 'kolab';
require_once(dirname(__FILE__) . '/lib/rcube_' . $driver . '_contacts.php');
// register hooks
$this->add_hook('addressbooks_list', array($this, 'address_sources'));
$this->add_hook('addressbook_get', array($this, 'get_address_book'));

View file

@ -177,11 +177,34 @@ CREATE TABLE `kolab_cache_freebusy` (
INDEX `freebusy_uid2msguid` (`folder_id`,`uid`,`msguid`)
) ROW_FORMAT=DYNAMIC ENGINE=INNODB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `kolab_cache_dav_contact`;
CREATE TABLE `kolab_cache_dav_contact` (
`folder_id` BIGINT UNSIGNED NOT NULL,
`uid` VARCHAR(512) NOT NULL,
`etag` VARCHAR(128) DEFAULT NULL,
`created` DATETIME DEFAULT NULL,
`changed` DATETIME DEFAULT NULL,
`data` LONGTEXT NOT NULL,
`tags` TEXT NOT NULL,
`words` TEXT NOT NULL,
`type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
`name` VARCHAR(255) NOT NULL,
`firstname` VARCHAR(255) NOT NULL,
`surname` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NOT NULL,
CONSTRAINT `fk_kolab_cache_dav_contact_folder` FOREIGN KEY (`folder_id`)
REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY(`folder_id`,`uid`),
INDEX `contact_type` (`folder_id`,`type`)
) ROW_FORMAT=DYNAMIC ENGINE=INNODB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `kolab_cache_dav_event`;
CREATE TABLE `kolab_cache_dav_event` (
`folder_id` BIGINT UNSIGNED NOT NULL,
`uid` VARCHAR(512) NOT NULL,
`etag` VARCHAR(128) DEFAULT NULL,
`created` DATETIME DEFAULT NULL,
`changed` DATETIME DEFAULT NULL,
`data` LONGTEXT NOT NULL,

View file

@ -0,0 +1,39 @@
DROP TABLE IF EXISTS `kolab_cache_dav_contact`;
CREATE TABLE `kolab_cache_dav_contact` (
`folder_id` BIGINT UNSIGNED NOT NULL,
`uid` VARCHAR(512) NOT NULL,
`etag` VARCHAR(128) DEFAULT NULL,
`created` DATETIME DEFAULT NULL,
`changed` DATETIME DEFAULT NULL,
`data` LONGTEXT NOT NULL,
`tags` TEXT NOT NULL,
`words` TEXT NOT NULL,
`type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
`name` VARCHAR(255) NOT NULL,
`firstname` VARCHAR(255) NOT NULL,
`surname` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NOT NULL,
CONSTRAINT `fk_kolab_cache_dav_contact_folder` FOREIGN KEY (`folder_id`)
REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY(`folder_id`,`uid`),
INDEX `contact_type` (`folder_id`,`type`)
) ROW_FORMAT=DYNAMIC ENGINE=INNODB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `kolab_cache_dav_event`;
CREATE TABLE `kolab_cache_dav_event` (
`folder_id` BIGINT UNSIGNED NOT NULL,
`uid` VARCHAR(512) NOT NULL,
`etag` VARCHAR(128) DEFAULT NULL,
`created` DATETIME DEFAULT NULL,
`changed` DATETIME DEFAULT NULL,
`data` LONGTEXT NOT NULL,
`tags` TEXT NOT NULL,
`words` TEXT NOT NULL,
`dtstart` DATETIME,
`dtend` DATETIME,
CONSTRAINT `fk_kolab_cache_dav_event_folder` FOREIGN KEY (`folder_id`)
REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY(`folder_id`,`uid`)
) ROW_FORMAT=DYNAMIC ENGINE=INNODB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

View file

@ -176,7 +176,7 @@ class kolab_dav_client
}
/**
* Create DAV object in a folder
* Create a DAV object in a folder
*/
public function create($location, $content)
{
@ -196,7 +196,15 @@ class kolab_dav_client
}
/**
* Delete DAV object from a folder
* Update a DAV object in a folder
*/
public function update($location, $content)
{
return $this->create($location, $content);
}
/**
* Delete a DAV object from a folder
*/
public function delete($location)
{
@ -210,17 +218,32 @@ class kolab_dav_client
*/
public function getIndex($location, $component = 'VEVENT')
{
$queries = [
'VEVENT' => 'calendar-query',
'VTODO' => 'calendar-query',
'VCARD' => 'addressbook-query',
];
$ns = [
'VEVENT' => 'caldav',
'VTODO' => 'caldav',
'VCARD' => 'carddav',
];
$filter = '';
if ($component != 'VCARD') {
$filter = '<c:comp-filter name="VCALENDAR">'
. '<c:comp-filter name="' . $component . '" />'
. '</c:comp-filter>';
}
$body = '<?xml version="1.0" encoding="utf-8"?>'
.' <c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">'
.' <c:' . $queries[$component] . ' xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:' . $ns[$component]. '">'
. '<d:prop>'
. '<d:getetag />'
. '</d:prop>'
. '<c:filter>'
. '<c:comp-filter name="VCALENDAR">'
. '<c:comp-filter name="' . $component . '" />'
. '</c:comp-filter>'
. '</c:filter>'
. '</c:calendar-query>';
. ($filter ? "<c:filter>$filter</c:filter>" : '')
. '</c:' . $queries[$component] . '>';
$response = $this->request($location, 'REPORT', $body, ['Depth' => 1, 'Prefer' => 'return-minimal']);
@ -240,7 +263,7 @@ class kolab_dav_client
/**
* Fetch DAV objects data from a folder
*/
public function getData($location, $hrefs = [])
public function getData($location, $component = 'VEVENT', $hrefs = [])
{
if (empty($hrefs)) {
return [];
@ -251,14 +274,32 @@ class kolab_dav_client
$body .= '<d:href>' . $href . '</d:href>';
}
$queries = [
'VEVENT' => 'calendar-multiget',
'VTODO' => 'calendar-multiget',
'VCARD' => 'addressbook-multiget',
];
$ns = [
'VEVENT' => 'caldav',
'VTODO' => 'caldav',
'VCARD' => 'carddav',
];
$types = [
'VEVENT' => 'calendar-data',
'VTODO' => 'calendar-data',
'VCARD' => 'address-data',
];
$body = '<?xml version="1.0" encoding="utf-8"?>'
.' <c:calendar-multiget xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">'
.' <c:' . $queries[$component] . ' xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:' . $ns[$component] . '">'
. '<d:prop>'
. '<d:getetag />'
. '<c:calendar-data />'
. '<c:' . $types[$component]. ' />'
. '</d:prop>'
. $body
. '</c:calendar-multiget>';
. '</c:' . $queries[$component] . '>';
$response = $this->request($location, 'REPORT', $body, ['Depth' => 1, 'Prefer' => 'return-minimal']);
@ -388,6 +429,9 @@ class kolab_dav_client
if ($data = $element->getElementsByTagName('calendar-data')->item(0)) {
$data = $data->nodeValue;
}
else if ($data = $element->getElementsByTagName('address-data')->item(0)) {
$data = $data->nodeValue;
}
if ($etag = $element->getElementsByTagName('getetag')->item(0)) {
$etag = $etag->nodeValue;

View file

@ -944,7 +944,7 @@ class kolab_storage
* Wrapper for rcube_imap::list_folders_subscribed()
* with support for temporarily subscribed folders
*/
protected static function _imap_list_subscribed($root, $mbox)
protected static function _imap_list_subscribed($root, $mbox, $filter = null)
{
$folders = self::$imap->list_folders_subscribed($root, $mbox);

View file

@ -61,8 +61,14 @@ class kolab_storage_dav
*/
public function get_folders($type)
{
$davTypes = [
'event' => 'VEVENT',
'task' => 'VTODO',
'contact' => 'VCARD',
];
// TODO: This should be cached
$folders = $this->dav->discover();
$folders = $this->dav->discover($davTypes[$type]);
if (is_array($folders)) {
foreach ($folders as $idx => $folder) {
@ -88,7 +94,7 @@ class kolab_storage_dav
/**
* Getter for a specific storage folder
*
* @param string Folder to access (UTF7-IMAP)
* @param string Folder to access
* @param string Expected folder type
*
* @return object kolab_storage_folder The folder object

View file

@ -233,18 +233,14 @@ class kolab_storage_dav_cache extends kolab_storage_cache
{
// read cache index
$sql_result = $this->db->query(
"SELECT `uid`, `data` FROM `{$this->cache_table}` WHERE `folder_id` = ?",
"SELECT `uid`, `etag` FROM `{$this->cache_table}` WHERE `folder_id` = ?",
$this->folder_id
);
$index = [];
// TODO: Store etag as a separate column
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
if ($object = json_decode($sql_arr['data'], true)) {
$index[$sql_arr['uid']] = $object['etag'];
}
$index[$sql_arr['uid']] = $sql_arr['etag'];
}
return $index;
@ -321,9 +317,10 @@ class kolab_storage_dav_cache extends kolab_storage_cache
$sql_data = $this->_serialize($object);
$sql_data['folder_id'] = $this->folder_id;
$sql_data['uid'] = $object['uid'];
$sql_data['etag'] = $object['etag'];
$args = [];
$cols = ['folder_id', 'uid', 'changed', 'data', 'tags', 'words'];
$cols = ['folder_id', 'uid', 'etag', 'changed', 'data', 'tags', 'words'];
$cols = array_merge($cols, $this->extra_cols);
foreach ($cols as $idx => $col) {
@ -510,7 +507,7 @@ class kolab_storage_dav_cache extends kolab_storage_cache
static $buffer = '';
$line = '';
$cols = ['folder_id', 'uid', 'created', 'changed', 'data', 'tags', 'words'];
$cols = ['folder_id', 'uid', 'etag', 'created', 'changed', 'data', 'tags', 'words'];
if ($this->extra_cols) {
$cols = array_merge($cols, $this->extra_cols);
}
@ -522,7 +519,7 @@ class kolab_storage_dav_cache extends kolab_storage_cache
// In Oracle we can't put long data inline, others we don't support yet
if (strpos($this->db->db_provider, 'mysql') !== 0) {
$extra_args = [];
$params = [$this->folder_id, $object['uid'], $sql_data['changed'],
$params = [$this->folder_id, $object['uid'], $object['etag'], $sql_data['changed'],
$sql_data['data'], $sql_data['tags'], $sql_data['words']];
foreach ($this->extra_cols as $col) {
@ -551,6 +548,7 @@ class kolab_storage_dav_cache extends kolab_storage_cache
$values = array(
$this->db->quote($this->folder_id),
$this->db->quote($object['uid']),
$this->db->quote($object['etag']),
$this->db->now(),
$this->db->quote($sql_data['changed']),
$this->db->quote($sql_data['data']),
@ -590,8 +588,6 @@ class kolab_storage_dav_cache extends kolab_storage_cache
protected function _unserialize($sql_arr)
{
if ($sql_arr['fast-mode'] && !empty($sql_arr['data']) && ($object = json_decode($sql_arr['data'], true))) {
$object['uid'] = $sql_arr['uid'];
foreach ($this->data_props as $prop) {
if (isset($object[$prop]) && is_array($object[$prop]) && $object[$prop]['cl'] == 'DateTime') {
$object[$prop] = new DateTime($object[$prop]['dt'], new DateTimeZone($object[$prop]['tz']));
@ -610,6 +606,8 @@ class kolab_storage_dav_cache extends kolab_storage_cache
}
$object['_type'] = $sql_arr['type'] ?: $this->folder->type;
$object['uid'] = $sql_arr['uid'];
$object['etag'] = $sql_arr['etag'];
}
// Fetch a complete object from the server
else {

View file

@ -0,0 +1,116 @@
<?php
/**
* Kolab storage cache class for contact objects
*
* @author Aleksander Machniak <machniak@apcheleia-it.ch>
*
* Copyright (C) 2013-2022, Apheleia IT AG <contact@apcheleia-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_cache_contact extends kolab_storage_cache
{
protected $extra_cols_max = 255;
protected $extra_cols = ['type', 'name', 'firstname', 'surname', 'email'];
protected $data_props = ['type', 'name', 'firstname', 'middlename', 'prefix', 'suffix', 'surname', 'email', 'organization', 'member'];
protected $fulltext_cols = ['name', 'firstname', 'surname', 'middlename', 'email:address'];
/**
* Helper method to convert the given Kolab object into a dataset to be written to cache
*
* @override
*/
protected function _serialize($object)
{
$sql_data = parent::_serialize($object);
$sql_data['type'] = $object['_type'];
// columns for sorting
$sql_data['name'] = rcube_charset::clean($object['name'] . $object['prefix']);
$sql_data['firstname'] = rcube_charset::clean($object['firstname'] . $object['middlename'] . $object['surname']);
$sql_data['surname'] = rcube_charset::clean($object['surname'] . $object['firstname'] . $object['middlename']);
$sql_data['email'] = rcube_charset::clean(is_array($object['email']) ? $object['email'][0] : $object['email']);
if (is_array($sql_data['email'])) {
$sql_data['email'] = $sql_data['email']['address'];
}
// avoid value being null
if (empty($sql_data['email'])) {
$sql_data['email'] = '';
}
// use organization if name is empty
if (empty($sql_data['name']) && !empty($object['organization'])) {
$sql_data['name'] = rcube_charset::clean($object['organization']);
}
// make sure some data is not longer that database limit (#5291)
foreach ($this->extra_cols as $col) {
if (strlen($sql_data[$col]) > $this->extra_cols_max) {
$sql_data[$col] = rcube_charset::clean(substr($sql_data[$col], 0, $this->extra_cols_max));
}
}
$sql_data['tags'] = ' ' . join(' ', $this->get_tags($object)) . ' '; // pad with spaces for strict/prefix search
$sql_data['words'] = ' ' . join(' ', $this->get_words($object)) . ' ';
return $sql_data;
}
/**
* Callback to get words to index for fulltext search
*
* @return array List of words to save in cache
*/
public function get_words($object)
{
$data = '';
foreach ($this->fulltext_cols as $colname) {
list($col, $field) = explode(':', $colname);
if ($field) {
$a = [];
foreach ((array)$object[$col] as $attr)
$a[] = $attr[$field];
$val = join(' ', $a);
}
else {
$val = is_array($object[$col]) ? join(' ', $object[$col]) : $object[$col];
}
if (strlen($val))
$data .= $val . ' ';
}
return array_unique(rcube_utils::normalize_string($data, true));
}
/**
* Callback to get object specific tags to cache
*
* @return array List of tags to save in cache
*/
public function get_tags($object)
{
$tags = [];
if (!empty($object['birthday'])) {
$tags[] = 'x-has-birthday';
}
return $tags;
}
}

View file

@ -23,8 +23,9 @@
class kolab_storage_dav_cache_event extends kolab_storage_dav_cache
{
protected $extra_cols = array('dtstart','dtend');
protected $data_props = array('categories', 'status', 'attendees', 'etag');
protected $extra_cols = ['dtstart','dtend'];
protected $data_props = ['categories', 'status', 'attendees'];
protected $fulltext_cols = ['title', 'description', 'location', 'attendees:name', 'attendees:email', 'categories'];
/**
* Helper method to convert the given Kolab object into a dataset to be written to cache
@ -63,6 +64,83 @@ class kolab_storage_dav_cache_event extends kolab_storage_dav_cache
}
}
$sql_data['tags'] = ' ' . join(' ', $this->get_tags($object)) . ' '; // pad with spaces for strict/prefix search
$sql_data['words'] = ' ' . join(' ', $this->get_words($object)) . ' ';
return $sql_data;
}
/**
* Callback to get words to index for fulltext search
*
* @return array List of words to save in cache
*/
public function get_words($object = [])
{
$data = '';
foreach ($this->fulltext_cols as $colname) {
list($col, $field) = explode(':', $colname);
if ($field) {
$a = [];
foreach ((array) $object[$col] as $attr) {
$a[] = $attr[$field];
}
$val = join(' ', $a);
}
else {
$val = is_array($object[$col]) ? join(' ', $object[$col]) : $object[$col];
}
if (strlen($val))
$data .= $val . ' ';
}
$words = rcube_utils::normalize_string($data, true);
// collect words from recurrence exceptions
if (is_array($object['exceptions'])) {
foreach ($object['exceptions'] as $exception) {
$words = array_merge($words, $this->get_words($exception));
}
}
return array_unique($words);
}
/**
* Callback to get object specific tags to cache
*
* @return array List of tags to save in cache
*/
public function get_tags($object)
{
$tags = [];
if (!empty($object['valarms'])) {
$tags[] = 'x-has-alarms';
}
// create tags reflecting participant status
if (is_array($object['attendees'])) {
foreach ($object['attendees'] as $attendee) {
if (!empty($attendee['email']) && !empty($attendee['status']))
$tags[] = 'x-partstat:' . $attendee['email'] . ':' . strtolower($attendee['status']);
}
}
// collect tags from recurrence exceptions
if (is_array($object['exceptions'])) {
foreach ($object['exceptions'] as $exception) {
$tags = array_merge($tags, $this->get_tags($exception));
}
}
if (!empty($object['status'])) {
$tags[] = 'x-status:' . strtolower($object['status']);
}
return array_unique($tags);
}
}

View file

@ -111,6 +111,11 @@ class kolab_storage_dav_folder extends kolab_storage_folder
return $this->attributes['name'];
}
public function get_folder_info()
{
return []; // todo ?
}
/**
* Getter for parent folder path
*
@ -400,12 +405,15 @@ class kolab_storage_dav_folder extends kolab_storage_folder
// generate and save object message
if ($content = $this->to_dav($object)) {
$result = $this->dav->create($this->object_location($object['uid']), $content);
$method = $uid ? 'update' : 'create';
$result = $this->dav->{$method}($this->object_location($object['uid']), $content);
// 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;
$this->cache->save($object, $uid);
$result = true;
}
}
@ -427,7 +435,7 @@ class kolab_storage_dav_folder extends kolab_storage_folder
}
$href = $this->object_location($uid);
$objects = $this->dav->getData($this->href, [$href]);
$objects = $this->dav->getData($this->href, $this->get_dav_type(), [$href]);
if (!is_array($objects) || count($objects) != 1) {
rcube::raise_error([
@ -476,7 +484,11 @@ class kolab_storage_dav_folder extends kolab_storage_folder
if ($this->type == 'event') {
$ical = libcalendaring::get_ical();
// TODO: Attachments?
if (!empty($object['exceptions'])) {
$object['recurrence']['EXCEPTIONS'] = $object['exceptions'];
}
$result = $ical->export([$object]);
}