diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 2c198f14..ba2f21e4 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -23,6 +23,7 @@ * along with this program. If not, see . */ +#[AllowDynamicProperties] class calendar extends rcube_plugin { const FREEBUSY_UNKNOWN = 0; @@ -42,6 +43,7 @@ class calendar extends rcube_plugin public $timezone; public $timezone_offset; public $gmt_offset; + public $dst_active; public $ui; public $defaults = [ @@ -1049,8 +1051,9 @@ $("#rcmfd_new_category").keypress(function(event) { $old = $this->driver->get_event($event); // load main event if savemode is 'all' or if deleting 'future' events - if (($event['_savemode'] == 'all' || ($event['_savemode'] == 'future' && $action == 'remove' && empty($event['_decline']))) - && !empty($old['recurrence_id']) + if (!empty($old['recurrence_id']) + && !empty($event['_savemode']) + && ($event['_savemode'] == 'all' || ($event['_savemode'] == 'future' && $action == 'remove' && empty($event['_decline']))) ) { $old['id'] = $old['recurrence_id']; $old = $this->driver->get_event($old); @@ -1447,7 +1450,7 @@ $("#rcmfd_new_category").keypress(function(event) { // $success is a new event ID if ($success !== true) { // 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']) ) { $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 - if ($event['_savemode'] == 'future' || $event['_savemode'] == 'new') { + if (!empty($event['_savemode']) && ($event['_savemode'] == 'future' || $event['_savemode'] == 'new')) { $old = null; } @@ -1472,7 +1475,7 @@ $("#rcmfd_new_category").keypress(function(event) { // send out notifications 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' 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 - $event['vurl'] = $event['url']; + $event['vurl'] = $event['url'] ?? null; $event['allDay'] = !empty($event['allday']); unset($event['url']); unset($event['allday']); @@ -2153,9 +2156,9 @@ $("#rcmfd_new_category").keypress(function(event) { // 'changed' might be empty for event recurrences (Bug #2185) '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, - 'title' => strval($event['title']), - 'description' => strval($event['description']), - 'location' => strval($event['location']), + 'title' => strval($event['title'] ?? null), + 'description' => strval($event['description'] ?? null), + 'location' => strval($event['location'] ?? null), ] + $event; } @@ -2460,7 +2463,7 @@ $("#rcmfd_new_category").keypress(function(event) { } if ($rsvp === null) { - $rsvp = !$old || $event['sequence'] > $old['sequence']; + $rsvp = !$old || ($event['sequence'] ?? 0) > ($old['sequence'] ?? 0); } $itip = $this->load_itip(); @@ -3990,11 +3993,15 @@ $("#rcmfd_new_category").keypress(function(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(); - // Use libkolab to compute recurring events (and libkolab plugin) - if (class_exists('kolabformat') && class_exists('kolabcalendaring') && class_exists('kolab_date_recurrence')) { + $driver_name = $this->rc->config->get('calendar_driver', 'database'); + + // 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->set($event); diff --git a/plugins/calendar/drivers/caldav/caldav_calendar.php b/plugins/calendar/drivers/caldav/caldav_calendar.php index 742e9dac..7d4e94f7 100644 --- a/plugins/calendar/drivers/caldav/caldav_calendar.php +++ b/plugins/calendar/drivers/caldav/caldav_calendar.php @@ -163,12 +163,8 @@ class caldav_calendar extends kolab_storage_dav_folder } 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 - else if (!empty($master['_instance']) && $master['_instance'] == $instance_id) { + if (!empty($master['_instance']) && $master['_instance'] == $instance_id) { $this->events[$id] = $master; } 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) { - $object = $event['_formatobj']; - - if (!is_object($object)) { - return []; - } - // determine a reasonable end date if none given if (!$end) { $end = clone $event['start']; @@ -641,7 +631,8 @@ class caldav_calendar extends kolab_storage_dav_folder } // 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 if (!($event['start'] <= $end && $event['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 - $recurrence = new kolab_date_recurrence($object); + $recurrence = libcalendaring::get_recurrence($event); $i = 0; 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); // use this event data for future recurring instances - if (!empty($futuredata[$datestr])) { - $overlay_data = $futuredata[$datestr]; - } + $overlay_data = $futuredata[$datestr] ?? null; $rec_id = $event['uid'] . '-' . $instance_id; $exception = !empty($exdata[$datestr]) ? $exdata[$datestr] : $overlay_data; @@ -762,17 +751,10 @@ class caldav_calendar extends kolab_storage_dav_folder // clean up exception data if (!empty($record['recurrence']) && !empty($record['recurrence']['EXCEPTIONS'])) { 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; } @@ -829,10 +811,7 @@ class caldav_calendar extends kolab_storage_dav_folder // clean up exception data if (!empty($event['exceptions'])) { - array_walk($event['exceptions'], function(&$exception) use ($cleanup_fn) { - unset($exception['_mailbox'], $exception['_msguid'], $exception['_formatobj']); - $cleanup_fn($exception); - }); + array_walk($event['exceptions'], $cleanup_fn); } // copy meta data (starting with _) from old object diff --git a/plugins/calendar/drivers/caldav/caldav_driver.php b/plugins/calendar/drivers/caldav/caldav_driver.php index 3ca6bfdd..f5f91c31 100644 --- a/plugins/calendar/drivers/caldav/caldav_driver.php +++ b/plugins/calendar/drivers/caldav/caldav_driver.php @@ -34,6 +34,8 @@ class caldav_driver extends kolab_driver public $alarm_types = ['DISPLAY', 'AUDIO']; public $categoriesimmutable = true; + protected $scheduling_properties = ['start', 'end', 'location']; + /** * Default constructor */ @@ -530,11 +532,6 @@ class caldav_driver extends kolab_driver */ 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); @@ -546,13 +543,8 @@ class caldav_driver extends kolab_driver */ 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']); + $recurrence = libcalendaring::get_recurrence($event); $count = 0; while (($next_event = $recurrence->next_instance()) && $next_event['start'] <= $dtstart && $count < 1000) { @@ -562,6 +554,98 @@ class caldav_driver extends kolab_driver 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 * diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index e0129c0a..408e4d7f 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -709,7 +709,8 @@ class kolab_calendar extends kolab_storage_folder_api } // 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 if (!($event['start'] <= $end && $event['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); // use this event data for future recurring instances - if (!empty($futuredata[$datestr])) { - $overlay_data = $futuredata[$datestr]; - } + $overlay_data = $futuredata[$datestr] ?? null; $rec_id = $event['uid'] . '-' . $instance_id; $exception = !empty($exdata[$datestr]) ? $exdata[$datestr] : $overlay_data; diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index 4062bb61..31ac42eb 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -1297,8 +1297,8 @@ class kolab_driver extends calendar_driver // when saving an instance in 'all' mode, copy recurrence exceptions over if (!empty($old['recurrence_id'])) { - $event['recurrence']['EXCEPTIONS'] = $master['recurrence']['EXCEPTIONS']; - $event['recurrence']['EXDATE'] = $master['recurrence']['EXDATE']; + $event['recurrence']['EXCEPTIONS'] = $master['recurrence']['EXCEPTIONS'] ?? []; + $event['recurrence']['EXDATE'] = $master['recurrence']['EXDATE'] ?? []; } else if (!empty($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 */ - public function check_scheduling(&$event, $old, $update = true) + protected function check_scheduling(&$event, $old, $update = true) { // skip this check when importing iCal/iTip events if (isset($event['sequence']) || !empty($event['_method'])) { @@ -2235,7 +2235,9 @@ class kolab_driver extends calendar_driver foreach ($event['attendees'] as $attendee) { if (in_array($attendee['email'], $user_emails)) { - $partstat = $attendee['status']; + if (!empty($attendee['status'])) { + $partstat = $attendee['status']; + } break; } } diff --git a/plugins/calendar/lib/calendar_itip.php b/plugins/calendar/lib/calendar_itip.php index 4f7f382f..4940fc32 100644 --- a/plugins/calendar/lib/calendar_itip.php +++ b/plugins/calendar/lib/calendar_itip.php @@ -1,7 +1,5 @@ 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']; if (count($ids)) { $uids = array_map([$this, 'id2uid'], $this->filter['ids']); @@ -1109,21 +1109,23 @@ class carddav_contacts extends rcube_addressbook switch ($this->sort_col) { case 'name': - $str = $rec['name'] . $rec['prefix']; + $str = ($rec['name'] ?? '') . ($rec['prefix'] ?? ''); case 'firstname': - $str .= $rec['firstname'] . $rec['middlename'] . $rec['surname']; + $str .= ($rec['firstname'] ?? '') . ($rec['middlename'] ?? '') . ($rec['surname'] ?? ''); break; case 'surname': - $str = $rec['surname'] . $rec['firstname'] . $rec['middlename']; + $str = ($rec['surname'] ?? '') . ($rec['firstname'] ?? '') . ($rec['middlename'] ?? ''); break; default: - $str = $rec[$this->sort_col]; + $str = $rec[$this->sort_col] ?? ''; 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); } diff --git a/plugins/kolab_addressbook/kolab_addressbook.php b/plugins/kolab_addressbook/kolab_addressbook.php index 4a60c755..8a6f0729 100644 --- a/plugins/kolab_addressbook/kolab_addressbook.php +++ b/plugins/kolab_addressbook/kolab_addressbook.php @@ -37,6 +37,7 @@ class kolab_addressbook extends rcube_plugin private $sources; private $rc; private $ui; + private $recurrent = false; const GLOBAL_FIRST = 0; const PERSONAL_FIRST = 1; @@ -221,8 +222,8 @@ class kolab_addressbook extends rcube_plugin $out .= $kolab . $spec; - $this->rc->output->set_env('contactgroups', array_filter($jsdata, function($src){ return $src['type'] == 'group'; })); - $this->rc->output->set_env('address_sources', 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 !isset($src['type']) || $src['type'] != 'group'; })); $args['content'] = html::tag('ul', $args, $out, html::$common_attrib); return $args; @@ -276,27 +277,27 @@ class kolab_addressbook extends rcube_plugin { $current = rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC); - if (!$source['virtual']) { + if (empty($source['virtual'])) { $jsdata[$id] = $source; $jsdata[$id]['name'] = html_entity_decode($source['name'], ENT_NOQUOTES, RCUBE_CHARSET); } // set class name(s) $classes = array('addressbook'); - if ($source['group']) + if (!empty($source['group'])) $classes[] = $source['group']; if ($current === $id) $classes[] = 'selected'; - if ($source['readonly']) + if (!empty($source['readonly'])) $classes[] = 'readonly'; - if ($source['virtual']) + if (!empty($source['virtual'])) $classes[] = 'virtual'; - if ($source['class_name']) + if (!empty($source['class_name'])) $classes[] = $source['class_name']; $name = !empty($source['listname']) ? $source['listname'] : (!empty($source['name']) ? $source['name'] : $id); $label_id = 'kabt:' . $id; - $inner = ($source['virtual'] ? + $inner = (!empty($source['virtual']) ? html::a(array('tabindex' => '0'), $name) : html::a(array( 'href' => $this->rc->url(array('_source' => $id)), @@ -331,12 +332,12 @@ class kolab_addressbook extends rcube_plugin return html::div(null, $inner); } - $out .= html::tag('li', array( + $out = html::tag('li', array( 'id' => 'rcmli' . rcube_utils::html_identifier($id, true), 'class' => join(' ', $classes), 'noclose' => true, ), - html::div($source['subscribed'] ? 'subscribed' : null, $inner) + html::div(!empty($source['subscribed']) ? 'subscribed' : null, $inner) ); $groupdata = array('out' => '', 'jsdata' => $jsdata, 'source' => $id); diff --git a/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php b/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php index f1423b09..ff5b5121 100644 --- a/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php +++ b/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php @@ -89,7 +89,6 @@ class kolab_addressbook_ui $content = html::tag('li', $idx ? null : array('class' => 'separator_above'), $this->plugin->api->output->button(array( 'label' => 'kolab_addressbook.'.str_replace('-', '', $command), - 'domain' => $this->ID, 'class' => str_replace('-', ' ', $command) . ' disabled', 'classact' => str_replace('-', ' ', $command) . ' active', 'command' => $command, diff --git a/plugins/libcalendaring/lib/libcalendaring_recurrence.php b/plugins/libcalendaring/lib/libcalendaring_recurrence.php index 4de96812..df802b7b 100644 --- a/plugins/libcalendaring/lib/libcalendaring_recurrence.php +++ b/plugins/libcalendaring/lib/libcalendaring_recurrence.php @@ -52,8 +52,6 @@ class libcalendaring_recurrence $this->duration = $event['start']->diff($event['end']); } - $event['start']->_dateonly = !empty($event['allday']); - $this->init($event['recurrence'], $event['start']); } } @@ -67,7 +65,7 @@ class libcalendaring_recurrence public function init($recurrence, $start) { $this->start = $start; - $this->dateonly = !empty($start->_dateonly); + $this->dateonly = !empty($start->_dateonly) || !empty($this->event['allday']); $this->recurrence = $recurrence; $event = [ @@ -160,6 +158,7 @@ class libcalendaring_recurrence $end->add(new DateInterval('P100Y')); } */ + return isset($end) ? $this->toDateTime($end) : false; } diff --git a/plugins/libcalendaring/lib/libcalendaring_vcalendar.php b/plugins/libcalendaring/lib/libcalendaring_vcalendar.php index b801981b..55420e43 100644 --- a/plugins/libcalendaring/lib/libcalendaring_vcalendar.php +++ b/plugins/libcalendaring/lib/libcalendaring_vcalendar.php @@ -582,8 +582,8 @@ class libcalendaring_vcalendar implements Iterator if (substr($value, 0, 4) == 'http' && !strpos($value, ':attachment:')) { $event['links'][] = $value; } - else if (strlen($value) && strtoupper($params['VALUE']) == 'BINARY') { - $attachment = self::map_keys($params, array('FMTTYPE' => 'mimetype', 'X-LABEL' => 'name', 'X-APPLE-FILENAME' => 'name')); + else if (is_string($value) && strlen($value) && !empty($params['VALUE']) && strtoupper($params['VALUE']) == 'BINARY') { + $attachment = self::map_keys($params, ['FMTTYPE' => 'mimetype', 'X-LABEL' => 'name', 'X-APPLE-FILENAME' => 'name']); $attachment['data'] = $value; $attachment['size'] = strlen($value); $event['attachments'][] = $attachment; diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php index 38884b81..76a57e49 100644 --- a/plugins/libcalendaring/libcalendaring.php +++ b/plugins/libcalendaring/libcalendaring.php @@ -36,7 +36,7 @@ class libcalendaring extends rcube_plugin public $gmt_offset; public $dst_active; public $timezone_offset; - public $ical_parts = array(); + public $ical_parts = []; public $ical_message; public $defaults = array( @@ -174,10 +174,10 @@ class libcalendaring extends rcube_plugin /** * Load recurrence computation engine */ - public static function get_recurrence() + public static function get_recurrence($object = null) { $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) { // 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']); } // set instance and 'savemode' according to recurrence-id diff --git a/plugins/libkolab/lib/kolab_format.php b/plugins/libkolab/lib/kolab_format.php index 325dd2c0..9f7c537f 100644 --- a/plugins/libkolab/lib/kolab_format.php +++ b/plugins/libkolab/lib/kolab_format.php @@ -739,7 +739,7 @@ abstract class kolab_format */ 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) if (!empty($object['deleted_attachments'])) { @@ -771,7 +771,7 @@ abstract class kolab_format else { // find attachment by name, so we can update it if exists // 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']) { $key = $cid; } diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php index ff5bd86e..e38c4190 100644 --- a/plugins/libkolab/lib/kolab_storage_cache.php +++ b/plugins/libkolab/lib/kolab_storage_cache.php @@ -49,6 +49,7 @@ class kolab_storage_cache protected $limit = null; protected $error = 0; protected $server_timezone; + protected $sync_start; /** @@ -769,6 +770,7 @@ class kolab_storage_cache public function select($query = array(), $uids = false, $fast = false) { $result = $uids ? array() : new kolab_storage_dataset($this); + $count = null; // read from local cache DB (assume it to be synchronized) if ($this->ready) { @@ -923,8 +925,12 @@ class kolab_storage_cache { if (!empty($sortcols)) { $sortcols = array_map(function($v) { - list($column, $order) = explode(' ', $v, 2); - return "`$column`" . ($order ? " $order" : ''); + $v = trim($v); + if (strpos($v, ' ')) { + list($column, $order) = explode(' ', $v, 2); + return "`{$column}` {$order}"; + } + return "`{$v}`"; }, (array) $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` = ?"; $max_lock_time = $this->_max_sync_lock_time(); + $sync_lock = intval($this->metadata['synclock'] ?? 0); // wait if locked (expire locks after 10 minutes) ... // ... or if setting lock fails (another process meanwhile set it) while ( - (intval($this->metadata['synclock']) + $max_lock_time > time()) || - (($res = $this->db->query($write_query, time(), $this->folder_id, intval($this->metadata['synclock']))) && - !($affected = $this->db->affected_rows($res))) + ($sync_lock + $max_lock_time > time()) || + (($res = $this->db->query($write_query, time(), $this->folder_id, $sync_lock)) + && !($affected = $this->db->affected_rows($res)) + ) ) { usleep(500000); $this->metadata = $this->db->fetch_assoc($this->db->query($read_query, $this->folder_id)); diff --git a/plugins/libkolab/lib/kolab_storage_cache_event.php b/plugins/libkolab/lib/kolab_storage_cache_event.php index b324f561..5ad9a3a1 100644 --- a/plugins/libkolab/lib/kolab_storage_cache_event.php +++ b/plugins/libkolab/lib/kolab_storage_cache_event.php @@ -39,7 +39,7 @@ class kolab_storage_cache_event extends kolab_storage_cache $sql_data['dtend'] = $this->_convert_datetime($object['end']); // extend date range for recurring events - if ($object['recurrence'] && $object['_formatobj']) { + if ($object['recurrence']) { $recurrence = new kolab_date_recurrence($object['_formatobj']); $dtend = $recurrence->end() ?: new DateTime('now +100 years'); $sql_data['dtend'] = $this->_convert_datetime($dtend); diff --git a/plugins/libkolab/lib/kolab_storage_dav.php b/plugins/libkolab/lib/kolab_storage_dav.php index ac313478..e71f3d0d 100644 --- a/plugins/libkolab/lib/kolab_storage_dav.php +++ b/plugins/libkolab/lib/kolab_storage_dav.php @@ -46,9 +46,6 @@ class kolab_storage_dav */ public function setup() { - $rcmail = rcube::get_instance(); - - $this->config = $rcmail->config; $this->dav = new kolab_dav_client($this->url); } diff --git a/plugins/libkolab/lib/kolab_storage_dav_cache.php b/plugins/libkolab/lib/kolab_storage_dav_cache.php index 5dec0fa9..528b5a71 100644 --- a/plugins/libkolab/lib/kolab_storage_dav_cache.php +++ b/plugins/libkolab/lib/kolab_storage_dav_cache.php @@ -159,6 +159,7 @@ class kolab_storage_dav_cache extends kolab_storage_cache // Fetch new objects and store in DB if (!empty($new_index)) { + $i = 0; foreach (array_chunk($new_index, $chunk_size, true) as $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) { $result = $uids ? [] : new kolab_storage_dataset($this); + $count = null; $this->_read_folder_data(); diff --git a/plugins/libkolab/lib/kolab_storage_dav_cache_contact.php b/plugins/libkolab/lib/kolab_storage_dav_cache_contact.php index ed55e21e..578002c8 100644 --- a/plugins/libkolab/lib/kolab_storage_dav_cache_contact.php +++ b/plugins/libkolab/lib/kolab_storage_dav_cache_contact.php @@ -36,21 +36,20 @@ class kolab_storage_dav_cache_contact extends kolab_storage_dav_cache protected function _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')) { $sql_data['type'] = 'group'; } // 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['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'] = ''; foreach ($object as $colname => $value) { - list($col, $field) = explode(':', $colname); - if ($col == 'email' && !empty($value)) { + if (!empty($value) && ($colname == 'email' || strpos($colname, 'email:') === 0)) { $sql_data['email'] = is_array($value) ? $value[0] : $value; break; } @@ -84,14 +83,14 @@ class kolab_storage_dav_cache_contact extends kolab_storage_dav_cache $data = ''; 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)) { $val = is_array($value) ? join(' ', $value) : $value; } - if (strlen($val)) { + if (is_string($val) && strlen($val)) { $data .= $val . ' '; } } diff --git a/plugins/libkolab/lib/kolab_storage_dav_cache_event.php b/plugins/libkolab/lib/kolab_storage_dav_cache_event.php index 8e7c7e75..cd35070d 100644 --- a/plugins/libkolab/lib/kolab_storage_dav_cache_event.php +++ b/plugins/libkolab/lib/kolab_storage_dav_cache_event.php @@ -41,19 +41,14 @@ class kolab_storage_dav_cache_event extends kolab_storage_dav_cache // extend date range for recurring events 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']); + $recurrence = libcalendaring::get_recurrence($object); $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'])) { + // FIXME: This should be done via libcalendaring_recurrence use above? + if (!empty($object['exceptions']) && is_array($object['exceptions'])) { foreach ($object['exceptions'] as $exception) { if ($exception['start'] instanceof DateTimeInterface) { $exstart = $this->_convert_datetime($exception['start']); @@ -86,12 +81,18 @@ class kolab_storage_dav_cache_event extends kolab_storage_dav_cache $data = ''; 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) { $a = []; foreach ((array) $object[$col] as $attr) { - $a[] = $attr[$field]; + if (!empty($attr[$field])) { + $a[] = $attr[$field]; + } } $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]; } - if (strlen($val)) + if (is_string($val) && strlen($val)) { $data .= $val . ' '; + } } $words = rcube_utils::normalize_string($data, true); // collect words from recurrence exceptions - if (is_array($object['exceptions'])) { + if (!empty($object['exceptions']) && is_array($object['exceptions'])) { foreach ($object['exceptions'] as $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 - if (is_array($object['exceptions'])) { + if (!empty($object['exceptions']) && 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']); + $tags[] = 'x-status:' . strtolower($object['status']); } return array_unique($tags);