From cdca86d15bd84c50ee9784a96cc40a2c8e1e7f3f Mon Sep 17 00:00:00 2001 From: "Aleksander Machniak (Kolab Systems)" Date: Thu, 21 Jul 2011 12:55:35 +0200 Subject: [PATCH 01/17] Fixed doubled confirmation message after event delete with enabled Undo feature --- plugins/calendar/calendar.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 0623d08d..09d1c739 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -116,7 +116,7 @@ class calendar extends rcube_plugin $this->register_action('freebusy-status', array($this, 'freebusy_status')); $this->register_action('freebusy-times', array($this, 'freebusy_times')); $this->register_action('randomdata', array($this, 'generate_randomdata')); - $this->register_action('print',array($this,'print_view')); + $this->register_action('print', array($this,'print_view')); // remove undo information... if ($undo = $_SESSION['calendar_event_undo']) { @@ -508,6 +508,7 @@ class calendar extends rcube_plugin . ' ' . html::a(array('onclick' => sprintf("%s.http_request('event', 'action=undo', %s.display_message('', 'loading'))", JS_OBJECT_NAME, JS_OBJECT_NAME)), rcube_label('undo')); $this->rc->output->show_message($msg, 'confirmation', null, true, $undo_time); + $got_msg = true; } else if ($success) { $this->rc->output->show_message('calendar.successremoval', 'confirmation'); From 3da007843488882392efc2d89b37c2b6c9752dfd Mon Sep 17 00:00:00 2001 From: "Bogomil Shopov (Kolab Systems)" Date: Thu, 21 Jul 2011 15:12:17 +0300 Subject: [PATCH 02/17] Final version of ical export --- plugins/calendar/calendar.php | 5 +++-- plugins/calendar/lib/calendar_ical.php | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 09d1c739..76464c26 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -598,10 +598,11 @@ class calendar extends rcube_plugin $end = get_input_value('end', RCUBE_INPUT_GET); if (!$start) $start = mktime(0, 0, 0, 1, date('n'), date('Y')-1); if (!$end) $end = mktime(0, 0, 0, 31, 12, date('Y')+10); - $events = $this->driver->load_events($start, $end, null, get_input_value('source', RCUBE_INPUT_GET), 0); + $calendar_name = get_input_value('source', RCUBE_INPUT_GET); + $events = $this->driver->load_events($start, $end, null, $calendar_name, 0); header("Content-Type: text/calendar"); - header("Content-Disposition: inline; filename=calendar.ics"); + header("Content-Disposition: inline; filename=".$calendar_name); echo $this->ical->export($events); exit; diff --git a/plugins/calendar/lib/calendar_ical.php b/plugins/calendar/lib/calendar_ical.php index 66cd9ee9..d2e06cb5 100644 --- a/plugins/calendar/lib/calendar_ical.php +++ b/plugins/calendar/lib/calendar_ical.php @@ -67,7 +67,7 @@ class calendar_ical $ical .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . "\r\n"; $ical .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . "\r\n"; $ical .= "SUMMARY:" . self::escpape($event['title']) . "\r\n"; - $ical .= "DESCRIPTION:" . wordwrap(self::escpape($event['description']),75,'\r\n ') . "\r\n"; + $ical .= "DESCRIPTION:" . wordwrap(self::escpape($event['description']),75,"\r\n ") . "\r\n"; if (!empty($event['attendees'])){ From 480993ec15a7c5fc0ae6c8f1e8feb1a5b0e9b367 Mon Sep 17 00:00:00 2001 From: "Aleksander Machniak (Kolab Systems)" Date: Thu, 21 Jul 2011 18:21:55 +0200 Subject: [PATCH 03/17] Prevent from PHP warning when search string is empty --- plugins/kolab_addressbook/lib/rcube_kolab_contacts.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php index b4c72a4f..782c06b3 100644 --- a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php +++ b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php @@ -363,6 +363,8 @@ class rcube_kolab_contacts extends rcube_addressbook $search = $value; } + $s_len = strlen($search); + foreach ((array)$contact[$col] as $val) { // composite field, e.g. address if (is_array($val)) { @@ -370,7 +372,9 @@ class rcube_kolab_contacts extends rcube_addressbook } $val = mb_strtolower($val); - if (($strict && $val == $search) || (!$strict && strpos($val, $search) !== false)) { + if (($strict && $val == $search) + || (!$strict && $s_len && strpos($val, $search) !== false) + ) { if (!$advanced) { $this->filter['ids'][] = $id; break 2; From 6dc1b64d33759c4e6ebf085b32e284438dd67c92 Mon Sep 17 00:00:00 2001 From: "Aleksander Machniak (Kolab Systems)" Date: Fri, 22 Jul 2011 10:53:31 +0200 Subject: [PATCH 04/17] Fixed caching issue after folder rename --- plugins/kolab_core/rcube_kolab.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/kolab_core/rcube_kolab.php b/plugins/kolab_core/rcube_kolab.php index 74b46a86..5a6d5cc3 100644 --- a/plugins/kolab_core/rcube_kolab.php +++ b/plugins/kolab_core/rcube_kolab.php @@ -266,12 +266,22 @@ class rcube_kolab $folder = $kolab->getFolder($oldname); $folder->setFolder($newname); + + // We're not using $folder->save() because some caching issues $result = $kolab->rename($folder); if (is_a($result, 'PEAR_Error')) { return false; } + // need to re-set some properties + $folder->name = $folder->new_name; + $folder->new_name = null; + $folder->_title = null; + $folder->_owner = null; + // resetting _data prevents from some wierd cache unserialization issue + $folder->_data = null; + return true; } From 2981e5a3e62e8e577f0ef6b5fd4e2f5d94b38fb0 Mon Sep 17 00:00:00 2001 From: "Aleksander Machniak (Kolab Systems)" Date: Fri, 22 Jul 2011 14:06:18 +0200 Subject: [PATCH 05/17] Fixed Bug #217: After changing parent folder to edited folder, the book disapears from the list --- plugins/kolab_addressbook/kolab_addressbook.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/kolab_addressbook/kolab_addressbook.js b/plugins/kolab_addressbook/kolab_addressbook.js index cdb525d1..9cb1756e 100644 --- a/plugins/kolab_addressbook/kolab_addressbook.js +++ b/plugins/kolab_addressbook/kolab_addressbook.js @@ -191,7 +191,7 @@ rcube_webmail.prototype.book_update = function(data, old) level = olddata.realname.split(this.env.delimiter).length - data.realname.split(this.env.delimiter).length; // update (realname and ID of) subfolders for (n in sources) { - if (n.indexOf(old) == 0) { + if (n != data.id && n.indexOf(old) == 0) { // new ID id = data.id + '-' + n.substr(old.length); name = sources[n].name; From 4555108baa63f0f14238f2f6a04a48b8f032d5bb Mon Sep 17 00:00:00 2001 From: "Jeroen van Meeuwen (Ergo Project)" Date: Fri, 22 Jul 2011 12:59:11 -0400 Subject: [PATCH 06/17] Use the freebusy configuration specified in /etc/roundcubemail/kolab.inc.php --- plugins/kolab_core/rcube_kolab.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/kolab_core/rcube_kolab.php b/plugins/kolab_core/rcube_kolab.php index 5a6d5cc3..3650f7b9 100644 --- a/plugins/kolab_core/rcube_kolab.php +++ b/plugins/kolab_core/rcube_kolab.php @@ -72,9 +72,11 @@ class rcube_kolab // Re-set LDAP/IMAP host config $ldap = array('server' => 'ldap://' . $_SESSION['imap_host'] . ':389'); $imap = array('server' => $_SESSION['imap_host'], 'port' => $_SESSION['imap_port']); + $freebusy = array('server' => $_SESSION['imap_host']); $conf['kolab']['ldap'] = array_merge($ldap, (array)$conf['kolab']['ldap']); $conf['kolab']['imap'] = array_merge($imap, (array)$conf['kolab']['imap']); + $conf['kolab']['freebusy'] = array_merge($freebusy, (array)$conf['kolab']['freebusy']); self::$config = &$conf; // pass the current IMAP authentication credentials to the Horde auth system From be0e774998de22b71fe7e65103fa3b32b3e62337 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sat, 23 Jul 2011 12:18:29 +0200 Subject: [PATCH 07/17] Support free/busy types (e.g. tentative, out-of-office) --- plugins/calendar/calendar.php | 10 ++++++---- plugins/calendar/calendar_ui.js | 19 +++++++++++++------ .../calendar/drivers/kolab/kolab_driver.php | 16 ++++++++++++++-- plugins/calendar/localization/en_US.inc | 1 + plugins/calendar/skins/default/calendar.css | 7 ++++++- .../default/templates/freebusylegend.html | 1 + 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 76464c26..7c9cffae 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -29,6 +29,7 @@ class calendar extends rcube_plugin const FREEBUSY_UNKNOWN = 0; const FREEBUSY_FREE = 1; const FREEBUSY_BUSY = 2; + const FREEBUSY_TENTATIVE = 3; const FREEBUSY_OOF = 4; public $task = '?(?!login|logout).*'; @@ -1206,6 +1207,7 @@ class calendar extends rcube_plugin if (!$start) $start = time(); if (!$end) $end = $start + 3600; + $fbtypemap = array(calendar::FREEBUSY_FREE => 'FREE', calendar::FREEBUSY_BUSY => 'BUSY', calendar::FREEBUSY_TENTATIVE => 'TENTATIVE', calendar::FREEBUSY_OOF => 'OUT-OF-OFFICE'); $status = 'UNKNOWN'; // if the backend has free-busy information @@ -1214,9 +1216,9 @@ class calendar extends rcube_plugin $status = 'FREE'; foreach ($fblist as $slot) { - list($from, $to) = $slot; + list($from, $to, $type) = $slot; if ($from <= $end && $to > $start) { - $status = 'BUSY'; + $status = $type && $fbtypemap[$type] ? $fbtypemap[$type] : 'BUSY'; break; } } @@ -1256,9 +1258,9 @@ class calendar extends rcube_plugin if (is_array($fblist)) { $status = self::FREEBUSY_FREE; foreach ($fblist as $slot) { - list($from, $to) = $slot; + list($from, $to, $type) = $slot; if ($from <= $t_end && $to > $t) { - $status = self::FREEBUSY_BUSY; + $status = isset($type) ? $type : self::FREEBUSY_BUSY; break; } } diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index 6530a012..b691d31c 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -46,6 +46,7 @@ function rcube_calendar_ui(settings) var attendees_list; var freebusy_ui = {}; var freebusy_data = {}; + var freebusy_needsupdate; // general datepicker settings var datepicker_settings = { @@ -330,6 +331,7 @@ function rcube_calendar_ui(settings) var $dialog = $("#eventedit"); var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:action=='new' }; me.selected_event = event; + freebusy_needsupdate = false; // reset dialog first, enable/disable fields according to editable state $('#eventtabs').get(0).reset(); @@ -799,8 +801,8 @@ function rcube_calendar_ui(settings) var allday = $('#edit-allday').get(0); me.selected_event.start = parse_datetime(allday.checked ? '00:00' : $('#edit-starttime').val(), $('#edit-startdate').val()); me.selected_event.end = parse_datetime(allday.checked ? '23:59' : $('#edit-endtime').val(), $('#edit-enddate').val()); - if (me.selected_event.attendees) - update_freebusy_status(me.selected_event); + if (event_attendees) + freebusy_needsupdate = true; $('#edit-startdate').data('duration', Math.round((me.selected_event.end.getTime() - me.selected_event.start.getTime()) / 1000)); } }; @@ -902,10 +904,12 @@ function rcube_calendar_ui(settings) var update_freebusy_status = function(event) { var icons = attendees_list.find('img.availabilityicon'); - for (var i=0; i < event.attendees.length; i++) { - if (icons.get(i) && event.attendees[i].email && event.attendees[i].status != 'ACCEPTED') - check_freebusy_status(icons.get(i), event.attendees[i].email, event); + for (var i=0; i < event_attendees.length; i++) { + if (icons.get(i) && event_attendees[i].email && event_attendees[i].status != 'ACCEPTED') + check_freebusy_status(icons.get(i), event_attendees[i].email, event); } + + freebusy_needsupdate = false; }; // load free-busy status from server and update icon accordingly @@ -1563,8 +1567,11 @@ function rcube_calendar_ui(settings) // init event dialog $('#eventtabs').tabs({ show: function(event, ui) { - if (ui.panel.id == 'event-tab-3') + if (ui.panel.id == 'event-tab-3') { $('#edit-attendee-name').select(); + if (freebusy_needsupdate && me.selected_event) + update_freebusy_status(me.selected_event); + } } }); $('#edit-enddate, input.edit-alarm-date').datepicker(datepicker_settings); diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index 2b22320e..3fd03989 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -693,9 +693,19 @@ class kolab_driver extends calendar_driver if (empty($email)/* || $end < time()*/) return false; + // map vcalendar fbtypes to internal values + $fbtypemap = array( + 'FREE' => calendar::FREEBUSY_FREE, + 'BUSY-TENTATIVE' => calendar::FREEBUSY_TENTATIVE, + 'X-OUT-OF-OFFICE' => calendar::FREEBUSY_OOF, + 'OOF' => calendar::FREEBUSY_OOF); + // ask kolab server first $fbdata = @file_get_contents(rcube_kolab::get_freebusy_url($email)); - + + if (!$fbdata) + $fbdata = file_get_contents('http://localhost/roundcube/kolab/sample.ifb'); + // get free-busy url from contacts if (!$fbdata) { $fburl = null; @@ -722,10 +732,12 @@ class kolab_driver extends calendar_driver $fbcal->parsevCalendar($fbdata); if ($fb = $fbcal->findComponent('vfreebusy')) { $result = array(); + $params = $fb->getExtraParams(); foreach ($fb->getBusyPeriods() as $from => $to) { if ($to == null) // no information, assume free break; - $result[] = array($from, $to); + $type = $params[$from]['FBTYPE']; + $result[] = array($from, $to, isset($fbtypemap[$type]) ? $fbtypemap[$type] : calendar::FREEBUSY_BUSY); } return $result; diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index a4a8a509..ab4bf5c4 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -95,6 +95,7 @@ $labels['roleresource'] = 'Resource'; $labels['availfree'] = 'Free'; $labels['availbusy'] = 'Busy'; $labels['availunknown'] = 'Unknown'; +$labels['availtentative'] = 'Tentative'; $labels['availoutofoffice'] = 'Out of Office'; $labels['scheduletime'] = 'Available times'; $labels['sendnotifications'] = 'Send notifications'; diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css index 47e47e8c..308b002f 100644 --- a/plugins/calendar/skins/default/calendar.css +++ b/plugins/calendar/skins/default/calendar.css @@ -566,7 +566,7 @@ td.topalign { } .availability img.availabilityicon.loading { - background: url('images/loading-small.gif') top left no-repeat; + background: url('images/loading-small.gif') middle middle no-repeat; } #schedule-freebusy-times td.unknown, @@ -584,6 +584,11 @@ td.topalign { background: #c00; } +#schedule-freebusy-times td.tentative, +.availability img.availabilityicon.tentative { + background: #66d; +} + #schedule-freebusy-times td.out-of-office, .availability img.availabilityicon.out-of-office { background: #f0b400; diff --git a/plugins/calendar/skins/default/templates/freebusylegend.html b/plugins/calendar/skins/default/templates/freebusylegend.html index c3d0a86e..5cc01a18 100644 --- a/plugins/calendar/skins/default/templates/freebusylegend.html +++ b/plugins/calendar/skins/default/templates/freebusylegend.html @@ -1,6 +1,7 @@
+
From 267e50cd9526aa021537bd732ba4f11e212e8f97 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sat, 23 Jul 2011 14:41:12 +0200 Subject: [PATCH 08/17] Remove static url for testing --- plugins/calendar/drivers/kolab/kolab_driver.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index 3fd03989..0430bb0d 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -703,9 +703,6 @@ class kolab_driver extends calendar_driver // ask kolab server first $fbdata = @file_get_contents(rcube_kolab::get_freebusy_url($email)); - if (!$fbdata) - $fbdata = file_get_contents('http://localhost/roundcube/kolab/sample.ifb'); - // get free-busy url from contacts if (!$fbdata) { $fburl = null; From dd5f8e56bde546a392086e142bea0756da34f0fe Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sat, 23 Jul 2011 17:08:31 +0200 Subject: [PATCH 09/17] Display current event time over free/busy grid --- plugins/calendar/calendar_ui.js | 95 +++++++++++++++++---- plugins/calendar/lib/calendar_ui.php | 5 +- plugins/calendar/skins/default/calendar.css | 16 +++- plugins/calendar/skins/default/iehacks.css | 9 ++ 4 files changed, 103 insertions(+), 22 deletions(-) diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index b691d31c..ed0318e4 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -330,7 +330,7 @@ function rcube_calendar_ui(settings) var $dialog = $("#eventedit"); var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:action=='new' }; - me.selected_event = event; + me.selected_event = $.extend({}, event); // clone event object freebusy_needsupdate = false; // reset dialog first, enable/disable fields according to editable state @@ -635,7 +635,7 @@ function rcube_calendar_ui(settings) fb_end.setTime(fb_start.getTime() + 86400000); freebusy_data = {}; - freebusy_ui.loading = 1; // prevent render_freebusy_grid() to load data + freebusy_ui.loading = 1; // prevent render_freebusy_grid() to load data yet freebusy_ui.numdays = allday.checked ? 7 : 1; freebusy_ui.interval = allday.checked ? 360 : 60; freebusy_ui.start = fb_start; @@ -673,6 +673,14 @@ function rcube_calendar_ui(settings) width: 850 }).show(); + // adjust dialog size to fit grid without scrolling + var gridw = $('#schedule-freebusy-times').width(); + var overflow = gridw - $('#attendees-freebusy-table td.times').width() + 1; + if (overflow > 0) { + $dialog.dialog('option', 'width', Math.min((window.innerWidth || document.documentElement.clientWidth) - 40, 850 + overflow)); + $dialog.dialog('option', 'position', ['center', 'center']); + } + // fetch data from server freebusy_ui.loading = 0; load_freebusy_data(freebusy_ui.start, freebusy_ui.interval); @@ -714,21 +722,72 @@ function rcube_calendar_ui(settings) times_html += '' + slots_row + ''; } - $('#schedule-freebusy-times > thead').html(dates_row + times_row); - $('#schedule-freebusy-times > tbody').html(times_html); + var table = $('#schedule-freebusy-times'); + table.children('thead').html(dates_row + times_row); + table.children('tbody').html(times_html); // if we have loaded free-busy data, show it if (!freebusy_ui.loading) { if (date2unixtime(freebusy_ui.start) < freebusy_data.start || date2unixtime(freebusy_ui.end) > freebusy_data.end || freebusy_ui.interval != freebusy_data.interval) { - load_freebusy_data(freebusy_ui.start, freebusy_ui.interval) - return; + load_freebusy_data(freebusy_ui.start, freebusy_ui.interval); } - - for (var email, i=0; i < event_attendees.length; i++) { - if ((email = event_attendees[i].email)) - update_freebusy_display(email); + else { + for (var email, i=0; i < event_attendees.length; i++) { + if ((email = event_attendees[i].email)) + update_freebusy_display(email); + } } } + + // render current event date/time selection over grid table + // use timeout to let the dom attributes (width/height/offset) be set first + window.setTimeout(function(){ render_freebusy_overlay(); }, 10); + }; + + // render overlay element over the grid to visiualize the current event date/time + var render_freebusy_overlay = function() + { + var overlay = $('#schedule-event-time'); + if (me.selected_event.end.getTime() < freebusy_ui.start.getTime() || me.selected_event.start.getTime() > freebusy_ui.end.getTime()) { + overlay.hide(); + } + else { + var table = $('#schedule-freebusy-times'), + width = 0, + pos = { top:table.children('thead').height(), left:0 }, + eventstart = Math.floor(me.selected_event.start.getTime() / 1000), + eventend = Math.floor(me.selected_event.end.getTime() / 1000), + slotstart = Math.floor(freebusy_ui.start.getTime() / 1000), + slotsize = freebusy_ui.interval * 60, + slotend, fraction, $cell; + + // iterate through slots to determine position and size of the overlay + table.children('thead').find('td').each(function(i, cell){ + slotend = slotstart + slotsize - 60; + // event starts in this slot: compute left + if (eventstart >= slotstart && eventstart <= slotend) { + fraction = 1 - (slotend - eventstart) / slotsize; + pos.left = Math.round(cell.offsetLeft + cell.offsetWidth * fraction); + } + // event ends in this slot: compute width + else if (eventend >= slotstart && eventend <= slotend) { + fraction = 1 - (slotend - eventend) / slotsize; + width = Math.round(cell.offsetLeft + cell.offsetWidth * fraction) - pos.left; + } + + slotstart = slotstart + slotsize; + }); + + if (!width) + width = table.width() - pos.left; + + // overlay is visible + if (width > 0) + overlay.css({ width: (width-5)+'px', height:(table.children('tbody').height() - 4)+'px', left:pos.left+'px', top:pos.top+'px' }).show(); + else + overlay.hide(); + } + }; // fetch free-busy information for each attendee from server @@ -1376,8 +1435,10 @@ function rcube_calendar_ui(settings) // callback for clicks in all-day box dayClick: function(date, allDay, e, view) { var now = new Date().getTime(); - if (now - day_clicked_ts < 400 && day_clicked == date.getTime()) // emulate double-click on day - return event_edit_dialog('new', { start:date, end:date, allDay:allDay, calendar:me.selected_calendar }); + if (now - day_clicked_ts < 400 && day_clicked == date.getTime()) { // emulate double-click on day + var enddate = new Date(); enddate.setTime(date.getTime() + 86400000 - 60000); + return event_edit_dialog('new', { start:date, end:enddate, allDay:allDay, calendar:me.selected_calendar }); + } if (!ignore_click) { view.calendar.gotoDate(date); @@ -1577,7 +1638,7 @@ function rcube_calendar_ui(settings) $('#edit-enddate, input.edit-alarm-date').datepicker(datepicker_settings); $('#edit-startdate').datepicker(datepicker_settings).datepicker('option', 'onSelect', shift_enddate).change(function(){ shift_enddate(this.value); }); $('#edit-enddate').datepicker('option', 'onSelect', event_times_changed).change(event_times_changed); - $('#edit-allday').click(function(){ $('#edit-starttime, #edit-endtime')[(this.checked?'hide':'show')](); }); + $('#edit-allday').click(function(){ $('#edit-starttime, #edit-endtime')[(this.checked?'hide':'show')](); event_times_changed(); }); // configure drop-down menu on time input fields based on jquery UI autocomplete $('#edit-starttime, #edit-endtime, input.edit-alarm-time') @@ -1637,13 +1698,13 @@ function rcube_calendar_ui(settings) event_freebusy_dialog(); }); - $('#shedule-freebusy-prev').button().click(function(){ render_freebusy_grid(-1); }); - $('#shedule-freebusy-next').button().click(function(){ render_freebusy_grid(1); }).parent().buttonset(); - + $('#shedule-freebusy-prev').html(bw.ie6 ? '<<' : '◄').button().click(function(){ render_freebusy_grid(-1); }); + $('#shedule-freebusy-next').html(bw.ie6 ? '>>' : '►').button().click(function(){ render_freebusy_grid(1); }).parent().buttonset(); + $('#schedule-freebusy-wokinghours').click(function(){ $('#workinghourscss').remove(); if (this.checked) - $('').appendTo('head'); + $('').appendTo('head'); }); // add proprietary css styles if not IE diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php index 7e44af93..74e1228c 100644 --- a/plugins/calendar/lib/calendar_ui.php +++ b/plugins/calendar/lib/calendar_ui.php @@ -594,7 +594,10 @@ class calendar_ui html::div(array('id' => 'schedule-attendees-list'), '') ); $table->add('times', - html::div('scroll', html::tag('table', array('id' => 'schedule-freebusy-times', 'border' => 0, 'cellspacing' => 0), html::tag('thead') . html::tag('tbody'))) + html::div('scroll', + html::tag('table', array('id' => 'schedule-freebusy-times', 'border' => 0, 'cellspacing' => 0), html::tag('thead') . html::tag('tbody')) . + html::div(array('id' => 'schedule-event-time', 'style' => 'display:none'), ' ') + ) ); return $table->show($attrib); diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css index 308b002f..aeb8a9ec 100644 --- a/plugins/calendar/skins/default/calendar.css +++ b/plugins/calendar/skins/default/calendar.css @@ -36,6 +36,7 @@ body.calendarmain { #datepicker .ui-datepicker { width: 97% !important; + box-shadow: none; -moz-box-shadow: none; -webkit-box-shadow: none; } @@ -656,6 +657,7 @@ td.topalign { } #attendees-freebusy-table div.scroll { + position: relative; overflow: auto; } @@ -665,10 +667,6 @@ td.topalign { border-color: #ccc; } -#attendees-freebusy-table div.timesheader { - padding: 0.3em; -} - #schedule-attendees-list div.attendee { padding: 3px 20px 3px 6px; border-top: 1px solid #ccc; @@ -693,6 +691,7 @@ td.topalign { border-width: 0 1px 0 1px; } +#attendees-freebusy-table div.timesheader, #schedule-freebusy-times tr.times td { min-width: 30px; font-size: 9px; @@ -700,6 +699,15 @@ td.topalign { text-align: center; } +#schedule-event-time { + position: absolute; + border: 2px solid #333; + background: #777; + background: rgba(60, 60, 60, 0.6); + opacity: 0.5; + border-radius: 4px; +} + #eventfreebusy .schedule-options { position: relative; margin-bottom: 2em; diff --git a/plugins/calendar/skins/default/iehacks.css b/plugins/calendar/skins/default/iehacks.css index fa282176..31f82d78 100644 --- a/plugins/calendar/skins/default/iehacks.css +++ b/plugins/calendar/skins/default/iehacks.css @@ -43,3 +43,12 @@ html #calendartoolbar a.buttonPas { .fc-header-title h2 { font-size: 16px; } + +#schedule-event-time { + filter: alpha(opacity=40); +} + +#eventfreebusy .schedule-buttons, +#edit-attendees-form #edit-attendee-schedule { + right: 0.6em; +} \ No newline at end of file From 0bc55bd1e01193cd7a40fedd07314cfd2e75f40d Mon Sep 17 00:00:00 2001 From: "Aleksander Machniak (Kolab Systems)" Date: Sun, 24 Jul 2011 10:56:47 +0200 Subject: [PATCH 10/17] Added contact validation, require name or email field --- .../lib/rcube_kolab_contacts.php | 33 ++++++++++++++++--- .../kolab_addressbook/localization/en_US.inc | 15 +++++---- .../kolab_addressbook/localization/pl_PL.inc | 15 +++++---- 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php index 782c06b3..5b897c27 100644 --- a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php +++ b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php @@ -896,6 +896,31 @@ class rcube_kolab_contacts extends rcube_addressbook return false; } + /** + * Check the given data before saving. + * If input not valid, the message to display can be fetched using get_error() + * + * @param array Associative array with contact data to save + * + * @return boolean True if input is valid, False if not. + */ + public function validate($save_data) + { + // validate e-mail addresses + $valid = parent::validate($save_data); + + // require at least one e-mail address (syntax check is already done) + if ($valid) { + if (!strlen($save_data['name']) + && !array_filter($this->get_col_values('email', $save_data, true)) + ) { + $this->set_error('warning', 'kolab_addressbook.noemailnamewarning'); + $valid = false; + } + } + + return $valid; + } /** * Establishes a connection to the Kolab_Data object for accessing contact data @@ -907,7 +932,6 @@ class rcube_kolab_contacts extends rcube_addressbook } } - /** * Establishes a connection to the Kolab_Data object for accessing groups data */ @@ -918,7 +942,6 @@ class rcube_kolab_contacts extends rcube_addressbook } } - /** * Simply fetch all records and store them in private member vars */ @@ -945,7 +968,6 @@ class rcube_kolab_contacts extends rcube_addressbook } } - /** * Callback function for sorting contacts */ @@ -954,7 +976,6 @@ class rcube_kolab_contacts extends rcube_addressbook return strcasecmp($a['name'], $b['name']); } - /** * Read distribution-lists AKA groups from server */ @@ -979,7 +1000,6 @@ class rcube_kolab_contacts extends rcube_addressbook } } - /** * Map fields from internal Kolab_Format to Roundcube contact format */ @@ -1030,6 +1050,9 @@ class rcube_kolab_contacts extends rcube_addressbook return array_filter($out); } + /** + * Map fields from Roundcube format to internal Kolab_Format + */ private function _from_rcube_contact($contact) { $object = array(); diff --git a/plugins/kolab_addressbook/localization/en_US.inc b/plugins/kolab_addressbook/localization/en_US.inc index ce90f60a..f3cb17ea 100644 --- a/plugins/kolab_addressbook/localization/en_US.inc +++ b/plugins/kolab_addressbook/localization/en_US.inc @@ -25,12 +25,13 @@ $labels['globalonly'] = 'Global address book(s) only'; $messages['bookdeleteconfirm'] = 'Do you really want to delete the selected address book and all contacts in it?'; $messages['bookdeleting'] = 'Deleting address book...'; $messages['booksaving'] = 'Saving address book...'; -$messages['bookdeleted'] = 'Address book deleted successfully'; -$messages['bookupdated'] = 'Address book updated successfully'; -$messages['bookcreated'] = 'Address book created successfully'; -$messages['bookdeleteerror'] = 'An error occured while deleting address book'; -$messages['bookupdateerror'] = 'An error occured while updating address book'; -$messages['bookcreateerror'] = 'An error occured while creating address book'; -$messages['nobooknamewarning'] = 'Please, enter address book name'; +$messages['bookdeleted'] = 'Address book deleted successfully.'; +$messages['bookupdated'] = 'Address book updated successfully.'; +$messages['bookcreated'] = 'Address book created successfully.'; +$messages['bookdeleteerror'] = 'An error occured while deleting address book.'; +$messages['bookupdateerror'] = 'An error occured while updating address book.'; +$messages['bookcreateerror'] = 'An error occured while creating address book.'; +$messages['nobooknamewarning'] = 'Please, enter address book name.'; +$messages['noemailnamewarning'] = 'Please, enter email address or contact name.'; ?> diff --git a/plugins/kolab_addressbook/localization/pl_PL.inc b/plugins/kolab_addressbook/localization/pl_PL.inc index 61887231..e2735783 100644 --- a/plugins/kolab_addressbook/localization/pl_PL.inc +++ b/plugins/kolab_addressbook/localization/pl_PL.inc @@ -25,12 +25,13 @@ $labels['parentbook'] = 'Książka nadrzędna'; $messages['bookdeleteconfirm'] = 'Czy na pewno chcesz usunąć wybraną książkę i wszystkie kontakty w niej zapisane?'; $messages['bookdeleting'] = 'Usuwanie książki adresowej...'; $messages['booksaving'] = 'Zapisywanie książki adresowej...'; -$messages['bookdeleted'] = 'Książka adresowa została usunięta'; -$messages['bookupdated'] = 'Książka adresowa została zaktualizowana'; -$messages['bookcreated'] = 'Książka adresowa została utworzona'; -$messages['bookdeleteerror'] = 'Wystąpił błąd podczas usuwania książki adresowej'; -$messages['bookupdateerror'] = 'Wystąpił błąd podczas zmiany książki adresowej'; -$messages['bookcreateerror'] = 'Wystąpił błąd podczas tworzenia książki adresowej'; -$messages['nobooknamewarning'] = 'Proszę podać nazwę książki adresowej'; +$messages['bookdeleted'] = 'Książka adresowa została usunięta.'; +$messages['bookupdated'] = 'Książka adresowa została zaktualizowana.'; +$messages['bookcreated'] = 'Książka adresowa została utworzona.'; +$messages['bookdeleteerror'] = 'Wystąpił błąd podczas usuwania książki adresowej.'; +$messages['bookupdateerror'] = 'Wystąpił błąd podczas zmiany książki adresowej.'; +$messages['bookcreateerror'] = 'Wystąpił błąd podczas tworzenia książki adresowej.'; +$messages['nobooknamewarning'] = 'Proszę podać nazwę książki adresowej.'; +$messages['noemailnamewarning'] = 'Proszę podać adres email lub nazwę kontaktu.'; ?> From ad5a5c6e84008a374c4973edc50c5087d572ee3b Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sun, 24 Jul 2011 12:46:54 +0200 Subject: [PATCH 11/17] Implement finding of free time slots --- plugins/calendar/calendar.php | 4 + plugins/calendar/calendar_ui.js | 139 ++++++++++++++++-- plugins/calendar/config.inc.php.dist | 6 + plugins/calendar/lib/calendar_ui.php | 2 +- plugins/calendar/localization/en_US.inc | 4 + plugins/calendar/skins/default/calendar.css | 38 ++++- .../skins/default/images/attendee-status.gif | Bin 1611 -> 2041 bytes .../skins/default/templates/calendar.html | 16 +- 8 files changed, 193 insertions(+), 16 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 7c9cffae..1c5801a1 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -52,6 +52,8 @@ class calendar extends rcube_plugin 'calendar_timeslots' => 2, 'calendar_first_day' => 1, 'calendar_first_hour' => 6, + 'calendar_work_start' => 6, + 'calendar_work_end' => 18, ); private $default_categories = array( @@ -627,6 +629,8 @@ class calendar extends rcube_plugin $settings['timeslots'] = (int)$this->rc->config->get('calendar_timeslots', $this->defaults['calendar_timeslots']); $settings['first_day'] = (int)$this->rc->config->get('calendar_first_day', $this->defaults['calendar_first_day']); $settings['first_hour'] = (int)$this->rc->config->get('calendar_first_hour', $this->defaults['calendar_first_hour']); + $settings['work_start'] = (int)$this->rc->config->get('calendar_work_start', $this->defaults['calendar_work_start']); + $settings['work_end'] = (int)$this->rc->config->get('calendar_work_end', $this->defaults['calendar_work_end']); $settings['timezone'] = $this->timezone; // localization diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index ed0318e4..6147da40 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -44,7 +44,7 @@ function rcube_calendar_ui(settings) var ignore_click = false; var event_attendees = null; var attendees_list; - var freebusy_ui = {}; + var freebusy_ui = { workinhoursonly:false }; var freebusy_data = {}; var freebusy_needsupdate; @@ -628,9 +628,15 @@ function rcube_calendar_ui(settings) endtime.val("23:59").hide(); } + // read attendee roles from drop-downs + $('select.edit-attendee-role').each(function(i, elem){ + if (event_attendees[i]) + event_attendees[i].role = $(elem).val(); + }); + // render time slots var now = new Date(), fb_start = new Date(), fb_end = new Date(); - fb_start.setTime(Math.max(now, event.start)); + fb_start.setTime(event.start); fb_start.setHours(0); fb_start.setMinutes(0); fb_start.setSeconds(0); fb_start.setMilliseconds(0); fb_end.setTime(fb_start.getTime() + 86400000); @@ -653,9 +659,23 @@ function rcube_calendar_ui(settings) $('#schedule-attendees-list').html(list_html); + // enable/disable buttons + $('#shedule-find-prev').button('option', 'disabled', (fb_start.getTime() < now.getTime())); + // dialog buttons var buttons = {}; + buttons[rcmail.gettext('adobt', 'calendar')] = function() { + $('#edit-startdate').val(startdate.val()); + $('#edit-starttime').val(starttime.val()); + $('#edit-enddate').val(enddate.val()); + $('#edit-endtime').val(endtime.val()); + if (freebusy_needsupdate) + update_freebusy_status(me.selected_event); + freebusy_needsupdate = false; + $dialog.dialog("close"); + }; + buttons[rcmail.gettext('cancel', 'calendar')] = function() { $dialog.dialog("close"); }; @@ -666,6 +686,8 @@ function rcube_calendar_ui(settings) closeOnEscape: true, title: rcmail.gettext('scheduletime', 'calendar'), close: function() { + if (bw.ie6) + $("#edit-attendees-table").css('visibility','visible'); $dialog.dialog("destroy").hide(); }, buttons: buttons, @@ -673,6 +695,10 @@ function rcube_calendar_ui(settings) width: 850 }).show(); + // hide edit dialog on IE6 because of drop-down elements + if (bw.ie6) + $("#edit-attendees-table").css('visibility','hidden'); + // adjust dialog size to fit grid without scrolling var gridw = $('#schedule-freebusy-times').width(); var overflow = gridw - $('#attendees-freebusy-table td.times').width() + 1; @@ -704,8 +730,8 @@ function rcube_calendar_ui(settings) lastdate = datestr; } - // TODO: define working hours by config - css = (freebusy_ui.numdays == 1 && (curdate.getHours() < 6 || curdate.getHours() > 18)) ? 'offhours' : 'workinghours'; + // set css class according to working hours + css = (freebusy_ui.numdays == 1 && (curdate.getHours() < settings['work_start'] || curdate.getHours() > settings['work_end'])) ? 'offhours' : 'workinghours'; times_row += '' + Q($.fullCalendar.formatDate(curdate, settings['time_format'])) + ''; slots_row += ' '; @@ -754,10 +780,10 @@ function rcube_calendar_ui(settings) else { var table = $('#schedule-freebusy-times'), width = 0, - pos = { top:table.children('thead').height(), left:0 }, - eventstart = Math.floor(me.selected_event.start.getTime() / 1000), - eventend = Math.floor(me.selected_event.end.getTime() / 1000), - slotstart = Math.floor(freebusy_ui.start.getTime() / 1000), + pos = { top:table.children('thead').height(), left:-1 }, + eventstart = date2unixtime(me.selected_event.start), + eventend = date2unixtime(me.selected_event.end), + slotstart = date2unixtime(freebusy_ui.start), slotsize = freebusy_ui.interval * 60, slotend, fraction, $cell; @@ -782,7 +808,7 @@ function rcube_calendar_ui(settings) width = table.width() - pos.left; // overlay is visible - if (width > 0) + if (width > 0 && pos.left >= 0) overlay.css({ width: (width-5)+'px', height:(table.children('tbody').height() - 4)+'px', left:pos.left+'px', top:pos.top+'px' }).show(); else overlay.hide(); @@ -852,6 +878,97 @@ function rcube_calendar_ui(settings) } }; + // attempt to find a time slot where all attemdees are available + var freebusy_find_slot = function(dir) + { + var event = me.selected_event, + eventstart = date2unixtime(event.start), // calculate with unitimes + eventend = date2unixtime(event.end), + duration = eventend - eventstart, + sinterval = freebusy_data.interval * 60, + numslots = Math.ceil(duration / sinterval), + checkdate, slotend, email, curdate; + + // shift event times to next possible slot + eventstart += sinterval * dir; + eventend += sinterval * dir; + + var candidatecount = 0, candidatestart = candidateend = success = false; + for (var slot = dir > 0 ? freebusy_data.start : freebusy_data.end - sinterval; (dir > 0 && slot < freebusy_data.end) || (dir < 0 && slot >= freebusy_data.start); slot += sinterval * dir) { + slotend = slot + sinterval; + if ((dir > 0 && slotend <= eventstart) || (dir < 0 && slot >= eventend)) // skip + continue; + + // respect workingours setting + if (freebusy_ui.workinhoursonly && freebusy_data.interval <= 60) { + curdate = fromunixtime(dir > 0 || !candidateend ? slot : (candidateend - duration)); + if (curdate.getHours() < settings['work_start'] || curdate.getHours() > settings['work_end']) { // skip off-hours + candidatestart = candidateend = false; + candidatecount = 0; + continue; + } + } + + if (!candidatestart) + candidatestart = slot; + + // check freebusy data for all attendees + for (var i=0; i < event_attendees.length; i++) { + if ((email = event_attendees[i].email) && freebusy_data[email][slot] > 1) { + candidatestart = false; + candidatecount = 0; + break; + } + } + + // occupied slot + if (!candidatestart) + continue; + + // set candidate end to slot end time + candidatecount++; + if (dir < 0 && !candidateend) + candidateend = slotend; + + // if candidate is big enough, this is it! + if (candidatecount == numslots) { + if (dir > 0) { + event.start = fromunixtime(candidatestart); + event.end = fromunixtime(candidatestart + duration); + } + else { + event.end = fromunixtime(candidateend); + event.start = fromunixtime(candidateend - duration); + } + success = true; + break; + } + } + + // update event date/time fields + if (success) { + $('#schedule-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])); + $('#schedule-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])); + $('#schedule-enddate').val($.fullCalendar.formatDate(event.end, settings['date_format'])); + $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])); + freebusy_needsupdate = true; + + // move freebusy grid if necessary + if (event.start.getTime() >= freebusy_ui.end.getTime()) + render_freebusy_grid(1); + else if (event.end.getTime() <= freebusy_ui.start.getTime()) + render_freebusy_grid(-1); + else + render_freebusy_overlay(); + + var now = new Date(); + $('#shedule-find-prev').button('option', 'disabled', (event.start.getTime() < now.getTime())); + } + else { + alert(rcmail.gettext('noslotfound','calendar')); + } + }; + // update event properties and attendees availability if event times have changed var event_times_changed = function() @@ -1701,7 +1818,11 @@ function rcube_calendar_ui(settings) $('#shedule-freebusy-prev').html(bw.ie6 ? '<<' : '◄').button().click(function(){ render_freebusy_grid(-1); }); $('#shedule-freebusy-next').html(bw.ie6 ? '>>' : '►').button().click(function(){ render_freebusy_grid(1); }).parent().buttonset(); + $('#shedule-find-prev').button().click(function(){ freebusy_find_slot(-1); }); + $('#shedule-find-next').button().click(function(){ freebusy_find_slot(1); }); + $('#schedule-freebusy-wokinghours').click(function(){ + freebusy_ui.workinhoursonly = this.checked; $('#workinghourscss').remove(); if (this.checked) $('').appendTo('head'); diff --git a/plugins/calendar/config.inc.php.dist b/plugins/calendar/config.inc.php.dist index aa580071..a6b23415 100644 --- a/plugins/calendar/config.inc.php.dist +++ b/plugins/calendar/config.inc.php.dist @@ -52,6 +52,12 @@ $rcmail_config['calendar_first_day'] = 1; // first hour of the calendar (0-23) $rcmail_config['calendar_first_hour'] = 6; +// working hours begin +$rcmail_config['calendar_work_start'] = 6; + +// working hours end +$rcmail_config['calendar_work_end'] = 18; + // event categories $rcmail_config['calendar_categories'] = array( 'Personal' => 'c0c0c0', diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php index 74e1228c..07eb7b0a 100644 --- a/plugins/calendar/lib/calendar_ui.php +++ b/plugins/calendar/lib/calendar_ui.php @@ -591,7 +591,7 @@ class calendar_ui $table->add('attendees', html::tag('h3', 'boxtitle', $this->calendar->gettext('tabattendees')) . html::div('timesheader', ' ') . - html::div(array('id' => 'schedule-attendees-list'), '') + html::div(array('id' => 'schedule-attendees-list', 'class' => 'attendees-list'), '') ); $table->add('times', html::div('scroll', diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index ab4bf5c4..81dc57bb 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -31,6 +31,7 @@ $labels['edit'] = 'Edit'; $labels['save'] = 'Save'; $labels['remove'] = 'Remove'; $labels['cancel'] = 'Cancel'; +$labels['adobt'] = 'Adopt changes'; $labels['print'] = 'Print calendars'; $labels['title'] = 'Summary'; $labels['description'] = 'Description'; @@ -100,6 +101,9 @@ $labels['availoutofoffice'] = 'Out of Office'; $labels['scheduletime'] = 'Available times'; $labels['sendnotifications'] = 'Send notifications'; $labels['onlyworkinghours'] = 'Show only working hours'; +$labels['prevslot'] = 'Previous Slot'; +$labels['nextslot'] = 'Next Slot'; +$labels['noslotfound'] = 'Unable to find a free time slot'; // event dialog tabs $labels['tabsummary'] = 'Summary'; diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css index aeb8a9ec..4dcf1011 100644 --- a/plugins/calendar/skins/default/calendar.css +++ b/plugins/calendar/skins/default/calendar.css @@ -597,6 +597,7 @@ td.topalign { #edit-attendees-legend { margin-top: 3em; + margin-bottom: 0.5em; } #edit-attendees-legend .legend { @@ -667,13 +668,33 @@ td.topalign { border-color: #ccc; } -#schedule-attendees-list div.attendee { - padding: 3px 20px 3px 6px; +.attendees-list .attendee { + padding: 3px 4px 3px 20px; + background: url('images/attendee-status.gif') 2px -97px no-repeat; +} + +.attendees-list div.attendee { border-top: 1px solid #ccc; } -#schedule-attendees-list div.loading { - background: url('images/loading-small.gif') top right no-repeat; +.attendees-list span.attendee { + margin-right: 2em; +} + +.attendees-list .organizer { + background-position: 3px -77px; +} + +.attendees-list .opt-participant { + background-position: 2px -117px; +} + +.attendees-list .chair { + background-position: 2px -137px; +} + +.attendees-list .loading { + background: url('images/loading-small.gif') 1px 50% no-repeat; } #schedule-freebusy-times { @@ -719,6 +740,15 @@ td.topalign { right: 0; } +#eventfreebusy .schedule-find-buttons { + padding-top:1em; +} + +#eventfreebusy .schedule-find-buttons button { + min-width: 9em; + text-align: center; +} + span.edit-alarm-set { white-space: nowrap; } diff --git a/plugins/calendar/skins/default/images/attendee-status.gif b/plugins/calendar/skins/default/images/attendee-status.gif index a5f65a02e14602aae4db5d4c2955bce679af565a..5c08aae316ad60f3b71dd178c6e0e922943888b1 100644 GIT binary patch literal 2041 zcmVuB@TiUv3>Hq-X%eBG5!T-!W?8}9?luFy}_0_$a_S~DMrl!Qi#Idol z;^N}Y+vA&?n`nK8ugIRpq*Bq*(Y3X;$<*4($;r97xr&{p-KP`%%`(&F?PYX(yLu;0 zX?9qf9Vez;M|IkNfsW0WZPycs!JXK*h zP+afNe9SX5X@igc+-GR3F=whUufodu_w&G7TKC_TO@$!4nN@O?TJXCaznxjmj1-+; z9@5&+Lx2z6q)Ow@&+yOB@4P$6ri8<-mdc}HZLc@&)1KVv@x_e>zGnc|q(a-FL$txi z$RHr<@aL*&BByg8@W))longzZcZWj&lTHQ6fGW?NJdk~9)!^po-Oq?1Afx+N2=ap$MqC!=qYS(c;9`;m?DR zmTfWsYlB*{!OHaK=YNx(*4o?(Xhea(=D3zWdl| zGC@h5vAJ@9hsDXujhCF_*1yi+>Hq1a_SBHVpIZI>{Ewrol&7!It!ALHx2mwR;=*~! zq)hzz_5c6>A^8LW00930EC2ui01g14000R80RIUbNU)&5I5jkk7@?tt!73#-z?itO zM3sSYN{rB=K}19tCQ1y1He!Q_At;BKFfjs}KnY^Pn9u-&!w?QI#7GdhAxsGgBQl^H zB4R|C4FWUx)UYAPkQx~@gvlT<=T8$us=&}tLJSOn8q8>rut9?uF(txk7?>f0m=Rey zwk24@Oo23MJa{Nz#$iDTdkc8jX|S;2!-x|rUd*`h;#?rE+_-TCM6QC_7R_*Qc@B!M1KoD~l$bkjx&!0T3K?uBJ+P3XHb{4p}A~2bcNO`c`wsS6$ zk7NQt9k*i#xE(~Be1H-t$}}SC*Z*k$av@3}6jTc~eMj_1H7Y0rkNjA$08u1O3w|6S zc+(6-)V-Q9t;e-?xW%{;E6y)@Sx(34Ddk2Krd)HW0x-wq{<7(@BktY$h@G6K}1|*j4{`IS>c^{ z=9wo3ya*rwFBljE03(kWGRPo?Ji-V71To^w0fMBEf*=Pt!^l9fJaRw^cXWD39tS)U zi$D$-B8aD-+98M`4hST`ASopOmuem<3?hI))WAxeb$8TCjX*t^dIxx&uG)h@G*q+c zu$`uGKs7WB^a=`)N=hjTuMkvBFSDR9$|xwz(n~Q0UC_)fyEJowp6s^ku0d-sD$o`< z#4`{8Yy1O88D9Le#W9xD`;Qm-*r2Z>q|mU85+MlD#s&3cVlW(vC=$vP{8-Y15EZN= zvA&aZfdK`dOftYBytpyR#Fuo@2+5b6EQuSJOst3!FOy+O2!}}Gz{haFLBY#q;7~!& zL6BjvFH_L-vJD~h)AJZ$`0|e$WsnleGRNS6!!Z?*QidA;m@|*aBR?Qc5GjLU&OgKG zpo0wN>|nP!GRUCAFz@#N?Kfg)1Valg!JJXp8PcROzyPU8V|YO<*uo4LU@&6~3kE4O z1|*zAGKMk+DZt7Eq))(#0tV?403oas5`ZoSxuQTRewLzuD+F~C!8E2Q5I_J5m@-WS zoe0zlFf7#a4C&9*!U8a@2=t3E1WZ840S6E=0Rh7NB9IiU^dbKE<4f^ zv&}es#4o@6+X#fm{rKm9|KI!{Mu$Ch@P|DN6NLmMAO!3PYo4>sUK1u8%T8kkLZea2sU+4f9DDe+U$f6Lalm#h4 z`3(|O;vbc;1}Y0-jaU8y9^`011CprAUheWARS-dh5TO006(8S~FJz=J<^TZaVPWyO3ctU8!ovS|cge}g{@iEp&wSk6 z+{`mGn3$N$006g@Q9NEC*5S{FhK9|nXVB2l(%R3k%bumBrCy>Xc9CIvs9MMX0PxLn zyOUbGnN`J&2D7uXO@$zBuQ#?~VQYh0a+O-+o}TQ&TJOhQ)YR1W+n(sKAnvvx_2|#p z!Je*RVXm&O;96RHt6JKmAlAK}%buRtARu&&Vd?JYrnR1^x1Php!};Kxz`(%w-<83^ z!K9?5prD|kp`rE6UEbc_%A;ZPy%X7?2%}nBkbP<1sTR@V#PGWv#GPS|j*jl0o>Gq> z?bDt-aScO&577Vs;^N}Q#>TD1p1@jK+@%oGs#@dE&!=-BRFoiDnj%Ss7EOv8$DW>= zcr@i&TF4+Ew6wJG##-p?=f$K^_uiUksxax@&)*;*@XybPARy`K>8=0(>hR}ltu<<` zH1^z>m6etG`T5JOciFgao`hbVfnB$obMe7a@70UfwR6CpTK3Ie#GqOG(p~%5Y3}as z;=+03$(85l=k(|2x3{;lkWAa=)0uo)znVw=*J5I#E$YEt;@85^vuxVBXUfXT=*)tv ztE_SBHwxnHrdu~V5DsfIwFgItkyQ7|Be zMwxdr=(|_buU3g`NuGjR$)rr>zg_0TXzRyi?a+)*l@pzTV8zA7`sJYhA^8LW00930EC2ui01g0N000R80RIUbNU)&5em)XixRA()K~W%Rj7c_T zAqY_e`GLSBz|D~z(ny>DF_6mz8b=iNKv8F%7q>7-p`^2HaKbYTz}EXaU?2OeM` z0}Egf#6lhwNB{u@5>$`}3j-1VB0~i?EMV&b4OAclAp&WEfdseW>W>5%Xd#dn9zZai zunj=qffoXqk*s&%E#Ryf0yXmM0_xdHY$F1Np$Dzp+Im9;dW10$9*m^AYO9RkL69ea zNYTe1fAk>*Af6a>1{9Ax0>v4V7Hsgr2IUgKKI;^G^r|xbx3C3G{$~GylBv z%mx#;U;s8kz_N)r)HFj24
- - +
+
  @@ -205,8 +205,20 @@  
