Projects
Kolab:Winterfell
roundcubemail-plugins-kolab
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 113
View file
roundcubemail-plugins-kolab.spec
Changed
@@ -41,9 +41,9 @@ %global dash_rel_suffix %{?rc_rel_suffix:-%{rc_rel_suffix}} Name: roundcubemail-plugins-kolab -Version: 3.4.3 +Version: 3.4.4 -Release: 2%{?dot_rel_suffix}%{?dist} +Release: 1%{?dot_rel_suffix}%{?dist} Summary: Kolab Groupware plugins for Roundcube Webmail @@ -63,10 +63,6 @@ Patch1001: roundcubemail-plugins-kolab-3.4-kolab-files-manticore-api.patch -Patch0002: 0002-Calendar-Fix-regression-where-changing-attendee-stat.patch -Patch0006: 0006-Calendar-Fix-literal-raquo-in-calendar-name-in-event.patch -Patch0011: 0011-Calendar-Fix-invalid-time-error-when-using-time-form.patch - BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) BuildArch: noarch @@ -1411,10 +1407,6 @@ %patch1001 -p1 -%patch0002 -p1 -%patch0006 -p1 -%patch0011 -p1 - find -type d -name "helpdocs" -exec rm -rvf {} \; 2>/dev/null || : rm -rf plugins/kolab_zpush @@ -2800,6 +2792,9 @@ %defattr(-,root,root,-) %changelog +* Mon Apr 15 2019 Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> - 3.4.4-1 +- Release of version 3.4.4 + * Thu Mar 28 2019 Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> - 3.4.3-3 - Fix literal in calendar name event - Fix calendar event attendee change regression
View file
0002-Calendar-Fix-regression-where-changing-attendee-stat.patch
Deleted
@@ -1,89 +0,0 @@ -From a71caa9a51ef83a5673005fa792f2d671a47fcae Mon Sep 17 00:00:00 2001 -From: Aleksander Machniak <machniak@kolabsys.com> -Date: Thu, 14 Mar 2019 15:08:53 +0000 -Subject: [PATCH 02/11] Calendar: Fix regression where changing attendee status - for an existing event wasn't working - -Also fixed bug where allday event accourrence could have been moved -one day back when changing the attendee status (action=rsvp). ---- - plugins/calendar/calendar_ui.js | 9 ++++++- - .../libkolab/lib/kolab_date_recurrence.php | 26 ++++++++++++++++++- - 2 files changed, 33 insertions(+), 2 deletions(-) - -diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js -index 812bf492..20f6cf41 100644 ---- a/plugins/calendar/calendar_ui.js -+++ b/plugins/calendar/calendar_ui.js -@@ -2446,9 +2446,16 @@ function rcube_calendar_ui(settings) - } - - // submit status change to server -- var submit_data = $.extend({}, me.selected_event, { source:null, comment:$('#reply-comment-event-rsvp').val(), _savemode: replymode || 'all' }, (delegate || {})), -+ var submit_data = $.extend({}, { source:null, comment:$('#reply-comment-event-rsvp').val(), _savemode: replymode || 'all' }, (delegate || {})), -+ submit_items = 'id,uid,_instance,calendar,_mbox,_uid,_part,attendees,free_busy,allDay', - noreply = $('#noreply-event-rsvp:checked').length ? 1 : 0; - -+ // Submit only that data we really need -+ $.each(submit_items.split(','), function() { -+ if (this in me.selected_event) -+ submit_data[this] = me.selected_event[this]; -+ }); -+ - // import event from mail (temporary iTip event) - if (submit_data._mbox && submit_data._uid) { - me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata'); -diff --git a/plugins/libkolab/lib/kolab_date_recurrence.php b/plugins/libkolab/lib/kolab_date_recurrence.php -index 2ec06c8a..5ab4532e 100644 ---- a/plugins/libkolab/lib/kolab_date_recurrence.php -+++ b/plugins/libkolab/lib/kolab_date_recurrence.php -@@ -32,6 +32,9 @@ class kolab_date_recurrence - private /* DateTime */ $next; - private /* cDateTime */ $cnext; - private /* DateInterval */ $duration; -+ private /* string */ $start_time; -+ private /* string */ $end_time; -+ - - /** - * Default constructor -@@ -47,6 +50,13 @@ class kolab_date_recurrence - $this->start = $this->next = $data['start']; - $this->cnext = kolab_format::get_datetime($this->next); - -+ if ($this->start && !empty($data['allday'])) { -+ $this->start_time = $data['start']->format('H:i:s'); -+ if ($data['end']) { -+ $this->end_time = $data['end']->format('H:i:s'); -+ } -+ } -+ - if (is_object($data['start']) && is_object($data['end'])) { - $this->duration = $data['start']->diff($data['end']); - } -@@ -91,7 +101,21 @@ class kolab_date_recurrence - $next_end = clone $next_start; - $next_end->add($this->duration); - -- $next = $this->object->to_array(); -+ $next = $this->object->to_array(); -+ -+ // it looks that for allday events the occurrence time -+ // is reset to 00:00:00, this is causing various issues -+ if (!empty($next['allday'])) { -+ if ($this->start_time) { -+ $time = explode(':', $this->start_time); -+ $next_start->setTime((int)$time[0], (int)$time[1], (int)$time[2]); -+ } -+ if ($this->start_end) { -+ $time = explode(':', $this->start_end); -+ $next_end->setTime((int)$time[0], (int)$time[1], (int)$time[2]); -+ } -+ } -+ - $next['start'] = $next_start; - $next['end'] = $next_end; - --- -2.20.1 -
View file
0006-Calendar-Fix-literal-raquo-in-calendar-name-in-event.patch
Deleted
@@ -1,26 +0,0 @@ -From bd62884ea230d8816b545282ac6ed7eee43fbab5 Mon Sep 17 00:00:00 2001 -From: Aleksander Machniak <machniak@kolabsys.com> -Date: Mon, 18 Mar 2019 15:32:35 +0000 -Subject: [PATCH 06/11] Calendar: Fix literal "»" in calendar name in - event dialog - ---- - plugins/calendar/calendar_ui.js | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js -index 20f6cf41..0280ccec 100644 ---- a/plugins/calendar/calendar_ui.js -+++ b/plugins/calendar/calendar_ui.js -@@ -397,7 +397,7 @@ function rcube_calendar_ui(settings) - if (event.valarms && event.alarms_text) - $('#event-alarm').show().find('.event-text').html(Q(event.alarms_text).replace(',', ',<br>')); - if (calendar.name) -- $('#event-calendar').show().find('.event-text').text(calendar.name).addClass('cal-'+calendar.id); -+ $('#event-calendar').show().find('.event-text').html(Q(calendar.name)).addClass('cal-'+calendar.id); - if (event.categories) - $('#event-category').show().find('.event-text').text(event.categories).addClass('cat-'+String(event.categories).toLowerCase().replace(rcmail.identifier_expr, '')); - if (event.free_busy) --- -2.20.1 -
View file
0011-Calendar-Fix-invalid-time-error-when-using-time-form.patch
Deleted
@@ -1,26 +0,0 @@ -From 54d171e10f0d116d3fd007189805ed49923a003a Mon Sep 17 00:00:00 2001 -From: Aleksander Machniak <machniak@kolabsys.com> -Date: Fri, 22 Mar 2019 12:22:10 +0000 -Subject: [PATCH 11/11] Calendar: Fix "invalid time" error when using time - format without a leading zero - ---- - plugins/calendar/calendar_ui.js | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js -index 0280ccec..6db0ad79 100644 ---- a/plugins/calendar/calendar_ui.js -+++ b/plugins/calendar/calendar_ui.js -@@ -812,7 +812,7 @@ function rcube_calendar_ui(settings) - save_func = function() { - var start = allday.checked ? '12:00' : $.trim(starttime.val()), - end = allday.checked ? '13:00' : $.trim(endtime.val()), -- re = /^((0[0-9])|(1[0-9])|(2[0-3])):([0-5][0-9])(\s*[ap]\.?m\.?)?$/i; -+ re = /^((0?[0-9])|(1[0-9])|(2[0-3])):([0-5][0-9])(\s*[ap]\.?m\.?)?$/i; - - if (!re.test(start) || !re.test(end)) { - rcmail.alert_dialog(rcmail.gettext('invalideventdates', 'calendar')); --- -2.17.2 -
View file
debian.changelog
Changed
@@ -1,3 +1,9 @@ +roundcubemail-plugins-kolab (1:3.4.4-0~kolab1) unstable; urgency=low + + * Release of version 3.4.4 + + -- Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Mon, 15 Apr 2019 11:11:11 +0200 + roundcubemail-plugins-kolab (1:3.4.3-0~kolab3) unstable; urgency=low * Fix invalid time error on formats without a leading zero
View file
debian.series
Changed
@@ -1,4 +1,1 @@ roundcubemail-plugins-kolab-3.4-kolab-files-manticore-api.patch -p1 -0002-Calendar-Fix-regression-where-changing-attendee-stat.patch -p1 -0006-Calendar-Fix-literal-raquo-in-calendar-name-in-event.patch -p1 -0011-Calendar-Fix-invalid-time-error-when-using-time-form.patch -p1
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/calendar/calendar.php -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/calendar/calendar.php
Changed
@@ -918,10 +918,6 @@ $event = rcube_utils::get_input_value('e', rcube_utils::INPUT_POST, true); $success = $reload = $got_msg = false; - // force notify if hidden + active - if ((int)$this->rc->config->get('calendar_itip_send_option', $this->defaults['calendar_itip_send_option']) === 1) - $event['_notify'] = 1; - // read old event data in order to find changes if (($event['_notify'] || $event['_decline']) && $action != 'new') { $old = $this->driver->get_event($event);
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/calendar/calendar_ui.js -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/calendar/calendar_ui.js
Changed
@@ -397,7 +397,7 @@ if (event.valarms && event.alarms_text) $('#event-alarm').show().find('.event-text').html(Q(event.alarms_text).replace(',', ',<br>')); if (calendar.name) - $('#event-calendar').show().find('.event-text').text(calendar.name).addClass('cal-'+calendar.id); + $('#event-calendar').show().find('.event-text').html(Q(calendar.name)).addClass('cal-'+calendar.id); if (event.categories) $('#event-category').show().find('.event-text').text(event.categories).addClass('cat-'+String(event.categories).toLowerCase().replace(rcmail.identifier_expr, '')); if (event.free_busy) @@ -565,7 +565,7 @@ open: function() { $dialog.attr('aria-hidden', 'false'); setTimeout(function(){ - $dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus(); + $dialog.parent().find('button:not(.ui-dialog-titlebar-close)').first().focus(); }, 5); }, beforeClose: function(e) { @@ -812,7 +812,7 @@ save_func = function() { var start = allday.checked ? '12:00' : $.trim(starttime.val()), end = allday.checked ? '13:00' : $.trim(endtime.val()), - re = /^((0[0-9])|(1[0-9])|(2[0-3])):([0-5][0-9])(\s*[ap]\.?m\.?)?$/i; + re = /^((0?[0-9])|(1[0-9])|(2[0-3])):([0-5][0-9])(\s*[ap]\.?m\.?)?$/i; if (!re.test(start) || !re.test(end)) { rcmail.alert_dialog(rcmail.gettext('invalideventdates', 'calendar')); @@ -1154,7 +1154,7 @@ open: function() { $dialog.attr('aria-hidden', 'false'); setTimeout(function(){ - $dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus(); + $dialog.parent().find('button:not(.ui-dialog-titlebar-close)').first().focus(); }, 5); }, close: function() { @@ -1277,12 +1277,10 @@ return false; }); - + // enable/disable buttons - // FIXME: .button() does nothing in Elastic skin - var disabled = fb_start.getTime() < now.getTime(); - $('#schedule-find-prev').button('option', 'disabled', disabled).prop('disabled', disabled); - + $('#schedule-find-prev').prop('disabled', fb_start.getTime() < now.getTime()); + // dialog buttons var buttons = [ { @@ -1829,7 +1827,8 @@ // move freebusy grid if necessary var event_start = 'toDate' in event.start ? event.start.toDate() : event.start, event_end = 'toDate' in event.end ? event.end.toDate() : event.end, - offset = Math.ceil((event_start.getTime() - freebusy_ui.end.getTime()) / DAY_MS); + offset = Math.ceil((event_start.getTime() - freebusy_ui.end.getTime()) / DAY_MS), + now = new Date(); if (event_start.getTime() >= freebusy_ui.end.getTime()) render_freebusy_grid(Math.max(1, offset)); @@ -1838,9 +1837,7 @@ else render_freebusy_overlay(); - var now = new Date(), disabled = event_start.getTime() < now.getTime(); - // FIXME: .button() does nothing in Elastic skin - $('#schedule-find-prev').button('option', 'disabled', disabled).prop('disabled', disabled); + $('#schedule-find-prev').prop('disabled', event_start.getTime() < now.getTime()); // speak new selection rcmail.display_message(rcmail.gettext('suggestedslot', 'calendar') + ': ' + me.event_date_text(event, true), 'voice'); @@ -2110,7 +2107,7 @@ height: 500 }).show(); - $('.ui-dialog-buttonset .ui-button', $dialog.parent()).first().attr('id', 'rcmbtncalresadd'); + $('.ui-dialog-buttonset button', $dialog.parent()).first().attr('id', 'rcmbtncalresadd'); me.dialog_resize($dialog.get(0), 540, Math.min(1000, $(window).width() - 50)); @@ -2129,7 +2126,7 @@ resources_treelist.addEventListener('select', function(node) { if (resources_data[node.id]) { resource_showinfo(resources_data[node.id]); - rcmail.enable_command('add-resource', me.selected_event && $("#eventedit").is(':visible') ? true : false); + rcmail.enable_command('add-resource', me.selected_event && $("#eventedit").is(':visible')); // on elastic mobile display resource info box if ($('html.layout-small,html.layout-phone').length) { @@ -2152,7 +2149,7 @@ rcmail.http_request('resources-list', {}, me.loading_lock); // register button - rcmail.register_button('add-resource', 'rcmbtncalresadd', 'uibutton'); + rcmail.register_button('add-resource', 'rcmbtncalresadd', 'button'); // initialize resource calendar display var resource_cal = $(rcmail.gui_objects.resourceinfocalendar); @@ -2373,10 +2370,8 @@ var add_resource2event = function() { var resource = resources_data[resources_treelist.get_selection()]; - if (resource) { - if (add_attendee($.extend({ role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' }, resource))) - rcmail.display_message(rcmail.get_label('resourceadded', 'calendar'), 'confirmation'); - } + if (resource) + add_attendee($.extend({ role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' }, resource)); } // when the user accepts or declines an event invitation @@ -2446,9 +2441,16 @@ } // submit status change to server - var submit_data = $.extend({}, me.selected_event, { source:null, comment:$('#reply-comment-event-rsvp').val(), _savemode: replymode || 'all' }, (delegate || {})), + var submit_data = $.extend({}, { source:null, comment:$('#reply-comment-event-rsvp').val(), _savemode: replymode || 'all' }, (delegate || {})), + submit_items = 'id,uid,_instance,calendar,_mbox,_uid,_part,attendees,free_busy,allDay', noreply = $('#noreply-event-rsvp:checked').length ? 1 : 0; + // Submit only that data we really need + $.each(submit_items.split(','), function() { + if (this in me.selected_event) + submit_data[this] = me.selected_event[this]; + }); + // import event from mail (temporary iTip event) if (submit_data._mbox && submit_data._uid) { me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata'); @@ -2613,6 +2615,10 @@ data._notify = settings.itip_notify; } } + else if (cal.group != 'shared') { + html += '<div class="message dialog-message ui alert boxwarning">' + $('#edit-localchanges-warning').html() + '</div>'; + data._notify = 0; + } } // recurring event: user needs to select the savemode @@ -2633,14 +2639,13 @@ (action != 'remove' ? '<a href="#new" class="button btn btn-secondary">' + rcmail.gettext('saveasnew', 'calendar') + '</a>' : '') + '</div>'; } - + // show dialog if (html) { var $dialog = $('<div>').html(html); - - $dialog.find('a.button').button().filter(':not(.disabled)').click(function(e) { + + $dialog.find('a.button').filter(':not(.disabled)').click(function(e) { data._savemode = String(this.href).replace(/.+#/, ''); - data._notify = settings.itip_notify; // open event edit dialog when saving as new if (data._savemode == 'new') { @@ -2661,7 +2666,7 @@ $dialog.dialog("close"); return false; }); - + var buttons = []; if (!event.recurrence) { @@ -2693,7 +2698,7 @@ buttons: buttons, open: function() { setTimeout(function(){ - $dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus(); + $dialog.parent().find('button:not(.ui-dialog-titlebar-close)').first().focus(); }, 5); }, close: function(){ @@ -3008,7 +3013,7 @@ if (form && form.elements._data.value) { rcmail.async_upload_form(form, 'import_events', function(e) { rcmail.set_busy(false, null, me.saving_lock); - $('.ui-dialog-buttonpane button', $dialog.parent()).button('enable'); + $('.ui-dialog-buttonpane button', $dialog.parent()).prop('disabled', false); // display error message if no sophisticated response from server arrived (e.g. iframe load error) if (me.import_succeeded === null) @@ -3020,7 +3025,7 @@ rcmail.env.request_timeout = 600; me.import_succeeded = null; me.saving_lock = rcmail.set_busy(true, 'uploading'); - $('.ui-dialog-buttonpane button', $dialog.parent()).button('disable'); + $('.ui-dialog-buttonpane button', $dialog.parent()).prop('disabled', true); // restore settings rcmail.env.request_timeout = timeout; @@ -3041,7 +3046,7 @@ closeOnEscape: false, title: rcmail.gettext('importevents', 'calendar'), close: function() { - $('.ui-dialog-buttonpane button', $dialog.parent()).button('enable'); + $('.ui-dialog-buttonpane button', $dialog.parent()).prop('disabled', false); $dialog.dialog("destroy").hide(); }, buttons: buttons, @@ -3124,7 +3129,7 @@ closeOnEscape: false, title: rcmail.gettext('exporttitle', 'calendar'), close: function() { - $('.ui-dialog-buttonpane button', $dialog.parent()).button('enable'); + $('.ui-dialog-buttonpane button', $dialog.parent()).prop('disabled', false); $dialog.dialog("destroy").hide(); }, buttons: buttons, @@ -4045,11 +4050,11 @@ event_freebusy_dialog(); }); - $('#schedule-freebusy-prev').html('◄').button().click(function(){ render_freebusy_grid(-1); }); - $('#schedule-freebusy-next').html('►').button().click(function(){ render_freebusy_grid(1); }); // FIXME .parent().buttonset(); + $('#schedule-freebusy-prev').html('◄').click(function() { render_freebusy_grid(-1); }); + $('#schedule-freebusy-next').html('►').click(function() { render_freebusy_grid(1); }); - $('#schedule-find-prev').button().click(function(){ freebusy_find_slot(-1); }); - $('#schedule-find-next').button().click(function(){ freebusy_find_slot(1); }); + $('#schedule-find-prev').click(function() { freebusy_find_slot(-1); }); + $('#schedule-find-next').click(function() { freebusy_find_slot(1); }); $('#schedule-freebusy-workinghours').click(function(){ freebusy_ui.workinhoursonly = this.checked;
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/calendar/composer.json -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/calendar/composer.json
Changed
@@ -4,7 +4,7 @@ "description": "Calendar plugin", "homepage": "https://git.kolab.org/diffusion/RPK/", "license": "AGPLv3", - "version": "3.4.3", + "version": "3.4.4", "authors": [ { "name": "Thomas Bruederli",
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/calendar/drivers/kolab/kolab_driver.php -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/calendar/drivers/kolab/kolab_driver.php
Changed
@@ -169,7 +169,7 @@ 'editname' => $cal->get_foldername(), 'color' => $cal->get_color(), 'active' => $cal->is_active(), - 'title' => $cal->get_owner(), + 'title' => $cal->get_title(), 'owner' => $cal->get_owner(), 'history' => false, 'virtual' => false,
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_2fa/composer.json -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_2fa/composer.json
Changed
@@ -4,7 +4,7 @@ "description": "Kolab 2-Factor Authentication", "homepage": "https://git.kolab.org/diffusion/RPK/", "license": "AGPLv3", - "version": "3.4.1", + "version": "3.4.4", "authors": [ { "name": "Thomas Bruederli",
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_2fa/kolab2fa.js -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_2fa/kolab2fa.js
Changed
@@ -100,7 +100,7 @@ open: function(event, ui) { $(event.target).find('input[name="_verify_code"]').keypress(function(e) { if (e.which == 13) { - $(e.target).closest('.ui-dialog').find('.ui-button.mainaction').click(); + $(e.target).closest('.ui-dialog').find('button.mainaction').click(); } }); }, @@ -242,7 +242,7 @@ // submit code on <Enter> $(event.target).find('input[name="_code"]').keypress(function(e) { if (e.which == 13) { - $(e.target).closest('.ui-dialog').find('.ui-button.mainaction').click(); + $(e.target).closest('.ui-dialog').find('button.mainaction').click(); } }).select(); },
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_auth/composer.json -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_auth/composer.json
Changed
@@ -4,7 +4,7 @@ "description": "Kolab authentication", "homepage": "https://git.kolab.org/diffusion/RPK/", "license": "AGPLv3", - "version": "3.4.3", + "version": "3.4.4", "authors": [ { "name": "Thomas Bruederli",
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_auth/kolab_auth_ldap.php -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_auth/kolab_auth_ldap.php
Changed
@@ -176,7 +176,7 @@ return; } - if ($rec = $this->get_entry($dn)) { + if ($rec = $this->get_entry($dn, $this->attributes)) { $rec = rcube_ldap_generic::normalize_entry($rec); $rec = $this->field_mapping($dn, $rec); }
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_files/composer.json -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_files/composer.json
Changed
@@ -4,7 +4,7 @@ "description": "User interface for Kolab File Manager (Chwala)", "homepage": "https://git.kolab.org/diffusion/RPK/", "license": "AGPLv3", - "version": "3.4.3", + "version": "3.4.4", "authors": [ { "name": "Aleksander Machniak",
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_files/kolab_files.js -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_files/kolab_files.js
Changed
@@ -778,7 +778,7 @@ create_func = function(dialog, editaction) { var sel, folder = select.val(), type = type_select.val(), name = input.val(); - if (!name || !folder) + if (!name || !folder || !file_api.is_writable(folder)) return; if (!/\.[a-z0-9]{1,5}$/.test(name)) { @@ -951,7 +951,7 @@ // Handle form submit with Enter key, click first dialog button instead function kolab_dialog_submit_handler() { - $(this).parents('.ui-dialog').find('.ui-button').first().click(); + $(this).parents('.ui-dialog').find('.ui-dialog-buttonpane button').first().click(); return false; }; @@ -2103,7 +2103,6 @@ { this.requests = {}; this.uploads = []; - this.workers = {}; this.list_updates = 0; /* @@ -2255,7 +2254,7 @@ }); // add Sessions entry - if (rcmail.task == 'files' && !rcmail.env.action && rcmail.env.files_caps && rcmail.env.files_caps.DOCEDIT) { + if (rcmail.task == 'files' && !rcmail.env.action && this.env.caps && this.env.caps.DOCEDIT) { rows.push($('<li class="mailbox collection sessions"></li>') .attr('id', 'rcmli' + rcmail.html_identifier_encode('folder-collection-sessions')) .append($('<a class="name"></a>').text(rcmail.gettext('kolab_files.sessions'))) @@ -2333,7 +2332,7 @@ // do not support fast folders list if (rcmail.env.files_api_version > 4) { var ref = this; - $.each(rcmail.env.files_caps.MOUNTPOINTS || [], function(k, v) { + $.each(this.env.caps.MOUNTPOINTS || [], function(k, v) { if (!v.FAST_FOLDER_LIST) ref.folder_list({level: 2, folder: k}, true); }); @@ -2812,7 +2811,7 @@ }; // folder create response handler - this.folder_rename_response = function(response, data) + this.folder_rename_response = function(response, params) { if (!this.response(response)) return; @@ -2820,8 +2819,15 @@ this.display_message('kolab_files.folderupdatenotice', 'confirmation'); // refresh folders and files list - if (this.env.folder == data.folder) - this.env.folder = data['new']; + if (this.env.folder == params.folder) + this.env.folder = params['new']; + + // Removed mount point, refresh capabilities stored in session + if (this.env.caps.MOUNTPOINTS[params.folder]) { + this.env.caps.MOUNTPOINTS[params['new']] = this.env.caps.MOUNTPOINTS[params.folder]; + delete this.env.caps.MOUNTPOINTS[params.folder]; + rcmail.http_post('files/reset', {}); + } // TODO: Don't reload the whole list this.folder_list(); @@ -2843,7 +2849,11 @@ this.display_message('kolab_files.foldermountnotice', 'confirmation'); if (response.result.capabilities) { - rcmail.env.files_caps.MOUNTPOINTS[params.folder] = response.result.capabilities; + // we make sure the result is an object not array + // when the list is empty it is an array, because of how works JSON encoding from PHP + var add = {}; + add[params.folder] = response.result.capabilities; + this.env.caps.MOUNTPOINTS = $.extend({}, this.env.caps.MOUNTPOINTS, add); } // Refresh capabilities stored in session @@ -2875,8 +2885,8 @@ } // Removed mount point, refresh capabilities stored in session - if (rcmail.env.files_caps.MOUNTPOINTS[params.folder]) { - delete rcmail.env.files_caps.MOUNTPOINTS[params.folder]; + if (this.env.caps.MOUNTPOINTS[params.folder]) { + delete this.env.caps.MOUNTPOINTS[params.folder]; rcmail.http_post('files/reset', {}); } @@ -3014,7 +3024,7 @@ // quota request this.quota = function() { - if (rcmail.env.files_quota && (this.env.folder || !rcmail.env.files_caps.NOROOT)) + if (rcmail.env.files_quota && (this.env.folder || !this.env.caps.NOROOT)) this.request('quota', {folder: this.env.folder}, 'quota_response'); }; @@ -3033,6 +3043,8 @@ if (!rcmail.gui_objects.sessionslist) return; + this.file_list_abort(true); + if (!params) params = {}; @@ -3081,23 +3093,11 @@ if (!rcmail.gui_objects.fileslist) return; + this.file_list_abort(true); + if (!params) params = {}; - // reset all pending list requests - $.each(this.requests, function(i, v) { - v.abort(); - rcmail.hide_message(i); - }); - - // reset folder_info workers - $.each(this.workers, function(i, v) { - clearTimeout(v); - }); - - this.workers = {}; - this.requests = {}; - if (params.all_folders) { params.collection = null; params.folder = null; @@ -3138,7 +3138,7 @@ if (params.collection || params.all_folders) this.file_list_loop(params); else if (this.env.folder) { - params.req_id = this.set_busy(true, 'loading'); + params.req_id = this.env.file_list_req_id = this.set_busy(true, 'loading'); this.requests[params.req_id] = this.request('file_list', params, 'file_list_response'); } }; @@ -3171,10 +3171,30 @@ this.request('folder_info', {folder: this.file_path(list[0].filename), sessions: 1}, 'folder_info_response'); }; + this.file_list_abort = function(all) + { + if (all) { + clearTimeout(this.file_list_worker); + this.file_list_worker = null; + } + + // reset all pending list requests + $.each(this.requests, function(i, v) { + v.abort(); + rcmail.hide_message(i); + }); + + this.requests = {}; + + rcmail.set_busy(false, null, this.env.file_list_req_id); + }; + // call file_list request for every folder (used for search and virt. collections) this.file_list_loop = function(params) { - var i, folders = [], limit = Math.max(this.env.search_threads || 1, 1); + var i, folders = [], + limit = Math.max(this.env.search_threads || 1, 1), + msg = rcmail.get_label('searching') + ' <a onclick="file_api.file_list_abort()">' + rcmail.get_label('kolab_files.abort') + '</a>'; if (params.collection) { if (!params.search) @@ -3191,32 +3211,38 @@ }); this.env.folders_loop = folders; - this.env.folders_loop_params = params; this.env.folders_loop_lock = false; + this.env.file_list_req_id = rcmail.display_message(msg, 'loading', 5 * 60 * 1000, 'files-file-search'); + rcmail.set_busy(true); for (i=0; i<folders.length && i<limit; i++) { - params.req_id = this.set_busy(true, 'loading'); + params.req_id = new Date().getTime(); params.folder = folders.shift(); this.requests[params.req_id] = this.request('file_list', params, 'file_list_loop_response'); } }; // file list response handler for loop'ed request - this.file_list_loop_response = function(response) + this.file_list_loop_response = function(response, params) { - var i, folders = this.env.folders_loop, - params = this.env.folders_loop_params, - limit = Math.max(this.env.search_threads || 1, 1), + var folders = this.env.folders_loop, valid = this.response(response); - if (response.req_id) - rcmail.hide_message(response.req_id); - - for (i=0; i<folders.length && i<limit; i++) { - params.req_id = this.set_busy(true, 'loading'); + if (folders.length) { + params.req_id = new Date().getTime(); params.folder = folders.shift(); this.requests[params.req_id] = this.request('file_list', params, 'file_list_loop_response'); } + else { + rcmail.set_busy(false, null, this.env.file_list_req_id); + } + + // refresh sessions info in time intervals (one request for all folders) + if (!this.file_list_worker && this.env.caps && this.env.caps.DOCEDIT && (rcmail.fileslist || rcmail.env.file)) + this.file_list_worker = setTimeout(function() { + var params = {req_id: file_api.set_busy(true, 'loading')}; + file_api.request('sessions', params, 'file_list_sessions_response'); + }, 0); rcmail.fileslist.resize(); @@ -3226,6 +3252,36 @@ this.file_list_loop_result_add(response.result); }; + // Update sessions metadata for files list (in multifolder mode) + this.file_list_sessions_response = function(response) + { + this.file_list_worker = setTimeout(function() { + var params = {req_id: file_api.set_busy(true, 'loading')}; + file_api.request('sessions', params, 'file_list_sessions_response'); + }, (rcmail.env.files_interval || 60) * 1000); + + if (response.req_id) + rcmail.hide_message(response.req_id); + + if (!this.response(response)) + return; + + this.sessions = []; + + $.each(response.result || [], function(sess_id, data) { + var folder = file_api.file_path(data.file); + if (!file_api.sessions[folder]) + file_api.sessions[folder] = {}; + file_api.sessions[folder][sess_id] = data; + }); + + // update files list with document session info + $.each(file_api.env.file_list || [], function(i, file) { + var folder = file_api.file_path(file.filename); + file_api.file_session_data_set(file, file_api.sessions[folder]); + }); + }; + // add files from list request to the table (with sorting) this.file_list_loop_result_add = function(result) { @@ -3241,9 +3297,19 @@ // lock table, other list responses will wait this.env.folders_loop_lock = true; - var n, i, len, elem, row, folder, list = [], + var n, i, len, elem, folder, list = [], index = this.env.file_list.length, - table = rcmail.fileslist; + table = rcmail.fileslist, + fn = function(result, key, before) { + var row = file_api.file_list_row(key, result[key], ++index); + table.insert_row(row, before); + result[key].row = row; + result[key].filename = key; + list.push(result[key]); + + if (!folder) + folder = file_api.file_path(key); + }; for (n=0, len=index; n<len; n++) { elem = this.env.file_list[n]; @@ -3251,15 +3317,7 @@ if (this.sort_compare(elem, result[i]) < 0) break; - row = this.file_list_row(i, result[i], ++index); - table.insert_row(row, elem.row); - result[i].row = row; - result[i].filename = i; - list.push(result[i]); - - if (!folder) - folder = this.file_path(i); - + fn(result, i, elem.row); delete result[i]; } @@ -3267,23 +3325,18 @@ } // add the rest of rows - $.each(result, function(key, data) { - var row = file_api.file_list_row(key, data, ++index); - table.insert_row(row); - result[key].row = row; - result[key].filename = key; - list.push(result[key]); - - if (!folder) - folder = file_api.file_path(key); - }); + for (i in result) fn(result, i); this.env.file_list = list; - this.env.folders_loop_lock = false; - // update document sessions info of this folder - if (folder) - this.request('folder_info', {folder: folder, sessions: 1}, 'folder_info_response'); + // update files list with document session info + if (folder && this.sessions[folder]) + $.each(list, function(i, file) { + if (folder == file_api.file_path(file.filename)) + file_api.file_session_data_set(file, file_api.sessions[folder]); + }); + + this.env.folders_loop_lock = false; }; // sort files list (without API request) @@ -3409,34 +3462,39 @@ // update files list with document session info $.each(file_api.env.file_list || [], function(i, file) { // skip files from a different folder (in multi-folder listing) - if (file.filename.indexOf(prefix) !== 0) - return; + if (file.filename.indexOf(prefix) >= 0) + file_api.file_session_data_set(file, response.result.sessions) + }); - var classes = []; + // refresh sessions info in time intervals + if (this.env.caps && this.env.caps.DOCEDIT && (rcmail.fileslist || rcmail.env.file)) + this.file_list_worker = setTimeout(function() { + file_api.request('folder_info', {folder: folder, sessions: 1}, 'folder_info_response'); + }, (rcmail.env.files_interval || 60) * 1000); + }; - if ($(file.row).is('.selected')) - classes.push('selected'); + // Set html classes on a file list entry according to defined document sessions + this.file_session_data_set = function(file, sessions) + { + var classes = [], old_class = file.row.className; - $.each(response.result.sessions || [], function(session_id, session) { - if (file.filename == session.file) { - if ($.inArray('session', classes) < 0) - classes.push('session'); - - if (session.is_owner && $.inArray('owner', classes) < 0) - classes.push('owner'); - else if (session.is_invited && $.inArray('invited', classes) < 0) - classes.push('invited'); - } - }); + if ($(file.row).is('.selected')) + classes.push('selected'); - $(file.row).attr('class', classes.join(' ')); + $.each(sessions || [], function(session_id, session) { + if (file.filename == session.file) { + if ($.inArray('session', classes) < 0) + classes.push('session'); + + if (session.is_owner && $.inArray('owner', classes) < 0) + classes.push('owner'); + else if (session.is_invited && $.inArray('invited', classes) < 0) + classes.push('invited'); + } }); - // refresh sessions info in time intervals - if (rcmail.env.files_caps && rcmail.env.files_caps.DOCEDIT && (rcmail.fileslist || rcmail.env.file)) - this.workers[folder] = setTimeout(function() { - file_api.request('folder_info', {folder: folder, sessions: 1}, 'folder_info_response'); - }, (rcmail.env.files_interval || 60) * 1000); + if (classes.length || old_class.length) + $(file.row).attr('class', classes.join(' ')); }; this.file_get = function(file, params) @@ -3682,7 +3740,7 @@ // open the file for editing if editable if (this.file_create_edit_file) { - var viewer = this.file_type_supported(this.file_create_edit_type, rcmail.env.files_caps); + var viewer = this.file_type_supported(this.file_create_edit_type, this.env.caps); this.file_open(this.file_create_edit_file, viewer, {action: 'edit'}); } };
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_files/lib/kolab_files_engine.php -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_files/lib/kolab_files_engine.php
Changed
@@ -702,6 +702,8 @@ $this->rc->output->include_script('list.js'); + $this->rc->output->add_label('kolab_files.abort', 'searching'); + // attach css rules for mimetype icons if (!$this->filetypes_style) { $this->plugin->include_stylesheet($this->url . '/skins/default/images/mimetypes/style.css');
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_files/localization/en_US.inc -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_files/localization/en_US.inc
Changed
@@ -68,6 +68,7 @@ $labels['collection_document'] = 'Documents'; $labels['sessions'] = 'Sessions'; +$labels['abort'] = 'Abort'; $labels['uploading'] = 'Uploading file(s)...'; $labels['attaching'] = 'Attaching file(s)...'; $labels['authenticating'] = 'Authenticating...';
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_notes/composer.json -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_notes/composer.json
Changed
@@ -4,7 +4,7 @@ "description": "Notes module for Roundcube connecting to a Kolab server for storage", "homepage": "https://git.kolab.org/diffusion/RPK/", "license": "AGPLv3", - "version": "3.4.3", + "version": "3.4.4", "authors": [ { "name": "Thomas Bruederli",
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_notes/notes.js -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_notes/notes.js
Changed
@@ -1316,8 +1316,8 @@ dialogClass: 'warning', open: function(event, ui) { $(this).parent().find('.ui-dialog-titlebar-close').hide(); - setTimeout(function(){ - dialog.parent().find('.ui-button:visible').first().focus(); + setTimeout(function() { + dialog.parent().find('button:visible').first().focus(); }, 10); }, close: function(event, ui) {
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_notes/notes_mail.js -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_notes/notes_mail.js
Changed
@@ -39,7 +39,7 @@ edit = url._id, title = edit ? rcmail.gettext('kolab_notes.editnote') : rcmail.gettext('kolab_notes.appendnote'), dialog_render = function(p) { - $dialog.parent().find('.ui-dialog-buttonset .ui-button') + $dialog.parent().find('.ui-dialog-buttonset button') .prop('disabled', p.readonly) .last().prop('disabled', false); };
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_sso/README -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_sso/README
Changed
@@ -2,7 +2,8 @@ --------------------------------------- This plugin adds possibility to authenticate users via external authentication -services. Currently the only supported method of authentication is OpenID Connect. +services. Currently it supports various authentication methods based on +OAuth2 and OpenID Connect (and requires JWT token use). Because Kolab backends do not support token authentication it is required to use master user (sasl proxy) authentication where possible and service
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_sso/composer.json -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_sso/composer.json
Changed
@@ -4,7 +4,7 @@ "description": "Single Sign On for Kolab", "homepage": "https://git.kolab.org/diffusion/RPK/", "license": "AGPLv3", - "version": "3.4.2", + "version": "3.4.4", "authors": [ { "name": "Aleksander Machniak",
View file
roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_sso/drivers/oauth2.php
Added
@@ -0,0 +1,436 @@ +<?php + +/** + * kolab_sso driver implementing OAuth2 Authorization (RFC6749) + * with use of JWT tokens. + * + * @author Aleksander Machniak <machniak@kolabsys.com> + * + * Copyright (C) 2018-2019, 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_sso_oauth2 +{ + protected $plugin; + protected $id = 'oauth2'; + protected $config = array(); + protected $params = array(); + protected $defaults = array( + 'scope' => 'email', + 'token_type' => 'access_token', + 'user_field' => 'email', + 'validate_items' => array('aud'), + ); + + + /** + * Object constructor + * + * @param rcube_plugin $plugin kolab_sso plugin object + * @param array $config Driver configuration + */ + public function __construct($plugin, $config) + { + $this->plugin = $plugin; + $this->config = $config; + + $this->plugin->require_plugin('libkolab'); + } + + /** + * Authentication request (redirect to SSO service) + */ + public function authorize() + { + $params = array( + 'response_type' => 'code', + 'scope' => $this->get_param('scope'), + 'client_id' => $this->get_param('client_id'), + 'state' => $this->plugin->rc->get_request_token(), + 'redirect_uri' => $this->redirect_uri(), + ); + + // Add extra request parameters (don't overwrite params set above) + if (!empty($this->config['extra_params'])) { + $params = array_merge((array) $this->config['extra_params'], $params); + } + + $url = $this->config['auth_uri'] ?: (unslashify($this->config['uri']) . '/authorize'); + $url .= '?' . http_build_query($params); + + $this->plugin->debug("[{$this->id}][authorize] Redirecting to $url"); + + header("Location: $url"); + die; + } + + /** + * Authorization response validation + */ + public function response() + { + $this->plugin->debug("[{$this->id}][authorize] Response: " . $_SERVER['REQUEST_URI']); + + $this->error = $this->error_message( + rcube_utils::get_input_value('error', rcube_utils::INPUT_GET), + rcube_utils::get_input_value('error_description', rcube_utils::INPUT_GET), + rcube_utils::get_input_value('error_uri', rcube_utils::INPUT_GET) + ); + + if ($this->error) { + return; + } + + $state = rcube_utils::get_input_value('state', rcube_utils::INPUT_GET); + $code = rcube_utils::get_input_value('code', rcube_utils::INPUT_GET); + + if (!$state) { + $this->plugin->debug("[{$this->id}][response] State missing"); + $this->error = $this->plugin->gettext('errorinvalidresponse'); + return; + } + + if ($state != $this->plugin->rc->get_request_token()) { + $this->plugin->debug("[{$this->id}][response] Invalid response state"); + $this->error = $this->plugin->gettext('errorinvalidresponse'); + return; + } + + if (!$code) { + $this->plugin->debug("[{$this->id}][response] Code missing"); + $this->error = $this->plugin->gettext('errorinvalidresponse'); + return; + } + + return $this->request_token($code); + } + + /** + * Error message for the response handler + */ + public function response_error() + { + if ($this->error) { + return $this->plugin->rc->gettext('loginfailed') . ' ' . $this->error; + } + } + + /** + * Existing session validation + */ + public function validate_session($session) + { + $this->plugin->debug("[{$this->id}][validate] Session: " . json_encode($session)); + + // Sanity checks + if (empty($session) || empty($session['code']) || empty($session['validto']) || empty($session['email'])) { + $this->plugin->debug("[{$this->id}][validate] Session invalid"); + return; + } + + // Check expiration time + $now = new DateTime('now', new DateTimezone('UTC')); + $validto = new DateTime($session['validto'], new DateTimezone('UTC')); + + // Don't refresh often than TTL/2 + $validto->sub(new DateInterval(sprintf('PT%dS', $session['ttl']/2))); + if ($now < $validto) { + $this->plugin->debug("[{$this->id}][validate] Token valid, skipping refresh"); + return $session; + } + + // No refresh_token, not possible to refresh + if (empty($session['refresh_token'])) { + $this->plugin->debug("[{$this->id}][validate] Session cannot be refreshed"); + return; + } + + // Renew tokens + $info = $this->request_token($session['code'], $session['refresh_token']); + + if (!empty($info)) { + // Make sure the email didn't change + if (!empty($info['email']) && $info['email'] != $session['email']) { + $this->plugin->debug("[{$this->id}][validate] Email address change"); + return; + } + + $session = array_merge($session, $info); + + $this->plugin->debug("[{$this->id}][validate] Session valid: " . json_encode($session)); + return $session; + } + } + + /** + * Authentication Token request (or token refresh) + */ + protected function request_token($code, $refresh_token = null) + { + $mode = $refresh_token ? 'token-refresh' : 'token'; + $url = $this->config['token_uri'] ?: ($this->config['uri'] . '/token'); + $params = array( + 'client_id' => $this->get_param('client_id'), + 'client_secret' => $this->get_param('client_secret'), + 'grant_type' => $refresh_token ? 'refresh_token' : 'authorization_code', + ); + + if ($refresh_token) { + $params['refresh_token'] = $refresh_token; + $params['scope'] = $this->get_param('scope'); + } + else { + $params['code'] = $code; + $params['redirect_uri'] = $this->redirect_uri(); + } + + // Add extra request parameters (don't overwrite params set above) + if (!empty($this->config['extra_params'])) { + $params = array_merge((array) $this->config['extra_params'], $params); + } + + $post = http_build_query($params); + + $this->plugin->debug("[{$this->id}][$mode] Requesting POST $url?$post"); + + try { + // TODO: JWT-based methods of client authentication + // https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.9 + + $request = $this->get_request($url, 'POST'); + $request->setAuth($params['client_id'], $params['client_secret']); + $request->setBody($post); + + $response = $request->send(); + $status = $response->getStatus(); + $response = $response->getBody(); + + $this->plugin->debug("[{$this->id}][$mode] Response: $response"); + + $response = @json_decode($response, true); + + if ($status != 200 || !is_array($response) || !empty($response['error'])) { + $err = $this->error_text(is_array($response) ? $response['error'] : null); + throw new Exception("OpenIDC request failed with error: $err"); + } + } + catch (Exception $e) { + $this->error = $this->plugin->gettext('errorunknown'); + rcube::raise_error(array( + 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), + true, false); + return; + } + + // Example response: { + // "access_token":"ACCESS_TOKEN", + // "token_type":"bearer", + // "expires_in":2592000, + // "refresh_token":"REFRESH_TOKEN", + // "scope":"read", + // "uid":100101, + // "info":{"name":"Mark E. Mark","email":"mark@example.com"} + // } + + if (empty($response['access_token']) || empty($response['token_type']) + || strtolower($response['token_type']) != 'bearer' + ) { + $this->error = $this->plugin->gettext('errorinvalidresponse'); + $this->plugin->debug("[{$this->id}][$mode] Error: Invalid or unsupported response"); + return; + } + + $ttl = $response['expires_in'] ?: 600; + $validto = new DateTime(sprintf('+%d seconds', $ttl), new DateTimezone('UTC')); + $token = $response[$this->get_param('token_type')]; + + $result = array( + 'code' => $code, + 'access_token' => $response['access_token'], + // 'token_type' => $response['token_type'], + 'validto' => $validto->format(DateTime::ISO8601), + 'ttl' => $ttl, + ); + + if (!empty($response['refresh_token'])) { + $result['refresh_token'] = $response['refresh_token']; + } + + if (!empty($token)) { + try { + $key = $params['client_secret']; + + if (!empty($this->config['pubkey'])) { + $pubkey = trim(preg_replace('/\r?\n[\s\t]+/', "\n", $this->config['pubkey'])); + + if (strpos($pubkey, '-----') !== 0) { + $pubkey = "-----BEGIN PUBLIC KEY-----\n" . trim(chunk_split($pubkey, 64, "\n")) . "\n-----END PUBLIC KEY-----"; + } + + if ($keyid = openssl_pkey_get_public($pubkey)) { + $key = $keyid; + } + else { + throw new Exception("Failed to extract public key"); + } + } + + $jwt = new Firebase\JWT\JWT; + $jwt::$leeway = 60; + + $payload = $jwt->decode($token, $key, array_keys(Firebase\JWT\JWT::$supported_algs)); + + $result['email'] = $this->validate_token_payload($payload); + } + catch (Exception $e) { + $this->error = $this->plugin->gettext('errorinvalidtoken'); + rcube::raise_error(array( + 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), + true, false); + return; + } + } + + return $result; + } + + /** + * Validates JWT token payload and returns user/email + */ + protected function validate_token_payload($payload) + { + $items = $this->get_maram('validate_items'); + $email = $this->config['debug_email'] ?: $payload->{$this->get_param('user_field')}; + + if (empty($email)) { + throw new Exception("No email address in JWT token"); + } + + foreach ((array) $items as $item_name) { + // More extended token validation + // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation + switch (strtolower($item_name)) { + case 'aud': + if (!in_array($this->get_param('client_id'), (array) $payload->aud)) { + throw new Exception("Token audience does not match"); + } + break; + } + } + + return $email; + } + + /** + * The URL to use when redirecting the user from SSO back to Roundcube + */ + protected function redirect_uri() + { + // response_uri is useful when the Provider does not allow + // URIs with parameters. In such case set response_uri = '/sso' + // and define a redirect in http server, example for Apache: + // RewriteRule "^sso" "/roundcubemail/?_task=login&_action=sso" [L,QSA] + + $redirect_params = empty($this->config['response_uri']) ? array('_action' => 'sso') : array(); + + $url = $this->plugin->rc->url($redirect_params, false, true); + + if (!empty($this->config['response_uri'])) { + $url = unslashify(preg_replace('/\?.*$/', '', $url)) . '/' . ltrim($this->config['response_uri'], '/'); + } + + return $url; + } + + /** + * Get HTTP/Request2 object + */ + protected function get_request($url, $type) + { + $config = array_intersect_key($this->config, array_flip(array( + 'ssl_verify_peer', + 'ssl_verify_host', + 'ssl_cafile', + 'ssl_capath', + 'ssl_local_cert', + 'ssl_passphrase', + 'follow_redirects', + ))); + + return libkolab::http_request($url, $type, $config); + } + + /** + * Returns (localized) user-friendly error message + */ + protected function error_message($error, $description, $uri) + { + if (empty($error)) { + return; + } + + $msg = $this->error_text($error); + + rcube::raise_error(array( + 'message' => "[SSO] $msg." . ($description ? " $description" : '') . ($uri ? " ($uri)" : '') + ), true, false); + + $label = 'error' . str_replace('_', '', $error); + if (!$this->plugin->rc->text_exists($label, 'kolab_sso')) { + $label = 'errorunknown'; + } + + return $this->plugin->gettext($label); + } + + /** + * Returns error text for specified OpenIDC error code + */ + protected function error_text($error) + { + switch ($error) { + case 'invalid_request': + return "Request malformed"; + case 'unauthorized_client': + return "The client is not authorized"; + case 'invalid_client': + return "Client authentication failed"; + case 'access_denied': + return "Request denied"; + case 'unsupported_response_type': + return "Unsupported response type"; + case 'invalid_grant': + return "Invalid authorization grant"; + case 'unsupported_grant_type': + return "Unsupported authorization grant"; + case 'invalid_scope': + return "Invalid scope"; + case 'server_error': + return "Server error"; + case 'temporarily_unavailable': + return "Service temporarily unavailable"; + } + + return "Unknown error"; + } + + /** + * Returns (hardcoded/configured/default) value of a configuration param + */ + protected function get_param($name) + { + return $this->params[$name] ?: ($this->config[$name] ?: $this->defaults[$name]); + } +}
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/kolab_sso/drivers/openidc.php -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_sso/drivers/openidc.php
Changed
@@ -23,371 +23,24 @@ * 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_sso_openidc -{ - protected $id = 'openidc'; - protected $config = array(); - protected $plugin; - - - public function __construct($plugin, $config) - { - $this->plugin = $plugin; - $this->config = $config; - - $this->plugin->require_plugin('libkolab'); - } - - /** - * Authentication request (redirect to SSO service) - */ - public function authorize() - { - $params = array( - 'response_type' => 'code', - 'scope' => 'openid email offline_access', - 'client_id' => $this->config['client_id'], - 'state' => $this->plugin->rc->get_request_token(), - 'redirect_uri' => $this->redirect_uri(), - ); - - // TODO: Other params by config: display, prompt, max_age, - - $url = $this->config['auth_uri'] ?: (unslashify($this->config['uri']) . '/authorize'); - $url .= '?' . http_build_query($params); - - $this->plugin->debug("[{$this->id}][authorize] Redirecting to $url"); - - header("Location: $url"); - die; - } - - /** - * Authorization response validation - */ - public function response() - { - $this->plugin->debug("[{$this->id}][authorize] Response: " . $_SERVER['REQUEST_URI']); - - $this->error = $this->error_message( - rcube_utils::get_input_value('error', rcube_utils::INPUT_GET), - rcube_utils::get_input_value('error_description', rcube_utils::INPUT_GET), - rcube_utils::get_input_value('error_uri', rcube_utils::INPUT_GET) - ); - - if ($this->error) { - return; - } - - $state = rcube_utils::get_input_value('state', rcube_utils::INPUT_GET); - $code = rcube_utils::get_input_value('code', rcube_utils::INPUT_GET); - - if (!$state) { - $this->plugin->debug("[{$this->id}][response] State missing"); - $this->error = $this->plugin->gettext('errorinvalidresponse'); - return; - } - - if ($state != $this->plugin->rc->get_request_token()) { - $this->plugin->debug("[{$this->id}][response] Invalid response state"); - $this->error = $this->plugin->gettext('errorinvalidresponse'); - return; - } - - if (!$code) { - $this->plugin->debug("[{$this->id}][response] Code missing"); - $this->error = $this->plugin->gettext('errorinvalidresponse'); - return; - } - return $this->request_token($code); - } - - /** - * Error message for the response handler - */ - public function response_error() - { - if ($this->error) { - return $this->plugin->rc->gettext('loginfailed') . ' ' . $this->error; - } - } - - /** - * Existing session validation - */ - public function validate_session($session) - { - $this->plugin->debug("[{$this->id}][validate] Session: " . json_encode($session)); - - // Sanity checks - if (empty($session) || empty($session['code']) || empty($session['validto']) || empty($session['email'])) { - $this->plugin->debug("[{$this->id}][validate] Session invalid"); - return; - } - - // Check expiration time - $now = new DateTime('now', new DateTimezone('UTC')); - $validto = new DateTime($session['validto'], new DateTimezone('UTC')); - - // Don't refresh often than TTL/2 - $validto->sub(new DateInterval(sprintf('PT%dS', $session['ttl']/2))); - if ($now < $validto) { - $this->plugin->debug("[{$this->id}][validate] Token valid, skipping refresh"); - return $session; - } - - // No refresh_token, not possible to refresh - if (empty($session['refresh_token'])) { - $this->plugin->debug("[{$this->id}][validate] Session cannot be refreshed"); - return; - } - - // Renew tokens - $info = $this->request_token($session['code'], $session['refresh_token']); +require_once __DIR__ . '/oauth2.php'; - if (!empty($info)) { - // Make sure the email didn't change - if (!empty($info['email']) && $info['email'] != $session['email']) { - $this->plugin->debug("[{$this->id}][validate] Email address change"); - return; - } - - $session = array_merge($session, $info); - - $this->plugin->debug("[{$this->id}][validate] Session valid: " . json_encode($session)); - return $session; - } - } - - /** - * Authentication Token request (or token refresh) - */ - protected function request_token($code, $refresh_token = null) - { - $mode = $refresh_token ? 'token-refresh' : 'token'; - $url = $this->config['token_uri'] ?: ($this->config['uri'] . '/token'); - $params = array( - 'client_id' => $this->config['client_id'], - 'client_secret' => $this->config['client_secret'], - 'grant_type' => $refresh_token ? 'refresh_token' : 'authorization_code', - ); - - if ($refresh_token) { - $params['refresh_token'] = $refresh_token; - $params['scope'] = 'openid email offline_access'; - } - else { - $params['code'] = $code; - $params['redirect_uri'] = $this->redirect_uri(); - } - - $post = http_build_query($params); - - $this->plugin->debug("[{$this->id}][$mode] Requesting POST $url?$post"); - - try { - // TODO: JWT-based methods of client authentication - // https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.9 - - $request = $this->get_request($url, 'POST'); - $request->setAuth($this->config['client_id'], $this->config['client_secret']); - $request->setBody($post); - - $response = $request->send(); - $status = $response->getStatus(); - $response = $response->getBody(); - - $this->plugin->debug("[{$this->id}][$mode] Response: $response"); - - $response = @json_decode($response, true); - - if ($status != 200 || !is_array($response) || !empty($response['error'])) { - $err = $this->error_text(is_array($response) ? $response['error'] : null); - throw new Exception("OpenIDC request failed with error: $err"); - } - } - catch (Exception $e) { - $this->error = $this->plugin->gettext('errorunknown'); - rcube::raise_error(array( - 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), - true, false); - return; - } - - // Example response: { - // "access_token":"ACCESS_TOKEN", - // "token_type":"bearer", - // "expires_in":2592000, - // "refresh_token":"REFRESH_TOKEN", - // "scope":"read", - // "uid":100101, - // "info":{"name":"Mark E. Mark","email":"mark@example.com"} - // } - - if (empty($response['access_token']) || empty($response['token_type']) - || strtolower($response['token_type']) != 'bearer' - ) { - $this->error = $this->plugin->gettext('errorinvalidresponse'); - $this->plugin->debug("[{$this->id}][$mode] Error: Invalid or unsupported response"); - return; - } - - $ttl = $response['expires_in'] ?: 600; - $validto = new DateTime(sprintf('+%d seconds', $ttl), new DateTimezone('UTC')); - - $result = array( - 'code' => $code, - 'access_token' => $response['access_token'], - // 'token_type' => $response['token_type'], - 'validto' => $validto->format(DateTime::ISO8601), - 'ttl' => $ttl, - ); - - if (!empty($response['refresh_token'])) { - $result['refresh_token'] = $response['refresh_token']; - } - - if (!empty($response['id_token'])) { - try { - $key = $this->config['client_secret']; - - if (!empty($this->config['pubkey'])) { - $pubkey = trim(preg_replace('/\r?\n[\s\t]+/', "\n", $this->config['pubkey'])); - - if (strpos($pubkey, '-----') !== 0) { - $pubkey = "-----BEGIN PUBLIC KEY-----\n" . trim(chunk_split($pubkey, 64, "\n")) . "\n-----END PUBLIC KEY-----"; - } - - if ($keyid = openssl_pkey_get_public($pubkey)) { - $key = $keyid; - } - else { - throw new Exception("Failed to extract public key"); - } - } - - $jwt = new Firebase\JWT\JWT; - $jwt::$leeway = 60; - - $payload = $jwt->decode($response['id_token'], $key, array_keys(Firebase\JWT\JWT::$supported_algs)); - $email = $this->config['debug_email'] ?: $payload->email; - - if (empty($email)) { - throw new Exception("No email address in JWT token"); - } - - if (!in_array($this->config['client_id'], (array) $payload->aud)) { - throw new Exception("Token audience does not match"); - } - - // More extended token validation - // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation - - $result['email'] = $email; - } - catch (Exception $e) { - $this->error = $this->plugin->gettext('errorinvalidtoken'); - rcube::raise_error(array( - 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), - true, false); - return; - } - } - - return $result; - } - - /** - * The URL to use when redirecting the user from SSO back to Roundcube - */ - protected function redirect_uri() - { - // response_uri is useful when the Provider does not allow - // URIs with parameters. In such case set response_uri = '/sso' - // and define a redirect in http server, example for Apache: - // RewriteRule "^sso" "/roundcubemail/?_task=login&_action=sso" [L,QSA] - - $redirect_params = empty($this->config['response_uri']) ? array('_action' => 'sso') : array(); - - $url = $this->plugin->rc->url($redirect_params, false, true); - - if (!empty($this->config['response_uri'])) { - $url = unslashify(preg_replace('/\?.*$/', '', $url)) . '/' . ltrim($this->config['response_uri'], '/'); - } - - return $url; - } - - /** - * Get HTTP/Request2 object - */ - protected function get_request($url, $type) - { - $config = array_intersect_key($this->config, array_flip(array( - 'ssl_verify_peer', - 'ssl_verify_host', - 'ssl_cafile', - 'ssl_capath', - 'ssl_local_cert', - 'ssl_passphrase', - 'follow_redirects', - ))); - - return libkolab::http_request($url, $type, $config); - } - - /** - * Returns (localized) user-friendly error message - */ - protected function error_message($error, $description, $uri) - { - if (empty($error)) { - return; - } - - $msg = $this->error_text($error); - - rcube::raise_error(array( - 'message' => "[SSO] $msg." . ($description ? " $description" : '') . ($uri ? " ($uri)" : '') - ), true, false); - - $label = 'error' . str_replace('_', '', $error); - if (!$this->plugin->rc->text_exists($label, 'kolab_sso')) { - $label = 'errorunknown'; - } - - return $this->plugin->gettext($label); - } +class kolab_sso_openidc extends kolab_sso_oauth2 +{ + protected $id = 'openidc'; + protected $params = array( + 'scope' => 'openid email offline_access', + 'token_type' => 'id_token', + ); /** * Returns error text for specified OpenIDC error code */ protected function error_text($error) { + // OpenIDC-specific codes switch ($error) { - // OAuth2 codes - case 'invalid_request': - return "Request malformed"; - case 'unauthorized_client': - return "The client is not authorized"; - case 'invalid_client': - return "Client authentication failed"; - case 'access_denied': - return "Request denied"; - case 'unsupported_response_type': - return "Unsupported response type"; - case 'invalid_grant': - return "Invalid authorization grant"; - case 'unsupported_grant_type': - return "Unsupported authorization grant"; - case 'invalid_scope': - return "Invalid scope"; - case 'server_error': - return "Server error"; - case 'temporarily_unavailable': - return "Service temporarily unavailable"; - // OpenIDC codes case 'interaction_required': return "End-User interaction required"; case 'login_required': @@ -408,6 +61,7 @@ return "Registration not supported"; } - return "Unknown error"; + // Fallback to OAuth2-specific codes + return parent::error_text($error); } }
View file
roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/kolab_sso/drivers/seas.php
Added
@@ -0,0 +1,38 @@ +<?php + +/** + * kolab_sso driver implementing Abraxas SEAS Portal OAuth2/JWT flow + * + * @author Aleksander Machniak <machniak@kolabsys.com> + * Beat Rubischon <beat.rubischon@adfinis-sygroup.ch> + * + * Copyright (C) 2018, Kolab Systems AG <contact@kolabsys.com> + * Copyright (C) 2019, Adfinis SyGroup AG + * + * 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__ . '/oauth2.php'; + + +class kolab_sso_seas extends kolab_sso_oauth2 +{ + protected $id = 'seas'; + protected $defaults = array( + 'scope' => 'USER', + 'token_type' => 'access_token', + 'user_field' => 'user_name', + 'validate_items' => array(), + ); +}
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/libcalendaring/composer.json -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/libcalendaring/composer.json
Changed
@@ -4,7 +4,7 @@ "description": "Library providing common functions for calendaring plugins", "homepage": "https://git.kolab.org/diffusion/RPK/", "license": "AGPLv3", - "version": "3.4.3", + "version": "3.4.4", "authors": [ { "name": "Thomas Bruederli",
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/libcalendaring/lib/libcalendaring_itip.php -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/libcalendaring/lib/libcalendaring_itip.php
Changed
@@ -870,6 +870,7 @@ 'value' => 1, 'disabled' => $disable, 'checked' => ($itip_sending & 1) == 0, + 'class' => 'pretty-checkbox', ); $rsvp_additions = html::label(array('class' => 'noreply-toggle'), html::tag('input', $toggle_attrib) . ' ' . $this->gettext('itipsuppressreply')
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/libcalendaring/libcalendaring.js -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/libcalendaring/libcalendaring.js
Changed
@@ -435,10 +435,15 @@ $(this).parent().find('span.edit-alarm-values')[(this.selectedIndex>0?'show':'hide')](); }); $(prefix+' select.edit-alarm-offset').change(function(){ - var val = $(this).val(), parent = $(this).parent(); + var val = $(this).val(), + parent = $(this).parent(), + class_map = {'0': 'ontime', '@': 'ondate'}; + parent.find('.edit-alarm-date, .edit-alarm-time')[val === '@' ? 'show' : 'hide'](); - parent.find('.edit-alarm-value').prop('disabled', val === '@' || val === '0'); + parent.find('.edit-alarm-value')[val === '@' || val === '0' ? 'hide' : 'show'](); parent.find('.edit-alarm-related')[val === '@' ? 'hide' : 'show'](); + parent.removeClass('offset-ontime offset-ondate offset-default') + .addClass('offset-' + (class_map[val] || 'default')); }); $(prefix+' .edit-alarm-date').removeClass('hasDatepicker').removeAttr('id').datepicker(this.datepicker_settings); @@ -455,7 +460,7 @@ // Elastic if (window.UI && UI.pretty_select) { - $(prefix + ' select').each(function() { UI.pretty_select(this); }); + $(prefix + ' select').each(function() { UI.pretty_select(this); }); } if (index) @@ -754,7 +759,7 @@ buttons: buttons, open: function() { setTimeout(function() { - me.alarm_dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus(); + me.alarm_dialog.parent().find('button:not(.ui-dialog-titlebar-close)').first().focus(); }, 5); }, close: function() { @@ -1284,7 +1289,7 @@ dialog = rcmail.show_popup_dialog(form, rcmail.gettext('delegateinvitation', 'itip'), buttons, { width: 460, open: function(event, ui) { - $(this).parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().addClass('mainaction'); + $(this).parent().find('button:not(.ui-dialog-titlebar-close)').first().addClass('mainaction'); $(this).find('#itip-saveto').val(''); // initialize autocompletion @@ -1375,7 +1380,7 @@ dialog = rcmail.show_popup_dialog(html, rcmail.gettext('declineattendee', 'itip'), buttons, { width: 460, open: function() { - $(this).parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().addClass('mainaction'); + $(this).parent().find('button:not(.ui-dialog-titlebar-close)').first().addClass('mainaction'); $('#itip-decline-comment').focus(); } });
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/libcalendaring/libcalendaring.php -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/libcalendaring/libcalendaring.php
Changed
@@ -53,8 +53,8 @@ 'Y/m/d' => array('M d Y', 'm/d', 'l m/d'), 'm/d/Y' => array('M d Y', 'm/d', 'l m/d'), 'd/m/Y' => array('d M Y', 'd/m', 'l d/m'), - 'd.m.Y' => array('d. M Y', 'd.m', 'l d.m.'), - 'd.m.Y' => array('d. M Y', 'd.m', 'l d.m.'), + 'd.m.Y' => array('d. M Y', 'd.m.', 'l d.m.'), + 'd.m.Y' => array('d. M Y', 'd.m.', 'l d.m.'), ), );
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/libkolab/composer.json -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/libkolab/composer.json
Changed
@@ -4,7 +4,7 @@ "description": "Plugin to setup a basic environment for the interaction with a Kolab server.", "homepage": "https://git.kolab.org/diffusion/RPK/", "license": "AGPLv3", - "version": "3.4.3", + "version": "3.4.4", "authors": [ { "name": "Thomas Bruederli",
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/libkolab/lib/kolab_date_recurrence.php -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/libkolab/lib/kolab_date_recurrence.php
Changed
@@ -32,6 +32,8 @@ private /* DateTime */ $next; private /* cDateTime */ $cnext; private /* DateInterval */ $duration; + private /* bool */ $allday; + /** * Default constructor @@ -45,6 +47,7 @@ $this->object = $object; $this->engine = $object->to_libcal(); $this->start = $this->next = $data['start']; + $this->allday = !empty($data['allday']); $this->cnext = kolab_format::get_datetime($this->next); if (is_object($data['start']) && is_object($data['end'])) { @@ -61,6 +64,7 @@ * Get date/time of the next occurence of this event * * @param boolean Return a Unix timestamp instead of a DateTime object + * * @return mixed DateTime object/unix timestamp or False if recurrence ended */ public function next_start($timestamp = false) @@ -69,9 +73,16 @@ if ($this->engine && $this->next) { if (($cnext = new cDateTime($this->engine->getNextOccurence($this->cnext))) && $cnext->isValid()) { - $next = kolab_format::php_datetime($cnext); + $next = kolab_format::php_datetime($cnext, $this->start->getTimezone()); $time = $timestamp ? $next->format('U') : $next; + if ($this->allday) { + // it looks that for allday events the occurrence time + // is reset to 00:00:00, this is causing various issues + $next->setTime($this->start->format('G'), $this->start->format('i'), $this->start->format('s')); + $next->_dateonly = true; + } + $this->cnext = $cnext; $this->next = $next; } @@ -91,11 +102,10 @@ $next_end = clone $next_start; $next_end->add($this->duration); - $next = $this->object->to_array(); - $next['start'] = $next_start; - $next['end'] = $next_end; - + $next = $this->object->to_array(); $recurrence_id_format = libkolab::recurrence_id_format($next); + $next['start'] = $next_start; + $next['end'] = $next_end; $next['recurrence_date'] = clone $next_start; $next['_instance'] = $next_start->format($recurrence_id_format); @@ -203,10 +213,6 @@ while ($next = $recurrence->next_start()) { $start = $next; if ($next->format('Y-m-d') >= $orig_date) { - if ($event['allday']) { - $next->setTime($orig_start->format('G'), $orig_start->format('i'), $orig_start->format('s')); - } - $found = true; break; } @@ -223,7 +229,7 @@ return null; } - if ($event['allday']) { + if ($this->allday) { $start->_dateonly = true; }
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/libkolab/skins/elastic/include/libcalendaring.less -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/libkolab/skins/elastic/include/libcalendaring.less
Changed
@@ -82,6 +82,7 @@ .itip-reply-controls { margin-top: .25rem; width: 100%; + line-height: 2; & > label { display: inline; @@ -427,6 +428,25 @@ .edit-alarm-values { margin-left: .25rem; flex: 5; + + &.offset-default { + select.edit-alarm-related { + border-radius: 0 .25rem .25rem 0; + } + } + &.offset-ontime { + select.edit-alarm-offset { + border-radius: .25rem 0 0 .25rem; + } + select.edit-alarm-related { + border-radius: 0 .25rem .25rem 0; + } + } + &.offset-ondate { + select.edit-alarm-offset { + border-radius: .25rem 0 0 .25rem; + } + } } .edit-alarm-offset {
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/libkolab/tests/kolab_date_recurrence.php -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/libkolab/tests/kolab_date_recurrence.php
Changed
@@ -230,5 +230,29 @@ $this->assertEquals($expected, $first ? $first->format('Y-m-d H:i:s') : ''); } -} + /** + * kolab_date_recurrence::next_instance() + */ + function test_next_instance() + { + date_default_timezone_set('America/New_York'); + + $start = new DateTime('2017-08-31 11:00:00', new DateTimeZone('Europe/Berlin')); + $event = array( + 'start' => $start, + 'recurrence' => array('FREQ' => 'WEEKLY', 'INTERVAL' => '1'), + 'allday' => true, + ); + + $object = kolab_format::factory('event', 3.0); + $object->set($event); + + $recurrence = new kolab_date_recurrence($object); + $next = $recurrence->next_instance(); + + $this->assertEquals($start->format('2017-09-07 H:i:s'), $next['start']->format('Y-m-d H:i:s'), 'Same time'); + $this->assertEquals($start->getTimezone()->getName(), $next['start']->getTimezone()->getName(), 'Same timezone'); + $this->assertSame($next['start']->_dateonly, true, '_dateonly flag'); + } +}
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/tasklist/composer.json -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/tasklist/composer.json
Changed
@@ -4,7 +4,7 @@ "description": "Task management plugin", "homepage": "https://git.kolab.org/diffusion/RPK/", "license": "AGPLv3", - "version": "3.4.3", + "version": "3.4.4", "authors": [ { "name": "Thomas Bruederli",
View file
roundcubemail-plugins-kolab-3.4.3.tar.gz/plugins/tasklist/tasklist.js -> roundcubemail-plugins-kolab-3.4.4.tar.gz/plugins/tasklist/tasklist.js
Changed
@@ -893,7 +893,7 @@ rcmail.async_upload_form(form, 'import', function(e) { rcmail.set_busy(false, null, saving_lock); saving_lock = null; - $('.ui-dialog-buttonpane button', $dialog.parent()).button('enable'); + $('.ui-dialog-buttonpane button', $dialog.parent()).prop('disabled', false); // display error message if no sophisticated response from server arrived (e.g. iframe load error) if (me.import_succeeded === null) @@ -905,7 +905,7 @@ rcmail.env.request_timeout = 600; me.import_succeeded = null; saving_lock = rcmail.set_busy(true, 'uploading'); - $('.ui-dialog-buttonpane button', $dialog.parent()).button('disable'); + $('.ui-dialog-buttonpane button', $dialog.parent()).prop('disabled', true); // restore settings rcmail.env.request_timeout = timeout; @@ -1300,7 +1300,6 @@ if (!rcmail.busy) { saving_lock = rcmail.set_busy(true, 'tasklist.savingdata'); rcmail.http_post('tasks/task', { action:action, t:rec, filter:filtermask }); - $('button.ui-button:ui-button').button('option', 'disabled', rcmail.busy); return true; } @@ -1382,7 +1381,7 @@ buttons: buttons, open: function() { setTimeout(function(){ - $dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus(); + $dialog.parent().find('button:not(.ui-dialog-titlebar-close)').first().focus(); }, 5); }, close: function(){ @@ -1404,7 +1403,6 @@ { if (saving_lock) { rcmail.set_busy(false, null, saving_lock); - $('button.ui-button:ui-button').button('option', 'disabled', false); saving_lock = null; // Elastic @@ -2183,7 +2181,7 @@ closeOnEscape: true, title: rcmail.gettext('taskdetails', 'tasklist'), open: function() { - $dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus(); + $dialog.parent().find('button:not(.ui-dialog-titlebar-close)').first().focus(); }, close: function() { $dialog.dialog('destroy').appendTo(document.body);
View file
roundcubemail-plugins-kolab.dsc
Changed
@@ -2,7 +2,7 @@ Source: roundcubemail-plugins-kolab Binary: roundcubemail-plugins-kolab Architecture: all -Version: 1:3.4.3-0~kolab3 +Version: 1:3.4.4-0~kolab1 Maintainer: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Uploaders: Paul Klos <kolab@klos2day.nl> Standards-Version: 3.9.3 @@ -37,5 +37,5 @@ roundcubemail-plugin-tinymce-config deb web extra roundcubemail-plugin-wap-client deb web extra Files: - 00000000000000000000000000000000 0 roundcubemail-plugins-kolab-3.4.3.tar.gz + 00000000000000000000000000000000 0 roundcubemail-plugins-kolab-3.4.4.tar.gz 00000000000000000000000000000000 0 debian.tar.gz
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.