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
|
||||
$(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
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
// backend type (database, kolab)
|
||||
// backend type (database, kolab, caldav)
|
||||
$config['calendar_driver'] = "database";
|
||||
|
||||
// default calendar view (agendaDay, agendaWeek, month)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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,
|
||||
|
|
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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue