Page MenuHomePhorge

No OneTemporary

diff --git a/plugins/calendar/drivers/caldav/caldav_driver.php b/plugins/calendar/drivers/caldav/caldav_driver.php
index 6e40c5c9..6b749225 100644
--- a/plugins/calendar/drivers/caldav/caldav_driver.php
+++ b/plugins/calendar/drivers/caldav/caldav_driver.php
@@ -1,530 +1,530 @@
<?php
/**
* CalDAV driver for the Calendar plugin.
*
* @author Aleksander Machniak <machniak@apheleia-it.ch>
*
* Copyright (C) 2012-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/>.
*/
require_once(__DIR__ . '/../kolab/kolab_driver.php');
class caldav_driver extends kolab_driver
{
// features this backend supports
public $alarms = true;
public $attendees = true;
public $freebusy = true;
public $attachments = false; // TODO
public $undelete = false; // TODO
public $alarm_types = ['DISPLAY', 'AUDIO'];
public $categoriesimmutable = true;
/**
* Default constructor
*/
public function __construct($cal)
{
$cal->require_plugin('libkolab');
// load helper classes *after* libkolab has been loaded (#3248)
require_once(__DIR__ . '/caldav_calendar.php');
// require_once(__DIR__ . '/kolab_user_calendar.php');
// require_once(__DIR__ . '/caldav_invitation_calendar.php');
$this->cal = $cal;
$this->rc = $cal->rc;
// Initialize the CalDAV storage
$url = $this->rc->config->get('calendar_caldav_server', 'http://localhost');
$this->storage = new kolab_storage_dav($url);
$this->cal->register_action('push-freebusy', [$this, 'push_freebusy']);
$this->cal->register_action('calendar-acl', [$this, 'calendar_acl']);
// $this->freebusy_trigger = $this->rc->config->get('calendar_freebusy_trigger', false);
// TODO: get configuration for the Bonnie API
// $this->bonnie_api = libkolab::get_bonnie_api();
}
/**
* Read available calendars from server
*/
protected function _read_calendars()
{
// already read sources
if (isset($this->calendars)) {
return $this->calendars;
}
// get all folders that support VEVENT, sorted by namespace/name
$folders = $this->storage->get_folders('event');
// + $this->storage->get_user_folders('event', true);
$this->calendars = [];
foreach ($folders as $folder) {
$calendar = $this->_to_calendar($folder);
if ($calendar->ready) {
$this->calendars[$calendar->id] = $calendar;
if ($calendar->editable) {
$this->has_writeable = true;
}
}
}
return $this->calendars;
}
/**
* Convert kolab_storage_folder into caldav_calendar
*/
protected function _to_calendar($folder)
{
if ($folder instanceof caldav_calendar) {
return $folder;
}
if ($folder instanceof kolab_storage_folder_user) {
$calendar = new kolab_user_calendar($folder, $this->cal);
$calendar->subscriptions = count($folder->children) > 0;
}
else {
$calendar = new caldav_calendar($folder, $this->cal);
}
return $calendar;
}
/**
* Get a list of available calendars from this source.
*
* @param int $filter Bitmask defining filter criterias
* @param object $tree Reference to hierarchical folder tree object
*
* @return array List of calendars
*/
public function list_calendars($filter = 0, &$tree = null)
{
$this->_read_calendars();
$folders = $this->filter_calendars($filter);
$calendars = [];
// include virtual folders for a full folder tree
/*
if (!is_null($tree)) {
$folders = $this->storage->folder_hierarchy($folders, $tree);
}
*/
$parents = array_keys($this->calendars);
foreach ($folders as $id => $cal) {
/*
$path = explode('/', $cal->name);
// find parent
do {
array_pop($path);
$parent_id = $this->storage->folder_id(implode('/', $path));
}
while (count($path) > 1 && !in_array($parent_id, $parents));
// restore "real" parent ID
if ($parent_id && !in_array($parent_id, $parents)) {
$parent_id = $this->storage->folder_id($cal->get_parent());
}
$parents[] = $cal->id;
if ($cal->virtual) {
$calendars[$cal->id] = [
'id' => $cal->id,
'name' => $cal->get_name(),
'listname' => $cal->get_foldername(),
'editname' => $cal->get_foldername(),
'virtual' => true,
'editable' => false,
'group' => $cal->get_namespace(),
];
}
else {
*/
// additional folders may come from kolab_storage_dav::folder_hierarchy() above
// make sure we deal with caldav_calendar instances
$cal = $this->_to_calendar($cal);
$this->calendars[$cal->id] = $cal;
$is_user = ($cal instanceof caldav_user_calendar);
$calendars[$cal->id] = [
'id' => $cal->id,
'name' => $cal->get_name(),
'listname' => $cal->get_foldername(),
'editname' => $cal->get_foldername(),
'title' => '', // $cal->get_title(),
'color' => $cal->get_color(),
'editable' => $cal->editable,
'group' => $is_user ? 'other user' : $cal->get_namespace(),
'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) {
$calendars[$cal->id] += [
'default' => $cal->default,
'rights' => $cal->rights,
'showalarms' => $cal->alarms,
'history' => !empty($this->bonnie_api),
'children' => true, // TODO: determine if that folder indeed has child folders
'parent' => $parent_id,
'subtype' => $cal->subtype,
'caldavurl' => '', // $cal->get_caldav_url(),
];
}
/*
}
*/
if ($cal->subscriptions) {
$calendars[$cal->id]['subscribed'] = $cal->is_subscribed();
}
}
/*
// list virtual calendars showing invitations
if ($this->rc->config->get('kolab_invitation_calendars') && !($filter & self::FILTER_INSERTABLE)) {
foreach ([self::INVITATIONS_CALENDAR_PENDING, self::INVITATIONS_CALENDAR_DECLINED] as $id) {
$cal = new caldav_invitation_calendar($id, $this->cal);
if (!($filter & self::FILTER_ACTIVE) || $cal->is_active()) {
$calendars[$id] = [
'id' => $cal->id,
'name' => $cal->get_name(),
'listname' => $cal->get_name(),
'editname' => $cal->get_foldername(),
'title' => $cal->get_title(),
'color' => $cal->get_color(),
'editable' => $cal->editable,
'rights' => $cal->rights,
'showalarms' => $cal->alarms,
'history' => !empty($this->bonnie_api),
'group' => 'x-invitations',
'default' => false,
'active' => $cal->is_active(),
'owner' => $cal->get_owner(),
'children' => false,
'counts' => $id == self::INVITATIONS_CALENDAR_PENDING,
];
if (is_object($tree)) {
$tree->children[] = $cal;
}
}
}
}
*/
// append the virtual birthdays calendar
if ($this->rc->config->get('calendar_contact_birthdays', false) && !($filter & self::FILTER_INSERTABLE)) {
$id = self::BIRTHDAY_CALENDAR_ID;
$prefs = $this->rc->config->get('kolab_calendars', []); // read local prefs
if (!($filter & self::FILTER_ACTIVE) || !empty($prefs[$id]['active'])) {
$calendars[$id] = [
'id' => $id,
'name' => $this->cal->gettext('birthdays'),
'listname' => $this->cal->gettext('birthdays'),
'color' => !empty($prefs[$id]['color']) ? $prefs[$id]['color'] : '87CEFA',
'active' => !empty($prefs[$id]['active']),
'showalarms' => (bool) $this->rc->config->get('calendar_birthdays_alarm_type'),
'group' => 'x-birthdays',
'editable' => false,
'default' => false,
'children' => false,
'history' => false,
];
}
}
return $calendars;
}
/**
* Get the caldav_calendar instance for the given calendar ID
*
- * @param string Calendar identifier (encoded imap folder name)
+ * @param string Calendar identifier
*
* @return ?caldav_calendar Object nor null if calendar doesn't exist
*/
public function get_calendar($id)
{
$this->_read_calendars();
// 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);
}
// for unsubscribed calendar folders
if ($id !== self::BIRTHDAY_CALENDAR_ID) {
$calendar = caldav_calendar::factory($id, $this->cal);
if ($calendar->ready) {
$this->calendars[$calendar->id] = $calendar;
}
}
}
return !empty($this->calendars[$id]) ? $this->calendars[$id] : null;
}
/**
* Search for shared or otherwise not listed calendars the user has access
*
* @param string Search string
* @param string Section/source to search
*
* @return array List of calendars
*/
public function search_calendars($query, $source)
{
$this->calendars = [];
$this->search_more_results = false;
/*
// find unsubscribed IMAP folders that have "event" type
if ($source == 'folders') {
foreach ((array) $this->storage->search_folders('event', $query, ['other']) as $folder) {
$calendar = new kolab_calendar($folder->name, $this->cal);
$this->calendars[$calendar->id] = $calendar;
}
}
// find other user's virtual calendars
else if ($source == 'users') {
// we have slightly more space, so display twice the number
$limit = $this->rc->config->get('autocomplete_max', 15) * 2;
foreach ($this->storage->search_users($query, 0, [], $limit, $count) as $user) {
$calendar = new caldav_user_calendar($user, $this->cal);
$this->calendars[$calendar->id] = $calendar;
// search for calendar folders shared by this user
foreach ($this->storage->list_user_folders($user, 'event', false) as $foldername) {
$cal = new caldav_calendar($foldername, $this->cal);
$this->calendars[$cal->id] = $cal;
$calendar->subscriptions = true;
}
}
if ($count > $limit) {
$this->search_more_results = true;
}
}
// don't list the birthday calendar
$this->rc->config->set('calendar_contact_birthdays', false);
$this->rc->config->set('kolab_invitation_calendars', false);
*/
return $this->list_calendars();
}
/**
* Get events from source.
*
* @param int Event's new start (unix timestamp)
* @param int Event's new end (unix timestamp)
* @param string Search query (optional)
* @param mixed List of calendar IDs to load events from (either as array or comma-separated string)
* @param bool Include virtual events (optional)
* @param int Only list events modified since this time (unix timestamp)
*
* @return array A list of event records
*/
public function load_events($start, $end, $search = null, $calendars = null, $virtual = 1, $modifiedsince = null)
{
if ($calendars && is_string($calendars)) {
$calendars = explode(',', $calendars);
}
else if (!$calendars) {
$this->_read_calendars();
$calendars = array_keys($this->calendars);
}
$query = [];
$events = [];
$categories = [];
if ($modifiedsince) {
$query[] = ['changed', '>=', $modifiedsince];
}
foreach ($calendars as $cid) {
if ($storage = $this->get_calendar($cid)) {
$events = array_merge($events, $storage->list_events($start, $end, $search, $virtual, $query));
$categories += $storage->categories;
}
}
// add events from the address books birthday calendar
if (in_array(self::BIRTHDAY_CALENDAR_ID, $calendars)) {
$events = array_merge($events, $this->load_birthday_events($start, $end, $search, $modifiedsince));
}
// add new categories to user prefs
$old_categories = $this->rc->config->get('calendar_categories', $this->default_categories);
$newcats = array_udiff(
array_keys($categories),
array_keys($old_categories),
function($a, $b) { return strcasecmp($a, $b); }
);
if (!empty($newcats)) {
foreach ($newcats as $category) {
$old_categories[$category] = ''; // no color set yet
}
$this->rc->user->save_prefs(['calendar_categories' => $old_categories]);
}
array_walk($events, 'caldav_driver::to_rcube_event');
return $events;
}
/**
* Create instances of a recurring event
*
* @param array Hash array with event properties
* @param DateTime Start date of the recurrence window
* @param DateTime End date of the recurrence window
*
* @return array List of recurring event instances
*/
public function get_recurring_events($event, $start, $end = null)
{
// load the given event data into a libkolabxml container
$event_xml = new kolab_format_event();
$event_xml->set($event);
$event['_formatobj'] = $event_xml;
$this->_read_calendars();
$storage = reset($this->calendars);
return $storage->get_recurring_events($event, $start, $end);
}
/**
*
*/
protected function get_recurrence_count($event, $dtstart)
{
// load the given event data into a libkolabxml container
$event_xml = new kolab_format_event();
$event_xml->set($event);
$event['_formatobj'] = $event_xml;
// use libkolab to compute recurring events
$recurrence = new kolab_date_recurrence($event['_formatobj']);
$count = 0;
while (($next_event = $recurrence->next_instance()) && $next_event['start'] <= $dtstart && $count < 1000) {
$count++;
}
return $count;
}
/**
* Callback function to produce driver-specific calendar create/edit form
*
* @param string Request action 'form-edit|form-new'
* @param array Calendar properties (e.g. id, color)
* @param array Edit form fields
*
* @return string HTML content of the form
*/
public function calendar_form($action, $calendar, $formfields)
{
$special_calendars = [
self::BIRTHDAY_CALENDAR_ID,
self::INVITATIONS_CALENDAR_PENDING,
self::INVITATIONS_CALENDAR_DECLINED
];
// show default dialog for birthday calendar
if (in_array($calendar['id'], $special_calendars)) {
if ($calendar['id'] != self::BIRTHDAY_CALENDAR_ID) {
unset($formfields['showalarms']);
}
// General tab
$form['props'] = [
'name' => $this->rc->gettext('properties'),
'fields' => $formfields,
];
return kolab_utils::folder_form($form, '', 'calendar');
}
$this->_read_calendars();
if (!empty($calendar['id']) && ($cal = $this->calendars[$calendar['id']])) {
$folder = $cal->get_realname(); // UTF7
$color = $cal->get_color();
}
else {
$folder = '';
$color = '';
}
$hidden_fields[] = ['name' => 'oldname', 'value' => $folder];
$form = [];
$protected = false; // TODO
// Disable folder name input
if ($protected) {
$input_name = new html_hiddenfield(['name' => 'name', 'id' => 'calendar-name']);
$formfields['name']['value'] = $this->storage->object_name($folder)
. $input_name->show($folder);
}
// calendar name (default field)
$form['props']['fields']['location'] = $formfields['name'];
if ($protected) {
// prevent user from moving folder
$hidden_fields[] = ['name' => 'parent', 'value' => '']; // TODO
}
else {
$select = $this->storage->folder_selector('event', ['name' => 'parent', 'id' => 'calendar-parent'], $folder);
$form['props']['fields']['path'] = [
'id' => 'calendar-parent',
'label' => $this->cal->gettext('parentcalendar'),
'value' => $select->show(strlen($folder) ? '' : ''), // TODO
];
}
// calendar color (default field)
$form['props']['fields']['color'] = $formfields['color'];
$form['props']['fields']['alarms'] = $formfields['showalarms'];
return kolab_utils::folder_form($form, $folder, 'calendar', $hidden_fields);
}
}
diff --git a/plugins/libkolab/lib/kolab_storage_dav_cache_event.php b/plugins/libkolab/lib/kolab_storage_dav_cache_event.php
index b6c3a16e..4d4f0363 100644
--- a/plugins/libkolab/lib/kolab_storage_dav_cache_event.php
+++ b/plugins/libkolab/lib/kolab_storage_dav_cache_event.php
@@ -1,146 +1,152 @@
<?php
/**
* Kolab storage cache class for calendar event objects
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
*
* 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_cache_event extends kolab_storage_dav_cache
{
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
*
* @override
*/
protected function _serialize($object)
{
$sql_data = parent::_serialize($object);
$sql_data['dtstart'] = $this->_convert_datetime($object['start']);
$sql_data['dtend'] = $this->_convert_datetime($object['end']);
// extend date range for recurring events
- if (!empty($object['recurrence']) && !empty($object['_formatobj'])) {
+ if (!empty($object['recurrence'])) {
+ if (empty($object['_formatobj'])) {
+ $event_xml = new kolab_format_event();
+ $event_xml->set($object);
+ $object['_formatobj'] = $event_xml;
+ }
+
$recurrence = new kolab_date_recurrence($object['_formatobj']);
$dtend = $recurrence->end() ?: new DateTime('now +100 years');
$sql_data['dtend'] = $this->_convert_datetime($dtend);
}
// extend start/end dates to spawn all exceptions
if (is_array($object['exceptions'])) {
foreach ($object['exceptions'] as $exception) {
if (is_a($exception['start'], 'DateTime')) {
$exstart = $this->_convert_datetime($exception['start']);
if ($exstart < $sql_data['dtstart']) {
$sql_data['dtstart'] = $exstart;
}
}
if (is_a($exception['end'], 'DateTime')) {
$exend = $this->_convert_datetime($exception['end']);
if ($exend > $sql_data['dtend']) {
$sql_data['dtend'] = $exend;
}
}
}
}
$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);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 17, 2:24 AM (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
178376
Default Alt Text
(25 KB)

Event Timeline