diff --git a/less-build.sh b/less-build.sh new file mode 100755 index 00000000..08bbace5 --- /dev/null +++ b/less-build.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# First you have to link/copy /skins directory from Roundcube repo +# into ./skins here + +# Note: You can remove -x option to generate non-minified file +# (remember to remove ".min" from the output file name) + +lessc --relative-urls -x plugins/libkolab/skins/elastic/libkolab.less > plugins/libkolab/skins/elastic/libkolab.min.css diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index a3fc6fb1..1cb36b24 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -3006,13 +3006,13 @@ class calendar extends rcube_plugin $calendar = $this->get_default_calendar($event['sensitivity'], $calendars); $metadata = array( - 'uid' => $event['uid'], + 'uid' => $event['uid'], '_instance' => $event['_instance'], - 'changed' => is_object($event['changed']) ? $event['changed']->format('U') : 0, - 'sequence' => intval($event['sequence']), - 'fallback' => strtoupper($status), - 'method' => $event['_method'], - 'task' => 'calendar', + 'changed' => is_object($event['changed']) ? $event['changed']->format('U') : 0, + 'sequence' => intval($event['sequence']), + 'fallback' => strtoupper($status), + 'method' => $event['_method'], + 'task' => 'calendar', ); // update my attendee status according to submitted method @@ -3039,9 +3039,9 @@ class calendar extends rcube_plugin if (!$reply_sender) { $sender_identity = $this->rc->user->list_emails(true); $event['attendees'][] = array( - 'name' => $sender_identity['name'], - 'email' => $sender_identity['email'], - 'role' => 'OPT-PARTICIPANT', + 'name' => $sender_identity['name'], + 'email' => $sender_identity['email'], + 'role' => 'OPT-PARTICIPANT', 'status' => strtoupper($status), ); $metadata['attendee'] = $sender_identity['email']; @@ -3067,23 +3067,27 @@ class calendar extends rcube_plugin // only update attendee status if ($event['_method'] == 'REPLY') { // try to identify the attendee using the email sender address - $existing_attendee = -1; + $existing_attendee = -1; $existing_attendee_emails = array(); + foreach ($existing['attendees'] as $i => $attendee) { $existing_attendee_emails[] = $attendee['email']; if ($this->itip->compare_email($attendee['email'], $event['_sender'], $event['_sender_utf'])) { $existing_attendee = $i; } } - $event_attendee = null; + + $event_attendee = null; $update_attendees = array(); + foreach ($event['attendees'] as $attendee) { if ($this->itip->compare_email($attendee['email'], $event['_sender'], $event['_sender_utf'])) { - $event_attendee = $attendee; - $update_attendees[] = $attendee; + $event_attendee = $attendee; + $update_attendees[] = $attendee; $metadata['fallback'] = $attendee['status']; $metadata['attendee'] = $attendee['email']; - $metadata['rsvp'] = $attendee['rsvp'] || $attendee['role'] != 'NON-PARTICIPANT'; + $metadata['rsvp'] = $attendee['rsvp'] || $attendee['role'] != 'NON-PARTICIPANT'; + if ($attendee['status'] != 'DELEGATED') { break; } @@ -3109,6 +3113,23 @@ class calendar extends rcube_plugin } } + // Accept sender as a new participant (different email in From: and the iTip) + // Use ATTENDEE entry from the iTip with replaced email address + if (!$event_attendee) { + // remove the organizer + $itip_attendees = array_filter($event['attendees'], function($item) { return $item['role'] != 'ORGANIZER'; }); + + // there must be only one attendee + if (is_array($itip_attendees) && count($itip_attendees) == 1) { + $event_attendee = $itip_attendees[key($itip_attendees)]; + $event_attendee['email'] = $event['_sender']; + $update_attendees[] = $event_attendee; + $metadata['fallback'] = $event_attendee['status']; + $metadata['attendee'] = $event_attendee['email']; + $metadata['rsvp'] = $event_attendee['rsvp'] || $event_attendee['role'] != 'NON-PARTICIPANT'; + } + } + // found matching attendee entry in both existing and new events if ($existing_attendee >= 0 && $event_attendee) { $existing['attendees'][$existing_attendee] = $event_attendee; @@ -3119,6 +3140,9 @@ class calendar extends rcube_plugin $existing['attendees'][] = $event_attendee; $success = $this->driver->update_attendees($existing, $update_attendees); } + else if (!$event_attendee) { + $error_msg = $this->gettext('errorunknownattendee'); + } else { $error_msg = $this->gettext('newerversionexists'); } diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index f0b3b8e0..03f41951 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -770,7 +770,7 @@ function rcube_calendar_ui(settings) // basic input validatetion if (start.getTime() > end.getTime()) { - alert(rcmail.gettext('invalideventdates', 'calendar')); + rcmail.alert_dialog(rcmail.gettext('invalideventdates', 'calendar')); return false; } @@ -1757,7 +1757,7 @@ function rcube_calendar_ui(settings) break; } } - + // update event date/time display if (success) { update_freebusy_dates(event.start, event.end); @@ -1779,7 +1779,7 @@ function rcube_calendar_ui(settings) rcmail.display_message(rcmail.gettext('suggestedslot', 'calendar') + ': ' + me.event_date_text(event, true), 'voice'); } else { - alert(rcmail.gettext('noslotfound','calendar')); + rcmail.alert_dialog(rcmail.gettext('noslotfound','calendar')); } }; @@ -1826,7 +1826,7 @@ function rcube_calendar_ui(settings) success = true; } else { - alert(rcmail.gettext('noemailwarning')); + rcmail.alert_dialog(rcmail.gettext('noemailwarning')); } } @@ -2004,7 +2004,7 @@ function rcube_calendar_ui(settings) { text: rcmail.gettext('addresource', 'calendar'), 'class': 'mainaction save', - click: function() { rcmail.command('add-resource'); } + click: function() { rcmail.command('add-resource'); $dialog.dialog("close"); } }, { text: rcmail.gettext('close'), @@ -2013,23 +2013,33 @@ function rcube_calendar_ui(settings) } ]; + var resize = function() { + var container = $(rcmail.gui_objects.resourceinfocalendar); + container.fullCalendar('option', 'height', container.height() + 1); + }; + // open jquery UI dialog $dialog.dialog({ modal: true, - resizable: true, + resizable: false, // prevents from Availability tab reflow bugs on resize closeOnEscape: true, title: rcmail.gettext('findresources', 'calendar'), + classes: {'ui-dialog': 'selection-dialog resources-dialog'}, open: function() { rcmail.ksearch_blur(); $dialog.attr('aria-hidden', 'false'); + + // for Elastic + if ($('html.layout-small,html.layout-phone').length) { + $('#eventresourcesdialog .resource-selection').css('display', 'flex'); + $('#eventresourcesdialog .resource-content').css('display', 'none'); + } + setTimeout(resize, 50); }, close: function() { $dialog.dialog('destroy').attr('aria-hidden', 'true').hide(); }, - resize: function(e) { - var container = $(rcmail.gui_objects.resourceinfocalendar); - container.fullCalendar('option', 'height', container.height() + 4); - }, + resize: resize, buttons: buttons, width: 900, height: 500 @@ -2055,6 +2065,14 @@ function rcube_calendar_ui(settings) if (resources_data[node.id]) { resource_showinfo(resources_data[node.id]); rcmail.enable_command('add-resource', me.selected_event && $("#eventedit").is(':visible') ? true : false); + + // on elastic mobile display resource info box + if ($('html.layout-small,html.layout-phone').length) { + $('#eventresourcesdialog .resource-selection').css('display', 'none'); + $('#eventresourcesdialog .resource-content').css('display', 'flex'); + $(window).resize(); + resize(); + } } else { rcmail.enable_command('add-resource', false); @@ -2130,9 +2148,9 @@ function rcube_calendar_ui(settings) for (var k in attribs) { if (typeof attribs[k] == 'undefined') continue; - table.append($('').addClass(k) - .append('' + Q(ucfirst(rcmail.get_label(k, 'calendar'))) + '') - .append('' + text2html(render_attrib(attribs[k])) + '') + table.append($('').addClass(k + ' form-group row') + .append('') + .append('' + text2html(render_attrib(attribs[k])) + '') ); } @@ -2388,7 +2406,7 @@ function rcube_calendar_ui(settings) } else { me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata'); - rcmail.http_post('event', { action:'rsvp', e:submit_data, status:response, attendees:attendees, noreply:noreply }); + rcmail.http_post('calendar/event', { action:'rsvp', e:submit_data, status:response, attendees:attendees, noreply:noreply }); } event_show_dialog(me.selected_event); @@ -4050,12 +4068,17 @@ function rcube_calendar_ui(settings) input.val(''); } }); - + $('#edit-resource-find').click(function(){ event_resources_dialog(); return false; }); + $('#resource-content a.nav-link').on('click', function() { + e.preventDefault(); + $(this).tab('show'); + }); + // handle change of "send invitations" checkbox $('#edit-attendees-invite').change(function() { $('#edit-attendees-donotify,input.edit-attendee-reply').prop('checked', this.checked); diff --git a/plugins/calendar/composer.json b/plugins/calendar/composer.json index 6936c79d..44da959f 100644 --- a/plugins/calendar/composer.json +++ b/plugins/calendar/composer.json @@ -4,7 +4,7 @@ "description": "Calendar plugin", "homepage": "https://git.kolab.org/diffusion/RPK/", "license": "AGPLv3", - "version": "3.3.5", + "version": "3.4.0", "authors": [ { "name": "Thomas Bruederli", @@ -26,7 +26,7 @@ "require": { "php": ">=5.3.0", "roundcube/plugin-installer": ">=0.1.3", - "kolab/libcalendaring": ">=3.3.0", - "kolab/libkolab": ">=3.3.0" + "kolab/libcalendaring": ">=3.4.0", + "kolab/libkolab": ">=3.4.0" } } diff --git a/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php b/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php index b41e2964..ac676796 100644 --- a/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php @@ -234,7 +234,7 @@ class kolab_invitation_calendar $events = array(); foreach (kolab_storage::list_folders('', '*', 'event', null) as $foldername) { $cal = $this->_get_calendar($foldername); - if ($cal->get_namespace() == 'other') + if (!$cal || $cal->get_namespace() == 'other') continue; foreach ($cal->list_events($start, $end, $search, 1, $query, array(array($subquery, 'OR'))) as $event) { @@ -288,7 +288,7 @@ class kolab_invitation_calendar $count = 0; foreach (kolab_storage::list_folders('', '*', 'event', null) as $foldername) { $cal = $this->_get_calendar($foldername); - if ($cal->get_namespace() == 'other') + if (!$cal || $cal->get_namespace() == 'other') continue; $count += $cal->count_events($start, $end, $filter); diff --git a/plugins/calendar/drivers/kolab/kolab_user_calendar.php b/plugins/calendar/drivers/kolab/kolab_user_calendar.php index 62e51414..185b6099 100644 --- a/plugins/calendar/drivers/kolab/kolab_user_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_user_calendar.php @@ -38,7 +38,8 @@ class kolab_user_calendar extends kolab_calendar */ public function __construct($user_or_folder, $calendar) { - $this->cal = $calendar; + $this->cal = $calendar; + $this->imap = $calendar->rc->get_storage(); // full user record is provided if (is_array($user_or_folder)) { diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php index 23b56cdc..72be0f91 100644 --- a/plugins/calendar/lib/calendar_ui.php +++ b/plugins/calendar/lib/calendar_ui.php @@ -805,20 +805,16 @@ class calendar_ui */ function resources_search_form($attrib) { - $attrib += array('command' => 'search-resource', 'id' => 'rcmcalresqsearchbox', 'autocomplete' => 'off'); - $attrib['name'] = '_q'; - - $input_q = new html_inputfield($attrib); - $out = $input_q->show(); + $attrib += array( + 'command' => 'search-resource', + 'reset-command' => 'reset-resource-search', + 'id' => 'rcmcalresqsearchbox', + 'autocomplete' => 'off', + 'form-name' => 'rcmcalresoursqsearchform', + ); // add form tag around text field - $out = $this->rc->output->form_tag(array( - 'name' => "rcmcalresoursqsearchform", - 'onsubmit' => rcmail_output::JS_OBJECT_NAME . ".command('" . $attrib['command'] . "'); return false", - 'style' => "display:inline"), - $out); - - return $out; + return $this->rc->output->search_form($attrib); } /** diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index 5d9bc093..7219f08d 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -222,6 +222,7 @@ $labels['addresource'] = 'Book resource'; $labels['findresources'] = 'Find resources'; $labels['resourcedetails'] = 'Details'; $labels['resourceavailability'] = 'Availability'; +$labels['resourceprops'] = 'Resource properties'; $labels['resourceowner'] = 'Owner'; $labels['resourceadded'] = 'The resource was added to your event'; @@ -254,6 +255,7 @@ $labels['nowritecalendarfound'] = 'No calendar found to save the event'; $labels['importedsuccessfully'] = 'The event was successfully added to \'$calendar\''; $labels['updatedsuccessfully'] = 'The event was successfully updated in \'$calendar\''; $labels['attendeupdateesuccess'] = 'Successfully updated the participant\'s status'; +$labels['errorunknownattendee'] = 'Failed to find the participant information.'; $labels['itipsendsuccess'] = 'Invitation sent to participants.'; $labels['itipresponseerror'] = 'Failed to send the response to this event invitation'; $labels['itipinvalidrequest'] = 'This invitation is no longer valid'; diff --git a/plugins/calendar/skins/elastic/templates/calendar.html b/plugins/calendar/skins/elastic/templates/calendar.html index 0265319c..2d562fa6 100644 --- a/plugins/calendar/skins/elastic/templates/calendar.html +++ b/plugins/calendar/skins/elastic/templates/calendar.html @@ -8,18 +8,8 @@
- +
@@ -136,6 +126,10 @@
+
+ + +
@@ -144,9 +138,6 @@
-
-
-