Remove dependency on kolabcalendaring/kolabformat in CalDAV driver, various PHP8 support fixes

This commit is contained in:
Aleksander Machniak 2022-12-05 15:07:23 +01:00
parent a9531c9336
commit 0530881f4b
19 changed files with 202 additions and 122 deletions

View file

@ -23,6 +23,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#[AllowDynamicProperties]
class calendar extends rcube_plugin class calendar extends rcube_plugin
{ {
const FREEBUSY_UNKNOWN = 0; const FREEBUSY_UNKNOWN = 0;
@ -42,6 +43,7 @@ class calendar extends rcube_plugin
public $timezone; public $timezone;
public $timezone_offset; public $timezone_offset;
public $gmt_offset; public $gmt_offset;
public $dst_active;
public $ui; public $ui;
public $defaults = [ public $defaults = [
@ -1049,8 +1051,9 @@ $("#rcmfd_new_category").keypress(function(event) {
$old = $this->driver->get_event($event); $old = $this->driver->get_event($event);
// load main event if savemode is 'all' or if deleting 'future' events // load main event if savemode is 'all' or if deleting 'future' events
if (($event['_savemode'] == 'all' || ($event['_savemode'] == 'future' && $action == 'remove' && empty($event['_decline']))) if (!empty($old['recurrence_id'])
&& !empty($old['recurrence_id']) && !empty($event['_savemode'])
&& ($event['_savemode'] == 'all' || ($event['_savemode'] == 'future' && $action == 'remove' && empty($event['_decline'])))
) { ) {
$old['id'] = $old['recurrence_id']; $old['id'] = $old['recurrence_id'];
$old = $this->driver->get_event($old); $old = $this->driver->get_event($old);
@ -1447,7 +1450,7 @@ $("#rcmfd_new_category").keypress(function(event) {
// $success is a new event ID // $success is a new event ID
if ($success !== true) { if ($success !== true) {
// send update notification on the main event // send update notification on the main event
if ($event['_savemode'] == 'future' && !empty($event['_notify']) if (!empty($event['_savemode']) && $event['_savemode'] == 'future' && !empty($event['_notify'])
&& !empty($old['attendees']) && !empty($old['recurrence_id']) && !empty($old['attendees']) && !empty($old['recurrence_id'])
) { ) {
$master = $this->driver->get_event(['id' => $old['recurrence_id'], 'calendar' => $old['calendar']], 0, true); $master = $this->driver->get_event(['id' => $old['recurrence_id'], 'calendar' => $old['calendar']], 0, true);
@ -1462,7 +1465,7 @@ $("#rcmfd_new_category").keypress(function(event) {
} }
// delete old reference if saved as new // delete old reference if saved as new
if ($event['_savemode'] == 'future' || $event['_savemode'] == 'new') { if (!empty($event['_savemode']) && ($event['_savemode'] == 'future' || $event['_savemode'] == 'new')) {
$old = null; $old = null;
} }
@ -1472,7 +1475,7 @@ $("#rcmfd_new_category").keypress(function(event) {
// send out notifications // send out notifications
if (!empty($event['_notify']) && (!empty($event['attendees']) || !empty($old['attendees']))) { if (!empty($event['_notify']) && (!empty($event['attendees']) || !empty($old['attendees']))) {
$_savemode = $event['_savemode']; $_savemode = $event['_savemode'] ?? null;
// send notification for the main event when savemode is 'all' // send notification for the main event when savemode is 'all'
if ($action != 'remove' && $_savemode == 'all' if ($action != 'remove' && $_savemode == 'all'
@ -2131,7 +2134,7 @@ $("#rcmfd_new_category").keypress(function(event) {
} }
// mapping url => vurl, allday => allDay because of the fullcalendar client script // mapping url => vurl, allday => allDay because of the fullcalendar client script
$event['vurl'] = $event['url']; $event['vurl'] = $event['url'] ?? null;
$event['allDay'] = !empty($event['allday']); $event['allDay'] = !empty($event['allday']);
unset($event['url']); unset($event['url']);
unset($event['allday']); unset($event['allday']);
@ -2153,9 +2156,9 @@ $("#rcmfd_new_category").keypress(function(event) {
// 'changed' might be empty for event recurrences (Bug #2185) // 'changed' might be empty for event recurrences (Bug #2185)
'changed' => !empty($event['changed']) ? $this->lib->adjust_timezone($event['changed'])->format('c') : null, 'changed' => !empty($event['changed']) ? $this->lib->adjust_timezone($event['changed'])->format('c') : null,
'created' => !empty($event['created']) ? $this->lib->adjust_timezone($event['created'])->format('c') : null, 'created' => !empty($event['created']) ? $this->lib->adjust_timezone($event['created'])->format('c') : null,
'title' => strval($event['title']), 'title' => strval($event['title'] ?? null),
'description' => strval($event['description']), 'description' => strval($event['description'] ?? null),
'location' => strval($event['location']), 'location' => strval($event['location'] ?? null),
] + $event; ] + $event;
} }
@ -2460,7 +2463,7 @@ $("#rcmfd_new_category").keypress(function(event) {
} }
if ($rsvp === null) { if ($rsvp === null) {
$rsvp = !$old || $event['sequence'] > $old['sequence']; $rsvp = !$old || ($event['sequence'] ?? 0) > ($old['sequence'] ?? 0);
} }
$itip = $this->load_itip(); $itip = $this->load_itip();
@ -3990,11 +3993,15 @@ $("#rcmfd_new_category").keypress(function(event) {
*/ */
public function find_first_occurrence($event) public function find_first_occurrence($event)
{ {
// Make sure libkolab plugin is loaded in case of Kolab driver // Make sure libkolab/libcalendaring plugins are loaded
$this->load_driver(); $this->load_driver();
// Use libkolab to compute recurring events (and libkolab plugin) $driver_name = $this->rc->config->get('calendar_driver', 'database');
if (class_exists('kolabformat') && class_exists('kolabcalendaring') && class_exists('kolab_date_recurrence')) {
// Use kolabcalendaring/kolabformat to compute recurring events only with the Kolab driver
if ($driver_name == 'kolab' && class_exists('kolabformat') && class_exists('kolabcalendaring')
&& class_exists('kolab_date_recurrence')
) {
$object = kolab_format::factory('event', 3.0); $object = kolab_format::factory('event', 3.0);
$object->set($event); $object->set($event);

View file

@ -163,12 +163,8 @@ class caldav_calendar extends kolab_storage_dav_folder
} }
if (!empty($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);
}
// check for match on the first instance already // check for match on the first instance already
else if (!empty($master['_instance']) && $master['_instance'] == $instance_id) { if (!empty($master['_instance']) && $master['_instance'] == $instance_id) {
$this->events[$id] = $master; $this->events[$id] = $master;
} }
else if (!empty($master['recurrence'])) { else if (!empty($master['recurrence'])) {
@ -580,12 +576,6 @@ class caldav_calendar extends kolab_storage_dav_folder
*/ */
public function get_recurring_events($event, $start, $end = null, $event_id = null, $limit = null) public function get_recurring_events($event, $start, $end = null, $event_id = null, $limit = null)
{ {
$object = $event['_formatobj'];
if (!is_object($object)) {
return [];
}
// determine a reasonable end date if none given // determine a reasonable end date if none given
if (!$end) { if (!$end) {
$end = clone $event['start']; $end = clone $event['start'];
@ -641,7 +631,8 @@ class caldav_calendar extends kolab_storage_dav_folder
} }
// Check first occurrence, it might have been moved // Check first occurrence, it might have been moved
if ($first = $exdata[$event['start']->format('Ymd')]) { if (!empty($exdata[$event['start']->format('Ymd')])) {
$first = $exdata[$event['start']->format('Ymd')];
// return it only if not already in the result, but in the requested period // return it only if not already in the result, but in the requested period
if (!($event['start'] <= $end && $event['end'] >= $start) if (!($event['start'] <= $end && $event['end'] >= $start)
&& ($first['start'] <= $end && $first['end'] >= $start) && ($first['start'] <= $end && $first['end'] >= $start)
@ -655,7 +646,7 @@ class caldav_calendar extends kolab_storage_dav_folder
} }
// use libkolab to compute recurring events // use libkolab to compute recurring events
$recurrence = new kolab_date_recurrence($object); $recurrence = libcalendaring::get_recurrence($event);
$i = 0; $i = 0;
while ($next_event = $recurrence->next_instance()) { while ($next_event = $recurrence->next_instance()) {
@ -663,9 +654,7 @@ class caldav_calendar extends kolab_storage_dav_folder
$instance_id = $next_event['start']->format($recurrence_id_format); $instance_id = $next_event['start']->format($recurrence_id_format);
// use this event data for future recurring instances // use this event data for future recurring instances
if (!empty($futuredata[$datestr])) { $overlay_data = $futuredata[$datestr] ?? null;
$overlay_data = $futuredata[$datestr];
}
$rec_id = $event['uid'] . '-' . $instance_id; $rec_id = $event['uid'] . '-' . $instance_id;
$exception = !empty($exdata[$datestr]) ? $exdata[$datestr] : $overlay_data; $exception = !empty($exdata[$datestr]) ? $exdata[$datestr] : $overlay_data;
@ -762,17 +751,10 @@ class caldav_calendar extends kolab_storage_dav_folder
// clean up exception data // clean up exception data
if (!empty($record['recurrence']) && !empty($record['recurrence']['EXCEPTIONS'])) { if (!empty($record['recurrence']) && !empty($record['recurrence']['EXCEPTIONS'])) {
array_walk($record['recurrence']['EXCEPTIONS'], function(&$exception) { array_walk($record['recurrence']['EXCEPTIONS'], function(&$exception) {
unset($exception['_mailbox'], $exception['_msguid'], $exception['_formatobj'], $exception['_attachments']); unset($exception['_attachments']);
}); });
} }
// Load the given event data into a libkolabxml container
// it's needed for recurrence resolving, which uses libcalendaring
// TODO: Drop dependency on libkolabxml?
$event_xml = new kolab_format_event();
$event_xml->set($record);
$record['_formatobj'] = $event_xml;
return $record; return $record;
} }
@ -829,10 +811,7 @@ class caldav_calendar extends kolab_storage_dav_folder
// clean up exception data // clean up exception data
if (!empty($event['exceptions'])) { if (!empty($event['exceptions'])) {
array_walk($event['exceptions'], function(&$exception) use ($cleanup_fn) { array_walk($event['exceptions'], $cleanup_fn);
unset($exception['_mailbox'], $exception['_msguid'], $exception['_formatobj']);
$cleanup_fn($exception);
});
} }
// copy meta data (starting with _) from old object // copy meta data (starting with _) from old object

View file

@ -34,6 +34,8 @@ class caldav_driver extends kolab_driver
public $alarm_types = ['DISPLAY', 'AUDIO']; public $alarm_types = ['DISPLAY', 'AUDIO'];
public $categoriesimmutable = true; public $categoriesimmutable = true;
protected $scheduling_properties = ['start', 'end', 'location'];
/** /**
* Default constructor * Default constructor
*/ */
@ -530,11 +532,6 @@ class caldav_driver extends kolab_driver
*/ */
public function get_recurring_events($event, $start, $end = null) 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(); $this->_read_calendars();
$storage = reset($this->calendars); $storage = reset($this->calendars);
@ -546,13 +543,8 @@ class caldav_driver extends kolab_driver
*/ */
protected function get_recurrence_count($event, $dtstart) 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 // use libkolab to compute recurring events
$recurrence = new kolab_date_recurrence($event['_formatobj']); $recurrence = libcalendaring::get_recurrence($event);
$count = 0; $count = 0;
while (($next_event = $recurrence->next_instance()) && $next_event['start'] <= $dtstart && $count < 1000) { while (($next_event = $recurrence->next_instance()) && $next_event['start'] <= $dtstart && $count < 1000) {
@ -562,6 +554,98 @@ class caldav_driver extends kolab_driver
return $count; return $count;
} }
/**
* Determine whether the current change affects scheduling and reset attendee status accordingly
*/
protected function check_scheduling(&$event, $old, $update = true)
{
// skip this check when importing iCal/iTip events
if (isset($event['sequence']) || !empty($event['_method'])) {
return false;
}
// iterate through the list of properties considered 'significant' for scheduling
$reschedule = $this->is_rescheduling_needed($event, $old);
// reset all attendee status to needs-action (#4360)
if ($update && $reschedule && !empty($event['attendees'])) {
$is_organizer = false;
$emails = $this->cal->get_user_emails();
$attendees = $event['attendees'];
foreach ($attendees as $i => $attendee) {
if ($attendee['role'] == 'ORGANIZER'
&& !empty($attendee['email'])
&& in_array(strtolower($attendee['email']), $emails)
) {
$is_organizer = true;
}
else if ($attendee['role'] != 'ORGANIZER'
&& $attendee['role'] != 'NON-PARTICIPANT'
&& $attendee['status'] != 'DELEGATED'
) {
$attendees[$i]['status'] = 'NEEDS-ACTION';
$attendees[$i]['rsvp'] = true;
}
}
// update attendees only if I'm the organizer
if ($is_organizer || (!empty($event['organizer']) && in_array(strtolower($event['organizer']['email']), $emails))) {
$event['attendees'] = $attendees;
}
}
return $reschedule;
}
/**
* Identify changes considered relevant for scheduling
*
* @param array Hash array with NEW object properties
* @param array Hash array with OLD object properties
*
* @return bool True if changes affect scheduling, False otherwise
*/
protected function is_rescheduling_needed($object, $old = null)
{
$reschedule = false;
foreach ($this->scheduling_properties as $prop) {
$a = $old[$prop] ?? null;
$b = $object[$prop] ?? null;
if (!empty($object['allday'])
&& ($prop == 'start' || $prop == 'end')
&& $a instanceof DateTimeInterface
&& $b instanceof DateTimeInterface
) {
$a = $a->format('Y-m-d');
$b = $b->format('Y-m-d');
}
if ($prop == 'recurrence' && is_array($a) && is_array($b)) {
unset($a['EXCEPTIONS'], $b['EXCEPTIONS']);
$a = array_filter($a);
$b = array_filter($b);
// advanced rrule comparison: no rescheduling if series was shortened
if ($a['COUNT'] && $b['COUNT'] && $b['COUNT'] < $a['COUNT']) {
unset($a['COUNT'], $b['COUNT']);
}
else if ($a['UNTIL'] && $b['UNTIL'] && $b['UNTIL'] < $a['UNTIL']) {
unset($a['UNTIL'], $b['UNTIL']);
}
}
if ($a != $b) {
$reschedule = true;
break;
}
}
return $reschedule;
}
/** /**
* Callback function to produce driver-specific calendar create/edit form * Callback function to produce driver-specific calendar create/edit form
* *

View file

@ -709,7 +709,8 @@ class kolab_calendar extends kolab_storage_folder_api
} }
// Check first occurrence, it might have been moved // Check first occurrence, it might have been moved
if ($first = $exdata[$event['start']->format('Ymd')]) { if (!empty($exdata[$event['start']->format('Ymd')])) {
$first = $exdata[$event['start']->format('Ymd')];
// return it only if not already in the result, but in the requested period // return it only if not already in the result, but in the requested period
if (!($event['start'] <= $end && $event['end'] >= $start) if (!($event['start'] <= $end && $event['end'] >= $start)
&& ($first['start'] <= $end && $first['end'] >= $start) && ($first['start'] <= $end && $first['end'] >= $start)
@ -731,9 +732,7 @@ class kolab_calendar extends kolab_storage_folder_api
$instance_id = $next_event['start']->format($recurrence_id_format); $instance_id = $next_event['start']->format($recurrence_id_format);
// use this event data for future recurring instances // use this event data for future recurring instances
if (!empty($futuredata[$datestr])) { $overlay_data = $futuredata[$datestr] ?? null;
$overlay_data = $futuredata[$datestr];
}
$rec_id = $event['uid'] . '-' . $instance_id; $rec_id = $event['uid'] . '-' . $instance_id;
$exception = !empty($exdata[$datestr]) ? $exdata[$datestr] : $overlay_data; $exception = !empty($exdata[$datestr]) ? $exdata[$datestr] : $overlay_data;

View file

@ -1297,8 +1297,8 @@ class kolab_driver extends calendar_driver
// when saving an instance in 'all' mode, copy recurrence exceptions over // when saving an instance in 'all' mode, copy recurrence exceptions over
if (!empty($old['recurrence_id'])) { if (!empty($old['recurrence_id'])) {
$event['recurrence']['EXCEPTIONS'] = $master['recurrence']['EXCEPTIONS']; $event['recurrence']['EXCEPTIONS'] = $master['recurrence']['EXCEPTIONS'] ?? [];
$event['recurrence']['EXDATE'] = $master['recurrence']['EXDATE']; $event['recurrence']['EXDATE'] = $master['recurrence']['EXDATE'] ?? [];
} }
else if (!empty($master['_instance'])) { else if (!empty($master['_instance'])) {
$event['_instance'] = $master['_instance']; $event['_instance'] = $master['_instance'];
@ -1388,7 +1388,7 @@ class kolab_driver extends calendar_driver
/** /**
* Determine whether the current change affects scheduling and reset attendee status accordingly * Determine whether the current change affects scheduling and reset attendee status accordingly
*/ */
public function check_scheduling(&$event, $old, $update = true) protected function check_scheduling(&$event, $old, $update = true)
{ {
// skip this check when importing iCal/iTip events // skip this check when importing iCal/iTip events
if (isset($event['sequence']) || !empty($event['_method'])) { if (isset($event['sequence']) || !empty($event['_method'])) {
@ -2235,7 +2235,9 @@ class kolab_driver extends calendar_driver
foreach ($event['attendees'] as $attendee) { foreach ($event['attendees'] as $attendee) {
if (in_array($attendee['email'], $user_emails)) { if (in_array($attendee['email'], $user_emails)) {
$partstat = $attendee['status']; if (!empty($attendee['status'])) {
$partstat = $attendee['status'];
}
break; break;
} }
} }

View file

@ -1,7 +1,5 @@
<?php <?php
require_once realpath(__DIR__ . '/../../libcalendaring/lib/libcalendaring_itip.php');
/** /**
* iTIP functions for the Calendar plugin * iTIP functions for the Calendar plugin
* *
@ -28,6 +26,8 @@ require_once realpath(__DIR__ . '/../../libcalendaring/lib/libcalendaring_itip.p
*/ */
class calendar_itip extends libcalendaring_itip class calendar_itip extends libcalendaring_itip
{ {
protected $db_itipinvitations;
/** /**
* Constructor to set text domain to calendar * Constructor to set text domain to calendar
*/ */

View file

@ -332,7 +332,7 @@ class carddav_contacts extends rcube_addressbook
$this->sortindex = array_merge($this->sortindex, $local_sortindex); $this->sortindex = array_merge($this->sortindex, $local_sortindex);
} }
} }
else if (is_array($this->filter['ids'])) { else if (isset($this->filter['ids']) && is_array($this->filter['ids'])) {
$ids = $this->filter['ids']; $ids = $this->filter['ids'];
if (count($ids)) { if (count($ids)) {
$uids = array_map([$this, 'id2uid'], $this->filter['ids']); $uids = array_map([$this, 'id2uid'], $this->filter['ids']);
@ -1109,21 +1109,23 @@ class carddav_contacts extends rcube_addressbook
switch ($this->sort_col) { switch ($this->sort_col) {
case 'name': case 'name':
$str = $rec['name'] . $rec['prefix']; $str = ($rec['name'] ?? '') . ($rec['prefix'] ?? '');
case 'firstname': case 'firstname':
$str .= $rec['firstname'] . $rec['middlename'] . $rec['surname']; $str .= ($rec['firstname'] ?? '') . ($rec['middlename'] ?? '') . ($rec['surname'] ?? '');
break; break;
case 'surname': case 'surname':
$str = $rec['surname'] . $rec['firstname'] . $rec['middlename']; $str = ($rec['surname'] ?? '') . ($rec['firstname'] ?? '') . ($rec['middlename'] ?? '');
break; break;
default: default:
$str = $rec[$this->sort_col]; $str = $rec[$this->sort_col] ?? '';
break; break;
} }
$str .= is_array($rec['email']) ? $rec['email'][0] : $rec['email']; if (!empty($rec['email'])) {
$str .= is_array($rec['email']) ? $rec['email'][0] : $rec['email'];
}
return mb_strtolower($str); return mb_strtolower($str);
} }

View file

@ -37,6 +37,7 @@ class kolab_addressbook extends rcube_plugin
private $sources; private $sources;
private $rc; private $rc;
private $ui; private $ui;
private $recurrent = false;
const GLOBAL_FIRST = 0; const GLOBAL_FIRST = 0;
const PERSONAL_FIRST = 1; const PERSONAL_FIRST = 1;
@ -221,8 +222,8 @@ class kolab_addressbook extends rcube_plugin
$out .= $kolab . $spec; $out .= $kolab . $spec;
$this->rc->output->set_env('contactgroups', array_filter($jsdata, function($src){ return $src['type'] == 'group'; })); $this->rc->output->set_env('contactgroups', array_filter($jsdata, function($src){ return isset($src['type']) && $src['type'] == 'group'; }));
$this->rc->output->set_env('address_sources', array_filter($jsdata, function($src){ return $src['type'] != 'group'; })); $this->rc->output->set_env('address_sources', array_filter($jsdata, function($src){ return !isset($src['type']) || $src['type'] != 'group'; }));
$args['content'] = html::tag('ul', $args, $out, html::$common_attrib); $args['content'] = html::tag('ul', $args, $out, html::$common_attrib);
return $args; return $args;
@ -276,27 +277,27 @@ class kolab_addressbook extends rcube_plugin
{ {
$current = rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC); $current = rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC);
if (!$source['virtual']) { if (empty($source['virtual'])) {
$jsdata[$id] = $source; $jsdata[$id] = $source;
$jsdata[$id]['name'] = html_entity_decode($source['name'], ENT_NOQUOTES, RCUBE_CHARSET); $jsdata[$id]['name'] = html_entity_decode($source['name'], ENT_NOQUOTES, RCUBE_CHARSET);
} }
// set class name(s) // set class name(s)
$classes = array('addressbook'); $classes = array('addressbook');
if ($source['group']) if (!empty($source['group']))
$classes[] = $source['group']; $classes[] = $source['group'];
if ($current === $id) if ($current === $id)
$classes[] = 'selected'; $classes[] = 'selected';
if ($source['readonly']) if (!empty($source['readonly']))
$classes[] = 'readonly'; $classes[] = 'readonly';
if ($source['virtual']) if (!empty($source['virtual']))
$classes[] = 'virtual'; $classes[] = 'virtual';
if ($source['class_name']) if (!empty($source['class_name']))
$classes[] = $source['class_name']; $classes[] = $source['class_name'];
$name = !empty($source['listname']) ? $source['listname'] : (!empty($source['name']) ? $source['name'] : $id); $name = !empty($source['listname']) ? $source['listname'] : (!empty($source['name']) ? $source['name'] : $id);
$label_id = 'kabt:' . $id; $label_id = 'kabt:' . $id;
$inner = ($source['virtual'] ? $inner = (!empty($source['virtual']) ?
html::a(array('tabindex' => '0'), $name) : html::a(array('tabindex' => '0'), $name) :
html::a(array( html::a(array(
'href' => $this->rc->url(array('_source' => $id)), 'href' => $this->rc->url(array('_source' => $id)),
@ -331,12 +332,12 @@ class kolab_addressbook extends rcube_plugin
return html::div(null, $inner); return html::div(null, $inner);
} }
$out .= html::tag('li', array( $out = html::tag('li', array(
'id' => 'rcmli' . rcube_utils::html_identifier($id, true), 'id' => 'rcmli' . rcube_utils::html_identifier($id, true),
'class' => join(' ', $classes), 'class' => join(' ', $classes),
'noclose' => true, 'noclose' => true,
), ),
html::div($source['subscribed'] ? 'subscribed' : null, $inner) html::div(!empty($source['subscribed']) ? 'subscribed' : null, $inner)
); );
$groupdata = array('out' => '', 'jsdata' => $jsdata, 'source' => $id); $groupdata = array('out' => '', 'jsdata' => $jsdata, 'source' => $id);

View file

@ -89,7 +89,6 @@ class kolab_addressbook_ui
$content = html::tag('li', $idx ? null : array('class' => 'separator_above'), $content = html::tag('li', $idx ? null : array('class' => 'separator_above'),
$this->plugin->api->output->button(array( $this->plugin->api->output->button(array(
'label' => 'kolab_addressbook.'.str_replace('-', '', $command), 'label' => 'kolab_addressbook.'.str_replace('-', '', $command),
'domain' => $this->ID,
'class' => str_replace('-', ' ', $command) . ' disabled', 'class' => str_replace('-', ' ', $command) . ' disabled',
'classact' => str_replace('-', ' ', $command) . ' active', 'classact' => str_replace('-', ' ', $command) . ' active',
'command' => $command, 'command' => $command,

View file

@ -52,8 +52,6 @@ class libcalendaring_recurrence
$this->duration = $event['start']->diff($event['end']); $this->duration = $event['start']->diff($event['end']);
} }
$event['start']->_dateonly = !empty($event['allday']);
$this->init($event['recurrence'], $event['start']); $this->init($event['recurrence'], $event['start']);
} }
} }
@ -67,7 +65,7 @@ class libcalendaring_recurrence
public function init($recurrence, $start) public function init($recurrence, $start)
{ {
$this->start = $start; $this->start = $start;
$this->dateonly = !empty($start->_dateonly); $this->dateonly = !empty($start->_dateonly) || !empty($this->event['allday']);
$this->recurrence = $recurrence; $this->recurrence = $recurrence;
$event = [ $event = [
@ -160,6 +158,7 @@ class libcalendaring_recurrence
$end->add(new DateInterval('P100Y')); $end->add(new DateInterval('P100Y'));
} }
*/ */
return isset($end) ? $this->toDateTime($end) : false; return isset($end) ? $this->toDateTime($end) : false;
} }

View file

@ -582,8 +582,8 @@ class libcalendaring_vcalendar implements Iterator
if (substr($value, 0, 4) == 'http' && !strpos($value, ':attachment:')) { if (substr($value, 0, 4) == 'http' && !strpos($value, ':attachment:')) {
$event['links'][] = $value; $event['links'][] = $value;
} }
else if (strlen($value) && strtoupper($params['VALUE']) == 'BINARY') { else if (is_string($value) && strlen($value) && !empty($params['VALUE']) && strtoupper($params['VALUE']) == 'BINARY') {
$attachment = self::map_keys($params, array('FMTTYPE' => 'mimetype', 'X-LABEL' => 'name', 'X-APPLE-FILENAME' => 'name')); $attachment = self::map_keys($params, ['FMTTYPE' => 'mimetype', 'X-LABEL' => 'name', 'X-APPLE-FILENAME' => 'name']);
$attachment['data'] = $value; $attachment['data'] = $value;
$attachment['size'] = strlen($value); $attachment['size'] = strlen($value);
$event['attachments'][] = $attachment; $event['attachments'][] = $attachment;

View file

@ -36,7 +36,7 @@ class libcalendaring extends rcube_plugin
public $gmt_offset; public $gmt_offset;
public $dst_active; public $dst_active;
public $timezone_offset; public $timezone_offset;
public $ical_parts = array(); public $ical_parts = [];
public $ical_message; public $ical_message;
public $defaults = array( public $defaults = array(
@ -174,10 +174,10 @@ class libcalendaring extends rcube_plugin
/** /**
* Load recurrence computation engine * Load recurrence computation engine
*/ */
public static function get_recurrence() public static function get_recurrence($object = null)
{ {
$self = self::get_instance(); $self = self::get_instance();
return new libcalendaring_recurrence($self); return new libcalendaring_recurrence($self, $object);
} }
/** /**
@ -1295,7 +1295,7 @@ class libcalendaring extends rcube_plugin
public static function identify_recurrence_instance(&$object) public static function identify_recurrence_instance(&$object)
{ {
// for savemode=all, remove recurrence instance identifiers // for savemode=all, remove recurrence instance identifiers
if (!empty($object['_savemode']) && $object['_savemode'] == 'all' && $object['recurrence']) { if (!empty($object['_savemode']) && $object['_savemode'] == 'all' && !empty($object['recurrence'])) {
unset($object['_instance'], $object['recurrence_date']); unset($object['_instance'], $object['recurrence_date']);
} }
// set instance and 'savemode' according to recurrence-id // set instance and 'savemode' according to recurrence-id

View file

@ -739,7 +739,7 @@ abstract class kolab_format
*/ */
public static function merge_attachments(&$object, $old) public static function merge_attachments(&$object, $old)
{ {
$object['_attachments'] = (array) $old['_attachments']; $object['_attachments'] = isset($old['_attachments']) && is_array($old['_attachments']) ? $old['_attachments'] : [];
// delete existing attachment(s) // delete existing attachment(s)
if (!empty($object['deleted_attachments'])) { if (!empty($object['deleted_attachments'])) {
@ -771,7 +771,7 @@ abstract class kolab_format
else { else {
// find attachment by name, so we can update it if exists // find attachment by name, so we can update it if exists
// and make sure there are no duplicates // and make sure there are no duplicates
foreach ((array) $object['_attachments'] as $cid => $att) { foreach ($object['_attachments'] as $cid => $att) {
if ($att && $attachment['name'] == $att['name']) { if ($att && $attachment['name'] == $att['name']) {
$key = $cid; $key = $cid;
} }

View file

@ -49,6 +49,7 @@ class kolab_storage_cache
protected $limit = null; protected $limit = null;
protected $error = 0; protected $error = 0;
protected $server_timezone; protected $server_timezone;
protected $sync_start;
/** /**
@ -769,6 +770,7 @@ class kolab_storage_cache
public function select($query = array(), $uids = false, $fast = false) public function select($query = array(), $uids = false, $fast = false)
{ {
$result = $uids ? array() : new kolab_storage_dataset($this); $result = $uids ? array() : new kolab_storage_dataset($this);
$count = null;
// read from local cache DB (assume it to be synchronized) // read from local cache DB (assume it to be synchronized)
if ($this->ready) { if ($this->ready) {
@ -923,8 +925,12 @@ class kolab_storage_cache
{ {
if (!empty($sortcols)) { if (!empty($sortcols)) {
$sortcols = array_map(function($v) { $sortcols = array_map(function($v) {
list($column, $order) = explode(' ', $v, 2); $v = trim($v);
return "`$column`" . ($order ? " $order" : ''); if (strpos($v, ' ')) {
list($column, $order) = explode(' ', $v, 2);
return "`{$column}` {$order}";
}
return "`{$v}`";
}, (array) $sortcols); }, (array) $sortcols);
$this->order_by = join(', ', $sortcols); $this->order_by = join(', ', $sortcols);
@ -1257,13 +1263,15 @@ class kolab_storage_cache
$write_query = "UPDATE `{$this->folders_table}` SET `synclock` = ? WHERE `folder_id` = ? AND `synclock` = ?"; $write_query = "UPDATE `{$this->folders_table}` SET `synclock` = ? WHERE `folder_id` = ? AND `synclock` = ?";
$max_lock_time = $this->_max_sync_lock_time(); $max_lock_time = $this->_max_sync_lock_time();
$sync_lock = intval($this->metadata['synclock'] ?? 0);
// wait if locked (expire locks after 10 minutes) ... // wait if locked (expire locks after 10 minutes) ...
// ... or if setting lock fails (another process meanwhile set it) // ... or if setting lock fails (another process meanwhile set it)
while ( while (
(intval($this->metadata['synclock']) + $max_lock_time > time()) || ($sync_lock + $max_lock_time > time()) ||
(($res = $this->db->query($write_query, time(), $this->folder_id, intval($this->metadata['synclock']))) && (($res = $this->db->query($write_query, time(), $this->folder_id, $sync_lock))
!($affected = $this->db->affected_rows($res))) && !($affected = $this->db->affected_rows($res))
)
) { ) {
usleep(500000); usleep(500000);
$this->metadata = $this->db->fetch_assoc($this->db->query($read_query, $this->folder_id)); $this->metadata = $this->db->fetch_assoc($this->db->query($read_query, $this->folder_id));

View file

@ -39,7 +39,7 @@ class kolab_storage_cache_event extends kolab_storage_cache
$sql_data['dtend'] = $this->_convert_datetime($object['end']); $sql_data['dtend'] = $this->_convert_datetime($object['end']);
// extend date range for recurring events // extend date range for recurring events
if ($object['recurrence'] && $object['_formatobj']) { if ($object['recurrence']) {
$recurrence = new kolab_date_recurrence($object['_formatobj']); $recurrence = new kolab_date_recurrence($object['_formatobj']);
$dtend = $recurrence->end() ?: new DateTime('now +100 years'); $dtend = $recurrence->end() ?: new DateTime('now +100 years');
$sql_data['dtend'] = $this->_convert_datetime($dtend); $sql_data['dtend'] = $this->_convert_datetime($dtend);

View file

@ -46,9 +46,6 @@ class kolab_storage_dav
*/ */
public function setup() public function setup()
{ {
$rcmail = rcube::get_instance();
$this->config = $rcmail->config;
$this->dav = new kolab_dav_client($this->url); $this->dav = new kolab_dav_client($this->url);
} }

View file

@ -159,6 +159,7 @@ class kolab_storage_dav_cache extends kolab_storage_cache
// Fetch new objects and store in DB // Fetch new objects and store in DB
if (!empty($new_index)) { if (!empty($new_index)) {
$i = 0;
foreach (array_chunk($new_index, $chunk_size, true) as $chunk) { foreach (array_chunk($new_index, $chunk_size, true) as $chunk) {
$objects = $this->folder->dav->getData($this->folder->href, $this->folder->get_dav_type(), $chunk); $objects = $this->folder->dav->getData($this->folder->href, $this->folder->get_dav_type(), $chunk);
@ -412,6 +413,7 @@ class kolab_storage_dav_cache extends kolab_storage_cache
public function select($query = [], $uids = false, $fast = false) public function select($query = [], $uids = false, $fast = false)
{ {
$result = $uids ? [] : new kolab_storage_dataset($this); $result = $uids ? [] : new kolab_storage_dataset($this);
$count = null;
$this->_read_folder_data(); $this->_read_folder_data();

View file

@ -36,21 +36,20 @@ class kolab_storage_dav_cache_contact extends kolab_storage_dav_cache
protected function _serialize($object) protected function _serialize($object)
{ {
$sql_data = parent::_serialize($object); $sql_data = parent::_serialize($object);
$sql_data['type'] = $object['_type'] ?: 'contact'; $sql_data['type'] = !empty($object['_type']) ? $object['_type'] : 'contact';
if ($sql_data['type'] == 'group' || (!empty($object['kind']) && $object['kind'] == 'group')) { if ($sql_data['type'] == 'group' || (!empty($object['kind']) && $object['kind'] == 'group')) {
$sql_data['type'] = 'group'; $sql_data['type'] = 'group';
} }
// columns for sorting // columns for sorting
$sql_data['name'] = rcube_charset::clean($object['name'] . $object['prefix']); $sql_data['name'] = rcube_charset::clean(($object['name'] ?? '') . ($object['prefix'] ?? ''));
$sql_data['firstname'] = rcube_charset::clean($object['firstname'] . $object['middlename'] . $object['surname']); $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['surname'] = rcube_charset::clean(($object['surname'] ?? '') . ($object['firstname'] ?? '') . ($object['middlename'] ?? ''));
$sql_data['email'] = ''; $sql_data['email'] = '';
foreach ($object as $colname => $value) { foreach ($object as $colname => $value) {
list($col, $field) = explode(':', $colname); if (!empty($value) && ($colname == 'email' || strpos($colname, 'email:') === 0)) {
if ($col == 'email' && !empty($value)) {
$sql_data['email'] = is_array($value) ? $value[0] : $value; $sql_data['email'] = is_array($value) ? $value[0] : $value;
break; break;
} }
@ -84,14 +83,14 @@ class kolab_storage_dav_cache_contact extends kolab_storage_dav_cache
$data = ''; $data = '';
foreach ($object as $colname => $value) { foreach ($object as $colname => $value) {
list($col, $field) = explode(':', $colname); list($col, $field) = strpos($colname, ':') ? explode(':', $colname) : [$colname, null];
$val = ''; $val = null;
if (in_array($col, $this->fulltext_cols)) { if (in_array($col, $this->fulltext_cols)) {
$val = is_array($value) ? join(' ', $value) : $value; $val = is_array($value) ? join(' ', $value) : $value;
} }
if (strlen($val)) { if (is_string($val) && strlen($val)) {
$data .= $val . ' '; $data .= $val . ' ';
} }
} }

View file

@ -41,19 +41,14 @@ class kolab_storage_dav_cache_event extends kolab_storage_dav_cache
// extend date range for recurring events // extend date range for recurring events
if (!empty($object['recurrence'])) { if (!empty($object['recurrence'])) {
if (empty($object['_formatobj'])) { $recurrence = libcalendaring::get_recurrence($object);
$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'); $dtend = $recurrence->end() ?: new DateTime('now +100 years');
$sql_data['dtend'] = $this->_convert_datetime($dtend); $sql_data['dtend'] = $this->_convert_datetime($dtend);
} }
// extend start/end dates to spawn all exceptions // extend start/end dates to spawn all exceptions
if (is_array($object['exceptions'])) { // FIXME: This should be done via libcalendaring_recurrence use above?
if (!empty($object['exceptions']) && is_array($object['exceptions'])) {
foreach ($object['exceptions'] as $exception) { foreach ($object['exceptions'] as $exception) {
if ($exception['start'] instanceof DateTimeInterface) { if ($exception['start'] instanceof DateTimeInterface) {
$exstart = $this->_convert_datetime($exception['start']); $exstart = $this->_convert_datetime($exception['start']);
@ -86,12 +81,18 @@ class kolab_storage_dav_cache_event extends kolab_storage_dav_cache
$data = ''; $data = '';
foreach ($this->fulltext_cols as $colname) { foreach ($this->fulltext_cols as $colname) {
list($col, $field) = explode(':', $colname); list($col, $field) = strpos($colname, ':') ? explode(':', $colname) : [$colname, null];
if (empty($object[$col])) {
continue;
}
if ($field) { if ($field) {
$a = []; $a = [];
foreach ((array) $object[$col] as $attr) { foreach ((array) $object[$col] as $attr) {
$a[] = $attr[$field]; if (!empty($attr[$field])) {
$a[] = $attr[$field];
}
} }
$val = join(' ', $a); $val = join(' ', $a);
} }
@ -99,14 +100,15 @@ class kolab_storage_dav_cache_event extends kolab_storage_dav_cache
$val = is_array($object[$col]) ? join(' ', $object[$col]) : $object[$col]; $val = is_array($object[$col]) ? join(' ', $object[$col]) : $object[$col];
} }
if (strlen($val)) if (is_string($val) && strlen($val)) {
$data .= $val . ' '; $data .= $val . ' ';
}
} }
$words = rcube_utils::normalize_string($data, true); $words = rcube_utils::normalize_string($data, true);
// collect words from recurrence exceptions // collect words from recurrence exceptions
if (is_array($object['exceptions'])) { if (!empty($object['exceptions']) && is_array($object['exceptions'])) {
foreach ($object['exceptions'] as $exception) { foreach ($object['exceptions'] as $exception) {
$words = array_merge($words, $this->get_words($exception)); $words = array_merge($words, $this->get_words($exception));
} }
@ -137,14 +139,14 @@ class kolab_storage_dav_cache_event extends kolab_storage_dav_cache
} }
// collect tags from recurrence exceptions // collect tags from recurrence exceptions
if (is_array($object['exceptions'])) { if (!empty($object['exceptions']) && is_array($object['exceptions'])) {
foreach ($object['exceptions'] as $exception) { foreach ($object['exceptions'] as $exception) {
$tags = array_merge($tags, $this->get_tags($exception)); $tags = array_merge($tags, $this->get_tags($exception));
} }
} }
if (!empty($object['status'])) { if (!empty($object['status'])) {
$tags[] = 'x-status:' . strtolower($object['status']); $tags[] = 'x-status:' . strtolower($object['status']);
} }
return array_unique($tags); return array_unique($tags);