+
+
+ + +
+
+
+ + + + +
From 2cefdc33434aac893ef970a91330303bd6a384d4 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sun, 24 Jul 2011 15:00:35 +0200 Subject: [PATCH 12/17] Fix/improve free time slot selection --- plugins/calendar/calendar_ui.js | 21 ++++++----- plugins/calendar/localization/en_US.inc | 2 +- plugins/calendar/skins/default/calendar.css | 4 +-- .../skins/default/templates/calendar.html | 35 +++++++++++-------- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index 6147da40..7a4af37f 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -626,6 +626,7 @@ function rcube_calendar_ui(settings) if (allday.checked) { starttime.val("00:00").hide(); endtime.val("23:59").hide(); + event.allDay = true; } // read attendee roles from drop-downs @@ -642,7 +643,7 @@ function rcube_calendar_ui(settings) freebusy_data = {}; freebusy_ui.loading = 1; // prevent render_freebusy_grid() to load data yet - freebusy_ui.numdays = allday.checked ? 7 : 1; + freebusy_ui.numdays = allday.checked ? 7 : Math.ceil(duration * 2 / 86400); freebusy_ui.interval = allday.checked ? 360 : 60; freebusy_ui.start = fb_start; freebusy_ui.end = new Date(freebusy_ui.start.getTime() + 86400000 * freebusy_ui.numdays); @@ -780,7 +781,7 @@ function rcube_calendar_ui(settings) else { var table = $('#schedule-freebusy-times'), width = 0, - pos = { top:table.children('thead').height(), left:-1 }, + pos = { top:table.children('thead').height(), left:0 }, eventstart = date2unixtime(me.selected_event.start), eventend = date2unixtime(me.selected_event.end), slotstart = date2unixtime(freebusy_ui.start), @@ -808,7 +809,7 @@ function rcube_calendar_ui(settings) width = table.width() - pos.left; // overlay is visible - if (width > 0 && pos.left >= 0) + if (width > 0) overlay.css({ width: (width-5)+'px', height:(table.children('tbody').height() - 4)+'px', left:pos.left+'px', top:pos.top+'px' }).show(); else overlay.hide(); @@ -886,12 +887,13 @@ function rcube_calendar_ui(settings) eventend = date2unixtime(event.end), duration = eventend - eventstart, sinterval = freebusy_data.interval * 60, + intvlslots = event.allDay ? 4 : 1, numslots = Math.ceil(duration / sinterval), checkdate, slotend, email, curdate; // shift event times to next possible slot - eventstart += sinterval * dir; - eventend += sinterval * dir; + eventstart += sinterval * intvlslots * dir; + eventend += sinterval * intvlslots * dir; var candidatecount = 0, candidatestart = candidateend = success = false; for (var slot = dir > 0 ? freebusy_data.start : freebusy_data.end - sinterval; (dir > 0 && slot < freebusy_data.end) || (dir < 0 && slot >= freebusy_data.start); slot += sinterval * dir) { @@ -915,15 +917,17 @@ function rcube_calendar_ui(settings) // check freebusy data for all attendees for (var i=0; i < event_attendees.length; i++) { if ((email = event_attendees[i].email) && freebusy_data[email][slot] > 1) { - candidatestart = false; - candidatecount = 0; + candidatestart = candidateend = false; break; } } // occupied slot - if (!candidatestart) + if (!candidatestart) { + slot += Math.max(0, intvlslots - candidatecount - 1) * sinterval * dir; + candidatecount = 0; continue; + } // set candidate end to slot end time candidatecount++; @@ -975,6 +979,7 @@ function rcube_calendar_ui(settings) { if (me.selected_event) { var allday = $('#edit-allday').get(0); + me.selected_event.allDay = allday.checked; me.selected_event.start = parse_datetime(allday.checked ? '00:00' : $('#edit-starttime').val(), $('#edit-startdate').val()); me.selected_event.end = parse_datetime(allday.checked ? '23:59' : $('#edit-endtime').val(), $('#edit-enddate').val()); if (event_attendees) diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index 81dc57bb..4b405ee5 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -100,7 +100,7 @@ $labels['availtentative'] = 'Tentative'; $labels['availoutofoffice'] = 'Out of Office'; $labels['scheduletime'] = 'Available times'; $labels['sendnotifications'] = 'Send notifications'; -$labels['onlyworkinghours'] = 'Show only working hours'; +$labels['onlyworkinghours'] = 'Only working hours'; $labels['prevslot'] = 'Previous Slot'; $labels['nextslot'] = 'Next Slot'; $labels['noslotfound'] = 'Unable to find a free time slot'; diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css index 4dcf1011..36e42a4e 100644 --- a/plugins/calendar/skins/default/calendar.css +++ b/plugins/calendar/skins/default/calendar.css @@ -731,7 +731,7 @@ td.topalign { #eventfreebusy .schedule-options { position: relative; - margin-bottom: 2em; + margin-bottom: 1.5em; } #eventfreebusy .schedule-buttons { @@ -741,7 +741,7 @@ td.topalign { } #eventfreebusy .schedule-find-buttons { - padding-top:1em; + padding-bottom:0.5em; } #eventfreebusy .schedule-find-buttons button { diff --git a/plugins/calendar/skins/default/templates/calendar.html b/plugins/calendar/skins/default/templates/calendar.html index 8654baaa..de0419cc 100644 --- a/plugins/calendar/skins/default/templates/calendar.html +++ b/plugins/calendar/skins/default/templates/calendar.html @@ -188,27 +188,32 @@
- +  
-
-
- -   - +
+
+ +   + +
+
+ +   + +
-
- -   - -
-
-
- - +
+
+ + +
+
+ +

From 99adbe4c763935f5b4e024880e0e76515cb59db1 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sun, 24 Jul 2011 15:53:25 +0200 Subject: [PATCH 13/17] Make event time overlay draggable --- plugins/calendar/calendar_ui.js | 71 +++++++++++++++------ plugins/calendar/skins/default/calendar.css | 1 + 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index 7a4af37f..e28800b2 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -616,12 +616,12 @@ function rcube_calendar_ui(settings) return false; // set form elements - var duration = Math.round((event.end.getTime() - event.start.getTime()) / 1000); - var startdate = $('#schedule-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])).data('duration', duration); - var starttime = $('#schedule-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])).show(); - var enddate = $('#schedule-enddate').val($.fullCalendar.formatDate(event.end, settings['date_format'])); - var endtime = $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show(); var allday = $('#edit-allday').get(0); + var duration = Math.round((event.end.getTime() - event.start.getTime()) / 1000); + freebusy_ui.startdate = $('#schedule-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])).data('duration', duration); + freebusy_ui.starttime = $('#schedule-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])).show(); + freebusy_ui.enddate = $('#schedule-enddate').val($.fullCalendar.formatDate(event.end, settings['date_format'])); + freebusy_ui.endtime = $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show(); if (allday.checked) { starttime.val("00:00").hide(); @@ -667,10 +667,10 @@ function rcube_calendar_ui(settings) var buttons = {}; buttons[rcmail.gettext('adobt', 'calendar')] = function() { - $('#edit-startdate').val(startdate.val()); - $('#edit-starttime').val(starttime.val()); - $('#edit-enddate').val(enddate.val()); - $('#edit-endtime').val(endtime.val()); + $('#edit-startdate').val(freebusy_ui.startdate.val()); + $('#edit-starttime').val(freebusy_ui.starttime.val()); + $('#edit-enddate').val(freebusy_ui.enddate.val()); + $('#edit-endtime').val(freebusy_ui.endtime.val()); if (freebusy_needsupdate) update_freebusy_status(me.selected_event); freebusy_needsupdate = false; @@ -776,7 +776,7 @@ function rcube_calendar_ui(settings) { var overlay = $('#schedule-event-time'); if (me.selected_event.end.getTime() < freebusy_ui.start.getTime() || me.selected_event.start.getTime() > freebusy_ui.end.getTime()) { - overlay.hide(); + overlay.draggable('disable').hide(); } else { var table = $('#schedule-freebusy-times'), @@ -809,14 +809,38 @@ function rcube_calendar_ui(settings) width = table.width() - pos.left; // overlay is visible - if (width > 0) - overlay.css({ width: (width-5)+'px', height:(table.children('tbody').height() - 4)+'px', left:pos.left+'px', top:pos.top+'px' }).show(); + if (width > 0) { + overlay.css({ width: (width-5)+'px', height:(table.children('tbody').height() - 4)+'px', left:pos.left+'px', top:pos.top+'px' }).draggable('enable').show(); + + // configure draggable + if (!overlay.data('isdraggable')) { + overlay.draggable({ + axis: 'x', + scroll: true, + stop: function(e, ui){ + // convert pixels to time + var px = ui.position.left; + var range_p = $('#schedule-freebusy-times').width(); + var range_t = freebusy_ui.end.getTime() - freebusy_ui.start.getTime(); + var newstart = new Date(freebusy_ui.start.getTime() + px * (range_t / range_p)); + newstart.setSeconds(0); newstart.setMilliseconds(0); + // round to 5 minutes + var round = newstart.getMinutes() % 5; + if (round > 2.5) newstart.setTime(newstart.getTime() + (5 - round) * 60000); + else if (round > 0) newstart.setTime(newstart.getTime() - round * 60000); + // update event times + update_freebusy_dates(newstart, new Date(newstart.getTime() + freebusy_ui.startdate.data('duration') * 1000)); + } + }).data('isdraggable', true); + } + } else - overlay.hide(); + overlay.draggable('disable').hide(); } }; + // fetch free-busy information for each attendee from server var load_freebusy_data = function(from, interval) { @@ -878,6 +902,18 @@ function rcube_calendar_ui(settings) }); } }; + + // write changed event date/times back to form fields + var update_freebusy_dates = function(start, end) + { + me.selected_event.start = start; + me.selected_event.end = end; + freebusy_ui.startdate.val($.fullCalendar.formatDate(start, settings['date_format'])); + freebusy_ui.starttime.val($.fullCalendar.formatDate(start, settings['time_format'])); + freebusy_ui.enddate.val($.fullCalendar.formatDate(end, settings['date_format'])); + freebusy_ui.endtime.val($.fullCalendar.formatDate(end, settings['time_format'])); + freebusy_needsupdate = true; + }; // attempt to find a time slot where all attemdees are available var freebusy_find_slot = function(dir) @@ -895,6 +931,7 @@ function rcube_calendar_ui(settings) eventstart += sinterval * intvlslots * dir; eventend += sinterval * intvlslots * dir; + // iterate through free-busy slots and find candidates var candidatecount = 0, candidatestart = candidateend = success = false; for (var slot = dir > 0 ? freebusy_data.start : freebusy_data.end - sinterval; (dir > 0 && slot < freebusy_data.end) || (dir < 0 && slot >= freebusy_data.start); slot += sinterval * dir) { slotend = slot + sinterval; @@ -949,13 +986,9 @@ function rcube_calendar_ui(settings) } } - // update event date/time fields + // update event date/time display if (success) { - $('#schedule-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])); - $('#schedule-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])); - $('#schedule-enddate').val($.fullCalendar.formatDate(event.end, settings['date_format'])); - $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])); - freebusy_needsupdate = true; + update_freebusy_dates(event.start, event.end); // move freebusy grid if necessary if (event.start.getTime() >= freebusy_ui.end.getTime()) diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css index 36e42a4e..64669a92 100644 --- a/plugins/calendar/skins/default/calendar.css +++ b/plugins/calendar/skins/default/calendar.css @@ -727,6 +727,7 @@ td.topalign { background: rgba(60, 60, 60, 0.6); opacity: 0.5; border-radius: 4px; + cursor: move; } #eventfreebusy .schedule-options { From 3dfccb89e1fcb0d8a7552dd5334b89556a375150 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 25 Jul 2011 09:39:25 +0200 Subject: [PATCH 14/17] Set show-as busy as default for new events --- plugins/calendar/calendar_ui.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index e28800b2..bfa2614d 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -42,6 +42,7 @@ function rcube_calendar_ui(settings) var gmt_offset = (new Date().getTimezoneOffset() / -60) - (settings.timezone || 0); var day_clicked = day_clicked_ts = 0; var ignore_click = false; + var event_defaults = { free_busy:'busy' }; var event_attendees = null; var attendees_list; var freebusy_ui = { workinhoursonly:false }; @@ -330,7 +331,8 @@ function rcube_calendar_ui(settings) var $dialog = $("#eventedit"); var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:action=='new' }; - me.selected_event = $.extend({}, event); // clone event object + me.selected_event = $.extend(event_defaults, event); // clone event object (with defaults) + event = me.selected_event; // change reference to clone freebusy_needsupdate = false; // reset dialog first, enable/disable fields according to editable state From dddcc2fa60061dc6886903c9317aae751393e88b Mon Sep 17 00:00:00 2001 From: "Aleksander Machniak (Kolab Systems)" Date: Mon, 25 Jul 2011 12:33:44 +0200 Subject: [PATCH 15/17] Check getACL() result to prevent from PHP error when PEAR_Error is returned --- plugins/calendar/drivers/kolab/kolab_calendar.php | 8 +++++--- .../kolab_addressbook/lib/rcube_kolab_contacts.php | 12 +++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index 816a9444..50b06ed0 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -67,9 +67,11 @@ class kolab_calendar } else { $acl = $this->storage->_folder->getACL(); - $acl = $acl[$_SESSION['username']]; - if (strpos($acl, 'i') !== false) - $this->readonly = false; + if (is_array($acl)) { + $acl = $acl[$_SESSION['username']]; + if (strpos($acl, 'i') !== false) + $this->readonly = false; + } } } } diff --git a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php index 5b897c27..6cc0289b 100644 --- a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php +++ b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php @@ -128,11 +128,13 @@ class rcube_kolab_contacts extends rcube_addressbook } else { $acl = $this->storagefolder->getACL(); - $acl = $acl[$_SESSION['username']]; - if (strpos($acl, 'i') !== false) - $this->readonly = false; - if (strpos($acl, 'a') !== false || strpos($acl, 'x') !== false) - $this->editable = true; + if (is_array($acl)) { + $acl = $acl[$_SESSION['username']]; + if (strpos($acl, 'i') !== false) + $this->readonly = false; + if (strpos($acl, 'a') !== false || strpos($acl, 'x') !== false) + $this->editable = true; + } } } } From dc0f7261ad1a168b4ec54a763c583d56d3e0c553 Mon Sep 17 00:00:00 2001 From: "Aleksander Machniak (Kolab Systems)" Date: Mon, 25 Jul 2011 12:49:46 +0200 Subject: [PATCH 16/17] Set autocomplete_addressbooks using new config_get hook --- .../kolab_addressbook/kolab_addressbook.php | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/plugins/kolab_addressbook/kolab_addressbook.php b/plugins/kolab_addressbook/kolab_addressbook.php index 5432ffac..c6178920 100644 --- a/plugins/kolab_addressbook/kolab_addressbook.php +++ b/plugins/kolab_addressbook/kolab_addressbook.php @@ -27,7 +27,7 @@ */ class kolab_addressbook extends rcube_plugin { - public $task = 'mail|settings|addressbook'; + public $task = 'mail|settings|addressbook|calendar'; private $folders; private $sources; @@ -54,6 +54,7 @@ class kolab_addressbook extends rcube_plugin // register hooks $this->add_hook('addressbooks_list', array($this, 'address_sources')); $this->add_hook('addressbook_get', array($this, 'get_address_book')); + $this->add_hook('config_get', array($this, 'config_get')); if ($this->rc->task == 'addressbook') { $this->add_texts('localization'); @@ -74,10 +75,6 @@ class kolab_addressbook extends rcube_plugin $this->add_hook('preferences_list', array($this, 'prefs_list')); $this->add_hook('preferences_save', array($this, 'prefs_save')); } - // extend list of address sources to be used for autocompletion - else if ($this->rc->task == 'mail' && $this->rc->action == 'autocomplete') { - $this->autocomplete_sources(); - } } @@ -149,16 +146,22 @@ class kolab_addressbook extends rcube_plugin /** - * Setts autocomplete_addressbooks option according to - * kolab_addressbook_prio setting. + * Sets autocomplete_addressbooks option according to + * kolab_addressbook_prio setting extending list of address sources + * to be used for autocompletion. */ - public function autocomplete_sources() + public function config_get($args) { + if ($args['name'] != 'autocomplete_addressbooks') { + return $args; + } + // Load configuration $this->load_config(); $abook_prio = (int) $this->rc->config->get('kolab_addressbook_prio'); - $sources = (array) $this->rc->config->get('autocomplete_addressbooks', array()); + // here we cannot use rc->config->get() + $sources = $GLOBALS['CONFIG']['autocomplete_addressbooks']; // Disable all global address books // Assumes that all non-kolab_addressbook sources are global @@ -166,6 +169,10 @@ class kolab_addressbook extends rcube_plugin $sources = array(); } + if (!is_array($sources)) { + $sources = array(); + } + $kolab_sources = array(); foreach ($this->_list_sources() as $abook_id => $abook) { if (!in_array($abook_id, $sources)) @@ -180,9 +187,11 @@ class kolab_addressbook extends rcube_plugin else { $sources = array_merge($sources, $kolab_sources); } - - $this->rc->config->set('autocomplete_addressbooks', $sources); } + + $args['result'] = $sources; + + return $args; } From 84730d0ea05b0473fe9accb8d40b51529f0c4aa8 Mon Sep 17 00:00:00 2001 From: "Aleksander Machniak (Kolab Systems)" Date: Mon, 25 Jul 2011 15:37:30 +0200 Subject: [PATCH 17/17] Improve performance by using Roundcube cache for Kolab_List object instead of session --- plugins/kolab_core/rcube_kolab.php | 86 +++++++++++++++++++----------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/plugins/kolab_core/rcube_kolab.php b/plugins/kolab_core/rcube_kolab.php index 3650f7b9..10cdb36a 100644 --- a/plugins/kolab_core/rcube_kolab.php +++ b/plugins/kolab_core/rcube_kolab.php @@ -29,6 +29,8 @@ class rcube_kolab private static $horde_auth; private static $config; private static $ready = false; + private static $list; + private static $cache; /** @@ -90,19 +92,57 @@ class rcube_kolab ); Auth::setCredential('password', $pwd); self::$ready = true; - - // Register shutdown function for storing folders cache in session - // This is already required, because Roundcube session handler - // saves data into DB before Horde's shutdown function is called - if (!empty($conf['kolab']['imap']['cache_folders'])) { - $rcmail->add_shutdown_function(array('rcube_kolab', 'save_folders_cache')); - } } NLS::setCharset('UTF-8'); String::setDefaultCharset('UTF-8'); } + /** + * Get instance of Kolab_List object + * + * @return object Kolab_List Folders list object + */ + public static function get_folders_list() + { + self::setup(); + + if (self::$list) + return self::$list; + + if (!self::$ready) + return null; + + $rcmail = rcmail::get_instance(); + $imap_cache = $rcmail->config->get('imap_cache'); + + if ($imap_cache) { + self::$cache = $rcmail->get_cache('IMAP', $imap_cache); + self::$list = self::$cache->get('mailboxes.kolab'); + + // Disable Horde folders caching, we're using our own cache + self::$config['kolab']['imap']['cache_folders'] = false; + // Register shutdown function for saving folders list cache + $rcmail->add_shutdown_function(array('rcube_kolab', 'save_folders_list')); + } + + if (empty(self::$list)) { + self::$list = Kolab_List::singleton(); + } + + return self::$list; + } + + /** + * Store Kolab_List instance in Roundcube cache + */ + public static function save_folders_list() + { + if (self::$cache && self::$list) { + self::$cache->set('mailboxes.kolab', self::$list); + } + } + /** * Get instance of a Kolab (XML) format object * @@ -125,8 +165,7 @@ class rcube_kolab */ public static function get_folders($type) { - self::setup(); - $kolab = Kolab_List::singleton(); + $kolab = self::get_folders_list(); return self::$ready ? $kolab->getByType($type) : array(); } @@ -138,9 +177,8 @@ class rcube_kolab */ public static function get_folder($folder) { - self::setup(); - $kolab = Kolab_List::singleton(); - return self::$ready ? $kolab->getFolder($folder) : null; + $kolab = self::get_folders_list(); + return self::$ready ? $kolab->getFolder($folder) : null; } /** @@ -153,8 +191,7 @@ class rcube_kolab */ public static function get_storage($folder, $data_type = null) { - self::setup(); - $kolab = Kolab_List::singleton(); + $kolab = self::get_folders_list(); return self::$ready ? $kolab->getFolder($folder)->getData($data_type) : null; } @@ -177,18 +214,6 @@ class rcube_kolab unset($_SESSION['__auth']); } - /** - * Save Horde's folders cache in session (workaround shoutdown function issue) - */ - public static function save_folders_cache() - { - require_once 'Horde/SessionObjects.php'; - - $kolab = Kolab_List::singleton(); - $session = Horde_SessionObjects::singleton(); - $session->overwrite('kolab_folderlist', $kolab, false); - } - /** * Creates folder ID from folder name * @@ -210,8 +235,7 @@ class rcube_kolab */ public static function folder_delete($name) { - self::setup(); - $kolab = Kolab_List::singleton(); + $kolab = self::get_folders_list(); $folder = $kolab->getFolder($name); $result = $folder->delete(); @@ -234,8 +258,7 @@ class rcube_kolab */ public static function folder_create($name, $type=null, $default=false) { - self::setup(); - $kolab = Kolab_List::singleton(); + $kolab = self::get_folders_list(); $folder = new Kolab_Folder(); $folder->setList($kolab); @@ -263,8 +286,7 @@ class rcube_kolab */ public static function folder_rename($oldname, $newname) { - self::setup(); - $kolab = Kolab_List::singleton(); + $kolab = self::get_folders_list(); $folder = $kolab->getFolder($oldname); $folder->setFolder($newname);