diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index f64bd1dd..765dfd18 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -523,8 +523,10 @@ class calendar extends rcube_plugin // create UID for new event $event['uid'] = $this->generate_uid(); $this->prepare_event($event, $action); - if ($success = $this->driver->new_event($event)) + if ($success = $this->driver->new_event($event)) { + $event['id'] = $event['uid']; $this->cleanup_event($event); + } $reload = true; break; @@ -1327,7 +1329,7 @@ class calendar extends rcube_plugin $headers['To'] = format_email_recipient($mailto, $attendee['name']); $headers['Subject'] = $this->gettext(array( - 'name' => $is_cancelled ? 'eventcancelsubject' : ($is_new ? 'invitationsubject' : 'eventupdatesubject'), + 'name' => $is_cancelled ? 'eventcancelsubject' : ($is_new ? 'invitationsubject' : ($event['title'] ? 'eventupdatesubject':'eventupdatesubjectempty')), 'vars' => array('title' => $event['title']), )); @@ -1367,8 +1369,9 @@ class calendar extends rcube_plugin $time_format = self::to_php_date_format($this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format'])); if ($event['allday']) { - $fromto = format_date($event['start'], $date_format) . - ($duration > 86400 || gmdate('d', $event['start']) != gmdate('d', $event['end']) ? ' - ' . format_date($event['end'], $date_format) : ''); + $fromto = format_date($event['start'], $date_format); + if (($todate = format_date($event['end'], $date_format)) != $fromto) + $fromto .= ' - ' . $todate; } else if ($duration < 86400 && gmdate('d', $event['start']) == gmdate('d', $event['end'])) { $fromto = format_date($event['start'], $date_format) . ' ' . format_date($event['start'], $time_format) . @@ -1448,7 +1451,8 @@ class calendar extends rcube_plugin list($from, $to, $type) = $slot; if ($from < $t_end && $to > $t) { $status = isset($type) ? $type : self::FREEBUSY_BUSY; - break; + if ($status == self::FREEBUSY_BUSY) // can't get any worse :-) + break; } } } @@ -1551,8 +1555,7 @@ class calendar extends rcube_plugin { // load iCalendar functions (if necessary) if (!empty($this->ics_parts)) { - require($this->home . '/lib/calendar_ical.php'); - $this->ical = new calendar_ical($this->rc); + $this->load_ical(); } $html = ''; @@ -1614,7 +1617,7 @@ class calendar extends rcube_plugin $this->load_ical(); $events = $this->ical->import($part, $charset); - + $error_msg = $this->gettext('errorimportingevent'); $success = false; @@ -1680,3 +1683,4 @@ class calendar extends rcube_plugin } } + diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index 64a68031..e0a123c4 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -704,8 +704,8 @@ function rcube_calendar_ui(settings) freebusy_data = { required:{}, all:{} }; freebusy_ui.loading = 1; // prevent render_freebusy_grid() to load data yet - freebusy_ui.numdays = allday.checked ? 7 : Math.ceil(duration * 2 / 86400); - freebusy_ui.interval = allday.checked ? 360 : 60; + freebusy_ui.numdays = Math.max(allday.checked ? 14 : 1, Math.ceil(duration * 2 / 86400)); + freebusy_ui.interval = allday.checked ? 1440 : 60; freebusy_ui.start = fb_start; freebusy_ui.end = new Date(freebusy_ui.start.getTime() + DAY_MS * freebusy_ui.numdays); render_freebusy_grid(0); @@ -787,10 +787,17 @@ function rcube_calendar_ui(settings) } var dayslots = Math.floor(1440 / freebusy_ui.interval); - var lastdate, datestr, css, curdate = new Date(), dates_row = '', times_row = '', slots_row = ''; + var date_format = 'ddd '+ (dayslots <= 2 ? settings.date_short : settings.date_format); + var lastdate, datestr, css, + curdate = new Date(), + allday = (freebusy_ui.interval == 1440), + times_css = (allday ? 'allday ' : ''), + dates_row = '', + times_row = '', + slots_row = ''; for (var s = 0, t = freebusy_ui.start.getTime(); t < freebusy_ui.end.getTime(); s++) { curdate.setTime(t); - datestr = fc.fullCalendar('formatDate', curdate, 'ddd '+settings['date_format']); + datestr = fc.fullCalendar('formatDate', curdate, date_format); if (datestr != lastdate) { dates_row += '' + Q(datestr) + ''; lastdate = datestr; @@ -798,7 +805,7 @@ function rcube_calendar_ui(settings) // set css class according to working hours css = is_weekend(curdate) || (freebusy_ui.interval <= 60 && !is_workinghour(curdate)) ? 'offhours' : 'workinghours'; - times_row += '' + Q($.fullCalendar.formatDate(curdate, settings['time_format'])) + ''; + times_row += '' + Q(allday ? rcmail.gettext('all-day','calendar') : $.fullCalendar.formatDate(curdate, settings['time_format'])) + ''; slots_row += ' '; t += freebusy_ui.interval * 60000; @@ -870,7 +877,7 @@ function rcube_calendar_ui(settings) width = 0, pos = { top:table.children('thead').height(), left:0 }, eventstart = date2unixtime(me.selected_event.start), - eventend = date2unixtime(me.selected_event.end), + eventend = date2unixtime(me.selected_event.end) - 60, slotstart = date2unixtime(freebusy_ui.start), slotsize = freebusy_ui.interval * 60, slotend, fraction, $cell; @@ -884,7 +891,7 @@ function rcube_calendar_ui(settings) pos.left = Math.round(cell.offsetLeft + cell.offsetWidth * fraction); } // event ends in this slot: compute width - else if (eventend >= slotstart && eventend <= slotend) { + if (eventend >= slotstart && eventend <= slotend) { fraction = 1 - (slotend - eventend) / slotsize; width = Math.round(cell.offsetLeft + cell.offsetWidth * fraction) - pos.left; } @@ -911,8 +918,10 @@ function rcube_calendar_ui(settings) 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); - // set time to 00:00 + // snap to day boundaries if (me.selected_event.allDay) { + if (newstart.getHours() >= 12) // snap to next day + newstart.setTime(newstart.getTime() + DAY_MS); newstart.setMinutes(0); newstart.setHours(0); } @@ -941,7 +950,7 @@ function rcube_calendar_ui(settings) var load_freebusy_data = function(from, interval) { var start = new Date(from.getTime() - DAY_MS * 2); // start 1 days before event - var end = new Date(start.getTime() + DAY_MS * 14); // load 14 days + var end = new Date(start.getTime() + DAY_MS * Math.max(14, freebusy_ui.numdays + 7)); // load min. 14 days freebusy_ui.numrequired = 0; // load free-busy information for every attendee @@ -1018,7 +1027,7 @@ function rcube_calendar_ui(settings) var ts = date2unixtime(freebusy_ui.start); var fbdata = freebusy_data[email]; - if (fbdata && fbdata[ts] && row.length) { + if (fbdata && fbdata[ts] !== undefined && row.length) { row.children().each(function(i, cell){ cell.className = cell.className.replace('unknown', fbdata[ts] ? status_classes[fbdata[ts]] : 'unknown'); @@ -1061,7 +1070,7 @@ function rcube_calendar_ui(settings) eventend = date2unixtime(event.end), duration = eventend - eventstart, sinterval = freebusy_data.interval * 60, - intvlslots = event.allDay ? 4 : 1, + intvlslots = 1, numslots = Math.ceil(duration / sinterval), checkdate, slotend, email, curdate; @@ -1780,7 +1789,7 @@ function rcube_calendar_ui(settings) table: rcmail.gettext('agenda', 'calendar') }, selectable: true, - selectHelper: true, + selectHelper: false, loading: function(isLoading) { me.is_loading = isLoading; this._rc_loading = rcmail.set_busy(isLoading, 'loading', this._rc_loading); diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php index 5904a377..5b22cd11 100644 --- a/plugins/calendar/drivers/database/database_driver.php +++ b/plugins/calendar/drivers/database/database_driver.php @@ -344,10 +344,6 @@ class database_driver extends calendar_driver $event['free_busy'] = intval($this->free_busy_map[strtolower($event['free_busy'])]); if (isset($event['allday'])) { - // set times to 00::00 and 23:59 (assuming the PHP and DB timezones are in sync) - $numdays = max(1, round(($event['end'] - $event['start']) / 86400)); - $event['start'] = mktime(0, 0, 0, date('n', $event['start']), date('j', $event['start']), date('Y', $event['start'])); - $event['end'] = $event['start'] + $numdays * 86400 - 60; $event['all_day'] = $event['allday'] ? 1 : 0; } @@ -620,6 +616,7 @@ class database_driver extends calendar_driver public function get_event($event) { $id = is_array($event) ? $event['id'] : $event; + $col = is_numeric($event['id']) ? 'event_id' : 'uid'; if ($this->cache[$id]) return $this->cache[$id]; @@ -627,7 +624,7 @@ class database_driver extends calendar_driver $result = $this->rc->db->query(sprintf( "SELECT * FROM " . $this->db_events . " WHERE calendar_id IN (%s) - AND event_id=?", + AND $col=?", $this->calendar_ids ), $id); diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index cb66a4c5..d37a0264 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -270,6 +270,7 @@ class kolab_calendar $saved = false; } else { + $event['id'] = $event['uid']; $this->events[$event['uid']] = $event; } diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index 576c2724..8ff8c7cd 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -34,6 +34,7 @@ class kolab_driver extends calendar_driver private $rc; private $cal; private $calendars; + private $has_writeable = false; /** * Default constructor @@ -79,6 +80,8 @@ class kolab_driver extends calendar_driver foreach ($names as $utf7name => $name) { $calendar = new kolab_calendar($utf7name, $this->cal); $this->calendars[$calendar->id] = $calendar; + if (!$calendar->readonly) + $this->has_writeable = true; } } @@ -92,9 +95,11 @@ class kolab_driver extends calendar_driver public function list_calendars() { // attempt to create a default calendar for this user - if (empty($this->calendars)) { - if ($this->create_calendar(array('name' => 'Calendar', 'color' => 'cc0000'))) + if (!$this->has_writeable) { + if ($this->create_calendar(array('name' => 'Calendar', 'color' => 'cc0000'))) { + unset($this->calendars); $this->_read_calendars(); + } } $calendars = $names = array(); diff --git a/plugins/calendar/lib/calendar_ical.php b/plugins/calendar/lib/calendar_ical.php index 66df53f5..09427abb 100644 --- a/plugins/calendar/lib/calendar_ical.php +++ b/plugins/calendar/lib/calendar_ical.php @@ -39,12 +39,18 @@ class calendar_ical private $rc; private $cal; - + private $timezone = 'Z'; function __construct($cal) { $this->cal = $cal; $this->rc = $cal->rc; + + // compose timezone string + if ($cal->timezone) { + $hours = floor($cal->timezone); + $this->timezone = sprintf('%s%02d:%02d', ($hours >= 0 ? '+' : ''), $hours, ($cal->timezone - $hours) * 60); + } } /** @@ -93,13 +99,14 @@ class calendar_ical // check for all-day dates if (is_array($event['start'])) { - $event['start'] = gmmktime(0, 0, 0, $event['start']['month'], $event['start']['mday'], $event['start']['year']) + $this->cal->gmt_offset; + // create timestamp at 00:00 in user's timezone + $event['start'] = strtotime(sprintf('%04d%02d%02dT000000%s', $event['start']['year'], $event['start']['month'], $event['start']['mday'], $this->timezone)); $event['allday'] = true; } if (is_array($event['end'])) { - $event['end'] = gmmktime(0, 0, 0, $event['end']['month'], $event['end']['mday'], $event['end']['year']) + $this->cal->gmt_offset - 60; + $event['end'] = strtotime(sprintf('%04d%02d%02dT000000%s', $event['end']['year'], $event['end']['month'], $event['end']['mday'], $this->timezone)) - 60; } - + // map other attributes to internal fields $_attendees = array(); foreach ($ve->getAllAttributes() as $attr) { diff --git a/plugins/calendar/lib/fullcalendar-rc.patch b/plugins/calendar/lib/fullcalendar-rc.patch index 666cf33b..e821ce4a 100644 --- a/plugins/calendar/lib/fullcalendar-rc.patch +++ b/plugins/calendar/lib/fullcalendar-rc.patch @@ -1,5 +1,5 @@ --- js/fullcalendar.js.orig 2011-04-09 14:13:16.000000000 +0200 -+++ js/fullcalendar.js 2011-07-31 13:19:40.000000000 +0200 ++++ js/fullcalendar.js 2011-08-07 18:43:34.000000000 +0200 @@ -47,12 +47,16 @@ titleFormat: { month: 'MMMM yyyy', @@ -115,6 +115,19 @@ fc.applyAll = applyAll; +@@ -3534,10 +3574,10 @@ + function slotSelectionMousedown(ev) { + if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button + unselect(ev); +- var dates; ++ var dates, helperOption = opt('selectHelper'); + hoverListener.start(function(cell, origCell) { + clearSelection(); +- if (cell && cell.col == origCell.col && !cellIsAllDay(cell)) { ++ if (cell && (cell.col == origCell.col || !helperOption) && !cellIsAllDay(cell)) { + var d1 = cellDate(origCell); + var d2 = cellDate(cell); + dates = [ @@ -3762,7 +3802,8 @@ height, slotSegmentContainer = getSlotSegmentContainer(), @@ -157,7 +170,7 @@ return smartProperty(v, viewNameOverride || viewName); } return v; -@@ -5204,5 +5248,560 @@ +@@ -5204,5 +5248,561 @@ }; } @@ -273,7 +286,8 @@ + } + + function sortCmp(a, b) { -+ return (a.start.getTime() - b.start.getTime()) + (a.end.getTime() - b.end.getTime()); ++ var sd = a.start.getTime() - b.start.getTime(); ++ return sd + (sd ? 0 : a.end.getTime() - b.end.getTime()); + } + + function renderSegs(segs, modifiedEventId) { diff --git a/plugins/calendar/lib/js/fullcalendar.js b/plugins/calendar/lib/js/fullcalendar.js index 2b0c76ef..fe387c8b 100644 --- a/plugins/calendar/lib/js/fullcalendar.js +++ b/plugins/calendar/lib/js/fullcalendar.js @@ -3574,10 +3574,10 @@ function AgendaView(element, calendar, viewName) { function slotSelectionMousedown(ev) { if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button unselect(ev); - var dates; + var dates, helperOption = opt('selectHelper'); hoverListener.start(function(cell, origCell) { clearSelection(); - if (cell && cell.col == origCell.col && !cellIsAllDay(cell)) { + if (cell && (cell.col == origCell.col || !helperOption) && !cellIsAllDay(cell)) { var d1 = cellDate(origCell); var d2 = cellDate(cell); dates = [ diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index 0c9c697a..91851da7 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -118,6 +118,7 @@ $labels['noslotfound'] = 'Unable to find a free time slot'; $labels['invitationsubject'] = 'You\'ve been invited to "$title"'; $labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with all the event details which you can import to your calendar application."; $labels['eventupdatesubject'] = '"$title" has been updated'; +$labels['eventupdatesubjectempty'] = 'An event that concerns you has been updated'; $labels['eventupdatemailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with the updated event details which you can import to your calendar application."; $labels['eventcancelsubject'] = '"$title" has been canceled'; $labels['eventcancelmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nThe event has been cancelled by \$organizer.\n\nPlease find attached an iCalendar file with the updated event details."; @@ -182,4 +183,4 @@ $labels['futurevents'] = 'Future'; $labels['allevents'] = 'All'; $labels['saveasnew'] = 'Save as new'; -?> \ No newline at end of file +?> diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css index 04bab248..c58d4534 100644 --- a/plugins/calendar/skins/default/calendar.css +++ b/plugins/calendar/skins/default/calendar.css @@ -770,6 +770,7 @@ td.topalign { border-color: #aaa; border-style: solid; border-width: 0 1px 0 1px; + white-space: nowrap; } #attendees-freebusy-table div.timesheader, @@ -780,6 +781,10 @@ td.topalign { text-align: center; } +#schedule-freebusy-times tr.times td.allday { + min-width: 60px; +} + #schedule-freebusy-times tr.times td { cursor: pointer; } diff --git a/plugins/calendar/skins/default/iehacks.css b/plugins/calendar/skins/default/iehacks.css index 59469034..2cefc0ba 100644 --- a/plugins/calendar/skins/default/iehacks.css +++ b/plugins/calendar/skins/default/iehacks.css @@ -58,3 +58,7 @@ html #calendartoolbar a.buttonPas { #schedule-freebusy-times td.all-out-of-office { background-image: url('images/freebusy-colors.gif'); } + +.ui-dialog .ui-dialog-titlebar { + width: expression((parseInt(this.parentNode.offsetWidth)-26)+'px'); +} diff --git a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php index 6c808abb..f8286d88 100644 --- a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php +++ b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php @@ -721,7 +721,6 @@ class rcube_kolab_contacts extends rcube_addressbook */ public function close() { - rcube_kolab::shutdown(); } diff --git a/plugins/kolab_core/rcube_kolab.php b/plugins/kolab_core/rcube_kolab.php index 38d4bfc4..5d8bb825 100644 --- a/plugins/kolab_core/rcube_kolab.php +++ b/plugins/kolab_core/rcube_kolab.php @@ -84,31 +84,25 @@ class rcube_kolab // pass the current IMAP authentication credentials to the Horde auth system self::$horde_auth = Auth::singleton('kolab'); - if (self::$horde_auth->isAuthenticated() || - self::$horde_auth->authenticate($_SESSION['username'], array('password' => $pwd), false) - ) { - // we could use self::$horde_auth->setAuth() here, but it requires - // the whole bunch of includes and global objects, do it as simple as possible - if (empty($_SESSION['__auth']['credentials'])) { - require_once 'Horde/Secret.php'; - $credentials = Secret::write(Secret::getKey('auth'), serialize(array('password' => $pwd))); - } - else { - $credentials = $_SESSION['__auth']['credentials']; - } - + if (self::$horde_auth->isAuthenticated()) { + self::$ready = true; + } + else if (self::$horde_auth->authenticate($_SESSION['username'], array('password' => $pwd), false)) { + // we could use Auth::setAuth() here, but it requires the whole bunch + // of includes and global objects, do it as simple as possible $_SESSION['__auth'] = array( - 'credentials' => $credentials, 'authenticated' => true, 'userId' => $_SESSION['username'], 'timestamp' => time(), 'remote_addr' => $_SERVER['REMOTE_ADDR'], ); - Auth::setCredential('password', $pwd); self::$ready = true; } + // Register shutdown function for saving cache/session objects + $rcmail->add_shutdown_function(array('rcube_kolab', 'shutdown')); + NLS::setCharset('UTF-8'); String::setDefaultCharset('UTF-8'); } @@ -137,8 +131,6 @@ class rcube_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)) { @@ -148,16 +140,6 @@ class rcube_kolab 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 * @@ -219,7 +201,7 @@ class rcube_kolab } /** - * Handle session data when done + * Do session/cache operations on shutdown */ public static function shutdown() { @@ -230,6 +212,11 @@ class rcube_kolab $session = Horde_SessionObjects::singleton(); $kolab = Horde_Kolab_Session::singleton(); $session->overwrite('kolab_session', $kolab, false); + + // Write Kolab_List object to cache + if (self::$cache && self::$list) { + self::$cache->set('mailboxes.kolab', self::$list); + } } }