CalDAV driver
This commit is contained in:
parent
af5461eb76
commit
5c6a7a2d6f
16 changed files with 377 additions and 52 deletions
|
@ -3658,9 +3658,10 @@ function rcube_calendar_ui(settings)
|
||||||
});
|
});
|
||||||
|
|
||||||
// register dbl-click handler to open calendar edit dialog
|
// 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/, '');
|
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
|
// Make Elastic checkboxes pretty
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
+-------------------------------------------------------------------------+
|
+-------------------------------------------------------------------------+
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// backend type (database, kolab)
|
// backend type (database, kolab, caldav)
|
||||||
$config['calendar_driver'] = "database";
|
$config['calendar_driver'] = "database";
|
||||||
|
|
||||||
// default calendar view (agendaDay, agendaWeek, month)
|
// default calendar view (agendaDay, agendaWeek, month)
|
||||||
|
|
|
@ -158,7 +158,7 @@ class caldav_calendar extends kolab_storage_dav_folder
|
||||||
|
|
||||||
// directly access storage object
|
// directly access storage object
|
||||||
if (empty($this->events[$id]) && $master_id == $id && ($record = $this->storage->get_object($id))) {
|
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
|
// 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);
|
$instance_id = substr($id, strlen($master_id) + 1);
|
||||||
|
|
||||||
if ($record = $this->storage->get_object($master_id)) {
|
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)
|
// check for match in top-level exceptions (aka loose single occurrences)
|
||||||
if (!empty($master['_formatobj']) && ($instance = $master['_formatobj']->get_instance($instance_id))) {
|
if (!empty($master['_formatobj']) && ($instance = $master['_formatobj']->get_instance($instance_id))) {
|
||||||
$this->events[$id] = $this->_to_driver_event($instance, false, true, $master);
|
$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) {
|
if ($exception) {
|
||||||
// copy data from 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;
|
$rec_event['id'] = $rec_id;
|
||||||
|
@ -776,7 +776,7 @@ class caldav_calendar extends kolab_storage_dav_folder
|
||||||
// TODO: Drop dependency on libkolabxml?
|
// TODO: Drop dependency on libkolabxml?
|
||||||
$event_xml = new kolab_format_event();
|
$event_xml = new kolab_format_event();
|
||||||
$event_xml->set($record);
|
$event_xml->set($record);
|
||||||
$event['_formatobj'] = $event_xml;
|
$record['_formatobj'] = $event_xml;
|
||||||
|
|
||||||
return $record;
|
return $record;
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,6 +184,9 @@ class caldav_driver extends kolab_driver
|
||||||
'active' => $cal->is_active(),
|
'active' => $cal->is_active(),
|
||||||
'owner' => $cal->get_owner(),
|
'owner' => $cal->get_owner(),
|
||||||
'removable' => !$cal->default,
|
'removable' => !$cal->default,
|
||||||
|
// extras to hide some elements in the UI
|
||||||
|
'subscriptions' => false,
|
||||||
|
'driver' => 'caldav',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!$is_user) {
|
if (!$is_user) {
|
||||||
|
@ -274,7 +277,7 @@ class caldav_driver extends kolab_driver
|
||||||
{
|
{
|
||||||
$this->_read_calendars();
|
$this->_read_calendars();
|
||||||
|
|
||||||
// create calendar object if necesary
|
// create calendar object if necessary
|
||||||
if (empty($this->calendars[$id])) {
|
if (empty($this->calendars[$id])) {
|
||||||
if (in_array($id, [self::INVITATIONS_CALENDAR_PENDING, self::INVITATIONS_CALENDAR_DECLINED])) {
|
if (in_array($id, [self::INVITATIONS_CALENDAR_PENDING, self::INVITATIONS_CALENDAR_DECLINED])) {
|
||||||
return new caldav_invitation_calendar($id, $this->cal);
|
return new caldav_invitation_calendar($id, $this->cal);
|
||||||
|
|
|
@ -381,15 +381,17 @@ class calendar_ui
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$content .= html::tag('input', [
|
if (!isset($prop['subscriptions']) || $prop['subscriptions'] !== false) {
|
||||||
'type' => 'checkbox',
|
$content .= html::tag('input', [
|
||||||
'name' => '_cal[]',
|
'type' => 'checkbox',
|
||||||
'value' => $id,
|
'name' => '_cal[]',
|
||||||
'checked' => !empty($prop['active']),
|
'value' => $id,
|
||||||
'aria-labelledby' => $label_id
|
'checked' => !empty($prop['active']),
|
||||||
])
|
'aria-labelledby' => $label_id
|
||||||
. html::span('actions', $actions)
|
])
|
||||||
. html::span(['class' => 'handle', 'style' => "background-color: #$color"], ' ');
|
. html::span('actions', $actions)
|
||||||
|
. html::span(['class' => 'handle', 'style' => "background-color: #$color"], ' ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$content = html::div(join(' ', $classes), $content);
|
$content = html::div(join(' ', $classes), $content);
|
||||||
|
|
|
@ -141,9 +141,11 @@
|
||||||
<div id="calendaractions-menu" class="popupmenu">
|
<div id="calendaractions-menu" class="popupmenu">
|
||||||
<h3 id="aria-label-calendaroptions" class="voice"><roundcube:label name="calendar.calendaractions" /></h3>
|
<h3 id="aria-label-calendaroptions" class="voice"><roundcube:label name="calendar.calendaractions" /></h3>
|
||||||
<ul class="menu listing" role="menu" aria-labelledby="aria-label-calendaroptions">
|
<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-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-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: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:if condition="env:calendar_driver == 'kolab'" />
|
||||||
<roundcube:button type="link-menuitem" command="calendar-remove" label="calendar.removelist" class="remove disabled" classAct="remove active" />
|
<roundcube:button type="link-menuitem" command="calendar-remove" label="calendar.removelist" class="remove disabled" classAct="remove active" />
|
||||||
<roundcube:endif />
|
<roundcube:endif />
|
||||||
|
|
|
@ -48,13 +48,14 @@ class kolab_addressbook extends rcube_plugin
|
||||||
*/
|
*/
|
||||||
public function init()
|
public function init()
|
||||||
{
|
{
|
||||||
require_once(dirname(__FILE__) . '/lib/rcube_kolab_contacts.php');
|
|
||||||
|
|
||||||
$this->rc = rcube::get_instance();
|
$this->rc = rcube::get_instance();
|
||||||
|
|
||||||
// load required plugin
|
// load required plugin
|
||||||
$this->require_plugin('libkolab');
|
$this->require_plugin('libkolab');
|
||||||
|
|
||||||
|
$driver = $this->rc->config->get('kolab_addressbook_driver') ?: 'kolab';
|
||||||
|
require_once(dirname(__FILE__) . '/lib/rcube_' . $driver . '_contacts.php');
|
||||||
|
|
||||||
// register hooks
|
// register hooks
|
||||||
$this->add_hook('addressbooks_list', array($this, 'address_sources'));
|
$this->add_hook('addressbooks_list', array($this, 'address_sources'));
|
||||||
$this->add_hook('addressbook_get', array($this, 'get_address_book'));
|
$this->add_hook('addressbook_get', array($this, 'get_address_book'));
|
||||||
|
|
|
@ -177,11 +177,34 @@ CREATE TABLE `kolab_cache_freebusy` (
|
||||||
INDEX `freebusy_uid2msguid` (`folder_id`,`uid`,`msguid`)
|
INDEX `freebusy_uid2msguid` (`folder_id`,`uid`,`msguid`)
|
||||||
) ROW_FORMAT=DYNAMIC ENGINE=INNODB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
) 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`;
|
DROP TABLE IF EXISTS `kolab_cache_dav_event`;
|
||||||
|
|
||||||
CREATE TABLE `kolab_cache_dav_event` (
|
CREATE TABLE `kolab_cache_dav_event` (
|
||||||
`folder_id` BIGINT UNSIGNED NOT NULL,
|
`folder_id` BIGINT UNSIGNED NOT NULL,
|
||||||
`uid` VARCHAR(512) NOT NULL,
|
`uid` VARCHAR(512) NOT NULL,
|
||||||
|
`etag` VARCHAR(128) DEFAULT NULL,
|
||||||
`created` DATETIME DEFAULT NULL,
|
`created` DATETIME DEFAULT NULL,
|
||||||
`changed` DATETIME DEFAULT NULL,
|
`changed` DATETIME DEFAULT NULL,
|
||||||
`data` LONGTEXT NOT NULL,
|
`data` LONGTEXT NOT NULL,
|
||||||
|
|
39
plugins/libkolab/SQL/mysql/2022100500.sql
Normal file
39
plugins/libkolab/SQL/mysql/2022100500.sql
Normal 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;
|
|
@ -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)
|
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)
|
public function delete($location)
|
||||||
{
|
{
|
||||||
|
@ -210,17 +218,32 @@ class kolab_dav_client
|
||||||
*/
|
*/
|
||||||
public function getIndex($location, $component = 'VEVENT')
|
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"?>'
|
$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:prop>'
|
||||||
. '<d:getetag />'
|
. '<d:getetag />'
|
||||||
. '</d:prop>'
|
. '</d:prop>'
|
||||||
. '<c:filter>'
|
. ($filter ? "<c:filter>$filter</c:filter>" : '')
|
||||||
. '<c:comp-filter name="VCALENDAR">'
|
. '</c:' . $queries[$component] . '>';
|
||||||
. '<c:comp-filter name="' . $component . '" />'
|
|
||||||
. '</c:comp-filter>'
|
|
||||||
. '</c:filter>'
|
|
||||||
. '</c:calendar-query>';
|
|
||||||
|
|
||||||
$response = $this->request($location, 'REPORT', $body, ['Depth' => 1, 'Prefer' => 'return-minimal']);
|
$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
|
* Fetch DAV objects data from a folder
|
||||||
*/
|
*/
|
||||||
public function getData($location, $hrefs = [])
|
public function getData($location, $component = 'VEVENT', $hrefs = [])
|
||||||
{
|
{
|
||||||
if (empty($hrefs)) {
|
if (empty($hrefs)) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -251,14 +274,32 @@ class kolab_dav_client
|
||||||
$body .= '<d:href>' . $href . '</d:href>';
|
$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"?>'
|
$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:prop>'
|
||||||
. '<d:getetag />'
|
. '<d:getetag />'
|
||||||
. '<c:calendar-data />'
|
. '<c:' . $types[$component]. ' />'
|
||||||
. '</d:prop>'
|
. '</d:prop>'
|
||||||
. $body
|
. $body
|
||||||
. '</c:calendar-multiget>';
|
. '</c:' . $queries[$component] . '>';
|
||||||
|
|
||||||
$response = $this->request($location, 'REPORT', $body, ['Depth' => 1, 'Prefer' => 'return-minimal']);
|
$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)) {
|
if ($data = $element->getElementsByTagName('calendar-data')->item(0)) {
|
||||||
$data = $data->nodeValue;
|
$data = $data->nodeValue;
|
||||||
}
|
}
|
||||||
|
else if ($data = $element->getElementsByTagName('address-data')->item(0)) {
|
||||||
|
$data = $data->nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($etag = $element->getElementsByTagName('getetag')->item(0)) {
|
if ($etag = $element->getElementsByTagName('getetag')->item(0)) {
|
||||||
$etag = $etag->nodeValue;
|
$etag = $etag->nodeValue;
|
||||||
|
|
|
@ -944,7 +944,7 @@ class kolab_storage
|
||||||
* Wrapper for rcube_imap::list_folders_subscribed()
|
* Wrapper for rcube_imap::list_folders_subscribed()
|
||||||
* with support for temporarily subscribed folders
|
* 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);
|
$folders = self::$imap->list_folders_subscribed($root, $mbox);
|
||||||
|
|
||||||
|
|
|
@ -61,8 +61,14 @@ class kolab_storage_dav
|
||||||
*/
|
*/
|
||||||
public function get_folders($type)
|
public function get_folders($type)
|
||||||
{
|
{
|
||||||
|
$davTypes = [
|
||||||
|
'event' => 'VEVENT',
|
||||||
|
'task' => 'VTODO',
|
||||||
|
'contact' => 'VCARD',
|
||||||
|
];
|
||||||
|
|
||||||
// TODO: This should be cached
|
// TODO: This should be cached
|
||||||
$folders = $this->dav->discover();
|
$folders = $this->dav->discover($davTypes[$type]);
|
||||||
|
|
||||||
if (is_array($folders)) {
|
if (is_array($folders)) {
|
||||||
foreach ($folders as $idx => $folder) {
|
foreach ($folders as $idx => $folder) {
|
||||||
|
@ -88,7 +94,7 @@ class kolab_storage_dav
|
||||||
/**
|
/**
|
||||||
* Getter for a specific storage folder
|
* Getter for a specific storage folder
|
||||||
*
|
*
|
||||||
* @param string Folder to access (UTF7-IMAP)
|
* @param string Folder to access
|
||||||
* @param string Expected folder type
|
* @param string Expected folder type
|
||||||
*
|
*
|
||||||
* @return object kolab_storage_folder The folder object
|
* @return object kolab_storage_folder The folder object
|
||||||
|
|
|
@ -233,18 +233,14 @@ class kolab_storage_dav_cache extends kolab_storage_cache
|
||||||
{
|
{
|
||||||
// read cache index
|
// read cache index
|
||||||
$sql_result = $this->db->query(
|
$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
|
$this->folder_id
|
||||||
);
|
);
|
||||||
|
|
||||||
$index = [];
|
$index = [];
|
||||||
|
|
||||||
// TODO: Store etag as a separate column
|
|
||||||
|
|
||||||
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
||||||
if ($object = json_decode($sql_arr['data'], true)) {
|
$index[$sql_arr['uid']] = $sql_arr['etag'];
|
||||||
$index[$sql_arr['uid']] = $object['etag'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $index;
|
return $index;
|
||||||
|
@ -321,9 +317,10 @@ class kolab_storage_dav_cache extends kolab_storage_cache
|
||||||
$sql_data = $this->_serialize($object);
|
$sql_data = $this->_serialize($object);
|
||||||
$sql_data['folder_id'] = $this->folder_id;
|
$sql_data['folder_id'] = $this->folder_id;
|
||||||
$sql_data['uid'] = $object['uid'];
|
$sql_data['uid'] = $object['uid'];
|
||||||
|
$sql_data['etag'] = $object['etag'];
|
||||||
|
|
||||||
$args = [];
|
$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);
|
$cols = array_merge($cols, $this->extra_cols);
|
||||||
|
|
||||||
foreach ($cols as $idx => $col) {
|
foreach ($cols as $idx => $col) {
|
||||||
|
@ -510,7 +507,7 @@ class kolab_storage_dav_cache extends kolab_storage_cache
|
||||||
static $buffer = '';
|
static $buffer = '';
|
||||||
|
|
||||||
$line = '';
|
$line = '';
|
||||||
$cols = ['folder_id', 'uid', 'created', 'changed', 'data', 'tags', 'words'];
|
$cols = ['folder_id', 'uid', 'etag', 'created', 'changed', 'data', 'tags', 'words'];
|
||||||
if ($this->extra_cols) {
|
if ($this->extra_cols) {
|
||||||
$cols = array_merge($cols, $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
|
// In Oracle we can't put long data inline, others we don't support yet
|
||||||
if (strpos($this->db->db_provider, 'mysql') !== 0) {
|
if (strpos($this->db->db_provider, 'mysql') !== 0) {
|
||||||
$extra_args = [];
|
$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']];
|
$sql_data['data'], $sql_data['tags'], $sql_data['words']];
|
||||||
|
|
||||||
foreach ($this->extra_cols as $col) {
|
foreach ($this->extra_cols as $col) {
|
||||||
|
@ -551,6 +548,7 @@ class kolab_storage_dav_cache extends kolab_storage_cache
|
||||||
$values = array(
|
$values = array(
|
||||||
$this->db->quote($this->folder_id),
|
$this->db->quote($this->folder_id),
|
||||||
$this->db->quote($object['uid']),
|
$this->db->quote($object['uid']),
|
||||||
|
$this->db->quote($object['etag']),
|
||||||
$this->db->now(),
|
$this->db->now(),
|
||||||
$this->db->quote($sql_data['changed']),
|
$this->db->quote($sql_data['changed']),
|
||||||
$this->db->quote($sql_data['data']),
|
$this->db->quote($sql_data['data']),
|
||||||
|
@ -590,8 +588,6 @@ class kolab_storage_dav_cache extends kolab_storage_cache
|
||||||
protected function _unserialize($sql_arr)
|
protected function _unserialize($sql_arr)
|
||||||
{
|
{
|
||||||
if ($sql_arr['fast-mode'] && !empty($sql_arr['data']) && ($object = json_decode($sql_arr['data'], true))) {
|
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) {
|
foreach ($this->data_props as $prop) {
|
||||||
if (isset($object[$prop]) && is_array($object[$prop]) && $object[$prop]['cl'] == 'DateTime') {
|
if (isset($object[$prop]) && is_array($object[$prop]) && $object[$prop]['cl'] == 'DateTime') {
|
||||||
$object[$prop] = new DateTime($object[$prop]['dt'], new DateTimeZone($object[$prop]['tz']));
|
$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['_type'] = $sql_arr['type'] ?: $this->folder->type;
|
||||||
|
$object['uid'] = $sql_arr['uid'];
|
||||||
|
$object['etag'] = $sql_arr['etag'];
|
||||||
}
|
}
|
||||||
// Fetch a complete object from the server
|
// Fetch a complete object from the server
|
||||||
else {
|
else {
|
||||||
|
|
116
plugins/libkolab/lib/kolab_storage_dav_cache_contact.php
Normal file
116
plugins/libkolab/lib/kolab_storage_dav_cache_contact.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,8 +23,9 @@
|
||||||
|
|
||||||
class kolab_storage_dav_cache_event extends kolab_storage_dav_cache
|
class kolab_storage_dav_cache_event extends kolab_storage_dav_cache
|
||||||
{
|
{
|
||||||
protected $extra_cols = array('dtstart','dtend');
|
protected $extra_cols = ['dtstart','dtend'];
|
||||||
protected $data_props = array('categories', 'status', 'attendees', 'etag');
|
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
|
* 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;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,11 @@ class kolab_storage_dav_folder extends kolab_storage_folder
|
||||||
return $this->attributes['name'];
|
return $this->attributes['name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_folder_info()
|
||||||
|
{
|
||||||
|
return []; // todo ?
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Getter for parent folder path
|
* Getter for parent folder path
|
||||||
*
|
*
|
||||||
|
@ -400,12 +405,15 @@ class kolab_storage_dav_folder extends kolab_storage_folder
|
||||||
|
|
||||||
// generate and save object message
|
// generate and save object message
|
||||||
if ($content = $this->to_dav($object)) {
|
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) {
|
if ($result !== false) {
|
||||||
// insert/update object in the cache
|
// insert/update object in the cache
|
||||||
$object['etag'] = $result;
|
$object['etag'] = $result;
|
||||||
$this->cache->save($object, $uid);
|
$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);
|
$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) {
|
if (!is_array($objects) || count($objects) != 1) {
|
||||||
rcube::raise_error([
|
rcube::raise_error([
|
||||||
|
@ -476,7 +484,11 @@ class kolab_storage_dav_folder extends kolab_storage_folder
|
||||||
|
|
||||||
if ($this->type == 'event') {
|
if ($this->type == 'event') {
|
||||||
$ical = libcalendaring::get_ical();
|
$ical = libcalendaring::get_ical();
|
||||||
// TODO: Attachments?
|
|
||||||
|
if (!empty($object['exceptions'])) {
|
||||||
|
$object['recurrence']['EXCEPTIONS'] = $object['exceptions'];
|
||||||
|
}
|
||||||
|
|
||||||
$result = $ical->export([$object]);
|
$result = $ical->export([$object]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue