diff -pruN 4.2.23-1/debian/changelog 4.2.27-1/debian/changelog
--- 4.2.23-1/debian/changelog	2017-09-27 19:28:25.000000000 +0000
+++ 4.2.27-1/debian/changelog	2019-10-18 18:38:43.000000000 +0000
@@ -1,3 +1,44 @@
+php-horde-kronolith (4.2.27-1) unstable; urgency=medium
+
+  * New upstream version 4.2.27
+  * Bump debhelper from old 11 to 12.
+  * d/control: Orphaning package (See #942282)
+
+ -- Mathieu Parent <sathieu@debian.org>  Fri, 18 Oct 2019 20:38:43 +0200
+
+php-horde-kronolith (4.2.25-1) unstable; urgency=medium
+
+  * New upstream version 4.2.25
+    - Remove patch for CVE-2017-16906, merged
+    - Remove patch for CVE-2017-16908, merged
+
+ -- Mathieu Parent <sathieu@debian.org>  Thu, 25 Oct 2018 21:36:52 +0200
+
+php-horde-kronolith (4.2.24-1) unstable; urgency=medium
+
+  * New upstream version 4.2.24
+  * CVE-2017-16906 XSS via URL field (Closes: #909737)
+  * CVE-2017-16908 XSS via Name field (Closes: #909738)
+
+ -- Mathieu Parent <sathieu@debian.org>  Mon, 08 Oct 2018 09:51:44 +0200
+
+php-horde-kronolith (4.2.23-3) unstable; urgency=medium
+
+  * Update Standards-Version to 4.1.4, no change
+  * Update Maintainer field
+
+ -- Mathieu Parent <sathieu@debian.org>  Tue, 15 May 2018 16:01:50 +0200
+
+php-horde-kronolith (4.2.23-2) unstable; urgency=medium
+
+  * Update Standards-Version to 4.1.3, no change
+  * Upgrade debhelper to compat 11
+  * Update Vcs-* fields
+  * Use secure copyright format URI
+  * Replace "Priority: extra" by "Priority: optional"
+
+ -- Mathieu Parent <sathieu@debian.org>  Fri, 06 Apr 2018 13:05:31 +0200
+
 php-horde-kronolith (4.2.23-1) unstable; urgency=medium
 
   * New upstream version 4.2.23
diff -pruN 4.2.23-1/debian/compat 4.2.27-1/debian/compat
--- 4.2.23-1/debian/compat	2017-09-27 19:28:25.000000000 +0000
+++ 4.2.27-1/debian/compat	1970-01-01 00:00:00.000000000 +0000
@@ -1 +0,0 @@
-9
diff -pruN 4.2.23-1/debian/control 4.2.27-1/debian/control
--- 4.2.23-1/debian/control	2017-09-27 19:28:25.000000000 +0000
+++ 4.2.27-1/debian/control	2019-10-18 18:38:43.000000000 +0000
@@ -1,13 +1,13 @@
 Source: php-horde-kronolith
 Section: php
-Priority: extra
-Maintainer: Horde Maintainers <pkg-horde-hackers@lists.alioth.debian.org>
-Uploaders: Mathieu Parent <sathieu@debian.org>
-Build-Depends: debhelper (>= 9), pkg-php-tools, pear-horde-channel, php-horde-role
-Standards-Version: 3.9.8
+Priority: optional
+Maintainer: Horde Maintainers <team+debian-horde-team@tracker.debian.org>
+Uploaders: Debian QA Group <packages@qa.debian.org>
+Build-Depends: pkg-php-tools, pear-horde-channel, php-horde-role, debhelper-compat (= 12)
+Standards-Version: 4.1.4
 Homepage: http://www.horde.org/
-Vcs-git: https://anonscm.debian.org/git/pkg-horde/PEAR/php-horde-kronolith.git
-Vcs-Browser: https://anonscm.debian.org/cgit/pkg-horde/PEAR/php-horde-kronolith.git
+Vcs-Git: https://salsa.debian.org/horde-team/php-horde-kronolith.git
+Vcs-Browser: https://salsa.debian.org/horde-team/php-horde-kronolith
 
 Package: php-horde-kronolith
 Architecture: all
diff -pruN 4.2.23-1/debian/copyright 4.2.27-1/debian/copyright
--- 4.2.23-1/debian/copyright	2017-09-27 19:28:25.000000000 +0000
+++ 4.2.27-1/debian/copyright	2019-10-18 18:38:43.000000000 +0000
@@ -1,4 +1,4 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 Upstream-Name: Horde kronolith (A web based calendar)
 Upstream-Contact: Horde <horde@lists.horde.org>
 		  Horde development <dev@lists.horde.org>
diff -pruN 4.2.23-1/kronolith-4.2.23/add.php 4.2.27-1/kronolith-4.2.23/add.php
--- 4.2.23-1/kronolith-4.2.23/add.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/add.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,96 +0,0 @@
-<?php
-/**
- * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- */
-
-require_once __DIR__ . '/lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->redirect();
-}
-
-do {
-    if (Horde_Util::getFormData('cancel')) {
-        break;
-    }
-
-    list($targetType, $targetcalendar) = explode('_', Horde_Util::getFormData('targetcalendar'), 2);
-    if (strpos($targetcalendar, '\\')) {
-        list($calendar_id, $user) = explode('\\', $targetcalendar, 2);
-    } else {
-        $calendar_id = $targetcalendar;
-        $user = $GLOBALS['registry']->getAuth();
-    }
-
-    try {
-        /* Permission checks on the target calendar . */
-        switch ($targetType) {
-        case 'internal':
-            $kronolith_calendar = $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $calendar_id);
-            break;
-        case 'remote':
-            $kronolith_calendar = $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_REMOTE_CALENDARS, $calendar_id);
-            break;
-        case 'resource':
-            $rid = Kronolith::getDriver('Resource')->getResourceIdByCalendar($calendar_id);
-            $kronolith_calendar = new Kronolith_Calendar_Resource(
-                array('resource' => Kronolith::getDriver('Resource')->getResource($rid)));
-            break;
-        default:
-            break 2;
-        }
-        if ($user == $GLOBALS['registry']->getAuth() &&
-            !$kronolith_calendar->hasPermission(Horde_Perms::EDIT)) {
-            $notification->push(_("You do not have permission to add events to this calendar."), 'horde.warning');
-            break;
-        }
-        if ($user != $GLOBALS['registry']->getAuth() &&
-            !$kronolith_calendar->hasPermission(Kronolith::PERMS_DELEGATE)) {
-            $notification->push(_("You do not have permission to delegate events to this user."), 'horde.warning');
-            break;
-        }
-        $perms = $GLOBALS['injector']->getInstance('Horde_Core_Perms');
-        if ($perms->hasAppPermission('max_events') !== true &&
-            $perms->hasAppPermission('max_events') <= Kronolith::countEvents()) {
-            Horde::permissionDeniedError(
-                'kronolith',
-                'max_events',
-                sprintf(_("You are not allowed to create more than %d events."), $perms->hasAppPermission('max_events'))
-            );
-            break;
-        }
-
-        $event = Kronolith::getDriver($targetType, $calendar_id)->getEvent();
-        $event->readForm();
-        try {
-            $event->save();
-            Kronolith::notifyOfResourceRejection($event);
-            if (Horde_Util::getFormData('sendupdates', false)) {
-                try {
-                    Kronolith::sendITipNotifications($event, $notification, Kronolith::ITIP_REQUEST);
-                } catch (Exception $e) {
-                    $notification->push($e, 'horde.error');
-                }
-            }
-        } catch (Exception $e) {
-            $notification->push(sprintf(_("There was an error adding the event: %s"), $e->getMessage()), 'horde.error');
-        }
-    } catch (Exception $e) {
-        $notification->push(sprintf(_("There was an error accessing the calendar: %s"), $e->getMessage()), 'horde.error');
-    }
-} while (false);
-
-if ($url = Horde::verifySignedUrl(Horde_Util::getFormData('url'))) {
-    $url = new Horde_Url($url, true);
-} else {
-    $url = Horde::url($prefs->getValue('defaultview') . '.php', true)
-        ->add(array('month' => Horde_Util::getFormData('month'),
-                    'year' => Horde_Util::getFormData('year')));
-}
-
-// Make sure URL is unique.
-$url->unique()->redirect();
diff -pruN 4.2.23-1/kronolith-4.2.23/attendees.php 4.2.27-1/kronolith-4.2.23/attendees.php
--- 4.2.23-1/kronolith-4.2.23/attendees.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/attendees.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,284 +0,0 @@
-<?php
-/**
- * Copyright 2004-2007 Code Fusion  <http://www.codefusion.co.za/>
- * Copyright 2004-2007 Stuart Binge <s.binge@codefusion.co.za>
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- */
-
-require_once __DIR__ . '/lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->redirect();
-}
-
-// Get the current attendees array from the session cache.
-$attendees = $session->get('kronolith', 'attendees', Horde_Session::TYPE_ARRAY);
-$resources = $session->get('kronolith', 'resources', Horde_Session::TYPE_ARRAY);
-$editAttendee = null;
-
-// Get the action ID and value. This specifies what action the user initiated.
-$actionID = Horde_Util::getFormData('actionID');
-if (Horde_Util::getFormData('clearAll')) {
-    $actionID =  'clear';
-}
-$actionValue = Horde_Util::getFormData('actionValue');
-// Perform the specified action, if there is one.
-switch ($actionID) {
-case 'add':
-    // Add new attendees and/or resources. Multiple attendees can be seperated
-    // on a single line by whitespace and/or commas. Resources are added one
-    // at a time (at least for now).
-    $newAttendees = trim(Horde_Util::getFormData('newAttendees'));
-    $newResource = trim(Horde_Util::getFormData('resourceselect'));
-
-    $newAttendees = Kronolith::parseAttendees($newAttendees);
-    if ($newAttendees) {
-        $session->set('kronolith', 'attendees', $attendees + $newAttendees);
-    }
-
-    // Any new resources?
-    if (!empty($newResource)) {
-        /* Get the requested resource */
-        $resource = Kronolith::getDriver('Resource')->getResource($newResource);
-
-        /* Do our best to see what the response will be. Note that this
-         * response is only guarenteed once the event is saved. */
-        $event = Kronolith::getDriver()->getEvent();
-        $event->start = new Horde_Date(Horde_Util::getFormData('startdate'));
-        $event->end = new Horde_Date(Horde_Util::getFormData('enddate'));
-        $event->start->setTimezone(date_default_timezone_get());
-        $event->end->setTimezone(date_default_timezone_get());
-        $response = $resource->getResponse($event);
-        $resources[$resource->getId()] = array(
-            'attendance' => Kronolith::PART_REQUIRED,
-            'response'   => $response,
-            'name'       => $resource->get('name'),
-        );
-
-        $session->set('kronolith', 'resources', $resources);
-    }
-
-    if (Horde_Util::getFormData('addNewClose')) {
-        echo Horde::wrapInlineScript(array('window.close();'));
-        exit;
-    }
-    break;
-
-case 'edit':
-    // Edit the specified attendee.
-    $actionValue = Horde_String::lower($actionValue);
-    if (isset($attendees[$actionValue])) {
-        if (empty($attendees[$actionValue]['name'])) {
-            $editAttendee = $actionValue;
-        } elseif (strpos($actionValue, '@') === false) {
-            $editAttendee = $attendees[$actionValue]['name'];
-        } else {
-            $tmp = new Horde_Mail_Rfc822_Address($actionValue);
-            $tmp->personal = $attendees[$actionValue]['name'];
-            $editAttendee = strval($tmp);
-        }
-        unset($attendees[$actionValue]);
-        $session->set('kronolith', 'attendees', $attendees);
-    }
-    break;
-
-case 'remove':
-    // Remove the specified attendee.
-    $actionValue = Horde_String::lower($actionValue);
-    if (isset($attendees[$actionValue])) {
-        unset($attendees[$actionValue]);
-        $session->set('kronolith', 'attendees', $attendees);
-    }
-    break;
-
-case 'removeResource':
-    // Remove the specified resource
-    if (isset($resources[$actionValue])) {
-        unset($resources[$actionValue]);
-        $session->set('kronolith', 'resources', $resources);
-    }
-    break;
-
-case 'changeResourceResp':
-    //@TODO: What else to do here? Disallow if responsetype is auto?
-    list($partval, $partname) = explode(' ', $actionValue, 2);
-    if (isset($resources[$partname])) {
-        $resources[$partname]['response'] = $partval;
-        $session->set('kronolith', 'resources', $resources);
-    }
-    break;
-
-case 'changeatt':
-    // Change the attendance status of an attendee
-    list($partval, $partname) = explode(' ', $actionValue, 2);
-    $partname = Horde_String::lower($partname);
-    if (isset($attendees[$partname])) {
-        $attendees[$partname]['attendance'] = $partval;
-        $session->set('kronolith', 'attendees', $attendees);
-    }
-    break;
-
-case 'changeResourceAtt':
-    // Change attendance status of a resource
-    list($partval, $partname) = explode(' ', $actionValue, 2);
-    $partname = Horde_String::lower($partname);
-    if (isset($resources[$partname])) {
-        $resources[$partname]['attendance'] = $partval;
-        $session->set('kronolith', 'resources', $resources);
-    }
-    break;
-
-case 'changeresp':
-    // Change the response status of an attendee
-    list($partval, $partname) = explode(' ', $actionValue, 2);
-    $partname = Horde_String::lower($partname);
-    if (isset($attendees[$partname])) {
-        $attendees[$partname]['response'] = $partval;
-        $session->set('kronolith', 'attendees', $attendees);
-    }
-    break;
-
-case 'dismiss':
-    // Close the attendee window.
-    if ($browser->hasFeature('javascript')) {
-        echo Horde::wrapInlineScript(array('window.close();'));
-        exit;
-    }
-
-    if ($url = Horde::verifySignedUrl(Horde_Util::getFormData('url'))) {
-        $url = new Horde_Url($url, true);
-    } else {
-        $date = new Horde_Date(Horde_Util::getFormData('startdate'));
-        $url = Horde::url($prefs->getValue('defaultview') . '.php', true)
-            ->add('date', $date->dateString());
-    }
-
-    // Make sure URL is unique.
-    $url->unique()->redirect();
-
-case 'clear':
-    // Remove all the attendees and resources.
-    $session->remove('kronolith', 'attendees');
-    $session->remove('kronolith', 'resources');
-    break;
-}
-
-/* Get list of resources for select list, and remove those we already added */
-if (!empty($conf['resource']['driver'])) {
-    $allResources = Kronolith::getDriver('Resource')->listResources(Horde_Perms::READ, array(), 'name');
-    foreach (array_keys($resources) as $id) {
-        unset($allResources[$id]);
-    }
-} else {
-    $allResources = array();
-}
-
-// Get the current Free/Busy view; default to the 'day' view if none specified.
-$view = Horde_Util::getFormData('view', 'Day');
-
-// Pre-format our delete image/link.
-$delimg = Horde::img('delete.png', _("Remove Attendee"));
-
-$ident = $injector->getInstance('Horde_Core_Factory_Identity')->create();
-$identities = $ident->getAll('id');
-$vars = Horde_Variables::getDefaultVariables();
-$tabs = new Horde_Core_Ui_Tabs(null, $vars);
-$tabs->addTab(_("Day"), new Horde_Url('javascript:switchView(\'Day\')'), 'Day');
-$tabs->addTab(_("Work Week"), new Horde_Url('javascript:switchView(\'Workweek\')'), 'Workweek');
-$tabs->addTab(_("Week"), new Horde_Url('javascript:switchView(\'Week\')'), 'Week');
-$tabs->addTab(_("Month"), new Horde_Url('javascript:switchView(\'Month\')'), 'Month');
-
-$attendee_view = &Kronolith_FreeBusy_View::singleton($view);
-
-// Add the creator as a required attendee in the Free/Busy display
-$cal = @unserialize($prefs->getValue('fb_cals'));
-if (!is_array($cal)) {
-    $cal = null;
-}
-
-// If the free/busy calendars preference is empty, default to the user's
-// default_share preference, and if that's empty, to their username.
-if (!$cal) {
-    $cal = 'internal_' . $prefs->getValue('default_share');
-    if (!$cal) {
-        $cal = 'internal_' . $GLOBALS['registry']->getAuth();
-    }
-    $cal = array($cal);
-}
-try {
-    $vfb = Kronolith_FreeBusy::generate($cal, null, null, true, $GLOBALS['registry']->getAuth());
-    $attendee_view->addRequiredMember($vfb);
-} catch (Exception $e) {
-    $notification->push(sprintf(_("Error retrieving your free/busy information: %s"), $e->getMessage()));
-}
-
-// Add the Free/Busy information for each attendee.
-foreach ($session->get('kronolith', 'attendees', Horde_Session::TYPE_ARRAY) as $email => $status) {
-    if (strpos($email, '@') !== false &&
-        ($status['attendance'] == Kronolith::PART_REQUIRED ||
-         $status['attendance'] == Kronolith::PART_OPTIONAL)) {
-        try {
-            $vfb = Kronolith_FreeBusy::get($email);
-            $organizer = $vfb->getAttribute('ORGANIZER');
-            if (empty($organizer)) {
-                $vfb->setAttribute('ORGANIZER', 'mailto:' . $email, array(),
-                                   false);
-            }
-            if ($status['attendance'] == Kronolith::PART_REQUIRED) {
-                $attendee_view->addRequiredMember($vfb);
-            } else {
-                $attendee_view->addOptionalMember($vfb);
-            }
-        } catch (Exception $e) {
-            $notification->push(
-                sprintf(_("Error retrieving free/busy information for %s: %s"),
-                        $email, $e->getMessage()));
-        }
-    }
-}
-
-// Add Free/Busy for resources
-if (count($resources)) {
-    $driver = Kronolith::getDriver('Resource');
-    foreach ($resources as $r_id => $resource) {
-        try {
-            $r = $driver->getResource($r_id);
-        } catch (Horde_Exception_NotFound $e) {
-            continue;
-        }
-        try {
-            $vfb = $r->getFreeBusy(null, null, true);
-            if ($resource['attendance'] == Kronolith::PART_REQUIRED) {
-                $attendee_view->addRequiredResourceMember($vfb);
-            } else {
-                $attendee_view->addOptionalResourceMember($vfb);
-            }
-        } catch (Horde_Exception $e) {
-            $notification->push(
-                sprintf(_("Error retrieving free/busy information for %s: %s"),
-                    $r_id, $e->getMessage()));
-        }
-    }
-}
-
-$date = new Horde_Date(Horde_Util::getFormData('startdate', date('Ymd') . '000000'));
-$end =  new Horde_Date(Horde_Util::getFormData('enddate', date('Ymd') . '000000'));
-
-$vfb_html = $attendee_view->render($date);
-
-$injector->getInstance('Horde_Core_Factory_Imple')->create('Kronolith_Ajax_Imple_ContactAutoCompleter', array(
-    'id' => 'newAttendees'
-));
-
-$title = _("Edit attendees");
-$page_output->sidebar = $page_output->topbar = false;
-$page_output->header(array(
-    'title' => $title
-));
-require KRONOLITH_TEMPLATES . '/javascript_defs.php';
-$notification->notify(array('listeners' => 'status'));
-require KRONOLITH_TEMPLATES . '/attendees/attendees.inc';
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/attend.php 4.2.27-1/kronolith-4.2.23/attend.php
--- 4.2.23-1/kronolith-4.2.23/attend.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/attend.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,84 +0,0 @@
-<?php
-/**
- * Copyright 2005-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-
-require_once __DIR__ . '/lib/Application.php';
-Horde_Registry::appInit('kronolith', array('authentication' => 'none'));
-
-$cal = Horde_Util::getFormData('c');
-$id = Horde_Util::getFormData('e');
-$uid = Horde_Util::getFormData('i');
-$user = Horde_Util::getFormData('u');
-
-switch (Horde_Util::getFormData('a')) {
-case 'accept':
-    $action = Kronolith::RESPONSE_ACCEPTED;
-    $msg = _("You have successfully accepted attendence to this event.");
-    break;
-
-case 'decline':
-    $action = Kronolith::RESPONSE_DECLINED;
-    $msg = _("You have successfully declined attendence to this event.");
-    break;
-
-case 'tentative':
-    $action = Kronolith::RESPONSE_TENTATIVE;
-    $msg = _("You have tentatively accepted attendence to this event.");
-    break;
-
-default:
-    $action = Kronolith::RESPONSE_NONE;
-    $msg = '';
-    break;
-}
-
-if (((empty($cal) || empty($id)) && empty($uid)) || empty($user)) {
-    $notification->push(_("The request was incomplete. Some parameters that are necessary to accept or decline an event are missing."), 'horde.error', array('sticky'));
-    $title = '';
-} else {
-    try {
-        if (empty($uid)) {
-            $event = Kronolith::getDriver(null, $cal)->getEvent($id);
-        } else {
-            $event = Kronolith::getDriver()->getByUID($uid);
-        }
-        if (!$event->hasAttendee($user)) {
-            $notification->push(_("You are not an attendee of the specified event."), 'horde.error', array('sticky'));
-            $title = $event->getTitle();
-        } else {
-            $event->addAttendee($user, Kronolith::PART_IGNORE, $action);
-            try {
-                $event->save();
-                if (!empty($msg)) {
-                    $notification->push($msg, 'horde.success', array('sticky'));
-                }
-            } catch (Exception $e) {
-                $notification->push($e, 'horde.error', array('sticky'));
-            }
-            $title = $event->getTitle();
-        }
-    } catch (Exception $e) {
-        $notification->push($e, 'horde.error', array('sticky'));
-        $title = '';
-    }
-}
-
-$page_output->topbar = $page_output->sidebar = false;
-$page_output->header(array(
-    'title' => $title
-));
-require KRONOLITH_TEMPLATES . '/javascript_defs.php';
-
-?>
-<div id="menu"><h1>&nbsp;<?php echo htmlspecialchars($title) ?></h1></div>
-<?php
-
-$notification->notify(array('listeners' => 'status'));
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/bin/kronolith-agenda 4.2.27-1/kronolith-4.2.23/bin/kronolith-agenda
--- 4.2.23-1/kronolith-4.2.23/bin/kronolith-agenda	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/bin/kronolith-agenda	1970-01-01 00:00:00.000000000 +0000
@@ -1,202 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * Copyright 2003-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Chuck Hagenbuch <chuck@horde.org>
- */
-
-if (file_exists(__DIR__ . '/../../kronolith/lib/Application.php')) {
-    $baseDir = __DIR__ . '/../';
-} else {
-    require_once 'PEAR/Config.php';
-    $baseDir = PEAR_Config::singleton()
-        ->get('horde_dir', null, 'pear.horde.org') . '/kronolith/';
-}
-require_once $baseDir . 'lib/Application.php';
-Horde_Registry::appInit('kronolith', array('cli' => true));
-
-send_agendas();
-
-/**
- */
-function send_agendas()
-{
-    if (isset($_SERVER['REQUEST_TIME'])) {
-        $runtime = $_SERVER['REQUEST_TIME'];
-    } else {
-        $runtime = time();
-    }
-
-    $kronolith_shares = $GLOBALS['injector']->getInstance('Kronolith_Shares');
-    $calendars = $kronolith_shares->listAllShares();
-
-    // If there are no calendars to check, we're done.
-    if (!count($calendars)) {
-        return;
-    }
-
-    if (empty($GLOBALS['conf']['reminder']['server_name']) ||
-        empty($GLOBALS['conf']['reminder']['from_addr'])) {
-        die('You must configure the server_name and from_addr settings for reminders in the calendar configuration.');
-    }
-
-    $_SERVER['SERVER_NAME'] = $GLOBALS['conf']['server']['name'] = $GLOBALS['conf']['reminder']['server_name'];
-
-    // Retrieve a list of users associated with each calendar, and
-    // thus a list of users who have used kronolith and
-    // potentially have an agenda preference set.
-    $users = array();
-    foreach (array_keys($calendars) as $calendarId) {
-        try {
-            $calendar = $kronolith_shares->getShare($calendarId);
-        } catch (Exception $e) {
-            continue;
-        }
-        $users = array_merge($users, $calendar->listUsers(Horde_Perms::READ));
-    }
-
-    // Remove duplicates.
-    $users = array_unique($users);
-
-    // Generate image mime part first and only once, because we need
-    // the Content-ID.
-    $image = Kronolith::getImagePart('big_agenda.png');
-
-    $runtime = new Horde_Date($runtime);
-    $default_timezone = date_default_timezone_get();
-    $kronolith_driver = Kronolith::getDriver();
-    $view = new Horde_View(array('templatePath' => KRONOLITH_TEMPLATES . '/agenda', 'encoding' => 'UTF-8'));
-    new Horde_View_Helper_Text($view);
-    $view->imageId = $image->getContentId();
-    $view->date = $runtime;
-    if (!$GLOBALS['prefs']->isLocked('daily_agenda')) {
-        $view->prefsUrl = Horde::url($GLOBALS['registry']->getServiceLink('prefs', 'kronolith'), true)->remove(session_name());
-    }
-
-    // Loop through the users and generate an agenda for them
-    foreach ($users as $user) {
-        $prefs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Prefs')->create('kronolith', array(
-            'user' => $user
-        ));
-        $agenda_calendars = $prefs->getValue('daily_agenda');
-
-        if (!$agenda_calendars) {
-            continue;
-        }
-
-        // Initialize the CalendarsManager for this user.
-        $GLOBALS['calendar_manager'] = new Kronolith_CalendarsManager($user);
-
-        // Try to find an email address for the user.
-        $identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($user);
-        $email = $identity->getDefaultFromAddress(true);
-
-        // Check if user has a timezone pref, and set it. Otherwise, make
-        // sure to use the server's default timezone.
-        $tz = $prefs->getValue('timezone');
-        date_default_timezone_set(empty($tz) ? $default_timezone : $tz);
-
-        // If we found an email address, generate the agenda.
-        switch ($agenda_calendars) {
-        case 'owner':
-            $calendars = $kronolith_shares->listShares(
-                $user,
-                array('perm' => Horde_Perms::SHOW,
-                      'attributes' => $user));
-            $calendars = array_keys($calendars);
-            break;
-
-        case 'read':
-            $calendars = $kronolith_shares->listShares(
-                $user,
-                array('perm' => Horde_Perms::READ));
-            $calendars = array_keys($calendars);
-            break;
-
-        case 'show':
-        default:
-            $calendars = array();
-            $shown_calendars = unserialize($prefs->getValue('display_cals'));
-            $cals = $kronolith_shares->listShares(
-                $user,
-                array('perm' => Horde_Perms::SHOW));
-            foreach (array_keys($cals) as $calId) {
-                if (in_array($calId, $shown_calendars)) {
-                    $calendars[] = $calId;
-                }
-            }
-        }
-
-        // Get a list of events for today
-        $eventlist = array();
-        foreach ($calendars as $calId) {
-            $kronolith_driver->open($calId);
-            $events = $kronolith_driver->listEvents(
-                $runtime, $runtime, array('show_recurrence' => true));
-            foreach ($events as $dayevents) {
-                foreach ($dayevents as $event) {
-                    // The event list contains events starting at 12am.
-                    if ($event->start->compareDate($runtime) || $event->isPrivate($user)) {
-                        continue;
-                    }
-                    $eventlist[] = $event;
-                }
-            }
-        }
-
-        if (!count($eventlist)) {
-            continue;
-        }
-
-        // If there are any events, generate and send the email.
-        usort($eventlist, 'sort_events');
-        $GLOBALS['registry']->setLanguageEnvironment($prefs->getValue('language'));
-        $twentyFour = $prefs->getValue('twentyFour');
-
-        $view->pad = max(Horde_String::length(_("All day")) + 2, $twentyFour ? 6 : 8);
-        $view->dateFormat = $prefs->getValue('date_format');
-        $view->timeformat = $twentyFour  ? 'H:i' : 'h:ia';
-        $view->user = $user;
-        $view->events = $eventlist;
-
-        $mime_mail = new Horde_Mime_Mail(array(
-            'Subject' => sprintf(_("Your daily agenda for %s"), $runtime->strftime($view->dateFormat)),
-            'To' => $email,
-            'From' => $GLOBALS['conf']['reminder']['from_addr'],
-            'User-Agent' => 'Kronolith ' . $GLOBALS['registry']->getVersion(),
-            'Auto-Submitted' => 'auto-generated'));
-        try {
-            $mime_mail->addRecipients($email);
-        } catch (Horde_Mime_Exception $e) {}
-
-        $mime_mail->setBasePart(Kronolith::buildMimeMessage($view, 'notification', $image));
-
-        Horde::log(sprintf('Sending daily agenda to %s', $email), 'DEBUG');
-        try {
-            $mime_mail->send($GLOBALS['injector']->getInstance('Horde_Mail'));
-        } catch (Horde_Mime_Exception $e) {
-            Horde::log(sprintf('Error sending daily agenda to %s: %s', $email, $e->getMessage()), 'WARN');
-        }
-    }
-}
-
-function sort_events($a, $b)
-{
-    if ($a->allday && $b->allday) {
-        return 0;
-    }
-
-    if ($a->allday || $b->allday) {
-        return ($a->allday) ? -1 : 1;
-    }
-
-    if ($a->start == $b->start) {
-        return 0;
-    }
-
-    return ($a->start < $b->start) ? -1 : 1;
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/bin/kronolith-convert-datatree-shares-to-sql 4.2.27-1/kronolith-4.2.23/bin/kronolith-convert-datatree-shares-to-sql
--- 4.2.23-1/kronolith-4.2.23/bin/kronolith-convert-datatree-shares-to-sql	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/bin/kronolith-convert-datatree-shares-to-sql	1970-01-01 00:00:00.000000000 +0000
@@ -1,157 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * This script migrates Kronolith's share data from the datatree Horde_Share
- * driver to the SQL Horde_Share driver.
- */
-if (file_exists(__DIR__ . '/../../kronolith/lib/Application.php')) {
-    $baseDir = __DIR__ . '/../';
-} else {
-    require_once 'PEAR/Config.php';
-    $baseDir = PEAR_Config::singleton()
-        ->get('horde_dir', null, 'pear.horde.org') . '/kronolith/';
-}
-require_once $baseDir . 'lib/Application.php';
-Horde_Registry::appInit('kronolith', array('cli' => true));
-
-$db = $injector->getInstance('Horde_Db_Adapter');
-$error_cnt = 0;
-$delete_dt_data = false;
-$answer = $cli->prompt('Do you want to keep your old datatree data or delete it?', array('Keep', 'Delete'));
-if ($answer == 1) {
-    $delete_dt_data = true;
-}
-$answer = $cli->prompt(sprintf("Data will be copied into the new tables, and %s be deleted from the datatree.\n Is this what you want?", (($delete_dt_data) ? 'WILL' : 'WILL NOT')), array('y' => 'Yes', 'n' => 'No'));
-if ($answer != 'y') {
-    exit;
-}
-
-/* Get the share entries */
-try {
-    $shares_result = $db->selectAssoc('SELECT datatree_id, datatree_name FROM horde_datatree WHERE group_uid = ' . $db->quoteString('horde.shares.kronolith'));
-} catch (Horde_Db_Exception $e) {
-    die($e->getMessage());
-}
-
-$query = 'SELECT attribute_name, attribute_key, attribute_value FROM horde_datatree_attributes WHERE datatree_id = ?';
-foreach ($shares_result as $share_id => $share_name) {
-    $data = array('share_name' => $share_name);
-    $rows = $db->selectAll($query, array($share_id));
-    $users = array();
-    $groups = array();
-    foreach ($rows as $row) {
-        if ($row['attribute_name'] == 'perm_groups') {
-            /* Group table entry */
-            $groups[] = array('group_uid' => $row['attribute_key'],
-                              'perm' => $row['attribute_value']);
-        } elseif ($row['attribute_name'] == 'perm_users') {
-            /* User table entry */
-            $users[] = array('user_uid' => $row['attribute_key'],
-                             'perm' => $row['attribute_value']);
-        } else {
-            /* Everything else goes in the main share table */
-            switch ($row['attribute_name']) {
-            case 'perm_creator':
-            case 'perm_default':
-            case 'perm_guest':
-                $data[$row['attribute_name']] = $row['attribute_value'];
-                break;
-
-            case 'owner':
-                $data['share_owner'] = $row['attribute_value'];
-                break;
-
-            case 'name':
-                // Note the key to the $data array is not related to
-                // the attribute_name field in the dt_attributes table.
-                $data['attribute_name'] = $row['attribute_value'];
-                break;
-
-            case 'desc':
-                $data['attribute_desc'] = $row['attribute_value'];
-                break;
-            }
-        }
-    }
-
-    /* Set flags */
-    $data['share_flags'] = 0;
-    if (count($users)) {
-        $data['share_flags'] |= 1;
-    }
-    if (count($groups)) {
-        $data['share_flags'] |= 2;
-    }
-
-    /* Insert the new data */
-    $cli->message('Migrating share data for share_id: ' . $share_id, 'cli.message');
-    $error = false;
-    $db->beginDbTransaction();
-    try {
-        $newId = insertData('kronolith_shares', $data, $db);
-    } catch (Horde_Db_Exception $e) {
-        $cli->message($e->getMessage(), 'cli.error');
-        $error=true;
-    }
-    if (count($groups)) {
-        foreach ($groups as $group) {
-            try {
-                $group['share_id'] = $newId;
-                insertData('kronolith_shares_groups', $group, $db);
-            } catch (Horde_Db_Exception $e) {
-                $cli->message($e->getMessage(), 'cli.error');
-                $error = true;
-            }
-        }
-    }
-    if (count($users)) {
-        foreach ($users as $user) {
-            try {
-                $user['share_id'] = $newId;
-                insertData('kronolith_shares_users', $user, $db);
-            } catch (Horde_Db_Exception $e) {
-                $cli->message($e->getMessage(), 'cli.error');
-                $error = true;
-            }
-        }
-    }
-
-    /* Delete the datatree data, but ONLY if it was requested */
-    if ($delete_dt_data && !$error) {
-        $cli->message('DELETING datatree data for share_id: ' . $share_id, 'cli.message');
-        try {
-            $db->delete('DELETE FROM horde_datatree_attributes WHERE datatree_id = ?', array($share_id));
-            $db->delete('DELETE FROM horde_datatree WHERE datatree_id = ?', array($share_id));
-        } catch (Horde_Db_Exception $e) {
-            $cli->message($e->getMessage(), 'cli.error');
-            $error = true;
-        }
-    }
-
-    /* Cleanup */
-    unset($row, $rows, $data, $groups, $users);
-    if ($error) {
-        $db->rollbackDbTransaction();
-        $cli->message('Rollback for share data for share_id: ' . $share_id, 'cli.message');
-        ++$error_cnt;
-    } else {
-        $db->commitDbTransaction();
-        $cli->message('Commit for share data for share_id: ' . $share_id, 'cli.message');
-    }
-}
-
-if ($error_cnt) {
-    $cli->message(sprintf("Encountered %u errors.", $error_cnt));
-}
-echo "\nDone.\n";
-
-/**
- * Helper function
- */
-function insertData($table, $data, $db)
-{
-    $fields = array_keys($data);
-    $values = array_values($data);
-    $sql = 'INSERT INTO ' . $table . ' (' . implode(', ', $fields) . ') VALUES (' . str_repeat('?, ', count($values) - 1) . '?)';
-    return $db->insert($sql, $values);
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/bin/kronolith-convert-sql-shares-to-sqlng 4.2.27-1/kronolith-4.2.23/bin/kronolith-convert-sql-shares-to-sqlng
--- 4.2.23-1/kronolith-4.2.23/bin/kronolith-convert-sql-shares-to-sqlng	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/bin/kronolith-convert-sql-shares-to-sqlng	1970-01-01 00:00:00.000000000 +0000
@@ -1,36 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * This script migrates Kronolith's share data from the SQL Horde_Share
- * driver to the next-generation SQL Horde_Share driver.
- *
- * It is supposed to run at any time after migrating Kronolith to the latest DB
- * schema version. The schema migration already migrates the data once, but
- * this script can be used to migrate the data again, e.g. if starting to use
- * the NG driver at a later time.
- */
-
-if (file_exists(__DIR__ . '/../../kronolith/lib/Application.php')) {
-    $baseDir = __DIR__ . '/../';
-} else {
-    require_once 'PEAR/Config.php';
-    $baseDir = PEAR_Config::singleton()
-        ->get('horde_dir', null, 'pear.horde.org') . '/kronolith/';
-}
-require_once $baseDir . 'lib/Application.php';
-Horde_Registry::appInit('kronolith', array('cli' => true));
-
-require_once $baseDir . 'migration/15_kronolith_upgrade_sqlng.php';
-
-$db = $injector->getInstance('Horde_Db_Adapter');
-$migration = new KronolithUpgradeSqlng($db);
-
-$delete = $cli->prompt('Delete existing shares from the NEW backend before migrating the OLD backend? This should be done to avoid duplicate entries or primary key collisions in the storage backend from earlier migrations.', array('y' => 'Yes', 'n' => 'No'), 'n');
-
-if ($delete == 'y' || $delete == 'Y') {
-    $db->delete('DELETE FROM kronolith_sharesng');
-    $db->delete('DELETE FROM kronolith_sharesng_users');
-    $db->delete('DELETE FROM kronolith_sharesng_groups');
-}
-
-$migration->dataUp();
diff -pruN 4.2.23-1/kronolith-4.2.23/bin/kronolith-convert-to-utc 4.2.27-1/kronolith-4.2.23/bin/kronolith-convert-to-utc
--- 4.2.23-1/kronolith-4.2.23/bin/kronolith-convert-to-utc	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/bin/kronolith-convert-to-utc	1970-01-01 00:00:00.000000000 +0000
@@ -1,89 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * This script converts all dates from the user's timezone to UTC.
- */
-
-if (file_exists(__DIR__ . '/../../kronolith/lib/Application.php')) {
-    $baseDir = __DIR__ . '/../';
-} else {
-    require_once 'PEAR/Config.php';
-    $baseDir = PEAR_Config::singleton()
-        ->get('horde_dir', null, 'pear.horde.org') . '/kronolith/';
-}
-require_once $baseDir . 'lib/Application.php';
-Horde_Registry::appInit('kronolith', array('cli' => true));
-
-/* Prepare DB stuff. */
-$db = $injector->getInstance('Horde_Db_Adapter');
-try {
-    $result = $db->selectAll('SELECT event_title, event_id, event_creator_id, event_start, event_end, event_allday, event_recurenddate, event_exceptionoriginaldate FROM ' . $conf['calendar']['params']['table'] . ' ORDER BY event_creator_id');
-} catch (Horde_Db_Exception $e) {
-    echo $e->getMessage() . "\n";
-    exit;
-}
-
-$stmt = 'UPDATE kronolith_events SET event_start = ?, event_end = ?, event_recurenddate = ?, event_exceptionoriginaldate = ? WHERE event_id = ?';
-
-/* Confirm changes. */
-if (!isset($argv[1]) || $argv[1] != '--yes') {
-    $answer = $cli->prompt('Running this script will convert all existing events to UTC. This conversion is not reversible. Is this what you want?', array('y' => 'Yes', 'n' => 'No'));
-    if ($answer != 'y') {
-        exit;
-    }
-}
-
-/* Loop through all events. */
-$creator = null;
-$utc = new DateTimeZone('UTC');
-echo "Converting events for:\n";
-$timezone = new DateTimeZone(date_default_timezone_get());
-foreach ($result as $row) {
-    if ($row['event_allday']) {
-        continue;
-    }
-    if ($row['event_creator_id'] != $creator) {
-        if (!is_null($creator)) {
-            echo "$count\n";
-        }
-        $prefs = $injector->getInstance('Horde_Core_Factory_Prefs')->create('horde', array(
-            'cache' => false,
-            'user' => $row['event_creator_id']
-        ));
-
-        $timezone = $prefs->getValue('timezone');
-        if (empty($timezone)) {
-            $timezone = date_default_timezone_get();
-        }
-        $timezone = new DateTimeZone($timezone);
-        $creator = $row['event_creator_id'];
-        $count = 0;
-        echo $creator . ': ';
-    }
-    $start = new DateTime($row['event_start'], $timezone);
-    $start->setTimezone($utc);
-    $end = new DateTime($row['event_end'], $timezone);
-    $end->setTimezone($utc);
-    if (!empty($row['event_recurenddate'])) {
-        $recur_end = new DateTime($row['event_recurenddate'], $timezone);
-        $recur_end->setTimezone($utc);
-    }
-    if (!empty($row['event_exceptionoriginaldate'])) {
-        $eod = new DateTime($row['event_exceptionoriginaldate'], $timezone);
-        $eod->setTimezone($utc);
-    }
-
-    try {
-        $db->update($stmt, array(
-            $start->format('Y-m-d H:i:s'),
-            $end->format('Y-m-d H:i:s'),
-            !empty($row['event_recurenddate']) ? $recur_end->format('Y-m-d H:i:s') : null,
-            !empty($row['event_exceptionoriginaldate']) ? $eod->format('Y-m-d H:i:s') : null,
-            $row['event_id']));
-    } catch (Horde_Db_Exception $e) {
-        echo $e->getMessage() . "\n";
-        exit;
-    }
-    $count++;
-}
-echo "$count\n";
diff -pruN 4.2.23-1/kronolith-4.2.23/bin/kronolith-import-icals 4.2.27-1/kronolith-4.2.23/bin/kronolith-import-icals
--- 4.2.23-1/kronolith-4.2.23/bin/kronolith-import-icals	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/bin/kronolith-import-icals	1970-01-01 00:00:00.000000000 +0000
@@ -1,58 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * This script imports iCalendar/vCalendar data into Kronolith calendars.
- * The data is read from standard input, the calendar and user name passed as
- * parameters.
- *
- * Copyright 2005-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Jan Schneider <jan@horde.org>
- */
-
-if (file_exists(__DIR__ . '/../../kronolith/lib/Application.php')) {
-    $baseDir = __DIR__ . '/../';
-} else {
-    require_once 'PEAR/Config.php';
-    $baseDir = PEAR_Config::singleton()
-        ->get('horde_dir', null, 'pear.horde.org') . '/kronolith/';
-}
-require_once $baseDir . 'lib/Application.php';
-Horde_Registry::appInit('kronolith', array('cli' => true));
-
-// Read command line parameters.
-if (count($argv) != 3) {
-    $cli->message('Too many or too few parameters.', 'cli.error');
-    usage();
-}
-$cal = $argv[1];
-$user = $argv[2];
-
-// Read standard input.
-$ical = $cli->readStdin();
-if (empty($ical)) {
-    $cli->message('No import data provided.', 'cli.error');
-    usage();
-}
-
-// Set user.
-$registry->setAuth($user, array());
-
-// Import data.
-try {
-    $result = $registry->call('calendar/import', array($ical, 'text/calendar', $cal));
-} catch (Horde_Exception $e) {
-    $cli->fatal($e->toString());
-}
-
-$cli->message('Imported successfully ' . count($result) . ' events', 'cli.success');
-
-function usage()
-{
-    $GLOBALS['cli']->writeln('Usage: kronolith-import-icals calendar user');
-    exit;
-}
-
diff -pruN 4.2.23-1/kronolith-4.2.23/bin/kronolith-import-openxchange 4.2.27-1/kronolith-4.2.23/bin/kronolith-import-openxchange
--- 4.2.23-1/kronolith-4.2.23/bin/kronolith-import-openxchange	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/bin/kronolith-import-openxchange	1970-01-01 00:00:00.000000000 +0000
@@ -1,275 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * This script imports Open-Xchange calendars into Kronolith.
- *
- * The first argument must be the API endpoint of an Open-Xchange servers,
- * usually something like http://servername/ajax.
- *
- * If called with three arguments, the further arguments must be the user name
- * (usually "Administrator") and password of an administrator user to import
- * public calendars.
- *
- * If called with two arguments, the second argument must be a file with user
- * names and cleartext passwords separated by spaces.
- *
- * Copyright 2014-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Jan Schneider <jan@horde.org>
- */
-
-// Init application.
-if (file_exists(__DIR__ . '/../../kronolith/lib/Application.php')) {
-    $baseDir = __DIR__ . '/../';
-} else {
-    require_once 'PEAR/Config.php';
-    $baseDir = PEAR_Config::singleton()
-        ->get('horde_dir', null, 'pear.horde.org') . '/kronolith/';
-}
-require_once $baseDir . 'lib/Application.php';
-Horde_Registry::appInit('kronolith', array('cli' => true, 'user_admin' => true));
-
-// Read command line parameters.
-if ($argc < 3 || $argc > 4) {
-    $cli->message('Too many or too few parameters.', 'cli.error');
-    $cli->writeln('Usage: kronolith-import-openxchange url [ file | user password ]');
-    $cli->writeln($cli->indent('url is the URL of an Open-Xchange AJAX endpoint'));
-    $cli->writeln($cli->indent('file is a file with space separated user names and passwords to import'));
-    $cli->writeln($cli->indent('personal calendars.'));
-    $cli->writeln($cli->indent('user and password are credentials of an administrator user to import public'));
-    $cli->writeln($cli->indent('calendars.'));
-    exit;
-}
-$admin = $argc == 4;
-
-// Basic objects and variables.
-$endpoint = parse_url($argv[1]);
-$cli->message('Opening endpoint ' . $argv[1]);
-$ox = new Horde_OpenXchange_Events(array('endpoint' => $argv[1]));
-$users = array();
-
-// Prepare handle on user/password list.
-if ($admin) {
-    $fp = fopen('php://temp', 'r+');
-    fwrite($fp, $argv[2] . ' ' . $argv[3]);
-    rewind($fp);
-} else {
-    if (!is_readable($argv[2]) || !filesize($argv[2])) {
-        $cli->message($argv[2] . ' is not readable or empty', 'cli.error');
-        exit(1);
-    }
-    $fp = fopen($argv[2], 'r');
-}
-if (!$fp) {
-    exit(1);
-}
-
-// Loop through all users.
-while ($row = fgetcsv($fp, 0, ' ')) {
-    $user = $row[0];
-    if (is_null($user)) {
-        continue;
-    }
-    $ox->logout();
-    $ox->login($user, $row[1]);
-
-    $registry->setAuth($user, array());
-    $cli->message('Importing ' . $user . '\'s calendars');
-
-    // Reset user prefs
-    $prefs = $injector->getInstance('Horde_Core_Factory_Prefs')
-        ->create('kronolith', array(
-            'cache' => false,
-            'user' => $user
-        )
-    );
-    $calendar_manager = $injector->createInstance('Kronolith_CalendarsManager');
-
-    // Load calendars for current user.
-    $targets = Kronolith::listInternalCalendars(true, Horde_Perms::EDIT);
-
-    $count = 0;
-    $calendars = $ox->listResources(
-        $admin
-            ? Horde_OpenXchange_Events::RESOURCE_PUBLIC
-            : Horde_OpenXchange_Events::RESOURCE_PRIVATE
-    );
-    $default = $ox->getConfig('folder/calendar');
-
-    // Loop through all calendars.
-    foreach ($calendars as $folderId => $calendar) {
-        // Check if we already have an calendar matching the name.
-        $target = null;
-        foreach ($targets as $id => $info) {
-            if ($calendar['label'] == $info->get('name')) {
-                $target = $id;
-                break;
-            }
-        }
-        if ($target) {
-            $cli->message('Calendar "' . $calendar['label'] . '" found, updating...');
-        } else {
-            // Create new calendar.
-            $cli->message('Calendar "' . $calendar['label'] . '" not found, creating...');
-            $params = array(
-                'name' => $calendar['label'],
-                'color' => Kronolith::randomColor(),
-                'description' => '',
-                'system' => $admin,
-                'tags' => array(),
-            );
-            $share = Kronolith::addShare($params);
-            foreach ($calendar['hordePermission']['group'] as $group => $perm) {
-                $share->addGroupPermission($group, $perm);
-            }
-            foreach ($calendar['hordePermission']['user'] as $user => $perm) {
-                $share->addUserPermission($user, $perm);
-            }
-            $target = $share->getName();
-        }
-
-        if ($folderId == $default) {
-            $prefs->setValue('default_share', $target);
-        }
-
-        // Initiate driver.
-        try {
-            $calendar = $calendar_manager->getEntry(
-                Kronolith::ALL_CALENDARS,
-                $target
-            );
-            $driver = Kronolith::getDriver(null, $calendar->share()->getName());
-        } catch (Kronolith_Exception $e) {
-            $cli->message('  ' . sprintf(_("Connection failed: %s"), $e->getMessage()), 'cli.error');
-            continue;
-        }
-
-        $events = $ox->listEvents($folderId);
-
-        // Loop through all events.
-        foreach ($events as $event) {
-            if ($event['recur_id'] && ($event['recur_id'] == $event['id'])) {
-                // Workaround because recurrence_master parameter doesn't work
-                // as advertised.
-                if ($event['recur_position'] > 1) {
-                    continue;
-                }
-
-                switch ($event['recur_type']) {
-                case 3:
-                    if ($event['day_in_month']) {
-                        $event['recur_type'] = Horde_Date_Recurrence::RECUR_MONTHLY_DATE;
-                    } else {
-                        $event['recur_type'] = Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY;
-                    }
-                    break;
-                case 4:
-                    if ($event['day_in_month']) {
-                        $event['recur_type'] = Horde_Date_Recurrence::RECUR_YEARLY_DATE;
-                    } else {
-                        $event['recur_type'] = Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY;
-                    }
-                    break;
-                }
-                if ($event['recur_end']) {
-                    $end = new Horde_Date($event['recur_end'] / 1000, 'UTC');
-                    $end->mday++;
-                    $event['recur_end_date'] = $end->format('Y-m-d');
-                }
-                $event['recur_data'] = $event['recur_days'];
-                $exceptions = array_merge(
-                    (array)$event['recur_delete_exceptions'],
-                    (array)$event['recur_change_exceptions']
-                );
-                $event['recur_exceptions'] = array();
-                foreach ($exceptions as $exception) {
-                    $exception = new Horde_Date($exception / 1000, 'UTC');
-                    $event['recur_exceptions'][] = $exception->format('Y-m-d');
-                }
-            }
-
-            $start = new Horde_Date($event['start'] / 1000, 'UTC');
-            $event['start_date'] = $start->format('Y-m-d');
-            $event['start_time'] = $start->format('H:i:s');
-            $end = new Horde_Date($event['end'] / 1000, 'UTC');
-            $event['end_date'] = $end->format('Y-m-d');
-            $event['end_time'] = $end->format('H:i:s');
-            $event['tags'] == $event['categories'];
-
-            $newEvent = $driver->getEvent();
-            $newEvent->fromHash($event);
-
-            if ($event['recur_id'] && ($event['recur_id'] != $event['id'])) {
-                $base = $ox->getEvent($folderId, $event['recur_id']);
-                $baseStart = new Horde_Date($base['start_date'] / 1000, 'UTC');
-                $baseStart->setTimezone($base['timezone']);
-                $newEvent->baseid = $base['uid'];
-                $newEvent->exceptionoriginaldate = new Horde_Date(
-                    $event['recur_change_exceptions'][0] / 1000, 'UTC'
-                );
-                $newEvent->exceptionoriginaldate->setTimezone($base['timezone']);
-                // This is a hack that will probably break if recurrences span
-                // DST changes. Correct, but also more complex would be to
-                // calculate the actual time of this recurrence.
-                // recur_change_exception only contains the date.
-                $newEvent->exceptionoriginaldate->hour = $baseStart->hour;
-                $newEvent->exceptionoriginaldate->min = $baseStart->min;
-                $newEvent->exceptionoriginaldate->sec = $baseStart->sec;
-                $newEvent->uid = null;
-            }
-            $newEvent->timezone = $timezone;
-            switch ($event['status']) {
-            case 1:
-                $newEvent->status = Kronolith::STATUS_CONFIRMED;
-                break;
-            case 2:
-                $newEvent->status = Kronolith::STATUS_TENTATIVE;
-                break;
-            case 3:
-                $newEvent->status = Kronolith::STATUS_CONFIRMED;
-                break;
-            case 4:
-                $newEvent->status = Kronolith::STATUS_FREE;
-                break;
-            }
-            if ($event['attendees']) {
-                foreach ($event['users'] as $attendee) {
-                    if (!isset($users[$attendee['id']])) {
-                        $users[$attendee['id']] = $ox->getUser($attendee['id']);
-                    }
-                    if (count($event['attendees']) == 1 &&
-                        $users[$attendee['id']]['login_info'] == $user) {
-                        break;
-                    }
-                    $newEvent->addAttendee(
-                        $users[$attendee['id']]['email1'],
-                        Kronolith::PART_REQUIRED,
-                        $attendee['confirmation'] + 1,
-                        $users[$attendee['id']]['display_name']
-                    );
-                }
-                foreach ($event['confirmations'] as $attendee) {
-                    $newEvent->addAttendee(
-                        $attendee['mail'],
-                        Kronolith::PART_REQUIRED,
-                        $attendee['status'] + 1,
-                        $attendee['display_name']
-                    );
-                }
-            }
-
-            try {
-                $newEvent->save();
-                $count++;
-            } catch (Kronolith_Exception $e) {
-                $cli->message('  ' . $e->getMessage(), 'cli.error');
-            }
-        }
-    }
-
-    $cli->message('  Added ' . $count . ' events', 'cli.success');
-    $count = 0;
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/bin/kronolith-import-squirrelmail-calendar 4.2.27-1/kronolith-4.2.23/bin/kronolith-import-squirrelmail-calendar
--- 4.2.23-1/kronolith-4.2.23/bin/kronolith-import-squirrelmail-calendar	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/bin/kronolith-import-squirrelmail-calendar	1970-01-01 00:00:00.000000000 +0000
@@ -1,126 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * This script imports SquirrelMail database calendars into Kronolith.
- *
- * The first argument must be a DSN to the database containing the calendar
- * and event tables, e.g.: "mysql://root:password@localhost/squirrelmail".
- *
- * Copyright 2008-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Jan Schneider <jan@horde.org>
- */
-
-if (file_exists(__DIR__ . '/../../kronolith/lib/Application.php')) {
-    $baseDir = __DIR__ . '/../';
-} else {
-    require_once 'PEAR/Config.php';
-    $baseDir = PEAR_Config::singleton()
-        ->get('horde_dir', null, 'pear.horde.org') . '/kronolith/';
-}
-require_once $baseDir . 'lib/Application.php';
-Horde_Registry::appInit('kronolith', array('cli' => true, 'user_admin' => true));
-
-// Read command line parameters.
-if ($argc != 2) {
-    $cli->message('Too many or too few parameters.', 'cli.error');
-    $cli->writeln('Usage: kronolith-import-squirrelmail-calendar DSN');
-    $cli->writeln($cli->indent('DSN are json-encoded connection parameters to the database containing the "userprefs" table. Example:'));
-    $cli->writeln($cli->indent('{"adapter":"mysql","user":"root","password":"password","host":"localhost","database":"squirrelmail"}'));
-    exit;
-}
-
-$db = $injector->getInstance('Horde_Db')->createDb(json_decode($argv[1]));
-$default_tz = date_default_timezone_get();
-
-// Loop through SquirrelMail calendars.
-$read_stmt = 'SELECT reader_name FROM calendar_readers WHERE calendar_id = ?';
-$write_stmt = 'SELECT writer_name FROM calendar_writers WHERE calendar_id = ?';
-$users = $db->select('SELECT id, name, owner_name FROM calendars, calendar_owners WHERE calendars.id = calendar_owners.calendar_id');
-foreach ($users as $row) {
-    $user = $row['owner_name'];
-    $registry->setAuth($user, array());
-    $cli->message('Creating calendar ' . $row['name']);
-
-    $kronolith_shares = $injector->getInstance('Kronolith_Shares');
-    $share = $kronolith_shares->newShare($GLOBALS['registry']->getAuth(), $row['id'], $row['name']);
-    $kronolith_shares->addShare($share);
-
-    // Add permissions.
-    $permissions = array();
-    $result = $db->select($read_stmt, array($row['id']));
-    foreach ($result as $perm_row) {
-        $permissions[$perm_row[0]] = Horde_Perms::READ | Horde_Perms::SHOW;
-    }
-    $result = $db->select($write_stmt, array($row['id']));
-    foreach ($result as $perm_row) {
-        if (isset($permissions[$perm_row[0]])) {
-            $permissions[$perm_row[0]] |= Horde_Perms::EDIT;
-        } else {
-            $permissions[$perm_row[0]] = Horde_Perms::EDIT;
-        }
-    }
-    if (count($permissions)) {
-        $perm = $share->getPermission();
-        foreach ($permissions as $key => $value) {
-            $perm->addUserPermission($key, $value, false);
-        }
-        $share->setPermission($perm);
-        $share->save();
-    }
-}
-
-$handle = $db->select('SELECT event_id, calendar_id, ical_raw, owner_name, prefval FROM events, event_owners LEFT JOIN userprefs ON event_owners.owner_name = userprefs.user AND userprefs.prefkey = \'timezone\' WHERE events.id = event_owners.event_key ORDER BY calendar_id, userprefs.prefval, event_owners.owner_name');
-$ical = new Horde_Icalendar();
-$tz = $calendar = $user = $count = null;
-foreach ($handle as $row) {
-    // Open calendar.
-    if ($calendar != $row['calendar_id']) {
-        if (!is_null($count)) {
-            $cli->message('  Added ' . $count . ' events', 'cli.success');
-        }
-        $calendar = $row['calendar_id'];
-        $cli->message('Importing events into ' . $calendar);
-        $kronolith_driver->open($calendar);
-        $count = 0;
-    }
-    // Set timezone.
-    if ($tz != $row['prefval']) {
-        $tz = $row['prefval'];
-        date_default_timezone_set($tz ? $tz : $default_tz);
-    }
-    // Set user.
-    if ($user != $row['owner_name']) {
-        $user = $row['owner_name'];
-        $registry->setAuth($user, array());
-    }
-    // Parse event.
-    try {
-        $ical->parsevCalendar($row['ical_raw']);
-    } catch (Horde_Icalendar_Exception $e) {
-        $cli->message('  ' . $e->getMessage(), 'cli.warning');
-        continue;
-    }
-    $components = $ical->getComponents();
-    if (!count($components)) {
-        $cli->message('  No iCalendar data was found.', 'cli.warning');
-        continue;
-    }
-
-    // Save event.
-    $event = $kronolith_driver->getEvent();
-    $event->fromiCalendar($components[0]);
-    try {
-        $event->save();
-    } catch (Exception $e) {
-        $cli->message('  ' . $e->getMessage(), 'cli.error');
-        continue;
-    }
-    $count++;
-}
-if (!is_null($count)) {
-    $cli->message('  Added ' . $count . ' events', 'cli.success');
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/calendars/create.php 4.2.27-1/kronolith-4.2.23/calendars/create.php
--- 4.2.23-1/kronolith-4.2.23/calendars/create.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/calendars/create.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,50 +0,0 @@
-<?php
-/**
- * Copyright 2002-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Chuck Hagenbuch <chuck@horde.org>
- */
-
-require_once __DIR__ . '/../lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->setAnchor('calendar:internal')->redirect();
-}
-
-// Exit if this isn't an authenticated user or if the user can't
-// create new calendars (default share is locked).
-if (!$GLOBALS['registry']->getAuth() || $prefs->isLocked('default_share')) {
-    Horde::url($prefs->getValue('defaultview') . '.php', true)->redirect();
-}
-
-$vars = Horde_Variables::getDefaultVariables();
-$form = new Kronolith_Form_CreateCalendar($vars);
-
-// Execute if the form is valid.
-if ($form->validate($vars)) {
-    try {
-        $calendar = $form->execute();
-        $notification->push(sprintf(_("The calendar \"%s\" has been created."), $vars->get('name')), 'horde.success');
-        Horde::url('calendars/edit.php')
-            ->add('c', $calendar->getName())
-            ->redirect();
-    } catch (Exception $e) {
-        $notification->push($e);
-    }
-}
-
-$injector->getInstance('Horde_Core_Factory_Imple')->create('Kronolith_Ajax_Imple_TagAutoCompleter', array(
-    'id' => 'tags'
-));
-
-$page_output->header(array(
-    'title' => $form->getTitle()
-));
-require KRONOLITH_TEMPLATES . '/javascript_defs.php';
-$notification->notify(array('listeners' => 'status'));
-echo $form->renderActive($form->getRenderer(), $vars, Horde::url('calendars/create.php'), 'post');
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/calendars/delete.php 4.2.27-1/kronolith-4.2.23/calendars/delete.php
--- 4.2.23-1/kronolith-4.2.23/calendars/delete.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/calendars/delete.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,57 +0,0 @@
-<?php
-/**
- * Copyright 2002-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Chuck Hagenbuch <chuck@horde.org>
- */
-
-require_once __DIR__ . '/../lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-// Exit if this isn't an authenticated user.
-$default = Horde::url($prefs->getValue('defaultview') . '.php', true);
-if (!$GLOBALS['registry']->getAuth()) {
-    $default->redirect();
-}
-
-$vars = Horde_Variables::getDefaultVariables();
-$calendar_id = $vars->get('c');
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->setAnchor('calendar:internal|' . $calendar_id)->redirect();
-}
-
-try {
-    $calendar = $injector->getInstance('Kronolith_Shares')->getShare($calendar_id);
-} catch (Exception $e) {
-    $notification->push($e);
-    $default->redirect();
-}
-if ($calendar->get('owner') != $GLOBALS['registry']->getAuth() &&
-    (!is_null($calendar->get('owner')) || !$registry->isAdmin())) {
-    $notification->push(_("You are not allowed to delete this calendar."), 'horde.error');
-    $default->redirect();
-}
-$form = new Kronolith_Form_DeleteCalendar($vars, $calendar);
-
-// Execute if the form is valid (must pass with POST variables only).
-if ($form->validate(new Horde_Variables($_POST))) {
-    try {
-        $form->execute();
-        $notification->push(sprintf(_("The calendar \"%s\" has been deleted."), $calendar->get('name')), 'horde.success');
-    } catch (Exception $e) {
-        $notification->push($e);
-    }
-    $default->redirect();
-}
-
-$page_output->header(array(
-    'title' => $form->getTitle()
-));
-require KRONOLITH_TEMPLATES . '/javascript_defs.php';
-$notification->notify(array('listeners' => 'status'));
-echo $form->renderActive($form->getRenderer(), $vars, Horde::url('calendars/delete.php'), 'post');
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/calendars/edit.php 4.2.27-1/kronolith-4.2.23/calendars/edit.php
--- 4.2.23-1/kronolith-4.2.23/calendars/edit.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/calendars/edit.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,77 +0,0 @@
-<?php
-/**
- * Copyright 2002-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Chuck Hagenbuch <chuck@horde.org>
- */
-
-require_once __DIR__ . '/../lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-$vars = Horde_Variables::getDefaultVariables();
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->setAnchor('calendar:internal|' . $vars->get('c'))->redirect();
-}
-
-// Exit if this isn't an authenticated user.
-$default = Horde::url($prefs->getValue('defaultview') . '.php', true);
-if (!$GLOBALS['registry']->getAuth()) {
-    $default->redirect();
-}
-
-try {
-    $calendar = $injector->getInstance('Kronolith_Shares')->getShare($vars->get('c'));
-} catch (Exception $e) {
-    $notification->push($e, 'horde.error');
-    $default->redirect();
-}
-$owner = $calendar->get('owner') == $GLOBALS['registry']->getAuth() ||
-    (is_null($calendar->get('owner')) && $GLOBALS['registry']->isAdmin());
-if (!$owner &&
-    !$calendar->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::READ)) {
-    $notification->push(_("You are not allowed to see this calendar."), 'horde.error');
-    $default->redirect();
-}
-$form = new Kronolith_Form_EditCalendar($vars, $calendar);
-
-// Execute if the form is valid.
-if ($owner && $form->validate($vars)) {
-    $original_name = $calendar->get('name');
-    try {
-        $form->execute();
-        if ($calendar->get('name') != $original_name) {
-            $notification->push(sprintf(_("The calendar \"%s\" has been renamed to \"%s\"."), $original_name, $calendar->get('name')), 'horde.success');
-        } else {
-            $notification->push(sprintf(_("The calendar \"%s\" has been saved."), $original_name), 'horde.success');
-        }
-    } catch (Exception $e) {
-        $notification->push($e, 'horde.error');
-    }
-    $default->redirect();
-}
-
-$vars->set('name', $calendar->get('name'));
-$vars->set('color', $calendar->get('color'));
-$vars->set('system', is_null($calendar->get('owner')));
-$vars->set('description', $calendar->get('desc'));
-$tagger = Kronolith::getTagger();
-$vars->set('tags', implode(',', array_values($tagger->getTags($calendar->getName(), Kronolith_Tagger::TYPE_CALENDAR))));
-
-$page_output->header(array(
-    'title' => $form->getTitle()
-));
-require KRONOLITH_TEMPLATES . '/javascript_defs.php';
-$notification->notify(array('listeners' => 'status'));
-if ($owner) {
-    $injector->getInstance('Horde_Core_Factory_Imple')->create('Kronolith_Ajax_Imple_TagAutoCompleter', array(
-        'id' => 'tags'
-    ));
-    echo $form->renderActive($form->getRenderer(), $vars, Horde::url('calendars/edit.php'), 'post');
-} else {
-    echo $form->renderInactive($form->getRenderer(), $vars);
-}
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/calendars/remote_edit.php 4.2.27-1/kronolith-4.2.23/calendars/remote_edit.php
--- 4.2.23-1/kronolith-4.2.23/calendars/remote_edit.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/calendars/remote_edit.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,78 +0,0 @@
-<?php
-/**
- * Copyright 2002-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Chuck Hagenbuch <chuck@horde.org>
- */
-
-require_once __DIR__ . '/../lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-$vars = Horde_Variables::getDefaultVariables();
-$url = $vars->get('url');
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->setAnchor('calendar:remote|' . rawurlencode($url))->redirect();
-}
-
-// Exit if this isn't an authenticated user or if the user can't
-// subscribe to remote calendars (remote_cals is locked).
-$default = Horde::url($prefs->getValue('defaultview') . '.php', true);
-if (!$GLOBALS['registry']->getAuth() || $prefs->isLocked('remote_cals')) {
-    $default->redirect();
-}
-
-$remote_calendar = null;
-$remote_calendars = unserialize($GLOBALS['prefs']->getValue('remote_cals'));
-foreach ($remote_calendars as $key => $calendar) {
-    if ($calendar['url'] == $url) {
-        $remote_calendar = $calendar;
-        break;
-    }
-}
-if (is_null($remote_calendar)) {
-    $notification->push(_("The remote calendar was not found."), 'horde.error');
-    $default->redirect();
-}
-$form = new Kronolith_Form_EditRemoteCalendar($vars, $remote_calendar);
-
-// Execute if the form is valid.
-if ($form->validate($vars)) {
-    try {
-        $form->execute();
-        $notification->push(sprintf(_("The calendar \"%s\" has been saved."), $vars->get('name')), 'horde.success');
-    } catch (Exception $e) {
-        $notification->push($e, 'horde.error');
-    }
-    $default->redirect();
-}
-
-$key = $registry->getAuthCredential('password');
-$username = $calendar['user'];
-$password = $calendar['password'];
-if ($key) {
-    $secret = $injector->getInstance('Horde_Secret');
-    $username = $secret->read($key, base64_decode($username));
-    $password = $secret->read($key, base64_decode($password));
-}
-
-$vars->set('name', $calendar['name']);
-$vars->set('url', $calendar['url']);
-if (isset($calendar['desc'])) {
-    $vars->set('desc', $calendar['desc']);
-}
-if (isset($calendar['color'])) {
-    $vars->set('color', $calendar['color']);
-}
-$vars->set('user', $username);
-$vars->set('password', $password);
-$page_output->header(array(
-    'title' => $form->getTitle()
-));
-require KRONOLITH_TEMPLATES . '/javascript_defs.php';
-$notification->notify(array('listeners' => 'status'));
-echo $form->renderActive($form->getRenderer(), $vars, Horde::url('calendars/remote_edit.php'), 'post');
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/calendars/remote_subscribe.php 4.2.27-1/kronolith-4.2.23/calendars/remote_subscribe.php
--- 4.2.23-1/kronolith-4.2.23/calendars/remote_subscribe.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/calendars/remote_subscribe.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,47 +0,0 @@
-<?php
-/**
- * Copyright 2002-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Chuck Hagenbuch <chuck@horde.org>
- */
-
-require_once __DIR__ . '/../lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-$vars = Horde_Variables::getDefaultVariables();
-$url = $vars->get('url');
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->setAnchor('calendar:remote|' . rawurlencode($url))->redirect();
-}
-
-// Exit if this isn't an authenticated user or if the user can't
-// subscribe to remote calendars (remote_cals is locked).
-$default = Horde::url($prefs->getValue('defaultview') . '.php', true);
-if (!$GLOBALS['registry']->getAuth() || $prefs->isLocked('remote_cals')) {
-    $default->redirect();
-}
-
-$form = new Kronolith_Form_SubscribeRemoteCalendar($vars);
-
-// Execute if the form is valid.
-if ($form->validate($vars)) {
-    try {
-        $form->execute();
-        $notification->push(sprintf(_("You have been subscribed to \"%s\" (%s)."), $vars->get('name'), $url), 'horde.success');
-    } catch (Exception $e) {
-        $notification->push($e, 'horde.error');
-    }
-    $default->redirect();
-}
-
-$page_output->header(array(
-    'title' => $form->getTitle()
-));
-require KRONOLITH_TEMPLATES . '/javascript_defs.php';
-$notification->notify(array('listeners' => 'status'));
-echo $form->renderActive($form->getRenderer(), $vars, Horde::url('calendars/remote_subscribe.php'), 'post');
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/calendars/remote_unsubscribe.php 4.2.27-1/kronolith-4.2.23/calendars/remote_unsubscribe.php
--- 4.2.23-1/kronolith-4.2.23/calendars/remote_unsubscribe.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/calendars/remote_unsubscribe.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,61 +0,0 @@
-<?php
-/**
- * Copyright 2002-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Chuck Hagenbuch <chuck@horde.org>
- */
-
-require_once __DIR__ . '/../lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-$vars = Horde_Variables::getDefaultVariables();
-$url = $vars->get('url');
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->setAnchor('calendar:remote|' . rawurlencode($url))->redirect();
-}
-
-// Exit if this isn't an authenticated user or if the user can't
-// subscribe to remote calendars (remote_cals is locked).
-$default = Horde::url($prefs->getValue('defaultview') . '.php', true);
-if (!$GLOBALS['registry']->getAuth() || $prefs->isLocked('remote_cals')) {
-    $default->redirect();
-}
-
-$remote_calendar = null;
-
-$remote_calendars = unserialize($GLOBALS['prefs']->getValue('remote_cals'));
-foreach ($remote_calendars as $key => $calendar) {
-    if ($calendar['url'] == $url) {
-        $remote_calendar = $calendar;
-        break;
-    }
-}
-if (is_null($remote_calendar)) {
-    $notification->push(_("The remote calendar was not found."), 'horde.error');
-    $default->redirect();
-}
-$form = new Kronolith_Form_UnsubscribeRemoteCalendar($vars, $remote_calendar);
-
-// Execute if the form is valid (must pass with POST variables only).
-if ($form->validate(new Horde_Variables($_POST))) {
-    try {
-        $calendar = $form->execute();
-        $notification->push(sprintf(_("You have been unsubscribed from \"%s\" (%s)."), $calendar['name'], $calendar['url']), 'horde.success');
-    } catch (Exception $e) {
-        $notification->push($e, 'horde.error');
-    }
-    $default->redirect();
-}
-
-$vars->set('url', $calendar['url']);
-$page_output->header(array(
-    'title' => $form->getTitle()
-));
-require KRONOLITH_TEMPLATES . '/javascript_defs.php';
-$notification->notify(array('listeners' => 'status'));
-echo $form->renderActive($form->getRenderer(), $vars, Horde::url('calendars/remote_unsubscribe.php'), 'post');
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/calendars/subscribe.php 4.2.27-1/kronolith-4.2.23/calendars/subscribe.php
--- 4.2.23-1/kronolith-4.2.23/calendars/subscribe.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/calendars/subscribe.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,24 +0,0 @@
-<?php
-/**
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Jan Schneider <jan@horde.org>
- */
-
-require_once __DIR__ . '/../lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-$calendar = Horde_Util::getFormData('calendar');
-$url = Horde::url('', true);
-if (Kronolith::showAjaxView()) {
-    $url->setAnchor('calendar:internal|' . $calendar);
-} else {
-    $url->setAnchor('calendar:' . $calendar);
-    if (!in_array($display_calendars, $calendar)) {
-        $url->add('toggle_calendar', $calendar);
-    }
-}
-$url->redirect();
diff -pruN 4.2.23-1/kronolith-4.2.23/config/conf.xml 4.2.27-1/kronolith-4.2.23/config/conf.xml
--- 4.2.23-1/kronolith-4.2.23/config/conf.xml	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/config/conf.xml	1970-01-01 00:00:00.000000000 +0000
@@ -1,147 +0,0 @@
-<?xml version="1.0"?>
-<!-- $Id: 380230c774efc2661b03a58bd71824d28cdc6040 $ -->
-<configuration>
- <configsection name="calendar">
-  <configheader>Calendar Driver Settings</configheader>
-  <configswitch name="driver" desc="What storage driver should we use?">sql
-   <case name="sql" desc="SQL">
-    <configsection name="params">
-     <configsql switchname="driverconfig">
-      <configstring name="table" desc="Database
-       table">kronolith_events</configstring>
-     </configsql>
-     <configboolean name="utc" desc="Store the dates in UTC format?
-     &lt;strong&gt;WARNING: if you are upgrading from Kronolith before version
-     3, you must convert the old events to UTC before activating this. See the
-     documentation in UPGRADING.&lt;/strong&gt;">true</configboolean>
-    </configsection>
-   </case>
-   <case name="kolab" desc="IMAP/Kolab" />
-  </configswitch>
- </configsection>
-
- <configsection name="storage">
-  <configheader>Free/Busy Driver Settings</configheader>
-  <configstring name="default_domain" desc="Default domain to add to attendee
-  email addresses if none is specified." required="false" />
-  <configswitch name="driver" desc="What free/busy driver should we use?">sql
-   <case name="sql" desc="SQL">
-    <configsection name="params">
-     <configsql switchname="driverconfig">
-      <configstring name="table" desc="Database
-       table">kronolith_storage</configstring>
-     </configsql>
-    </configsection>
-   </case>
-   <case name="kolab" desc="Kolab">
-    <configsection name="freebusy">
-     <configenum name="protocol" desc="Protocol to use for accessing the
-     Kolab server's free/busy information">
-      <values>
-       <value>https</value>
-       <value>http</value>
-      </values>
-     </configenum>
-     <configinteger name="port" desc="Port to use for accessing the
-     Kolab server's free/busy information">443</configinteger>
-    </configsection>
-   </case>
-  </configswitch>
- </configsection>
-
- <configsection name="calendars">
-  <configheader>Calendar Handler Settings</configheader>
-  <configswitch name="driver" desc="What type of calendar handler should we use?">default
-   <case name="default" desc="Default" />
-   <case name="kolab" desc="IMAP/Kolab" />
-  </configswitch>
- </configsection>
-
-  <configsection name="resource">
-   <configheader>Resource Handler Settings</configheader>
-   <configswitch name="driver" desc="What type of resource handler should we use?">sql
-    <case name="sql" desc="SQL">
-     <configsection name="params">
-      <configsql switchname="driverconfig">
-       <configstring name="table" desc="Database
-        table">kronolith_resources</configstring>
-      </configsql>
-      <configboolean name="utc" desc="Store the dates in UTC format? If your
-      calendar driver has this option, you should choose the same value here as
-      well.">true</configboolean>
-       </configsection>
-    </case>
-    <case name="false" desc="No Resource Support" />
-   </configswitch>
-  </configsection>
-
- <configsection name="reminder">
-  <configheader>Reminder Settings</configheader>
-  <configstring name="server_name" desc="Server name from which reminder
-   emails should be sent" required="false"/>
-  <configstring name="from_addr" desc="Email address from which reminder
-   emails should be sent" required="false"/>
- </configsection>
-
- <configsection name="autoshare">
-  <configheader>Share Settings</configheader>
-  <configenum name="shareperms" desc="When a new user is created, his
-   default calendar can be automatically shared with his group(s). Which
-   permissions should the group have?">
-   <values>
-    <value desc="None (no sharing)">none</value>
-    <value desc="Read-only">read</value>
-    <value desc="Read and write">edit</value>
-    <value desc="Read, write and delete">full</value>
-   </values>
-  </configenum>
- </configsection>
-
- <configsection name="share">
-  <configboolean name="notify" desc="Notify users and groups per e-mail if
-  they have been granted access to a shared calendar (or tasklist), or are the
-  new owner of the share?">false</configboolean>
- </configsection>
-
- <configsection name="holidays">
-   <configheader>Displaying Holidays</configheader>
-   <configboolean name="enable" desc="Should support for holidays be enabled?"
-   required="false">true</configboolean>
- </configsection>
-
- <configsection name="menu">
-  <configheader>Menu Settings</configheader>
-  <configboolean name="import_export" desc="Should we display an Import/Export
-   link in the Horde application menus?">true</configboolean>
- </configsection>
-
- <configsection name="maps">
-  <configheader>Maps</configheader>
-  <configswitch name="driver" desc="Which driver should we use for inline
-   maps?">
-   <case name="false" desc="No inline map support" />
-   <case name="Horde" desc="Horde driver (support for various map providers)">
-    <configmultienum name="providers" desc="Which layers should we add to the
-    map?">
-     <values>
-       <configspecial application="horde" name="mapsources"/>
-     </values>
-    </configmultienum>
-    <configenum default="false" name="geocoder" desc="Which Geocoder service
-    should we use?">
-     <values>
-       <configspecial application="horde" name="geocoders"/>
-     </values>
-    </configenum>
-    <configenum default="false" name="geodriver" desc="Which driver should we
-    use for storing geolocation information?">
-     <values>
-      <value desc="None" default="true">false</value>
-      <value desc="MySQL spatial extensions">Mysql</value>
-      <value desc="General SQL (no spatial index support)">Sql</value>
-     </values>
-    </configenum>
-   </case>
-  </configswitch>
- </configsection>
-</configuration>
diff -pruN 4.2.23-1/kronolith-4.2.23/config/hooks.php.dist 4.2.27-1/kronolith-4.2.23/config/hooks.php.dist
--- 4.2.23-1/kronolith-4.2.23/config/hooks.php.dist	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/config/hooks.php.dist	1970-01-01 00:00:00.000000000 +0000
@@ -1,35 +0,0 @@
-<?php
-/**
- * Kronolith Hooks configuration file.
- *
- * THE HOOKS PROVIDED IN THIS FILE ARE EXAMPLES ONLY.  DO NOT ENABLE THEM
- * BLINDLY IF YOU DO NOT KNOW WHAT YOU ARE DOING.  YOU HAVE TO CUSTOMIZE THEM
- * TO MATCH YOUR SPECIFIC NEEDS AND SYSTEM ENVIRONMENT.
- *
- * For more information please see the horde/config/hooks.php.dist file.
- *
- * $Id: 9fa067aefa51feab931f784caf652a2cc48c4032 $
- */
-
-class Kronolith_Hooks
-{
-    /**
-     * Adds user-defined AJAX action handlers.
-     *
-     * @param string $action         The AJAX action.
-     * @param Horde_Variables $vars  The URL parameters.
-     *
-     * @return mixed  The data to send to the browser (will be JSON encoded).
-     * @throws Horde_Exception
-     */
-//     public function ajaxaction($action, $vars)
-//     {
-//        switch ($action) {
-//        case 'Foo':
-//            return 'bar';
-//        }
-//
-//        throw new Horde_Exception('Unknown action');
-//     }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/config/.htaccess 4.2.27-1/kronolith-4.2.23/config/.htaccess
--- 4.2.23-1/kronolith-4.2.23/config/.htaccess	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/config/.htaccess	1970-01-01 00:00:00.000000000 +0000
@@ -1,6 +0,0 @@
-<IfModule authz_core_module>
-    Require all denied
-</IfModule>
-<IfModule !authz_core_module>
-    Deny from all
-</IfModule>
diff -pruN 4.2.23-1/kronolith-4.2.23/config/menu.php.dist 4.2.27-1/kronolith-4.2.23/config/menu.php.dist
--- 4.2.23-1/kronolith-4.2.23/config/menu.php.dist	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/config/menu.php.dist	1970-01-01 00:00:00.000000000 +0000
@@ -1,55 +0,0 @@
-<?php
-/**
- * $Id: a618cf79069c41de2254dc3bc6ec9292789a06e3 $
- *
- * This file lets you extend Kronolith's menu with your own items.
- *
- * To add a new menu item, simply add a new entry to the $_menu array.
- * Valid attributes for a new menu item are:
- *
- *  'url'       The URL value for the menu item.
- *  'text'      The text to accompany the menu item.
- *
- * These attributes are optional:
- *
- *  'icon'      The filename of an icon to use for the menu item.
- *  'icon_path' The path to the icon if it doesn't exist in the graphics/
- *              directory.
- *  'target'    The "target" of the link (e.g. '_top', '_blank').
- *  'onclick'   Any JavaScript to execute on the "onclick" event.
- *
- * Here's an example entry:
- *
- *  $_menu[] = array(
- *      'url' =>        'http://www.example.com/',
- *      'text' =>       'Example, Inc.',
- *      'icon' =>       'example.png',
- *      'icon_path' =>  'http://www.example.com/images/',
- *      'target' =>     '_blank',
- *      'onclick' =>    ''
- *  );
- *
- * To add items in a separate container, separated by line separators, use a
- * unique 'container' property for each item to appear in the same container.
- *
- * For example, the following two entries will appear in the same container:
- *
- *  $_menu[] = array(
- *      'url' =>        'http://www.example.com/',
- *      'text' =>       'Example, Inc.',
- *      'icon' =>       'example.png',
- *      'icon_path' =>  'http://www.example.com/images/',
- *      'target' =>     '_blank',
- *      'container' =>  'mylinks'
- *  );
- *  $_menu[] = array(
- *      'url' =>        'http://www.another-example.com/',
- *      'text' =>       'Another Example, Inc.',
- *      'target' =>     '_blank',
- *      'container' =>  'mylinks'
- *  );
- */
-
-$_menu = array();
-
-/* Add your custom entries below this line. */
diff -pruN 4.2.23-1/kronolith-4.2.23/config/prefs.php 4.2.27-1/kronolith-4.2.23/config/prefs.php
--- 4.2.23-1/kronolith-4.2.23/config/prefs.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/config/prefs.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,533 +0,0 @@
-<?php
-/**
- * See horde/config/prefs.php for documentation on the structure of this file.
- *
- * IMPORTANT: DO NOT EDIT THIS FILE! DO NOT COPY prefs.php TO prefs.local.php!
- * Local overrides ONLY MUST be placed in prefs.local.php or prefs.d/.
- * If the 'vhosts' setting has been enabled in Horde's configuration, you can
- * use prefs-servername.php.
- */
-
-$prefGroups['view'] = array(
-    'column' => _("Display Preferences"),
-    'label' => _("User Interface"),
-    'desc' => _("Select confirmation preferences, how to display the different views and choose default view."),
-    'members' => array(
-        'dynamic_view', 'confirm_delete', 'defaultview', 'max_events',
-        'time_between_days', 'week_start_monday', 'day_hour_start',
-        'day_hour_end', 'day_hour_force', 'slots_per_hour', 'show_icons',
-        'show_time', 'show_location', 'show_fb_legend',
-        'show_shared_side_by_side'
-    ),
-);
-
-$prefGroups['share'] = array(
-    'column' => _("Calendars"),
-    'label' => _("Default Calendar"),
-    'desc' => _("Choose your default calendar."),
-    'members' => array('default_share'),
-);
-
-$prefGroups['sync'] = array(
-    'column' => _("Calendars"),
-    'label' => _("Synchronization Preferences"),
-    'desc' => _("Choose the calendars to use for synchronization with external devices."),
-    'members' => array('sync_calendars', 'activesync_no_multiplex', 'activesync_identity'),
-);
-
-$prefGroups['event_options'] = array(
-    'column' => _("Events"),
-    'label' => _("Event Defaults"),
-    'desc' => _("Set default values for new events."),
-    'members' => array('default_alarm_management')
-);
-
-$prefGroups['logintasks'] = array(
-    'column' => _("Events"),
-    'label' => _("Login Tasks"),
-    'desc' => sprintf(_("Customize tasks to run upon logon to %s."), $GLOBALS['registry']->get('name')),
-    'members' => array(
-        'purge_events', 'purge_events_interval', 'purge_events_keep'
-    )
-);
-
-$prefGroups['notification'] = array(
-    'column' => _("Events"),
-    'label' => _("Notifications"),
-    'desc' => _("Choose how you want to be notified about event changes, event alarms and upcoming events."),
-    'members' => array(
-        'event_notification', 'event_notification_exclude_self',
-        'daily_agenda', 'event_reminder', 'event_alarms_select'
-    )
-);
-
-$prefGroups['freebusy'] = array(
-    'column' => _("Calendars"),
-    'label' => _("Free/Busy Information"),
-    'desc' => _("Set your Free/Busy calendars and your own and other users' Free/Busy preferences."),
-    'members' => array('fb_url', 'fb_cals', 'freebusy_days'),
-);
-
-$prefGroups['addressbooks'] = array(
-    'column' => _("Other Preferences"),
-    'label' => _("Address Books"),
-    'desc' => _("Select address book sources for adding and searching for addresses."),
-    'members' => array('display_contact', 'sourceselect'),
-    'suppress' => function() {
-        return !$GLOBALS['registry']->hasMethod('contacts/sources');
-    }
-);
-
-// Show dynamic view?
-$_prefs['dynamic_view'] = array(
-   'value' => 1,
-   'type' => 'checkbox',
-   'desc' => _("Show the dynamic view by default, if the browser supports it?")
-);
-
-// confirm deletion of events which don't recur?
-$_prefs['confirm_delete'] = array(
-    'value' => 1,
-    'type' => 'checkbox',
-    'desc' => _("Confirm deletion of events?")
-);
-
-// default view
-$_prefs['defaultview'] = array(
-    'value' => 'month',
-    'type' => 'enum',
-    'enum' => array(
-        'day' => _("Day"),
-        'week' => _("Week"),
-        'workweek' => _("Work Week"),
-        'month' => _("Month")
-    ),
-    'desc' => _("Select the view to display on startup:")
-);
-
-$_prefs['max_events'] = array(
-    'value' => 3,
-    'type' => 'number',
-    'zero' => true,
-    'desc' => _("How many events should be displayed per day in the month view? Set to 0 to always show all events."),
-);
-
-// Display the timeslots between each day column, in week view.
-$_prefs['time_between_days'] = array(
-    'value' => 0,
-    'type' => 'checkbox',
-    'desc' => _("Show time of day between each day in week views?") . ' (<em>' . _("Basic view only") . '</em>'
-);
-
-// what day does the week start with
-$_prefs['week_start_monday'] = array(
-    'value' => '0',
-    'type' => 'enum',
-    'desc' => _("Select the first weekday:"),
-    'enum' => array(
-        '0' => _("Sunday"),
-        '1' => _("Monday")
-    )
-);
-
-// start of the time range in day/week views:
-// Time array is dynamically built when prefs screen is displayed
-$_prefs['day_hour_start'] = array(
-    'value' => 16,
-    'type' => 'enum',
-    'enum' => array(),
-    'desc' => _("What time should day and week views start, when there are no earlier events?") . ' (<em>' . _("Basic view only") . '</em>',
-    'on_init' => function($ui) {
-        $enum = array();
-        $fmt = $GLOBALS['prefs']->getValue('twentyFour')
-            ? 'G:i'
-            : 'g:ia';
-        for ($i = 0; $i <= 48; ++$i) {
-            $enum[$i] = date($fmt, mktime(0, $i * 30, 0));
-        }
-        $ui->prefs['day_hour_start']['enum'] = $enum;
-    }
-);
-
-// end of the time range in day/week views:
-// Time array is dynamically built when prefs screen is displayed
-$_prefs['day_hour_end'] = array(
-    'value' => 48,
-    'type' => 'enum',
-    'enum' => array(),
-    'desc' => _("What time should day and week views end, when there are no later events?") . ' (<em>' . _("Basic view only") . '</em>',
-    'on_init' => function($ui) {
-        $enum = array();
-        $fmt = $GLOBALS['prefs']->getValue('twentyFour')
-            ? 'G:i'
-            : 'g:ia';
-        for ($i = 0; $i <= 48; ++$i) {
-            $enum[$i] = date($fmt, mktime(0, $i * 30, 0));
-        }
-        $ui->prefs['day_hour_end']['enum'] = $enum;
-    }
-);
-
-// enforce hour slots?
-$_prefs['day_hour_force'] = array(
-    'value' => 0,
-    'type' => 'checkbox',
-    'desc' => _("Restrict day and week views to these time slots, even if there <strong>are</strong> earlier or later events?"),
-);
-
-// number of slots in each hour:
-$_prefs['slots_per_hour'] = array(
-    'value' => 1,
-    'type' => 'enum',
-    'desc' => _("How long should the time slots on the day and week views be?") . ' (<em>' . _("Basic view only") . '</em>',
-    'enum' => array(
-        4 => _("15 minutes"),
-        3 => _("20 minutes"),
-        2 => _("30 minutes"),
-        1 => _("1 hour")
-    ),
-);
-
-// show delete/alarm icons in the calendar view?
-$_prefs['show_icons'] = array(
-    'value' => 1,
-    'type' => 'checkbox',
-    'desc' => _("Show delete, alarm, and recurrence icons in calendar views?") . ' (<em>' . _("Basic view only") . '</em>',
-);
-
-// show event start/end times in the calendar and/or print views?
-$_prefs['show_time'] = array(
-    'value' => 'a:1:{i:0;s:5:"print";}',
-    'type' => 'multienum',
-    'enum' => array(
-        'screen' => _("Month, Week, and Day Views"),
-        'print' => _("Print Views")
-     ),
-    'desc' => _("Choose the views to show event start and end times in:"),
-);
-
-// show event location in the calendar and/or print views?
-$_prefs['show_location'] = array(
-    'value' => 'a:1:{i:0;s:5:"print";}',
-    'type' => 'multienum',
-    'enum' => array(
-        'screen' => _("Month, Week, and Day Views"),
-        'print' => _("Print Views")
-     ),
-    'desc' => _("Choose the views to show event locations in:") . ' (<em>' . _("Basic view only") . '</em>',
-);
-
-// show Free/Busy legend?
-// a value of 0 = no, 1 = yes
-$_prefs['show_fb_legend'] = array(
-    'value' => 1,
-    'type' => 'checkbox',
-    'desc' => _("Show Free/Busy legend?") . ' (<em>' . _("Basic view only") . '</em>',
-);
-
-// collapsed or side by side view
-$_prefs['show_shared_side_by_side'] = array(
-    'value' => 0,
-    'type' => 'checkbox',
-    'desc' => _("Show shared calendars side-by-side?") . ' (<em>' . _("Basic view only") . '</em>',
-);
-
-// default calendar
-// Set locked to true if you don't want users to have multiple calendars.
-$_prefs['default_share'] = array(
-    'value' => '',
-    'type' => 'enum',
-    'enum' => array(),
-    'desc' => _("Your default calendar:"),
-    'on_init' => function($ui) {
-        $enum = array();
-        foreach (Kronolith::listInternalCalendars(false, Horde_Perms::EDIT) as $id => $calendar) {
-            $enum[$id] = Kronolith::getLabel($calendar);
-        }
-        $ui->prefs['default_share']['enum'] = $enum;
-    },
-    'on_change' => function() {
-        $GLOBALS['injector']->getInstance('Kronolith_Factory_Calendars')
-            ->create()
-            ->setDefaultShare($GLOBALS['prefs']->getValue('default_share'));
-        $sync = @unserialize($GLOBALS['prefs']->getValue('sync_calendars'));
-        $haveDefault = false;
-        $default = Kronolith::getDefaultCalendar(Horde_Perms::EDIT);
-        foreach ($sync as $cid) {
-            if ($cid == $default) {
-                $haveDefault = true;
-                break;
-            }
-        }
-        if (!$haveDefault) {
-            $sync[] = $default;
-            $GLOBALS['prefs']->setValue('sync_calendars', serialize($sync));
-        }
-    }
-);
-// Calendars use for synchronization
-$_prefs['sync_calendars'] = array(
-    'value' => 'a:0:{}',
-    'type' => 'multienum',
-    'enum' => array(),
-    'desc' => _("Select the calendars that, in addition to the default, should be used for synchronization with external devices:"),
-    'on_init' => function($ui) {
-        $enum = array();
-        $sync = @unserialize($GLOBALS['prefs']->getValue('sync_calendars'));
-        if (empty($sync)) {
-            $default_calendar = Kronolith::getDefaultCalendar(Horde_Perms::EDIT);
-            $sync_list = !empty($default_calendar)
-                ? array($default_calendar)
-                : array();
-            $GLOBALS['prefs']->setValue('sync_calendars', serialize($sync_list));
-        }
-        foreach (Kronolith::listInternalCalendars(!$GLOBALS['prefs']->getValue('activesync_no_multiplex'), Horde_Perms::DELETE) as $key => $cal) {
-            if ($cal->getName() != Kronolith::getDefaultCalendar(Horde_Perms::DELETE)) {
-                $enum[$key] = Kronolith::getLabel($cal);
-            }
-        }
-        $ui->prefs['sync_calendars']['enum'] = $enum;
-    },
-    'on_change' => function() {
-        $sync = @unserialize($GLOBALS['prefs']->getValue('sync_calendars'));
-        $haveDefault = false;
-        $default = Kronolith::getDefaultCalendar(Horde_Perms::DELETE);
-        foreach ($sync as $cid) {
-            if ($cid == $default) {
-                $haveDefault = true;
-                break;
-            }
-        }
-        if (!$haveDefault && !empty($default)) {
-            $sync[] = $default;
-            $GLOBALS['prefs']->setValue('sync_calendars', serialize($sync));
-        }
-        if ($GLOBALS['conf']['activesync']['enabled'] && !$GLOBALS['prefs']->getValue('activesync_no_multiplex')) {
-            try {
-                $sm = $GLOBALS['injector']->getInstance('Horde_ActiveSyncState');
-                $sm->setLogger($GLOBALS['injector']->getInstance('Horde_Log_Logger'));
-                $devices = $sm->listDevices($GLOBALS['registry']->getAuth());
-                foreach ($devices as $device) {
-                    $sm->removeState(array(
-                        'devId' =>$device['device_id'],
-                        'id' => Horde_Core_ActiveSync_Driver::APPOINTMENTS_FOLDER_UID,
-                        'user' => $GLOBALS['registry']->getAuth()
-                    ));
-                }
-                $GLOBALS['notification']->push(_("All state removed for your ActiveSync devices. They will resynchronize next time they connect to the server."));
-            } catch (Horde_ActiveSync_Exception $e) {
-                $GLOBALS['notification']->push(_("There was an error communicating with the ActiveSync server: %s"), $e->getMessage(), 'horde.error');
-            }
-        }
-    }
-);
-
-// @todo We default to using multiplex since that is the current behavior
-// For Kronolith 5 we should default to separate.
-$_prefs['activesync_no_multiplex'] = array(
-    'type' => 'checkbox',
-    'desc' => _("Support separate calendars?"),
-    'value' => 0);
-
-// Which drivers are we supposed to use to examine holidays?
-$_prefs['holiday_drivers'] = array(
-    'value' => 'a:0:{}'
-);
-
-// store the calendars to diplay
-$_prefs['display_cals'] = array(
-    'value' => 'a:0:{}'
-);
-
-$_prefs['display_resource_cals'] = array(
-    'value' => 'a:0:{}'
-);
-
-// default alarm
-$_prefs['default_alarm'] = array(
-    'value' => ''
-);
-
-$_prefs['default_alarm_management'] = array(
-    'type' => 'special',
-    'handler' => 'Kronolith_Prefs_Special_DefaultAlarm',
-    'requires_nolock' => array('default_alarm')
-);
-
-// remote calendars
-$_prefs['remote_cals'] = array(
-    'value' => 'a:0:{}'
-);
-
-// store the remote calendars to display
-$_prefs['display_remote_cals'] = array(
-    'value' => 'a:0:{}'
-);
-
-// store the external calendars to display
-$_prefs['display_external_cals'] = array(
-    'value' => 'a:0:{}'
-);
-
-// new event notifications
-$_prefs['event_notification'] = array(
-    'value' => '',
-    'type' => 'enum',
-    'enum' => array(
-        '' => _("No"),
-        'owner' => _("On my calendars only"),
-        'show' => _("On all shown calendars"),
-        'read' => _("On all calendars I have explicitly read access to")
-    ),
-    'desc' => _("Choose if you want to be notified of new, edited, and deleted events by email:")
-);
-
-// daily agenda
-$_prefs['daily_agenda'] = array(
-    'value' => '',
-    'type' => 'enum',
-    'enum' => array(
-        '' => _("No"),
-        'owner' => _("On my calendars only"),
-        'show' => _("On all shown calendars"),
-        'read' => _("On all calendars I have read access to")
-    ),
-    'desc' => _("Choose if you want to receive daily agenda email reminders:")
-);
-
-$_prefs['event_notification_exclude_self'] = array(
-    'value' => 0,
-    'type' => 'checkbox',
-    'desc' => _("Don't send me a notification if I've added, changed or deleted the event?")
-);
-
-// reminder notifications
-$_prefs['event_reminder'] = array(
-    'value' => 'owner',
-    'type' => 'enum',
-    'enum' => array(
-        '' => _("No"),
-        'owner' => _("On my calendars only"),
-        'show' => _("On all shown calendars"),
-        'read' => _("On all calendars I have read access to")
-    ),
-    'desc' => _("Choose if you want to receive reminders for events with alarms:")
-);
-
-// alarm methods
-$_prefs['event_alarms_select'] = array(
-    'type' => 'special',
-    'handler' => 'Kronolith_Prefs_Special_EventAlarms',
-    'suppress' => function() {
-        return empty($GLOBALS['conf']['alarms']['driver']);
-    }
-);
-
-$_prefs['event_alarms'] = array(
-    'value' => 'a:1:{s:6:"notify";a:0:{}}'
-);
-
-// number of days to generate Free/Busy information for:
-$_prefs['freebusy_days'] = array(
-    'value' => 30,
-    'type' => 'number',
-    'desc' => _("How many days of Free/Busy information should be generated?")
-);
-
-// By default, display all contacts in the address book when loading
-// the contacts screen.  If your default address book is large and
-// slow to display, you may want to disable and lock this preference.
-$_prefs['display_contact'] = array(
-    'value' => 1,
-    'type' => 'checkbox',
-    'desc' => _("List all contacts when loading the contacts screen? (if disabled, you will only see contacts that you search for explicitly)"),
-);
-
-// address book selection widget
-$_prefs['sourceselect'] = array(
-    'type' => 'special',
-    'handler' => 'Kronolith_Prefs_Special_Sourceselect',
-    'requires_nolock' => array('search_sources')
-);
-
-// Address book(s) to use when expanding addresses
-// Refer to turba/config/sources.php for possible source values
-//
-// You can provide default values this way:
-//   'value' => json_encode(array('source_one', 'source_two'))
-$_prefs['search_sources'] = array(
-    'value' => ''
-);
-
-// Field(s) to use when expanding addresses
-// Refer to turba/config/sources.php for possible source and field values
-//
-// If you want to provide a default value, this field depends on the
-// search_sources preference. For example:
-//   'value' => json_encode(array(
-//       'source_one' => array('field_one', 'field_two'),
-//       'source_two' => array('field_three')
-//   ))
-// will search the fields 'field_one' and 'field_two' in source_one and
-// 'field_three' in source_two.
-$_prefs['search_fields'] = array(
-    'value' => ''
-);
-
-$_prefs['fb_url'] = array(
-    'value' => '<strong>' . _("My Free/Busy URL") . '</strong><div class="fburl"><div>' . _("Copy this URL for use wherever you need your Free/Busy URL:") . '</div><div class="fixed">' . Horde::url('fb.php', true, array('append_session' => -1))->add('u', $GLOBALS['registry']->getAuth()) . '</div></div>',
-    'type' => 'rawhtml'
-);
-
-// Calendars to include in generating Free/Busy URLs.
-$_prefs['fb_cals'] = array(
-    'value' => 'a:0:{}',
-    'type' => 'multienum',
-    'enum' => array(),
-    'desc' => _("Choose the calendars to include in the above Free/Busy URL:"),
-    'on_init' => function($ui) {
-        $enum = array();
-        foreach (Kronolith::listCalendars() as $fb_cal => $cal) {
-            if ($cal->display()) {
-                $enum[htmlspecialchars($fb_cal)] = htmlspecialchars($cal->name());
-            }
-        }
-        $ui->prefs['fb_cals']['enum'] = $enum;
-    },
-    'on_change' => function() {
-        $GLOBALS['injector']->getInstance('Horde_Cache')
-            ->expire('kronolith.fb.u.' . $GLOBALS['registry']->getAuth());
-    }
-);
-
-// Login Tasks preferences
-
-$_prefs['purge_events'] = array(
-    'value' => 0,
-    'type' => 'checkbox',
-    'desc' => _("Purge old events from your calendar?"),
-);
-
-$_prefs['purge_events_interval'] = array(
-    'value' => Horde_LoginTasks::MONTHLY,
-    'type' => 'enum',
-    'enum' => Horde_LoginTasks::getLabels(),
-    'desc' => _("Purge old events how often:"),
-);
-
-$_prefs['purge_events_keep'] = array(
-    'value' => 365,
-    'type' => 'number',
-    'desc' => _("Purge old events older than this amount of days."),
-);
-
-// End Login Tasks preferences
-
-$_prefs['activesync_identity'] = array(
-    'value' => 'horde',
-    'type' => 'enum',
-    'enum' => array_merge($GLOBALS['injector']
-                ->getInstance('Horde_Core_Factory_Identity')
-                ->create($GLOBALS['registry']->getAuth())->getAll('id'), array('horde' => _("Use Horde Default"))),
-    'desc' => _("Choose the identity to use for ActiveSync. This determines the email address used as the ORGANIZER for events you create.")
-);
diff -pruN 4.2.23-1/kronolith-4.2.23/contacts.php 4.2.27-1/kronolith-4.2.23/contacts.php
--- 4.2.23-1/kronolith-4.2.23/contacts.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/contacts.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,78 +0,0 @@
-<?php
-/**
- * Copyright 2002-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- */
-
-require_once __DIR__ . '/lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->redirect();
-}
-
-if (!$GLOBALS['registry']->getAuth()) {
-    echo Horde::wrapInlineScript(array('window.close();'));
-    exit;
-}
-
-/* Get the lists of address books through API */
-$source_list = $registry->call('contacts/sources');
-
-/* If we self-submitted, use that source. Otherwise, choose a good
- * source. */
-$source = Horde_Util::getFormData('source');
-if (empty($source) || !isset($source_list[$source])) {
-    /* We don't just pass the second argument to getFormData() because
-     * we want to trap for invalid sources, not just no source. */
-    $source = key($source_list);
-}
-
-/* Get the search as submitted (defaults to '' which should list everyone). */
-$search = Horde_Util::getFormData('search');
-
-if ($search || $prefs->getValue('display_contact')) {
-    $searchpref = Kronolith::getAddressbookSearchParams();
-    $fields = isset($searchpref[$source])
-        ? array($source => $searchpref[$source])
-        : array();
-
-    try {
-        $results = $registry->call('contacts/search', array($search, array(
-            'fields' => $fields,
-            'sources' => array($source)
-        )));
-    } catch (Exception $e) {
-        $results = array();
-    }
-} else {
-    $results = array();
-}
-
-/* The results list returns an array for each source searched - at least
-   that's how it looks to me. Make it all one array instead. */
-$addresses = array();
-foreach ($results as $r) {
-    $addresses = array_merge($addresses, $r);
-}
-
-/* If self-submitted, preserve the currently selected users encoded by
-   javascript to pass as value|text. */
-$selected_addresses = array();
-$sa = explode('|', Horde_Util::getFormData('sa'));
-for ($i = 0; $i < count($sa) - 1; $i += 2) {
-    $selected_addresses[$sa[$i]] = $sa[$i + 1];
-}
-
-/* Set the default list display (name or email). */
-$display = Horde_Util::getFormData('display', 'name');
-
-/* Display the form. */
-$page_output->header(array(
-    'title' => _("Address Book")
-));
-require KRONOLITH_TEMPLATES . '/javascript_defs.php';
-require KRONOLITH_TEMPLATES . '/contacts/contacts.inc';
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/COPYING 4.2.27-1/kronolith-4.2.23/COPYING
--- 4.2.23-1/kronolith-4.2.23/COPYING	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/COPYING	1970-01-01 00:00:00.000000000 +0000
@@ -1,281 +0,0 @@
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users.  This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it.  (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.)  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
-  To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have.  You must make sure that they, too, receive or can get the
-source code.  And you must show them these terms so they know their
-rights.
-
-  We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
-  Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software.  If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
-  Finally, any free program is threatened constantly by software
-patents.  We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary.  To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                    GNU GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License.  The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language.  (Hereinafter, translation is included without limitation in
-the term "modification".)  Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
-  1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
-  2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) You must cause the modified files to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    b) You must cause any work that you distribute or publish, that in
-    whole or in part contains or is derived from the Program or any
-    part thereof, to be licensed as a whole at no charge to all third
-    parties under the terms of this License.
-
-    c) If the modified program normally reads commands interactively
-    when run, you must cause it, when started running for such
-    interactive use in the most ordinary way, to print or display an
-    announcement including an appropriate copyright notice and a
-    notice that there is no warranty (or else, saying that you provide
-    a warranty) and that users may redistribute the program under
-    these conditions, and telling the user how to view a copy of this
-    License.  (Exception: if the Program itself is interactive but
-    does not normally print such an announcement, your work based on
-    the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
-    a) Accompany it with the complete corresponding machine-readable
-    source code, which must be distributed under the terms of Sections
-    1 and 2 above on a medium customarily used for software interchange; or,
-
-    b) Accompany it with a written offer, valid for at least three
-    years, to give any third party, for a charge no more than your
-    cost of physically performing source distribution, a complete
-    machine-readable copy of the corresponding source code, to be
-    distributed under the terms of Sections 1 and 2 above on a medium
-    customarily used for software interchange; or,
-
-    c) Accompany it with the information you received as to the offer
-    to distribute corresponding source code.  (This alternative is
-    allowed only for noncommercial distribution and only if you
-    received the program in object code or executable form with such
-    an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it.  For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable.  However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License.  Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
-  5. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Program or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
-  6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
-  7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded.  In such case, this License incorporates
-the limitation as if written in the body of this License.
-
-  9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation.  If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
-  10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission.  For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this.  Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
-                            NO WARRANTY
-
-  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
-  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
-                    END OF TERMS AND CONDITIONS
-
diff -pruN 4.2.23-1/kronolith-4.2.23/data.php 4.2.27-1/kronolith-4.2.23/data.php
--- 4.2.23-1/kronolith-4.2.23/data.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/data.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,245 +0,0 @@
-<?php
-/**
- * Copyright 2001-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-
-require_once __DIR__ . '/lib/Application.php';
-$app_ob = Horde_Registry::appInit('kronolith');
-
-if ((Kronolith::showAjaxView() && !(Horde_Util::getPost('import_ajax'))) ||
-    (!$conf['menu']['import_export'])) {
-    Horde::url('', true)->redirect();
-}
-
-/* Importable file types. */
-$file_types = array('csv'       => _("Comma separated values"),
-                    'icalendar' => _("vCalendar/iCalendar"));
-
-/* Templates for the different import steps. */
-$templates = array(
-    Horde_Data::IMPORT_CSV => array($registry->get('templates', 'horde') . '/data/csvinfo.inc'),
-    Horde_Data::IMPORT_MAPPED => array($registry->get('templates', 'horde') . '/data/csvmap.inc'),
-    Horde_Data::IMPORT_DATETIME => array($registry->get('templates', 'horde') . '/data/datemap.inc')
-);
-
-$perms = $GLOBALS['injector']->getInstance('Horde_Core_Perms');
-if ($perms->hasAppPermission('max_events') !== true &&
-    $perms->hasAppPermission('max_events') <= Kronolith::countEvents()) {
-    Horde::permissionDeniedError(
-        'kronolith',
-        'max_events',
-        sprintf(_("You are not allowed to create more than %d events."), $perms->hasAppPermission('max_events'))
-    );
-    $templates[Horde_Data::IMPORT_FILE] = array(KRONOLITH_TEMPLATES . '/data/export.inc');
-} else {
-    $templates[Horde_Data::IMPORT_FILE] = array(KRONOLITH_TEMPLATES . '/data/import.inc', KRONOLITH_TEMPLATES . '/data/export.inc');
-}
-
-/* Initial values. */
-$import_step   = Horde_Util::getFormData('import_step', 0) + 1;
-$actionID      = Horde_Util::getFormData('actionID');
-$next_step     = Horde_Data::IMPORT_FILE;
-$app_fields    = array('title' => _("Title"),
-                       'start_date' => _("Start Date"),
-                       'start_time' => _("Start Time"),
-                       'end_date' => _("End Date"),
-                       'end_time' => _("End Time"),
-                       'alarm' => _("Alarm Span (minutes)"),
-                       'alarm_date' => _("Alarm Date"),
-                       'alarm_time' => _("Alarm Time"),
-                       'description' => _("Description"),
-                       'location' => _("Location"),
-                       'recur_type' => _("Recurrence Type"),
-                       'recur_end_date' => _("Recurrence End Date"),
-                       'recur_interval' => _("Recurrence Interval"),
-                       'recur_data' => _("Recurrence Data"));
-$time_fields   = array('start_date'     => 'date',
-                       'start_time'     => 'time',
-                       'end_date'       => 'date',
-                       'end_time'       => 'time',
-                       'recur_end_date' => 'date');
-$param         = array('time_fields' => $time_fields,
-                       'file_types'  => $file_types);
-$import_format = Horde_Util::getFormData('import_format', '');
-$storage = $injector->getInstance('Horde_Core_Data_Storage');
-
-switch ($actionID) {
-case Horde_Data::IMPORT_FILE:
-    $storage->set('import_cal', Horde_Util::getFormData('importCal'));
-    $storage->set('purge', Horde_Util::getFormData('purge'));
-    break;
-}
-
-if ($import_format) {
-    $data = null;
-    try {
-        $data = $injector->getInstance('Horde_Core_Factory_Data')->create($import_format, array('cleanup' => array($app_ob, 'cleanupData')));
-
-        if ($actionID == Horde_Data::IMPORT_FILE) {
-            $cleanup = true;
-            try {
-                if (!in_array($storage->get('import_cal'), array_keys(Kronolith::listCalendars(Horde_Perms::EDIT)))) {
-                    $notification->push(_("You have specified an invalid calendar or you do not have permission to add events to the selected calendar."), 'horde.error');
-                } else {
-                    $next_step = $data->nextStep($actionID, $param);
-                    $cleanup = false;
-                }
-            } catch (Exception $e) {
-                $notification->push($e, 'horde.error');
-            }
-
-            if ($cleanup) {
-                $next_step = $data->cleanup();
-            }
-        } else {
-            $next_step = $data->nextStep($actionID, $param);
-        }
-    } catch (Exception $e) {
-        if ($data) {
-            $notification->push($e, 'horde.error');
-            $next_step = $data->cleanup();
-        } else {
-            $notification->push(_("This file format is not supported."), 'horde.error');
-            $next_step = Horde_Data::IMPORT_FILE;
-        }
-    }
-}
-
-/* We have a final result set. */
-if (is_array($next_step)) {
-    $events = array();
-    $error = false;
-    $max_events = $perms->hasAppPermission('max_events');
-    if ($max_events !== true) {
-        $num_events = Kronolith::countEvents();
-    }
-    list($type, $calendar) = explode('_', $storage->get('import_cal'), 2);
-    $kronolith_driver = Kronolith::getDriver($type, $calendar);
-
-    if (!count($next_step)) {
-        $notification->push(sprintf(_("The %s file didn't contain any events."),
-                                    $file_types[$storage->get('format')]), 'horde.error');
-        $error = true;
-    } else {
-        /* Purge old calendar if requested. */
-        if ($storage->get('purge')) {
-            try {
-                $kronolith_driver->delete($calendar);
-                $notification->push(_("Calendar successfully purged."), 'horde.success');
-            } catch (Exception $e) {
-                $notification->push(sprintf(_("The calendar could not be purged: %s"), $e->getMessage()), 'horde.error');
-            }
-        }
-    }
-
-    $recurrences = array();
-    $ical = null;
-
-    foreach ($next_step as $row) {
-        if ($max_events !== true && $num_events >= $max_events) {
-            Horde::permissionDeniedError(
-                'kronolith',
-                'max_events',
-                sprintf(_("You are not allowed to create more than %d events."), $perms->hasAppPermission('max_events'))
-            );
-            break;
-        }
-
-        if ($row instanceof Horde_Icalendar_Vevent) {
-            if (!$ical) {
-                $ical = new Horde_Icalendar();
-            }
-            $ical->addComponent($row);
-            if ($max_events !== true) {
-                $num_events++;
-            }
-            continue;
-        }
-
-        try {
-            $event = $kronolith_driver->getEvent();
-        } catch (Exception $e) {
-            $msg = _("Can't create a new event.")
-                . ' ' . sprintf(_("This is what the server said: %s"), $e->getMessage());
-            $notification->push($msg, 'horde.error');
-            $error = true;
-            break;
-        }
-        if ($row instanceof Horde_Icalendar) {
-            // Skip other iCalendar components for now.
-            continue;
-        } else {
-            try {
-                $event->fromHash($row);
-            } catch (Exception $e) {
-                $notification->push($e, 'horde.error');
-                $error = true;
-                break;
-            }
-        }
-
-        try {
-            $event->save();
-        } catch (Exception $e) {
-            $notification->push($e, 'horde.error');
-            $error = true;
-            break;
-        }
-
-        if ($max_events !== true) {
-            $num_events++;
-        }
-    }
-
-    if (!empty($ical)) {
-        $ical_importer = new Kronolith_Icalendar_Handler_Base($ical, $kronolith_driver);
-        try {
-            $ical_importer->process();
-        } catch (Kronolith_Exception $e) {
-            $msg = _("Can't create a new event.")
-                . ' ' . sprintf(_("This is what the server said: %s"), $e->getMessage());
-            $notification->push($msg, 'horde.error');
-            $error = true;
-        }
-    }
-
-    if (!$error) {
-        $notification->push(sprintf(_("%s file successfully imported"),
-                                    $file_types[$storage->get('format')]), 'horde.success');
-    }
-    if (Horde_Util::getFormData('import_ajax')) {
-        $page_output->includeScriptFiles();
-        $page_output->addInlineScript('(function(window){window.KronolithCore.loading--;if(!window.KronolithCore.loading)window.$(\'kronolithLoading\').hide();window.KronolithCore.loadCalendar(\'' . $type . '\', \'' . $calendar . '\');})(window.parent)');
-    }
-    $next_step = $data->cleanup();
-}
-
-if (Horde_Util::getFormData('import_ajax')) {
-    $page_output->addInlineScript('window.parent.$(window.name).remove();');
-    $page_output->outputInlineScript();
-    exit;
-}
-
-$import_calendars = $export_calendars = array();
-if ($GLOBALS['registry']->getAuth()) {
-    $import_calendars = Kronolith::listCalendars(Horde_Perms::EDIT, true);
-}
-$export_calendars = Kronolith::listCalendars(Horde_Perms::READ, true);
-
-$page_output->header(array(
-    'title' => _("Import/Export Calendar")
-));
-require KRONOLITH_TEMPLATES . '/javascript_defs.php';
-$notification->notify(array('listeners' => 'status'));
-
-foreach ($templates[$next_step] as $template) {
-    require $template;
-}
-
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/day.php 4.2.27-1/kronolith-4.2.23/day.php
--- 4.2.23-1/kronolith-4.2.23/day.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/day.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,33 +0,0 @@
-<?php
-/**
- * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Kronolith
- */
-
-require_once __DIR__ . '/lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->setAnchor('day:' . Kronolith::currentDate()->dateString())->redirect();
-}
-
-$view = Kronolith::getView('Day');
-
-$page_output->addScriptFile('tooltips.js', 'horde');
-Kronolith::addCalendarLinks();
-
-$page_output->header(array(
-    'body_class' => $prefs->getValue('show_panel') ? 'rightPanel' : null,
-    'title' => $view->getTime($prefs->getValue('date_format'))
-));
-require KRONOLITH_TEMPLATES . '/javascript_defs.php';
-$notification->notify(array('listeners' => 'status'));
-Kronolith::tabs($view);
-$view->html(KRONOLITH_TEMPLATES);
-require KRONOLITH_TEMPLATES . '/calendar_titles.inc';
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/delete.php 4.2.27-1/kronolith-4.2.23/delete.php
--- 4.2.23-1/kronolith-4.2.23/delete.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/delete.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,107 +0,0 @@
-<?php
-/**
- * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Kronolith
- */
-
-require_once __DIR__ . '/lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->redirect();
-}
-
-$c = Horde_Util::getFormData('calendar');
-$driver = Horde_Util::getFormData('type');
-$kronolith_driver = Kronolith::getDriver($driver, $c);
-if ($eventID = Horde_Util::getFormData('eventID')) {
-    try {
-        $event = $kronolith_driver->getEvent($eventID);
-    } catch(Exception $e) {
-        if ($url = Horde::verifySignedUrl(Horde_Util::getFormData('url'))) {
-            $url = new Horde_Url($url);
-        } else {
-            $url = Horde::url($prefs->getValue('defaultview') . '.php', true);
-        }
-        $url->redirect();
-    }
-    if ($driver != 'resource') {
-        if ($driver == 'remote') {
-            /* The remote server is doing the permission checks for us. */
-            $have_perms = true;
-        } else {
-            $share = $injector->getInstance('Kronolith_Shares')->getShare($event->calendar);
-            if (!$share->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::DELETE, $event->creator)) {
-                $notification->push(_("You do not have permission to delete this event."), 'horde.warning');
-            } else {
-                $have_perms = true;
-            }
-        }
-    } else {
-        if (!$registry->isAdmin()) {
-            $notification->push(_("You do not have permission to delete this event."), 'horde.warning');
-        } else {
-            $have_perms = true;
-        }
-    }
-
-    if (!empty($have_perms)) {
-        $notification_type = Kronolith::ITIP_CANCEL;
-        $instance = null;
-        if (Horde_Util::getFormData('future')) {
-            $recurEnd = new Horde_Date(array('hour' => 0, 'min' => 0, 'sec' => 0,
-                                             'month' => Horde_Util::getFormData('month', date('n')),
-                                             'mday' => Horde_Util::getFormData('mday', date('j')) - 1,
-                                             'year' => Horde_Util::getFormData('year', date('Y'))));
-            if ($event->end->compareDate($recurEnd) > 0) {
-                try {
-                    $kronolith_driver->deleteEvent($event->id);
-                } catch (Exception $e) {
-                    $notification->push($e, 'horde.error');
-                }
-            } else {
-                $event->recurrence->setRecurEnd($recurEnd);
-                $event->save();
-            }
-            $notification_type = Kronolith::ITIP_REQUEST;
-        } elseif (Horde_Util::getFormData('current')) {
-            $event->recurrence->addException(Horde_Util::getFormData('year'),
-                                             Horde_Util::getFormData('month'),
-                                             Horde_Util::getFormData('mday'));
-            $event->save();
-            $instance = new Horde_Date(array('year' => Horde_Util::getFormData('year'),
-                                             'month' => Horde_Util::getFormData('month'),
-                                             'mday' => Horde_Util::getFormData('mday')));
-        }
-
-        if (!$event->recurs() ||
-            Horde_Util::getFormData('all') ||
-            !$event->recurrence->hasActiveRecurrence()) {
-            try {
-                $kronolith_driver->deleteEvent($event->id);
-            } catch (Exception $e) {
-                $notification->push($e, 'horde.error');
-            }
-        }
-
-        if (Horde_Util::getFormData('sendupdates', false)) {
-            Kronolith::sendITipNotifications($event, $notification, $notification_type, $instance);
-        }
-    }
-}
-
-if ($url = Horde::verifySignedUrl(Horde_Util::getFormData('url'))) {
-    $url = new Horde_Url($url, true);
-} else {
-    $date = new Horde_Date(Horde_Util::getFormData('date'));
-    $url = Horde::url($prefs->getValue('defaultview') . '.php', true)
-        ->add('date', Horde_Util::getFormData('date', date('Ymd')));
-}
-
-// Make sure URL is unique.
-$url->unique()->redirect();
diff -pruN 4.2.23-1/kronolith-4.2.23/docs/CHANGES 4.2.27-1/kronolith-4.2.23/docs/CHANGES
--- 4.2.23-1/kronolith-4.2.23/docs/CHANGES	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/docs/CHANGES	1970-01-01 00:00:00.000000000 +0000
@@ -1,1979 +0,0 @@
--------
-v4.2.23
--------
-
-[jan] Officially support PHP 7.
-[jan] Fix time offsets when importing CSV data with two-digit years.
-[jan] Fix updating and deleting events from external CalDAV servers
-      (martin1@k0k.net, Bug #14021).
-[jan] Fix exporting multiple tags to iCalendar CATEGORIES (Bug #14057).
-
-
--------
-v4.2.22
--------
-
-[jan] SECURITY: Fix open redirects.
-[mjr] Prevent broken iCalendar files from causing fatal errors (Bug #14672).
-[jan] Work around calendar servers advertising as CalDAV-capable, but ignoring
-      CalDAV requests (Bug #14662).
-[jan] Fix displaying yesterday's event in Prior Events portal block
-      (admin@layertec.de, Bug #14638).
-
-
--------
-v4.2.21
--------
-
-[mjr] Don't hide the reservee's identity on the resource calendar (Bug #14609).
-
-
--------
-v4.2.20
--------
-
-[mjr] Fix saving sync preference when no default calendar exists (Bug #14585).
-[mjr] Fix display of date in ITIP_CANCEL notifications of deleted instances of
-      recurring events (PR: 214, almarin@um.es).
-
-
--------
-v4.2.19
--------
-
-[mjr] Fix allowing ics import on when user has edit permissions on a shared
-      calendar (Bug #14501).
-[jan] Disallow setting of creator permissions too if sharing with world is
-      disabled.
-[jan] Split shared users by linebreaks too.
-
-
--------
-v4.2.18
--------
-
-[mjr] Fix potential memory issue with really long events (Bug #14453).
-[mjr] Fix setting DELETE permissions (Bug #14447).
-[mjr] More fixes and improvements related to preventing empty date values.
-[jan] Fix losing shared users when adding invalid user name (Bug #14415).
-[mjr] Fix displaying tag search results (Bug #14412).
-[jan] Fix sender of event change notification messages.
-
-
--------
-v4.2.17
--------
-
-[jan] Update Portuguese translation.
-[jan] Update Basque translation.
-[mjr] Fix issue where it is possible to save an event with an empty start or
-      end date on some browsers.
-[mjr] Require Horde_Perms::DELETE for including a calendar as sync-able.
-[mjr] Fix end date when toggling an all day event off (Bug #13979).
-[jan] Fix showing error message if loading of remote calendars failed.
-
-
--------
-v4.2.16
--------
-
-[mjr] Fix updating list of resource calendars in resource group dialog (Bug
-      #14281).
-[jan] Fixed several issues with resource management.
-[jan] Update Greek translation (Limperis Antonis <limperis@cti.gr>).
-
-
--------
-v4.2.15
--------
-
-[mjr] Fix typo preventing deletion of events from dynamic view (Bug #14256).
-[jan] Handle gracefully if the base event of an imported recurrence exception
-      is no longer recurring (Bug #14249).
-
-
--------
-v4.2.14
--------
-
-[mjr] Fix issue deleting and closing event dialog during event deletion.
-[mjr] Honor the SCHEDULE-AGENT attribute during CalDAV import.
-
-
--------
-v4.2.13
--------
-
-[mjr] Add missing EAS ghosted property support for all EAS versions. Prevents
-      potential loss of event data during synchronization.
-
-
--------
-v4.2.12
--------
-
-[mjr] Fix missing truncated event description when using ActiveSync.
-[jan] Fix week number in basic view if week starts on Sundays.
-[mjr] Fix issue where new event could be created with exceptions from
-      previously edited event.
-[jan] Mark preferences only available in basic mode.
-[jan] Use access rules compatible with both Apache 2.2 and 2.4.
-[jan] Fix accidental deletion of events if importing recurring events without a
-      UID attribute (Bug #14208).
-[mjr] Honor confirm_delete preference in dynamic view (Bug #14188).
-[mjr] Correctly deal with cancelled meetings via ActiveSync.
-[mjr] Fix visibility of alarm titles when alarm is generated via CLI (Bug
-      #14154).
-[mjr] Fix display of embed code by adding the full url.
-
-
--------
-v4.2.11
--------
-
-[mjr] Fix issue when replacing an event via the API and the attendee list
-      changes (Bug #14118).
-[mjr] Fix fatal test error related to Content_Tagger.
-
-
--------
-v4.2.10
--------
-
-[mjr] Fix exporting events from Horde_Perms::SHOW calendars over DAV (Bug
-      #14128).
-[mjr] Send CANCEL iTip when attendee is removed from a meeting (Request
-      #14118).
-[mjr] Remove object reference from Content when object is deleted (Bug #14112).
-[mjr] Fix incorrect offset of freebusy times in certain cases (Bug #14084).
-[mjr] Fix handling of email groups as attendees (Bug #14093).
-[mjr] Fix importing text/calendar data via API (Bug #14089).
-
-
-------
-v4.2.9
-------
-
-[mjr] Fix enabling deletion of resources for non-admin users (Bug #14069).
-[mjr] Honor workweek view as the login_view (Bug #14042).
-[mjr] Fix Permission Denied error after removing a currently displayed
-      timeObject (Bug #14022).
-[jan] Enable purge calendar checkbox if user has delete permissions.
-[mjr] Honor locking the remote_cals preference.
-[mjr] Fix detecting duplicate events during import via the API (Bug #14018).
-
-
-------
-v4.2.8
-------
-
-[jan] Don't delete existing exceptions and completions when editing recurring
-      events or tasks.
-
-
-------
-v4.2.7
-------
-
-[jan] Fix importing iCalendar events via the API (Bug #13975).
-
-
-------
-v4.2.6
-------
-
-[jan] Fix building recipient list for event update notifications.
-[mjr] Honor background color for listTimeObjects API calendars (Bug #13951).
-[jan] Always sort users in advanced sharing dialog (Bug #13938).
-[mjr] Fix saving recurring event after a change in resources (Bug #13921).
-[mjr] Fix creating recurrence exceptions from CalDAV clients.
-[jan] Added an index to find base events of recurrence exceptions faster.
-[jan] Fix finding duplicate events by UID.
-[mjr] Fix updating FB data when start date changes.
-[mjr] Fix detecting attendees when email address is not lowercase (Bug #13905).
-[jan] Fix duplicate group names in sharing dialog.
-[jan] Fix JavaScript error when saving an unselected calendar.
-[mjr] Fix using custom SQL configuration parameters.
-
-
-------
-v4.2.5
-------
-
-[mjr] Fix generating calendar embed code (Bug #13828).
-[jan] Don't wipe out whole calendars when PUTing individual events via WebDAV.
-
-
-------
-v4.2.4
-------
-
-[mjr] SECURITY: Don't show private event details in daily agenda emails if not
-      the owner (Bug #13660).
-[jan] Make access to non-CalDAV remote calendars faster (Bug #12379).
-[jan] Continue with further events if parsing of one remote event date fails.
-[jan] Fix JS error in month view with more events today than the maximum
-      threshold.
-[mjr] Fix fatal error when creating or modifying an entry via PUT.
-
-
-------
-v4.2.3
-------
-
-[jan] Export nicer display names for users and resources via WebDAV.
-[jan] Export custom owner and read-only properties via WebDAV.
-[jan] Fix finding modification times of shared calendars via WebDAV.
-[jan] Support time label rotation in week and day views on IE 11.
-[jan] Fix updating of task list view if no tasks are returned from server.
-[jan] Fix ordering of sub-tasks.
-[jan] Send iTip notifications even to attendees that are not required to attend
-      (Bug #13600).
-[jan] Fix notification and alarm issues if alarm is longer than a day (Bug
-      #13584).
-[jan] Fix closure compiler errors (Bug #13593).
-[mjr] Fix checking resource availability when adding multiple resources (Bug
-      #13561).
-
-
-------
-v4.2.2
-------
-
-[mjr] Fix removing resource event when editing the bound event causes the
-      resource to reject the event (Bug #13466).
-[mjr] Fix refreshing resource events in the UI when the host event changes
-      (Bug #13465).
-[mjr] Fix updating the exceptionoriginaldate field when the base event changes
-      (Bug #13512).
-[mjr] Fix moving recurring events with exceptions from one calendar to another
-      (Bug #13524).
-[mjr] Fix honoring resource_management permission (Bug #13049).
-[jan] Fix visual overlapping of certain events (Bug #13376).
-[jan] Fix error editing events if using exotic date formats.
-[jan] Fix JavaScript error if not showing all events in month view (Jasper
-      Olbrich <Jasper.Olbrich@students.uni-marburg.de>, Bug #13433).
-[jan] Fix displayed CalDAV URLs for shared calendars in basic view too
-      (piper@hrz.uni-marburg.de, Bug #12380).
-
-
-------
-v4.2.1
-------
-
-[jan] Fix displayed CalDAV URLs for shared calendars (Bug #12380).
-[jan] Don't show today's events that are over in portal (Bug #13368).
-[mjr] Fix exporting to ics file when event contains exceptions and an explicit
-      timezone (Bug #13369).
-[mjr] Fix importing iCalendar data that contains timezone parameters.
-[mjr] Fix both importing and replacing text/calendar data via the API when
-      recurrence exceptions are present (Bug #13399).
-[mjr] Ensure default share attribute is set when auto determining a default
-      share.
-[mjr] Fix fatal error when calculating alarms on a recurring event that has
-      completed.
-[mjr] Fix catching minical clicks on browsers that return element names in
-      uppercase (Jasper.Olbrich@students.uni-marburg.de, Bug #13370).
-
-
-------
-v4.2.0
-------
-
-[jan] Fix selecting days in the sidebar mini calendar (Bug #13327).
-[mjr] Issue a cancellation iTip when an event's status changes to cancelled
-      (Bug #13321).
-[jan] Fix loading events for intial view in smartphone mode.
-
-
----------
-v4.2.0RC2
----------
-
-[jan] Fix issues with event URL field not updating (Carlos Timóteo
-      <ctimoteo@sapo.pt>).
-
-
----------
-v4.2.0RC1
----------
-
-[jan] Fix error when deleting unselected calendars.
-[jan] Improve DTEND compatibility with other programs using iCalendar.
-[mjr] Fix sending iTip invitations when Save As New is used (Bug #13239).
-[jan] Fix free/busy display with 24-hours-busy days.
-[jan] Fix navigating free/busy dates.
-[jan] Fix hover style in year view (Bug #13057).
-[jan] Update Polish translation (Maciej Uhlig <maciej.uhlig@us.edu.pl>).
-
-
------------
-v4.2.0beta2
------------
-
-[jan] Add synchronization of attendees via CalDAV (Request #13027).
-[mjr] Fix moving an event from one calendar to another while resources are
-      disabled (Bug #13201, Carlos Timóteo <ctimoteo@sapo.pt>)
-
-
------------
-v4.2.0beta1
------------
-
-[mjr] Do not include resources with no email address when synching to EAS
-      clients.
-
-
-------------
-v4.2.0alpha2
-------------
-
-[jan] Fix dependency on Nag (Bug #13166).
-[jan] Use the event's or calendar's owner identity when sending notifications
-      (Bug #13158).
-
-
-------------
-v4.2.0alpha1
-------------
-
-[mjr] Gracefully handle import errors in dynamic interface (Bug #11127).
-[mjr] Show reservee of a resource in resource's event description (Request
-      #13037).
-[mjr] Add resource_management permission (Request #13049).
-[jan] Allow to synchronize shared calendars via CalDAV (Request #12380).
-[jan] Add script to import events from Open-Xchange.
-[mjr] Fix various issues with alarms for recurring events.
-[jan] Add calendar legend to print output.
-[jan] Make SQL backend compatible with Oracle.
-[mjr] Allow for exporting of resource calendars (Request #12372).
-[mjr] Make event url property a true hyperlink in dynamic view (Request
-      #12920).
-[jan] Support task assignees.
-[jan] Support parent tasks.
-[jan] Indent sub tasks.
-[jan] Make shares table compatible with Oracle.
-[mjr] Improve display of Free/Busy information and add pagination (Requests
-      #12899, #12676).
-[mjr] Add ability to export event to a timeslice (Request #11307).
-[mjr] Add API support for supporting multiple calendar sync via ActiveSync.
-[jan] Improve sort order of events in month view (Request #11155).
-
-
-------
-v4.1.6
-------
-
-[jan] Fix history not always returning the last modification time of an event
-      (Bug #13113).
-[jan] Support timezone aliases when importing events (Bug #13100).
-[mjr] Honor the no_sharing configuration option in dynamic view (Bug #13002).
-[mjr] Fix issue that could remove all events from shared calendars if a user
-      with Horde_Perms::EDIT on that calendar is removed via the removeUserData
-      API (Bug #12524).
-[jan] Fix TRIGGER generation if alarm value is negative (Bug #13064).
-[jan] Update Korean translation (Deokgon Kim <dgkim@dgkim.net>).
-
-
-------
-v4.1.5
-------
-
-[mjr] Fix triggering alarms for cancelled events (Bug #12928).
-[jan] Update Danish translation (Erling Preben Hansen <erling@eph.dk>).
-[mjr] Fix exporting recurring events with no recur_end value to CSV.
-[mjr] No longer include AIRSYNCBASEBODY when exporting for ActiveSync if there
-      is no description.
-[mjr] Properly set event status to Cancelled on Resource calendars when the
-      original event status is set to Cancelled.
-[mjr] Fix updating free/busy display when updating event times (Bug #12676).
-[mjr] Fix issue that could cause calendar sync to break when creating an
-      exception on EAS versions < 14.0
-[jan] Fix rounding of start time when dragging events (Bug #11904).
-[mjr] Fix fatal error when searching if Holiday driver is disabled (Bug
-      #12870).
-[jan] Ignore iCalendar alarms without action, and use first, not last alarm
-      (Bug #12865).
-[mjr] Fix incorrect case comparison when synchronizing tags with Kolab (Bug
-      #12770).
-[mjr] Honor recurrence-id range values when deleting recurring events (Bug
-      #12746).
-[mjr] Fix deleting and sending iTip notifications for recurring events (Bug
-      #12746).
-[jan] Fix calculation of work week start if on a weekend.
-[jan] Fix deleting calendars with exceptions to recurring events.
-[jan] Fix fatal error if recurrence exceptions have timezones set (Bug #12801).
-
-
-------
-v4.1.4
-------
-
-[jan] SECURITY: Protect against CSRF attacks on share permissions form (Bug
-      #12804, CVE-2013-6365).
-[jan] SECURITY: Fix XSS vulnerabilities when deleting calendars and resources.
-[jan] Fix edge case that allowed to enter start time after end time (Bug
-      #12752).
-[mjr] Expire fb cache when fb_cals preference changes (Bug #12714).
-[jan] Fix setting DTEND in iCalendar data if event has a timezone (Bug #12693).
-[mjr] Fix importing new ActiveSync events created on the client with
-      recurrence.
-[mjr] Fix some issues with recurring event exceptions due to improper timezone
-      handling (Bug #12630).
-
-
-------
-v4.1.3
-------
-
-[jan] Fix inconsistent handling of all-day events (Bug #12627).
-[mjr] Fix infinite loop for Kolab driver when event has recurrence (Bug #12160,
-      Thomas Jarosch <thomas.jarosch@intra2net.com>).
-[jan] Fix fatal errors if DAV support is disabled (Bug #12481).
-[jan] Fix synchronization of shared events in Kolab driver (Thomas Jarosch
-      <thomas.jarosch@intra2net.com>, Bug #12585).
-[jan] Link to CalDAV help text from calendar dialog.
-[mjr] Fix requesting changes by modification sequence (Bug #12508, Thomas
-      Jarosch <thomas.jarosch@intra2net.com>).
-[mjr] Fix subscribing to internal calendars via subscription URL (Bug #12495).
-
-
-------
-v4.1.2
-------
-
-[jan] Display CalDAV URL of system calendars and tasklists (Bug #12342).
-[jan] Fix system calendar listing via WebDAV.
-[rla] Add system calendar support for Kronolith CalDAV access (Request #12342).
-[mjr] Fix automatically adding organizer as attendee in dynamic view.
-[mjr] Prompt for sending cancellation iTip when deleting an event (Bug #12415).
-[rla] Properly display system-owned calendars when browsing DAV (Bug #12325).
-
-
-------
-v4.1.1
-------
-
-[mjr] Fix setting RECURRENCE-ID field when exporting ics file (Bug #12368).
-[jan] Fix propagating calendar URLs if import/export is turned off.
-[mjr] Add API methods for using history modification sequences.
-
-
-------
-v4.1.0
-------
-
-[jan] Add help text for CalDAV usage.
-
-
----------
-v4.1.0RC1
----------
-
-[jan] Fix editing of events per CalDAV if client sends LAST-MODIFIED attribute
-      (Bug #12244).
-
-
------------
-v4.1.0beta2
------------
-
-[jan] Fix incorrect dependencies.
-
-
------------
-v4.1.0beta1
------------
-
-[jan] Add CalDAV server support (Request #4267).
-[jan] Use Horde_Dav for accessing remote calendars.
-[mjr] Expose Kronolith_FreeBusy::get() via the external API.
-
-
-------
-v4.0.5
-------
-
-[jan] Don't throw error on importing events with unknown timezones (Bug
-      #11688).
-[jan] Don't send notifications for all recurrence exceptions when deleting
-      events.
-[jan] Only send update notifications for local calendars.
-[jan] Re-enable users in drop-down list after revoking permissions (Bug
-      #12000).
-[jan] Fix exporting events of a certain time span (piper@hrz.uni-marburg.de,
-      Bug #12074).
-[mjr] Always populate the meetingstatus property in ActiveSync requests  to
-      avoid issues with some versions of iOS (Bug #12056).
-[mjr] Fix deletion of resource events after deleting resource (Bug #12034).
-
-
-------
-v4.0.4
-------
-
-[jan] Update alarms, resources, and tags when deleting Kolab events (Bug
-      #11988).
-[jan] Update French translation (Paul De Vlieger
-      <paul.de_vlieger@moniut.univ-bpclermont.fr>).
-[jan] Fix some issues with workweek views if first day of week is Sunday (Bug
-      #11973).
-[jan] Fix adding holiday calendars if not using English locale (Bug #11961).
-[jan] More robust date parsing during event importing (Bug #11916).
-[jan] Always export year numbers with four digits to CSV (Request #11917).
-[jan] Fix JavaScript error if Nag is not installed.
-
-
-------
-v4.0.3
-------
-
-[jan] Fix setting default start time in new event dialog of basic view (Bug
-      #11861).
-[jan] Fix saving alarm methods to Kolab backend (Thomas Jarosch
-      <thomas.jarosch@intra2net.com>, Bug #11880).
-[jan] Update Basque translation (Ibon Igartua <ibon.igartua@ehu.es>).
-[jan] Fix moving events between calendars (Bug #11830).
-[jan] Fix fatal error on recurring events with custom timezone (Bug #11587).
-[jan] Fix double owner name on shared calendars.
-[jan] Use default Kolab folder for default calendar preference.
-[jan] Mark Kolab folder as default when changing default calendar preference.
-[jan] Defer navigation in dynamic view until environment has been initialized.
-[jan] Fix receiving resource calendars in dynamic view (Bug #11776).
-
-
-------
-v4.0.2
-------
-
-[jan] Use Kolab modification and creation dates in the UI (Bug #11591).
-[jan] Try to translate holiday drivers (Request #11755).
-[jan] Fix timezone when reading events from Kolab (Bug #11695).
-[jan] Use encoded UIDs as IDs in Kolab driver.
-[jan] Fix saving of events if no calendar has been loaded yet.
-[mjr] Fix importing events from ActiveSync with empty descriptions in EAS
-      version 2.5
-
-
-------
-v4.0.1
-------
-
-[jan] Fix migration of exceptionoriginaldate field (Bug #11654).
-[jan] Fix generating calendar names when exporting non-internal calendars to
-      iCalendar.
-[jan] Fix authentication to remote calendars (Bug #11643).
-[jan] Fix incorrect nesting of VTIMEZONE components when exporting to iCalendar
-      (Bug #11636).
-[jan] Fix fatal error when moving non-recurring events with resources (Bug
-      #11629).
-
-
-------
-v4.0.0
-------
-
-[jan] Update Polish translation (Krzysztof Kozera <krzysztof113@o2.pl>).
-[jan] Update Dutch translation (Arjen de Korte <arjen+horde@de-korte.org>).
-
-
----------
-v4.0.0RC1
----------
-
-[jan] Show completed future tasks in complete category, not in future category
-      (Bug #11562).
-[jan] Fix exporting of recurring events with exceptions from Kolab backends.
-[jan] Fix export links for calendars (Bug #11576).
-[jan] Fix broken Apple iCal behavior with exception dates (Evert Pot
-      <evert@rooftopsolutions.nl>).
-[jan] Use more compatible message format for iTip notifications (Bug #9854).
-[jan] Fix free/busy retrieval on Kolab.
-
-
------------
-v4.0.0beta2
------------
-
-[jan] Synchronize tags with Kolab categories.
-[mjr] Improvements to resource calendar handling.
-[gwr] Mark the initial default share as such with the Kolab backend.
-[gwr] Always mark the initial share as default calendar.
-[jan] Fix attendees button in traditional view.
-[jan] Fix sending alarm emails.
-
-
------------
-v4.0.0beta1
------------
-
-[jan] Add quick task adding to dynamic view.
-[rla] Fix sender of appointment notification (Bug #11198)
-[mjr] Fix tagging in traditional interface.
-[rla] Make system shares browseable in WebDAV (Request #10998).
-[jan] Make calendar lists collapsible (Request #10865).
-[jan] Delay calendar list loading to speed up initial display.
-[mjr] Implement resource scheduling in dynamic view (Request #9774).
-[mjr] Fix issue that could cause incorrect freebusy data to be displayed in
-      certain circumstances.
-[mjr] Prompt to send updates when modifying an event with attendees (Request
-      #10724).
-[mjr] Add heatmap to year view (Request #10658).
-[mjr] Implement proper editing and deleting of recurrence series and exceptions
-      in ajax view (Request #9926).
-[jan] Remove print buttons.
-[jan] Add work week view to dynamic interface (rsalmon@mbpgroup.com, Request
-      #10519).
-[jan] Support importing of iCalendar events with non-local timezones (Request
-      #7156).
-[jan] Include timezone information when exporting events to iCalendar (Request
-      #7156).
-[jan] Allow to set individual timezones for each event (Request #10727).
-
-
------------
-v3.0.18-git
------------
-
-[mms] Fix permissions checking in the 'path_delete' API call.
-[mms] SECURITY: Fix XSS vulnerabilities in the portal blocks.
-[mjr] Fix setting permissions in dynamic mode while $conf[share][world] is
-      false (Bug #11359).
-[mjr] Fix issue causing broken alarm values from being imported from certain
-      ics files.
-[rla] Fix sender of appointment notification (Bug #11198).
-[mjr] Use SSL for map providers if current connection is SSL (Request #11193).
-[jan] Don't show location of private events (Bug #11235).
-[jan] Fix exporting multi-all-day events to iCalendar.
-[jan] Update Turkish translation (İstanbul Technical University).
-[jan] Update Swedish translation (Jakob Alvermark
-      <jakob.alvermark@bsdlabs.com>).
-
-
--------
-v3.0.17
--------
-
-[jan] SECURITY: Fix XSS vulnerabilities in tasks view and search view (Bug
-      #11189).
-[jan] Update Italian translation (Massimo Malabotta <mmalabotta@units.it>).
-[jan] Improve print styles.
-[jan] Catch if external client doesn't send LAST-MODIFIED attributes (Bug
-      #11130).
-[jan] Don't stop agenda script if there is an error with a single user (Bug
-      #11129).
-[jan] Update Hungarian translation (Zoltán Németh <nemeth.zoltan@etit.hu>).
-[jan] Show round corners only on the start and end of multi-day events
-      (Request #11067).
-
-
--------
-v3.0.16
--------
-
-[jan] Use preferred date format in week and agenda views (Bug #11089).
-[jan] Fix fatal error if no external calendars are available (Bug #11079).
-[jan] Fix positioning of events on DST changing dates (Bug #11070).
-[mjr] Fix bug that caused time shift of all day events when imported from
-      ActiveSync devices for users in certain timezones (Bug #10991).
-[mjr] Correctly deal with deleted calendars in sync clients (Bug #10969).
-
-
--------
-v3.0.15
--------
-
-[jan] Search complete names and addresses when auto-completing attendees.
-[jan] Fix syntax error in migration script (Bug #10902).
-
-
--------
-v3.0.14
--------
-
-[mjr] Fix bug that could cause incorrect event exceptions to be created when
-      drag and dropping events in month view.
-[jan] Fix page titles not always updating correctly.
-[jan] Display event time in dynamic view if requested (Request #9866).
-[jan] Only show end time if different from start time.
-[jan] Fix day sorting in agenda view.
-[jan] Sort user and group names in permission screen (Request #10896).
-
-
--------
-v3.0.13
--------
-
-[jan] Hide disabled calendar sections (Bug #9815).
-[gwr] Fixed recurrence handling with the Kolab backend.
-[gwr] Fixed naming of the default calendar for the Kolab backend.
-[gwr] Fixed deleting events with the Kolab backend.
-[gwr] Fixed migrations for SQLite as database backend.
-[mjr] Fix keyboard navigation of time entry fields in dynamic view (Bug
-      #10799).
-[mjr] Fix parsing display_cal variables for internal calendar links on various
-      blocks and embedded imples (Bug #10767).
-[mjr] Fix adding events directly to a resource's calendar (Bug #10827).
-[mjr] Fix regression introduced in 3.0.12 that broke checking a Resource's
-      availability in certain cases (Bug #10806).
-[jan] Don't fail on empty CalDAV calendars (christof@buergi.lugs.ch, Bug
-      #10739).
-[jan] Check permissions when building application drop down
-      (sberthelot@emisfr.com, Bug #10811).
-[gwr] Fix saving Kolab events.
-[jan] Update Japanese translation (Hiromi Kimura <hiromi@tac.tsukuba.ac.jp>).
-
-
--------
-v3.0.12
--------
-
-[jan] Show status of events in agenda messages (Request #10517).
-[mjr] Fix selecting custom time from time selection drop down (Bug #10737).
-[jan] Always use fresh Horde_Http_Client instances for remote calendars (Bug
-      #10740).
-[jan] Add confirmation screen when deleting events in dynamic view (Request
-      #10725).
-[jan] Fix holidays disappearing from month view if deleting another event.
-[jan] Fix resetting attendee response status when saving events in dynamic
-      view (Bug #10620).
-[jan] Support CalDAV servers that don't use DAV: as the default namespace
-      (christof@buergi.lugs.ch, Bug #10716).
-[jan] Allow to view and create copies of holiday events (Bug #10650).
-[jan] Don't show import/export tabs in dynamic view if disabled (Bug #10705).
-[jan] Don't show option to share with everyone in basic permissions if disabled
-      (piper@hrz.uni-marburg.de, Bug #10706).
-[mjr] Only set ORGANIZER field if the event is a group meeting (Bug #10697).
-
-
--------
-v3.0.11
--------
-
-[mjr] Fix alarms for recurring events (Bug #10678).
-[mjr] Allow filtering listTagInfo results by user.
-[jan] Fix uncompleting tasks in dynamic view (Carlos Timóteo, Bug #10653).
-[cjh/jan] Improve design.
-[mjr] Fix displaying resources when no attendees are present (Bug #10507).
-
-
--------
-v3.0.10
--------
-
-[jan] Fix opening events from agenda view.
-[mjr] Always included the current default calendar in the sync_calendars
-      preference.
-[mjr] Ensure exception gets start and end times when created from month view
-      (Bug #10496)
-[jan] Update group permission when changing group down list in basic
-      permissions (Bug #10482).
-[jan] Fix race condition when editing group permission in basic permission
-      screen (Bug #10482).
-[mjr] Fix removeUserData API (Bug #10241).
-[jan] Fix deleting events with a WebDAV client (Bug #10478).
-
-
-------
-v3.0.9
-------
-
-[jan] Fix fatal error while searching if hitting certain recurring events.
-[jan] Fix exporting events with snoozed alarms (Bug #10438).
-
-
-------
-v3.0.8
-------
-
-[jan] Fix deleting resources (Bug #10427)
-[jan] Support snoozing alarms with Sundbird/Lightning (Request #7470).
-[jan] Import VALARM components from iCalendar 2.0 data (Request #6665).
-[jan] Fix exporting all-day events to Funambol clients (Thomas Ilsche
-      <git@zulan.net>, Bug #10349).
-[jan] Fix creating events from dynamic view on Windows (Bug #9916).
-[mjr] Fix displaying of freebusy data in dynamic view (adominguez@cne.gob.ve,
-      Bug #10347).
-
-
-------
-v3.0.7
-------
-
-[mjr] Fix updating attendees (Bug #10411).
-[jan] Fix error if Nag is not installed (Bug #10406).
-
-
-------
-v3.0.6
-------
-
-[jan] Display event time in dynamic agenda/search view.
-[jan] Don't load all shares with requested permissions from the backend if
-      $conf['share']['hidden'] is enabled.
-[jan] Improve exporting all-day events to Funambol clients.
-[mjr] Fix removing an exception from a recurrence series (Bug # 10365).
-[jan] Close session while listing events to speed up parallel loading from
-      different calendars.
-[jan] Share list of displayed task lists with Nag (requires Nag 3.0.3).
-[jan] Don't show import/export link in preferences if using the dynamic view.
-[mjr] Force ActiveSync device reset after sync_calendars pref is changed
-      (Request #10342).
-[jan] Try to work with remote servers even if they don't support a recent
-      WebDAV protocol version.
-[jan] Support updating attendees from vCalendar 1.0 iTip responses.
-[jan] Fix sending invitations to attendees from traditional view.
-[jan] Fix organizer attribute when exporting to vCalendar 1.0.
-[mjr] Fix issue that prevented synching when no sync_calendar pref was set
-      (Bug #10329).
-
-
-------
-v3.0.5
-------
-
-[jan] Update task due date in interface after dragging a task (Bug #10001).
-[jan] Correctly sort events in portal block or widget if only displaying a
-      single calendar (Bug #10099).
-[mjr] Allow syncing of multiple owner-owned calendars (Request #8734).
-[jan] Don't show calendar drop down in event form without delete permissions
-      (Bug #10201).
-[jan] Don't change event creator when saving events.
-[jan] Fetch events immediately after saving remote calendar.
-
-
-------
-v3.0.4
-------
-
-[jan] Fix holidays being off with certain system timezones (Bug #9989).
-[jan] Only highlight days in the mini calendar that are currently displayed
-      (Bug #10048).
-[jan] Fix advanced search with empty search criteria (Bug #10038).
-
-
-------
-v3.0.3
-------
-
-[jan] Add header for timeobjects calendars.
-[jan] Fix date picker in advanced search form (Bug #10039).
-[mjr] Remove visible exceptions when deleting recurring events (Bug #10013).
-[mjr] Fix issue causing listTimeObject calendars to not appear (Bug #10012).
-
-
-------
-v3.0.2
-------
-
-[jan] Don't show event edit links if user doesn't have edit permissions on any
-      calendar.
-[jan] Fix parsing of recurrence end date in dynamic view (Bug #10025).
-[jan] Fix displaying of new tasks in new task lists.
-[jan] Fix displaying colors of task lists.
-[jan] Delete tasks from tasks view when deleting tasks lists.
-[jan] Don't export multi-day events as multiple events to iCalendar data (Bug
-      #10007).
-[jan] Fix dragging tasks in the dynamic view.
-
-
-------
-v3.0.1
-------
-
-[mjr] Fix calendar embedding.
-[jan] Don't store all task lists in the session.
-[jan] Fix new task lists not showing up until next login (Bug #9814).
-[mjr] Fix broken event alarm links in sidebar (Bug #9897).
-[mjr] Fix drag/drop editing of recurring events (Bug #9880).
-[jan] Fix undefined function on Windows (Bug #9916).
-[jan] Support adding and editing of CalDAV events in dynamic view (Bug #9865).
-[mjr] Allow migrations to complete despite of missing shares.
-[jan] Remove debug code in Ajax library (Bug #9818).
-
-
-----
-v3.0
-----
-
-[jan] Fix application-specific permission checks (Bug #9786).
-[jan] Fix fatal error when using the max_events permission (Bug #9784).
-[mjr] Datatree share to SQL upgrade script refactored for Horde 4.
-[mjr] Add migration for mysql specific geospatial fields (Bug #9758).
-[jan] Set default alarm in dynamic view (Bug #9752).
-[jan] Move all executable scripts to bin/ and prefix with kronolith-.
-
-
---------
-v3.0-RC2
---------
-
-[jan] Fix javascript error when viewing events with desktop notifications
-      (Bug #9727).
-
-
---------
-v3.0-RC1
---------
-
-[jan] Update installation and upgrade instructions.
-[jan] Fix custom event alarm settings in traditional view (Bug #9154).
-
-
-----------
-v3.0-BETA1
-----------
-
-[jan] Only load external events in IFRAME if configured.
-[jan] Fix retrieving event UIDs without date limit (Simon Bühler, Bug #9651).
-[jan] Fix saving display status of other applications' calendars (Bug #9636).
-[mjr] Add migration for converting existing event categories to tags.
-
-
------------
-v3.0-ALPHA1
------------
-
-[jan] Add configuration whether to load other applications in an IFRAME.
-[jan] Provide default configuration files instead of .dist versions.
-[jan] Send a subscription link with the notification about shared
-      calendars if necessary.
-[jan] Default calendars no longer have the user name as the ID.
-[jan] Create a default calendar if the user doesn't own any yet.
-[jan] Add CalDAV client support (Request #8525).
-[jan] Send agenda emails with HTML part and convert to Horde_View.
-[mjr] More complete handling of recurring event exceptions when dealing with the
-      iCalendar format (Request #9091).
-[jan] Add remote calendars to the possible list of calendars for free/busy
-      information.
-[jan] Send alarm and iTip notifications with HTML part and convert to
-      Horde_View.
-[jan] Add option to notify users about calendar permission changes.
-[mjr] listTimeObject calendars are now viewable independently in the calendar
-      portal blocks (Request #8530).
-[mjr] Recurring event exceptions are now indicated as such in the UI.
-[mjr] Deleting a recurring event now also deletes all exceptions to that event.
-[mjr] Add support for inline maps to the Ajax interface.
-[jan] Add preference to limit the events per day in the month view.
-[jan] Add system calendars (Request #2059).
-[jan] Add URL field to events.
-[jan] Add task management capabilities to the Ajax interface.
-[jan] Manage holidays like any other calendar driver.
-[mjr] Add resource scheduling.
-[jan] Integrate tasks into Ajax interface (Gonçalo Queirós
-      <mail@goncaloqueiros.net>).
-[mjr] Extend listTimeObjects API to include optional links and icons.
-[jan] Allow searching of any type of calendar and improve searching of
-      recurring events.
-[cjh] With only SHOW permissions, display event titles as "busy".
-[mjr] Replace categories and keywords by tags.
-[jan] Set colors per calendar (Request #7480).
-[jan] Add individual notification methods for single events (Alfonso Marín
-      Marín <almarin@um.es>).
-[jan] Add Ajax interface.
-[jan] Store events in UTC and convert to the user's timezone on the fly.
-[jan] Remove alarm reminder code.
-[jan] Change listEvents API method to return keys as dates instead of
-      timestamps.
-
-
-------
-v2.3.6
-------
-
-[jan] Add upgrade scripts for next-generation SQL share driver.
-[jan] Export recurrence exceptions in a more portable way.
-
-
-------
-v2.3.5
-------
-
-[gwr] Fix moving events between calendars for the Kolab driver (Bug #7932)
-[jan] Import and export the privacy field in CSV data (Request #9139).
-[mjr] Send an iTip REQUEST, not ADD, when updating an existing event
-      (Bug #9131).
-[jan] Fix all-day events sometimes showing up an day early too (Bug #9012).
-[mjr] Sort exception list before displaying (Request #7688).
-[mjr] Do not remove history entries when removing user data (Bug #8755).
-
-
-------
-v2.3.4
-------
-
-[jan] Delete Horde alarms when resetting an event alarm.
-[jan] Set an alarm to one minute, if users try to set an alarm without time.
-[gwr] Fix editing the start date of recurring events (kolab/issue3885).
-
-
-------
-v2.3.3
-------
-
-[jan] Fix editing URLs of remote calendars.
-[gwr] Fix recurring weekly events with fixed number of occurences but multiple
-      incidences per week (kolab/issue3846).
-[gwr] Fix recurring weekly events with intervals > 1 (Bug #8546).
-[jan] Optimize event link generation (patrick.abiven@apitech.fr).
-[jan] Fix Oracle SQL scripts.
-[jan] Fix charset of ajax responses.
-[jan] Add Croatian translation (Matej Vela <matej.vela@carnet.hr>).
-[jan] Speed up listing alarms (patrick.abiven@apitech.fr, Request #8638).
-[jan] Simplify and improve portability of listing events without date limits
-      in the SQL driver (Bug #8590).
-[mjr] Fix issue that caused the default alarm time to be applied to
-      listTimeObjects events (Bug #8588).
-
-
-------
-v2.3.2
-------
-
-[mms] Upgrade prototype.js to v1.6.1.
-[jan] Allow to add address lists as attendees through the address book popup
-      (Bug #7834).
-[jan] Fix exporting certain weekday recurrence rules to iCalendar
-      (lst_hoe02@kwsoft.de, Bug #8501).
-[jan] Export recurrence end dates as date-time values in iCalendar data to
-      improve compatibility with external clients (Bug #7846).
-[jan] Fix synchronization with output compression enabled (Bug #7769).
-[jan] Add configuration whether to show user drop down list or text field in
-      permissions dialog (requires Horde 3.3.5).
-[jan] Propagate the 'modified' API method (Request #8274).
-[cjh] Fix the signature of the kronolith/import API method
-      (rpolli@babel.it, Bug #8309).
-[jan] Correctly create all-day events if using the all-day link in the day
-      view (stpierre@nebrwesleyan.edu, Bug #8220).
-[jan] Fix rendering of all-day events (Bug #8176).
-
-
-------
-v2.3.1
-------
-
-[jan] Render contact autocompletion list on top of free/busy blocks (Bug #7273).
-[jan] Save recurrence exceptions when copying events (Bug #7689).
-[jan] Prevent warnings if trying to add address lists as attendees (Bug #7834).
-[jan] Fix start and end time calculation of multi-day events (Bug #7788).
-[jan] Wrap URL in calendar information in IE7 (almarin@um.es, Bug #8043).
-[jan] Fix "Date-string has wrong format" error with holidays (Bug #7961).
-[jan] Use improved translation support in Date_Holidays (requires 0.21.0 now,
-      Bug #7272).
-[jan] Empty view cache when changing calendar selection
-      (stpierre@nebrwesleyan.edu, Bug #7879).
-[mjr] Fix category CSS being included on embedded widgets.
-[cjh] Work around BC break with Horde versions before 3.2 (Bug #7820).
-[jan] Allow to manually change attendee responses (paul@carnie.com.au,
-      Request #7443).
-[jan] Fix linking to last week across certain year boundaries (Bug #7827).
-[jan] Add script to import events from SquirrelMail database.
-[cjh] Add private-***.png graphics to Tango and Silver themes (Bug #7531).
-[cjh] Fix listing of users in the calendar/browse API call.
-[jan] Set timezone per user preference and fix recurring events when sending
-      reminders through Kronolith (develop@kristov.de, Bug #7038).
-[cjh] Add a PostgreSQL-specific upgrade script for 2.2 to 2.3
-      (michael.menge@zdv.uni-tuebingen.de, Bug #7454).
-[mms] Upgrade prototype.js to v1.6.0.3.
-
-
-----
-v2.3
-----
-
-[jan] Change group field in shares table to work with LDAP groups (Bug #6883).
-[jan] Fix user name conversion with user hooks in the calendar panel
-      (Bug #7366).
-[gwr] Fixed handling of free events in the Kolab driver
-      (noethen@uni-paderborn.de, Bug #7192).
-[jan] Differentiate between days with events and days with busy events in the
-      year view.
-
-
---------
-v2.3-RC1
---------
-
-[jan] Send correct iTip notifications when deleting recurring event instances
-      (Bug #6636).
-[mjr] Add support for embedding calendar widgets on external websites.
-[jan] Log moving of events in the history backend (Bug #3207).
-[jan] Fix listing alarms of recurring events crossing day boundaries
-      (Bug #7035).
-[jan] Fix deleting all events over WebDAV (Bug #7005).
-[jan] Add Atom feeds.
-[jan] Fix encoding of holiday names.
-[jan] Add Estonian translation (Alar Sing <alar.sing@err.ee>).
-[jan] Consider RECURRENCE-ID attributes in iCalendar data.
-[cjh] Fix a problem with the put() API call and events that already exist
-      (almarin@um.es, Bug #7088).
-[jan] Fix importing iCalendar events with only a single recurrence exception
-      (guyzdm@gmail.com, Bug #7068).
-[jan] Add Basque translation (Euskal Herriko Unibertsitatea EHU/UPV
-      <xabier.arrieta@ehu.es>).
-[mjr] Add support for maintenance tasks and add a task for purging old events.
-[jan] Fix future events not being received under certain circumstances
-      (Bugs #6946, #6966).
-[jan] Correctly sort recurring events (develop@kristov.de, Bug #7037).
-[jan] Fix display of recurring events crossing midnight in the portal block
-      (develop@kristov.de, Bug #7036).
-[jan] Fix agenda emails containing wrong events.
-[mjr] Remove user permissions on all shares when deleting a user.
-[jan] Fix warning when importing events through the API (Bug #6865).
-[cjh] Be case insensitive when comparing attendees
-      (aloyse.gilbert@gmail.com, Bug #6833).
-[jan] Use smaller icons for private events.
-
-
-----
-v2.2
-----
-
-[jan] Identify the correct event when using attend.php to update invitations
-      (Bug #6606).
-[cjh] Improve resource usage in datatree_to_sql share migration script
-      (Bug #6740).
-[jan] Decode user names and passwords when editing remote calendars.
-[cjh] Ensure that event_category is always a string (Bug #6696).
-
-
---------
-v2.2-RC3
---------
-
-[cjh] Apply fix for http://dev.rubyonrails.org/ticket/11473 to prototype.js
-      (Request #6590).
-[cjh] In WebDAV PUT operations, only look for existing events on the calendar
-      being uploaded (Bug #6617).
-[cjh] Add an upgrade script for the new SQL share driver (Request #6109).
-[cjh] Use json_encode and json_decode if they are available (Bug #6457).
-[cjh] Use the first matching free/busy URL if more than one is found
-      (almarin@um.es, Request #6638).
-[cjh] Allow subscribing/unsubscribing from external calendars through the API
-      (duck@obala.net, Request #6626).
-[jan] Add another directory level for calendar owners to the WebDAV interface.
-[jan] Cut off categories at 80 characters to save them correctly (Bug #5211).
-[gwr] Fixed removal of recurrence in the Kolab driver.
-[jan] Show full title in email alarms (Bug #6510).
-[jan] Add export of single events.
-[gwr] Fixed History support in the Kolab driver.
-[cjh] Don't report cancelled events as busy (m.gabriel@das-netzwerkteam.de,
-      Bug #6376).
-[cjh] Show who created or made the last change to an event along with the date
-      (Request #6305).
-[cjh] Make sure event time and location are shown in the day and week views
-      (Bug #6158).
-[cjh] Include Location and Attendee information in the plain-text portion
-      of iTip messages (almarin@um.es, Request #6271).
-[jan] Add Turkish translation (METU <horde-tr@metu.edu.tr>).
-
-
---------
-v2.2-RC2
---------
-
-[jan] SECURITY: Fix privilege escalation in Horde API.
-[cjh] SECURITY: Fix missing ownership validation on share changes.
-[cjh] Fix month links in the year view when the first day of the month is on
-      the first day of the week (hofsteda@gmail.com, Bug #6172).
-[cjh] Make sure calendar info boxes show the full remote URL (Bug #6134).
-[jan] Fix importing of events with an existing UID (Bug #6122).
-[cjh] Fix timestamps in portal block event links.
-[mjr] Fix issue that caused recurring events on 2/29 to appear on 3/1 when
-      recurrence type is HORDE_DATE_RECUR_YEARLY_DATE.
-[cjh] If JavaScript is available, open the Attendees window set to the current
-      event's date (Request #5813).
-[cjh] Fix display of events that end at 00:00 at a non-zero second (Bug #6047).
-[cjh] Fix handling of local names in attendees.php
-      (m.gabriel@das-netzwerkteam.de, Bug #6041).
-[cjh] Fix inline calendar search in the calendar panel.
-[cjh] New share management UI that doesn't require JavaScript.
-
-
---------
-v2.2-RC1
---------
-
-[jan] Don't allow to modify private events throught the API (Bug #5736).
-[jan] Don't overwrite event owner when changing events over WebDAV (Bug #5736).
-[jan] Implement WebDAV DELETE.
-[cjh] Event exceptions are now correctly reflected when subscribing to
-      Kronolith calendars from Apple iCal.
-[cjh] When adding attendees, parse email groups and add each member
-      individually (Bug #5812).
-[cjh] External events are now enabled/disabled by individual category, such
-      as showing a particular tasklist on your calendar, but not all tasks
-      (Request #4581).
-[jan] Add SQL upgrade script.
-[cjh] Let the "show" URL of a calendar always show that calendar, instead of
-      toggling whether or not it is displayed.
-[cjh] Fix permissions granted to remote calendar subscribers on calendars
-      that do not have guest permissions.
-[cjh] Fix generation of UIDs with PHP 5.2+.
-[cjh] Add preferences for showing event times and locations in screen
-      views, print views, both, or neither (Request #1982).
-[cjh] Run attendee email address through MIME::encodeAddress() before
-      validating them (Bug #5670).
-
-
-----------
-v2.2-ALPHA
-----------
-
-[cjh] Add vCalendar 2.0 alarm export (munzli@olmero.ch, Bug #4851).
-[jan] Add Ukrainian translation (Andriy Kopystyansky <anri@polynet.lviv.ua>).
-[cjh] Inline PHP-generated javascript to avoid cross-domain leaking
-      (Bug #5307).
-[cjh] Add a preference to not send notifications to the person who is
-      adding/updating/deleting the event (steven.deboeck@excentis.com,
-      Request #4770).
-[cjh] Add calendar/subscribe and calendar/unsubscribe methods for adding/
-      removing calendars to the currently displayed calendars
-      (duck@obala.net).
-[cjh] Remove unneeded Kronolith_Driver::close method.
-[cjh] Fix error handling when moving events
-      (thomas.jarosch@intra2net.com, Bug #5401).
-[jan] Show icon for private events (Request #5190).
-[jan] Allow to edit existing event attendees.
-[jan] Add links to delete exceptions from recurring events.
-[jan] Add recurrence types by day of year and weekday of year.
-[jan] Add recurrence durations by number of recurrences.
-[jan] Allow to edit exceptions from recurring events.
-[jan] Allow to specify attendees without email addresses.
-[jan] Allow to specify attendee names with the email addresses.
-[cjh] Link to remote URLs in iCalendar events, if present (Duck).
-[jan] Hide less important event settings in the edit form by default.
-[jan] Add support for the Horde_Alarm framework (requires Horde 3.2).
-[cjh] Add ContactAutoCompleter javascript to the attendees screen
-      (Request #1635).
-[jan] Add month tab to free/busy view (Gunnar Wrobel <wrobel@pardus.de>,
-      Request #4896).
-[cjh] Use extended Free/Busy information from Kolab (wrobel@pardus.de,
-      Request #4869).
-[ben] Add daily agenda reminder (Request #4655).
-[cjh] Include event location in reminders (webmgr@muskingum.edu,
-      Request #4771).
-[cjh] Use global proxy configuration for HTTP requests (Request #1039).
-[jan] Display holidays generated with the Date_Holiday package (Stephan
-      Hohmann <webmaster@dasourcerer.net>, Request #3862).
-[cjh] Consider failing to find any attendees to update an error
-      (Bug #4588).
-[cjh] Include a summary of which calendars were printed in print views
-      (Request #3263).
-[jan] Add support for reading and saving events through external clients
-      (requires Horde 3.2, d.bussink@student.utwente.nl, Request #3032).
-[jan] Limit number of events in portal summary block (Duck <duck@obala.net>).
-[cjh] Add a live search to the calendar panel (Request #4607).
-[mas] Conform to WCAG 1.0 Priority 2/Section 508 accessibility guidelines.
-      (Request #4080)
-[cjh] Fix Day and Week display of events that overlap with an event that
-      doesn't start all the way to the left (Bug #2967).
-[cjh] Move calendar selection/deselection to a collapsible panel.
-[jan] Allow to set user name and password for remote calendars
-      (tevans@tachometry.com, Request #3696).
-[jan] Allow to edit remote calendars (tevans@tachometry.com, Request #3696).
-[cjh] Move almost all hiding of elements during printing to CSS classes.
-[cjh] Allow the listEvents api call to list events from multiple calendars,
-      and filter by whether or not events have an alarm
-      (Ben Klang <ben@alkaloid.net>, Bug #4104).
-[mjr] Support for removing user data from backend when user is removed from
-      Horde.
-[jan] Add private flag.
-[cjh] Add a default alarm preference.
-[cjh] Always include X-WR-CALNAME in iCalendar exports (Request #3618).
-[jan] Add multidomain support for Kolab servers (tokoe@kde.org, Request #3579).
-[jan] Allow delegation of events.
-[cjh] Dynamic switching between Display, Edit, and Delete screens.
-[jan] Add preference to force day and week views to selected hours.
-[cjh] Make the duration input a text field instead of a 365-day dropdown.
-[mdj] Add support for split read/write database.
-[mdj] Add failover support for SQL backend.
-[cjh] Use partial page fetches to update the main "page" div if the browser
-      supports the necessary JavaScript.
-
-
-------
-v2.1.7
-------
-
-[jan] SECURITY: Fix privilege escalation in Horde API.
-[cjh] SECURITY: Fix missing ownership validation on share changes.
-[jan] Set the user's timezone in the free/busy information.
-[jan] Fix redirection after editing an event from the sidebar menu (Bug #5769).
-
-
-------
-v2.1.6
-------
-
-[cjh] Display attendees list correctly without a mail/compose method (Bug
-      #5127).
-[cjh] Send iCalendar data as UTF-8 (Bug #5427).
-[jan] Fix duration of remote and imported full-day events (Bugs #4617, #4794).
-[jan] Fix recurring multiday events crossing a month border (Bug #5205).
-[jan] Fix duration calculation in edit form for events covering more than a
-      month (Bug #5120).
-
-
-------
-v2.1.5
-------
-
-[jan] Fix character set encoding of exported vCalendar/iCalendar data.
-[jan] Fixed MS-SQL driver (Bug #5088).
-[jan] Add Catalan translation (Jordi Giralt <projecte.k2@upcnet.es>).
-[jan] Fix searching for non-ascii strings (Bug #4773).
-[cjh] Display partial hours in free/busy view (cbs@cts.ucla.edu,
-      Request #4755).
-[cjh] Honor date_format preference in reminders and notifications (Bug #4726).
-
-
-------
-v2.1.4
-------
-
-[cjh] SECURITY: Close arbitrary file inclusion in free/busy views.
-
-
-------
-v2.1.3
-------
-
-[cjh] Set vEvent ORGANIZER to the event creator, not the calendar owner
-      (kajtzu@basen.net, Bug #4527).
-[jan] Fix importing of events without end date and duration (Bug #4519).
-[jan] Correctly display recurring events spanning multiple days (Bug #4438).
-[jan] Fix recurrence end dates with SQLite backends (Bug #4219).
-[jan] Fix calculation of recurrence ends with imported and remote events
-      (Bug #2813).
-[cjh] Consolidate the check for whether or not users can add events in
-      the day and month views (Bug #4373).
-[jan] Fix alarms for recurring events in Kolab driver
-      (michael.sheldon@credativ.de, Bug #4326).
-[jan] Show error message if imported file didn't contain events.
-[cjh] Fix the SQL types of several recurrence fields.
-[jan] Add categories from imported events to the user's categories.
-[jan] Add Slovenian translation (Duck <duck@obala.net>).
-[jan] Deal with attendee email addresses case insensitively.
-[jan] Don't consider events from remote calendars as busy time.
-[jan] Always try to return the correct event instance if requesting an event
-      by its UID (Thorsten Schaub, Bug #1994).
-[cjh] Fix permissions for the calendar list in advanced search (Bug #4093).
-
-
-------
-v2.1.2
-------
-
-[jan] Don't show calendar creation form if multiple calendars are not allowed
-      (Bug #3988).
-[jan] Add "Save and Finish" button to attendees popup (Request #1158).
-[jan] Add parameter to Upcoming Events portal block to limit number of
-      displayed events (dorm@dorm.org, Request #3905).
-[jan] Allow adding of events if not using a permanent DataTree backend.
-[jan] Fix fatal error for new users on some sytems (Bug #3711).
-
-
-------
-v2.1.1
-------
-
-[jan] Fix updating attendee status from KOrganizer replies.
-[jan] Fix events of status "free" being included in busy information.
-[jan] Automatically add current user's free/busy information to meeting
-      planner.
-[jan] Add 1.1 to 2.x upgrade script for Oracle.
-[cjh] Prevent listAlarms() from causing a fatal error if no calendar is
-      open (Bug #3717).
-[jan] Update free/busy information on Kolab server after editing events
-      (requires Horde 3.1.1) (tokoe@kde.org, Request #3654).
-[cjh] Honor time format and language preferences in email notifications
-      (Bug #2064).
-[cjh] Add missing display_contact preference (Bug #3575).
-[cjh] Fix fatal error during first login of new users.
-
-
-----
-v2.1
-----
-
-[cjh] There are now several guards against users being stuck with no selected
-      calendars and no way to select one (Bug #3538).
-
-
---------
-v2.1-RC3
---------
-
-[jan] Fix retrieving of free/busy URLs from Kolab servers (tokoe@kde.org,
-      Bug #3450).
-[jan] Fix recurring events without end dates using the Kolab driver
-      (tokoe@kde.org, Bug #2734).
-[jan] Allow to specify port and protocol of Kolab server for free/busy request
-      (aseigo@kde.org, tokoe@kde.org, Request #2178).
-[jan] Improve attendees and contacts forms.
-
-
---------
-v2.1-RC2
---------
-
-[jan] Show subscription URL for calendars in "My Calendars" overview
-      (kevin_myer@iu13.org, Request #3256).
-[jan] Fix browser redirection after saving events.
-
-
---------
-v2.1-RC1
---------
-
-[cjh] The calendar(s) to export data from are now configurable (Request #2330).
-[jan] Add Portuguese translation (Manuel Menezes de Sequeira
-      <Manuel.Sequeira>).
-[cjh] Use HTTP_Request to retrieve Free/Busy information so we don't
-      rely on allow_url_fopen.
-[cjh] Remove disabled PDB import code.
-[cjh] Fix display when only remote calendars are active (Bug #2777).
-[cjh] Fix alarms for recurring events.
-[ben] Add RSVP to iTip so Outlook will send a reply message when the
-      invitation is accepted/declined/etc.
-[jan] Allow automatic sharing of new calendars with users' groups
-      (gauret@free.fr, Request #1366).
-[jan] Add preference to only retrieve event reminders for certain calendars
-      (kevin_myer@iu13.org, Request #649).
-[jan] Always return to last screen after editing or deleting events.
-[jan] Allow to freely set event years.
-[jan] Add event status of "Free".
-[jan] Add CLI script to import iCalendar/vCalendar data.
-[cjh] Deprecate the MCAL driver.
-[mas] Allow replacing a calendar with an imported one. (Bug #1364)
-[mas] Change any output of <b> and <i> tags to <strong> and <em> for better
-      accessibility support.
-[cjh] Use CSS for all category colors instead of generating them inline.
-[cjh] Use Horde_Template for day and week view rows.
-[cjh] The length of time slots in the day and week views is now configurable
-      (Bug #1952).
-[cjh] Add a tree block for showing current alarms.
-[jan] Add permissions to restrict number of events.
-[cjh] Show event owners in tooltips.
-[cjh] Fix assumptions about users being logged in when fetching free/busy
-      information (tasin (at) fhm (dot) edu, Bug #1850).
-[jan] Add calendar popup window to side-by-side view to search for calendars
-      and toggle their visibility.
-[jan] Add icon to calendar names to remove calendars from side-by-side view.
-[cjh] fb.php now accepts ?u= arguments as well as ?c=, for displaying a user's
-      chosen free/busy calendars, allowing users control of which calendars
-      block out free/busy time without changing the free/busy URL
-      (kevin_myer@iu13.org, Bug #1683).
-[cjh] Add exists() method to calendar drivers for checking if events exist by
-      UID (Roel Gloudemans <roel@gloudemans.info>).
-[jan] Send links in iTip requests to let users quickly reply to an invitation.
-[cjh] Add ics.php (also mirrored by an exportCalendar() API method), which can
-      be used for integration with Sunbird, iCal, et. al. (srrafa@usc.es,
-      Bug #917).
-[jan] Add preference to send email notifications to users when events have
-      been added, edited, or deleted in their calendars.
-[cjh] Add a Year view (mbydalek@mobilemini.com) (Bug #1238).
-[jan] Use OPTGROUPs in calendar drop down list.
-
-
-------
-v2.0.7
-------
-
-[cjh] SECURITY: Close arbitrary file inclusion in free/busy views.
-
-
-------
-v2.0.6
-------
-
-[cjh] Close several XSS problems with calendar and event fields.
-
-
-------
-v2.0.5
-------
-
-[jan] Fix warnings with Internet Explorer on HTTPS connections.
-[jan] Fix reminder emails.
-
-
-------
-v2.0.4
-------
-
-[jan] Allow to import more than one event from iCalendar data at once.
-[cjh] Avoid loops and other errors by immediately failing nextRecurrence()
-      checking for any event with a recurInterval of 0 (Bug #2709).
-
-
-----------
-v2.0.4-RC1
-----------
-
-[jan] Fix navigation popup with Safari browsers (t.zell@gmx.de, Bug #2447).
-[cjh] Fix infinite loop with some recurring events (Bug #2346).
-[cjh] Fix All Past and All Future searches (Bug #2257).
-[cjh] Be smarter about enforcing "end before beginning" check for
-      am/pm users when creating an event than spans noon (Bug #1731).
-[jan] Sort search results by start date and show category colors.
-[cjh] Respect twenty_four preference in reminders script (Bug #1896).
-
-
-------
-v2.0.3
-------
-
-[jan] Respect SMTP authentication settings when sending reminders and
-      notifications (requires Horde 3.0.4).
-[jan] Include date in reminder message (Bug #1855).
-[jan] Fix time grids in free/busy view (Bugs #1557, #1868, tasin@fhm.edu).
-
-
-----------
-v2.0.3-RC1
-----------
-
-[jan] Fix browsing through work weeks in attendees view (Bug #1852,
-      tasin@fhm.edu).
-[jan] Fix updating of attendee status in attendees view (Bug #1188).
-[jan] Don't show tasks without due dates in all timezones (Bug #514).
-[jan] Include attendees in iTip event invitations (Bug #1676).
-[jan] Fix generated HTML if no calendar has been selected.
-[cjh] Add a PostgreSQL upgrade script (Bug #1778).
-[cjh] Show the calendar selection dropdown if there are remote calendars
-      regardless of number of shares (Bug #1640).
-[cjh] Separate buttons on attendees screen (kevin_myer@iu13.org, Bug #1635).
-[cjh] Use bind variables in the SQL drivers (selsky@columbia.edu, Bugs #1669,
-      #1670).
-[jan] Send reminders in users' languages (Bug #1654).
-[cjh] Don't leave events in the database with no recurrences left (Bug #1503).
-[cjh] Make sure that remote events can be edited for saving as new, and never
-      show a delete button for them (Bug #1639).
-[cjh] Always use eventID, not eventIndex. Fixes problems with only one remote
-      event showing up per day (Bug #1630).
-[jan] Fix free/busy generation on broken PHP CGI SAPIs (Bug #1599).
-[cjh] Set recurrence end date if present in iCal data (Bug #1365, #1582).
-[cjh] Alarms that are a calendar day or more in the future are now sent on
-      time (Bug #1356)
-[jan] Catch errors if importing incomplete events (Bug #1429).
-[cjh] Fix display errors with overlapping repeating events and PHP4
-      (Bug #1200).
-[ben] Allow setting the default domain on attendee email addresses when
-      checking for freebusy urls.
-[cjh] Treat webcal:// URLs as http:// for remote calendars (Bug #1480).
-[cjh] Fix problems with PostgreSQL and non-ISO date formats (Bug #1482).
-[jan] Workaround IE warning when entering Kronolith over HTTPS (Bug #910).
-[jan] Add shortcut icon (favicon.ico).
-[jan] Sort whole day events alphabetically (Bug #222).
-
-
-------
-v2.0.2
-------
-
-[jan] Fix portal block for missing events at the turn of the year (Bug #1337).
-
-
-----------
-v2.0.2-RC1
-----------
-
-[jan] Fix free/busy views with some languages (Bug #1036).
-[cjh] Prevent infinite loops if events are set to recur weekly on no
-      weekdays (Bug #1074).
-[cjh] Set iCalendar events with weekly recurrence to recur on the day
-      of the week of the first recurrence if no BYDAY data is present
-      (Bug #1074).
-[cjh] Make sure that event UIDs are properly set in iTip notifications
-      (Bug #1153).
-[jan] Add Japanese translation (Hiromi Kimura <hiromi@tac.tsukuba.ac.jp>).
-[cjh] Check the completion status of tasks, not just their due date,
-      before showing them on the calendar (Bug #947).
-
-
-------
-v2.0.1
-------
-
-[cjh] Fix one page load delay in showing new/removing deleted shares
-      on the My Calendars screen (Bug #1048).
-[cjh] Fix labeling of weekday columns in the Month view when the
-      week starts on Monday and show_shared_side_by_side preference
-      is on (Bug #1049).
-[cjh] Fix fatal error when in calendar/import API call
-      (Sebastián Calero <scalero@datadec.es>).
-[cjh] Fix a week boundary problem that took you to the previous
-      week when switching between the week and work week views.
-[cjh] Fix several errors in calculating the week number at the
-      rollover between years.
-[cjh] Fix an off-by-one error in the work week view when the user
-      views weeks starts on Sunday (Bug #1046).
-[cjh] Add PostgreSQL version of 2004-12-21_add_event_uid (Bug #1028).
-[cjh] Fix typo that prevented attendees from being saved (Bug #1019).
-
-
-----
-v2.0
-----
-
-[cjh] UIDs need to be stored in the History system with a kronolith:
-      prefix and with the calendar so as not to confuse different
-      instances of the same event (if two users both have a event on
-      their seperate calendars, they should have unique histories for
-      that event).
-[cjh] The My Calendars page now has a tool for generating Free/Busy URLs
-      (Bug #571).
-[cjh] Pass Horde_Date objects instead of timestamps when exporting to
-      iCalendar (Bug #950).
-[cjh] Clone events before changing their start dates to the current day
-      in the day view so that the week view isn't wrong.
-[cjh] Fix an off-by-one error in the week view when the user views weeks
-      starting on Sunday.
-[cjh] Add a preference for whether or not to show the background color
-      for tasks shown on the calendar.
-[cjh] Don't produce invalid exception dates when exporting iCal files or
-      when loading SQL events.
-[cjh] Users can now get to the category/color edit screen directly
-      from the category legend, instead of having to find them in
-      Horde's prefs.
-
-
---------
-v2.0-RC3
---------
-
-[cjh] Add All Past as an option on simple searches.
-[cjh] Add My Calendars menu entry.
-[cjh] Event exceptions are now correctly exported in iCalendar files.
-[cjh] Fix recurring events that recur on the same day of the month every n
-      months where n > 1 (Bug #907).
-[cjh] Fix month view for months starting on Sunday with the first displayed
-      day set to Monday (Bug #907).
-[cjh] Show event state by CSS styling (Derek P. Moore <derekm@hackunix.org>).
-[jan] Correctly highlight the current view's tab (Bug #896).
-
-
---------
-v2.0-RC2
---------
-
-[jan] Fix recurring events from remote calendars (Bug #559).
-[cjh] The menubar Goto popup is now always on top of <select> boxes,
-      even in Internet Explorer (Bug #822).
-[cjh] Menubar Goto link works on the Options screen again.
-
-
---------
-v2.0-RC1
---------
-
-[cjh] Fix the menubar popup date navigator on the New Event screen.
-[cjh] Remove unused specialized notification listener.
-[cjh] Update icons.
-[jan] Fix importing of non-recurring events.
-[jan] Save categories of imported events.
-[cjh] Show different calendar views in tabs, not in the menu.
-[cjh] Show free/busy view only in the event screen.
-
-
----------
-v2.0-BETA
----------
-
-[cjh] The guts of Kronolith_Event objects are now based on Horde_Date
-      objects instead of timestamps.
-[cjh] Lots of date-related functions moved to the Horde_Date class.
-[jan] Add preference for address books to search for free/busy urls.
-[jan] Add special black-on-white styles for message printing.
-[cjh] Add title field to the basic search.
-[cjh] Allow searches for "All Future" events.
-[cjh] Add search functionality (Meilof <meilof@gmail.com>).
-[jan] Add access keys.
-[cjh] Add Kolab drivers (Stuart Bingë <s.binge@codefusion.co.za>).
-
-
-----------
-v2.0-ALPHA
-----------
-
-[cjh] Remote calendars are now parsed with the Horde_iCalendar library,
-      which allows for much better iCalendar support, better handling of
-      timezones, etc.
-[cjh] The month view no longer shows blank boxes for days in the grid that are
-      outside of the current month - instead, it goes back into the previous
-      month and forward into the next month however many days are showable.
-[cjh] Kronolith now includes the functionality, previously in Moment,
-      to track attendees for events, send invitations, updates, and
-      cancellations, and do scheduling based on freebusy information
-      (Stuart Binge <s.binge@codefusion.co.za>).
-[cjh] Kronolith_Driver::move() added to prevent event IDs changing when moving
-      between calendars, if possible.
-[cjh] GUIDs now only contain kronolith: and the event ID - sharename is not
-      needed.
-[cjh] Event IDs are now 32-character unique strings, to be useable as GUIDs.
-      The SQL table definition has changed; conversion scripts are in scripts/.
-[cjh] eventd now uses the Horde_Scheduler API's ability to serialize and
-      save state; it's now meant to be run out of cron every minute (no
-      more slow memory munching).
-[cjh] Show start and end times in event tooltips
-      (Payton <payton@repligen.com>).
-[cjh] Fix events that recur every n months (where n != 1) on the same
-      weekday when using the SQL driver.
-[cjh] Track addition, modification, and deletion of events
-      with the Horde History:: API.
-[cjh] The event editor now switches the list of categories to match the
-      calendar that you are adding the event to.
-[cjh] Show tooltips with event descriptions.
-[cjh] Show a legend mapping categories to colors
-      (Jason Rust <jrust@rustyparts.com>).
-[mir] listEvents now supports an option to NOT do recurrence (useful for
-      synchronizing with other systems).
-[cjh] First shot at a reminder daemon, written in PHP and using the
-      Horde_Scheduler framework.
-[cjh] Cache free/busy information for an hour.
-[cjh] Add viewing of shared calendars side-by-side in the month view
-      (j.huinink@wanadoo.nl).
-[cjh] Kronolith now generates free/busy information for any calendar that the
-      viewer has PERMS_SHOW permission to. This can be used in, e.g., Outlook
-      for scheduling meetings, etc.
-[cjh] Support for guest calendars.
-[cjh] Use the global shares editing page for changing/assigning share
-      permissions.
-[mac] Add support for subscribing to remote iCalendar files.
-[cjh] Remove the Share header for now; takes up far too much space
-      to communicate limited information.
-[mac] Add ability to display multiple calendars at a time.
-[cjh] Palm DateBook (PDB) import support.
-[mac] Add shared calendars support (Joel Vandal <joel@scopserv.com>).
-[cjh] Kronolith can now send iCalendar invitations to an event.
-[cjh] Import/Export of iCalendar data works for basic attributes now.
-[cjh] Use Horde::compressOutput() for output compression.
-
-
-------
-v1.1.4
-------
-
-[jan] Close XSS when setting the parent frame's page title by javascript (cjh).
-
-
-------
-v1.1.3
-------
-
-[jan] Fix display of category colors in week and day views (Björn Tackmann
-      <bjoern.tackmann@consultico.de>).
-[jan] Add Latvian translation (Ivars Stivrins <ivars@kraslava.lv>).
-[jan] Fix erroneous javascript in recurrence input fields of the event form
-      (Bugs 403, 408).
-
-
-------
-v1.1.2
-------
-
-[cjh] Fix a bug in the SQL driver preventing events from being displayed.
-
-
-------
-v1.1.1
-------
-
-[jan] Fix some display bugs with recurring events in the sql driver (cjh).
-
-
-----------
-v1.1.1-RC1
-----------
-
-[jan] Fix broken image tag in day and week views.
-[jan] Fix "New Category" selection in event edit view.
-[jan] Add Arabic (Syria) translation (Platinum Development Team
-      <devteam@platinum-sy.net>).
-
-
-----
-v1.1
-----
-
-[cjh] Kronolith_Event objects now know how to obtain their driver,
-      rather than storing a reference. Makes for much smaller/better
-      return values over XML-RPC, at least.
-[cjh] Fix a bug in the spanning algorithm when there were two
-      independant sets of overlapping events that were not common
-      factors (i.e, a set of 2 events and a set of 3 events). Also
-      start to document the row spanning code.
-[mir] Implement week of month recurrence for SQL driver.
-[cjh] Use Kronolith::listEvents() everywhere, so there is only one body
-      of code to debug/add to for listing of events.
-[cjh] Show Nag tasks, if requested, on all views.
-[cjh] Use Kronolith::listEvents() in the week views; should be much
-      more efficient (fewer SQL queries/MCAL calls).
-[cjh] Fix summary display of events after an empty day.
-[cjh] Fix links to events on other calendars in the summary.
-[cjh] Show category colors in the Kronolith summary.
-[cjh] Show current category color in category color options screen
-      (Jan Kuipers <jrkuipers@lauwerscollege.nl>).
-[jan] Add Romanian translation (Eugen Hoanca <eugenh@urban-grafx.ro>,
-      Marius Dragulescu <mariusd@urban-grafx.ro>).
-[jan] Add Print button for day, week, month views (mac).
-[jan] Clean up url creation and XHTML (mac).
-[jan] Add Norwegian Bokmaal translation (Torstein S. Hansen <huleboer@techbee.no>).
-[jan] Add Kronolith::addParameter().
-[jan] Add Lithuanian translation (Darius Matuliauskas <darius@lnk.lt>).
-[mac] Clean up url creation and XHTML.
-[jan] Add Simplified Chinese translation (Zhang Bo <boozhang@sdb.ac.cn>).
-[jan] Add Bulgarian translation (Miroslav Pendev <miro@cybershade.us>).
-[jan] Replace <?= with <?php echo, and remove the short_open_tags requirement.
-[jan] Add delete link to event view.
-[jan] Remove deprecated DB::isWarning() calls.
-
-
-----
-v1.0
-----
-
-[jan] Add Hungarian translation (Laszlo L. Tornoci <torlasz@xenia.sote.hu>).
-[jan] Add Korean translation (J.I Kim <aporie@netian.com>).
-[jan] Add Danish translation (Bill Edgington <horde@0x20.com>).
-[jan] Add Norwegian Nynorsk translation (Per-Stian Vatne <psv@orsta.org>).
-[jan] Add event view page and make it the standard if clicking on an event
-      link (Brandon Knitter <knitterb@blandsite.org>).
-[jan] Show event description in link tooltip.
-[jan] Add Event::getLink().
-[cjh] Track when events are modified.
-[cjh] Add exceptions to recurring events.
-[cjh] Allow same deletion options as Palm for recurring events.
-[cjh] Add a preference for confirming event deletion.
-[jan] Add Slovak translation (Leo Mrafko <leo@oel.sk>).
-[jan] Add "Save as new" button (Michael Cochrane <mike@graftonhall.co.nz>).
-[cjh] Close a potential problem with register_globals On and $js_onLoad.
-[jan] Add Finnish translation (Leena Heino <liinu@uta.fi>).
-[cjh] Add new event link to the summary (Quinn Wilson <qwilson@midworld.org>).
-[cjh] Use the new PrefsUI class.
-[cjh] Show events that extend past the end hour of the day view.
-[jon] Adapt to the new Horde::img() syntax.
-[cjh] Switch output compression to ob_gzhandler().
-[cjh] Fix column widths in week view to be consistent
-      (Jeff Graves <jeff@image-src.com>).
-[jan] Add Polish translation (Maciek Uhlig <muhlig@us.edu.pl>).
-[cjh] Add preferences for the hour range shown in the day/week views.
-[cjh] Use KRONOLITH_TEMPLATES constant for all template paths.
-[cjh] Use $registry->get() for all Registry information.
-[cjh] Use the new Notification system.
-[jan] Add Swedish translation (Andreas Dahlén <andreas@dahlen.ws>).
-[jon] Enable the "portability" option in the SQL driver.
-[jan] Use the new notification framework. This breaks compatibility with
-      Horde 2.0.
-[jan] Remove the standard value for the language preference. The language to
-      fall back to should be set Horde wide in lang.php instead.
-[jan] Change '24hr' preference to 'twentyFour' to be ldap compliant.
-
-
-------
-v0.0.3
-------
-
-[bjn] Change 'en' and 'en_EN' locales to 'en_US' (default).
-[jan] Add Brazilian Portuguese translation (Samuel Mota
-      <samuel@bumerangue.com>).
-[jan] Add DHTML date navigator.
-[jan] Add Greek translation (Stefanos I. Dimitriou <sdimitri@teiath.gr>).
-[jan] Add Russian translation (Anton Nekhoroshih <anton@valuehost.ru>).
-[jan] Recode the html display functions and add extra row for all day events.
-[cjh] Let the Registry handle retrieving preferences.
-[jan] Add icon for recurring events.
-[jan] Add preference to show due tasks from Nag in the calendar.
-[jan] Add import/export capability.
-[jan] Add Italian translation (Giovanni Meneghetti <gmeneghetti@infvic.it>).
-[cjh] Add Traditional Chinese translation (David Chang <david@thbuo.gov.tw>).
-[jan] Add support for timezones
-[jan] Put new-event-link into week header instead around times in week view.
-[jan] Add day of month into week headers
-[cjh] Add Czech translation (pchytil@asp.ogi.edu).
-[jan] The menu buttons now open the day/week/month view corresponding to the
-      actual view. New 'Today' button.
-[jan] Make sure that events before 8am are shown correctly.
-[cjh] Add an initial MySQL driver (Luc Saillard <luc+kronolith@alcove.fr>).
-[avsm] Replace $conf['paths'] with the $registry equivalents.
-[jan] Let the week alternatively start with Sunday. ISO conform week number
-      calculation.
-[cjh] Prevent all day events from causing the day to start displaying at 12am.
-[cjh] Add $conf['menu']['apps'] support and a help link.
-[jan] Let the users alternatively select end time or duration of an event.
-[jan] Add support for alarms.
-[cjh] Add a preference to use hour increments instead of half-hour (Jan
-      Schneider <janmailing@gmx.de>).
-[cjh] Bold in progress events in the summary and don't show events that are
-      over (Jan Schneider <janmailing@gmx.de>).
-[cjh] Add delete links on event edit pages, and make sure delete and cancel
-      work without javascript (Jan Schneider <janmailing@gmx.de>).
-[cjh] Add translation framework.
-[cjh] Make sure events are sorted by time in the summary.
-[cjh] Use the *url() functions more consistently to make sure that
-      cookie-less sessions work.
-
-
-------
-v0.0.2
-------
-
-[cjh] Allow all-day events and montly-on-weekday recurrence.
-[cjh] Add a Horde summary API function.
-[cjh] Use 24hr time preference (Jan Schneider <janmailing@gmx.de>).
-[cjh] Added Preferences framework into Kronolith.
-[jon] Merge doctype.inc into common-header.inc.
-[jon] Allow the registry to handle Kronolith's configuration values.
-[jon] HORDE_BASE is now defined in lib/base.php instead of config/conf.php.
-[cjh] Define the HORDE_BASE constant in config/conf.php, and use it when
-      referring to any of Horde's files.
-[jon] Retired config/menu.txt in favor of config/menu.php.  This file follows
-      a new format based on native PHP data structures.
-[cjh] Add an optional keywords section, and a keywords definition file.
-[cjh] Add a location field to events.
-[cjh] Add the ability to re-label the category field, and to make it a
-      system-defined list of options.
-[cjh] Added Kronolith::weeksInYear($year) and Kronolith_Day::isToday().
-[cjh] Add week and workweek (mon-fri) views.
-[cjh] Allow events to recur every other week, every 3 days, etc.
-[cjh] Reorganize the event adding/editing screen and add javascript to give
-      the user sane defaults.
-[cjh] Remove year.php; it's ugly and not really useful.
-[cjh] Open the calender in base.php.
-[cjh] Remove most of the remaining mcal-dependent code.
-[cjh] Kronolith now uses Horde authentication instead of its own. This means
-      that whatever auth method you are using for Horde will be used to
-      authenticate to Kronolith, and in return you get single sign-on with any
-      other apps using Horde authentication. This also means that Kronolith
-      is completely independent of MCAL for authentication now; if you have
-      one MCAL user, that's enough to provide calendars for all Horde users.
-[cjh] Add lib/base.php to include all globally needed conf files and
-      libraries, set up the language, etc.
-[cjh] Abstract the event creation/edit code into the
-      Kronolith_Driver/Kronolith_Event classes.
-[cjh] Changes to work with register_globals = Off. (IN PROGRESS)
-[cjh] Add an optional "get the time this event would start on day x" feature
-      to the Kronolith_Event:: class, allowing status.php to work with
-      recurring events.
-[cjh] We now have the Kronolith_Event abstraction class that abstracts away
-      details of events, so that the frontend scripts aren't dependant on
-      mcal.
-[cjh] Display events which are in progress, just starting, or start in under
-      two hours along with other messages just under the menu.
-[cjh] Much slicker UI.
-[cjh] Use mcal_popen instead of mcal_open - might give us a performance gain.
-
-
-------
-v0.0.1
-------
-
-[cjh] Don't display delete links in month view; slimmer UI.
-[jon] Silence session_start warnings.
-[jon] Use Horde::url() in place of the deprecated buildURL() function.
-[cjh] Update to work with new slimmed down/Horde:: integrated session code.
-[cjh] Use the new css.php script to generate our stylesheets.
-[cjh] Revising the look and feel to use the new stylesheets and conventions.
-[cjh] Added initial CHANGES file and documentation.
diff -pruN 4.2.23-1/kronolith-4.2.23/docs/CREDITS 4.2.27-1/kronolith-4.2.23/docs/CREDITS
--- 4.2.23-1/kronolith-4.2.23/docs/CREDITS	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/docs/CREDITS	1970-01-01 00:00:00.000000000 +0000
@@ -1,110 +0,0 @@
-==================
- Acknowledgements
-==================
-
-Development of the Ajax interface sponsored by Portugal Telecom's SAPO division
-(http://softwarelivre.sapo.pt/).
-
-
-============================
- Kronolith Development Team
-============================
-
-
-Core Developers
-===============
-
-- Chuck Hagenbuch <chuck@horde.org>
-- Jan Schneider <jan@horde.org>
-- Michael J Rubinsky <mrubinsk@horde.org>
-
-Localization
-============
-
-=====================   ======================================================
-Arabic (Syria)          Platinum Development Team <devteam@platinum-sy.net>
-Basque                  Edurne <edurne@uzei.com>
-                        Ibon Igartua <ibon.igartua@ehu.eus>
-Brazilian Portuguese    Samuel Mota <samuel@bumerangue.com>
-                        Fabio Gomes <flgoms@uol.com.br>
-                        Luis Felipe Marzagao <duli@fedoraproject.org>
-                        Eduardo de Carli <carliedu@ig.com.br>
-Bulgarian               Miroslav Pendev <miro@cybershade.us>
-Catalan                 Jordi Giralt <projecte.k2@upcnet.es>
-                        correuUPC <admin.atic@upcnet.es>
-Chinese (Simplified)    Zhang Bo <boozhang@sdb.ac.cn>
-                        Anna Chen <annachen2008@yahoo.com.cn>
-Chinese (Traditional)   David Chang <david@tmv.gov.tw>
-Croatian                Matej Vela <matej.vela@carnet.hr>
-Czech                   Pavel Chytil <pchytil@asp.ogi.edu>
-                        Michael Grafnetter <michael.grafnetter@outlook.com>
-Danish                  Bill Edgington <horde@0x20.com>
-                        Martin List-Petersen <martin@list-petersen.dk>
-                        Brian Truelsen <horde+i18n@briantruelsen.dk>
-                        Niels Baggesen <nba@users.sourceforge.net>
-                        Erling Preben Hansen <erling@eph.dk>
-Dutch                   Jan Kuipers <jrkuipers@lauwerscollege.nl>
-                        Han Spruyt <han.spruyt@ijsselgroep.nl>
-                        Arjen de Korte <build+horde@de-korte.org>
-Estonian                Alar Sing <alar.sing@err.ee>
-Finnish                 Leena Heino <liinu@uta.fi>
-                        Jouko Nikula <jouko@mailpriva.com>
-French                  Mikhaël Janson <mik@galaxie.net>
-                        Benoit St-André <ben@benoitst-andre.net>
-                        Pierre Lachance <pl@pierrelachance.net>
-                        Vincent Vinet <vvinet@revolutionlinux.com>
-                        Yannick Sebastia <yannick.sebastia@ecole-navale.fr>
-                        Laurent Foucher <laurent.foucher@iut-tlse3.fr>
-                        Paul De Vlieger
-                        <paul.de_vlieger@moniut.univ-bpclermont.fr>
-German                  Jan Schneider <jan@horde.org>
-Greek                   Stefanos I. Dimitriou <support@teiath.gr>
-                        Silligardos Xristoforos
-                        Anagnostopoulos Apostolis
-                        Konstantinos C. Milosis <kmilosis@yahoo.com>
-                        Giorgos Strimpakos <strimpak.geo@gmail.com>
-                        Terpou Maria <mterpou@cti.gr>
-                        Drakopoulos Takis <tdrakop@cti.gr>
-Hungarian               Laszlo L. Tornoci <torlasz@xenia.sote.hu>
-                        Andras Galos <galosa@netinform.hu>
-                        Zoltán Németh <nemeth.zoltan@etit.hu>
-                        Kiraly Laszlo <kiru@madalbal.hu>
-Italian                 Giovanni Meneghetti <gmeneghetti@infvic.it>
-                        Marco Pirovano <marco.pirovano@unibocconi.it>
-                        Cristian Manoni <cristian.manoni@nethesis.it>
-                        Massimo Malabotta <mmalabotta@units.it>
-                        Massimo Balestrieri <massimo@balestrieri.eu>
-Japanese                Hiromi Kimura <hiromi@tac.tsukuba.ac.jp>
-Korean                  J.I Kim <aporie@netian.com>
-                        Deokgon Kim <dgkim@dgkim.net>
-                        Josh Kim <joshkkim@gmail.com>
-Latvian                 Ivars Stivrins <ivars@kraslava.lv>
-Lithuanian              Darius Matuliauskas <darius@lnk.lt>
-                        Vilius Šumskas <vilius@lnk.lt>
-Norwegian Bokmaal       Torstein S. Hansen <huleboer@techbee.no>
-                        Torbjorn Grindhaug <grindhaug@gmail.com>
-                        Andreas Gunleikskaas <andreas@gunleikskaas.no>
-                        Trond Bjørstad <trb@ndn.no>
-                        Thomas Chr. Dahl <tcd@ndn.no>
-Norwegian Nynorsk       Per-Stian Vatne <psv@orsta.org>
-Polish                  Maciek Uhlig <muhlig@us.edu.pl>
-                        Piotr Adamcio <adamcios@o2.pl>
-                        Krzysztof Kozera <krzysztof113@o2.pl>
-                        Maciej Uhlig <maciej.uhlig@us.edu.pl>
-Portuguese              Manuel Menezes de Sequeira <Manuel.Sequeira>
-Romanian                Eugen Hoanca <eugenh@urban-grafx.ro>
-                        Marius Dragulescu <mariusd@urban-grafx.ro>
-Russian                 Anton Nekhoroshih <anton@valuehost.ru>
-                        Fedor A. Fetisov <faf@ssc.ru>
-Slovak                  Leo Mrafko <leo@oel.sk>
-                        Martin Matuška <martin@matuska.org>
-Slovenian               Duck <duck@obala.net>
-Spanish                 Manuel Perez Ayala <mperaya@alcazaba.unex.es>
-                        Juan C. Blanco <jcblanco@fi.upm.es>
-Swedish                 Andreas Dahlén <andreas@dahlen.ws>
-                        Joaquim Homrighausen <joho@webbplatsen.se>
-                        Jakob Alvermark <jakob.alvermark@bsdlabs.com>
-Turkish                 Middle East Technical University <horde-tr@metu.edu.tr>
-                        İstanbul Technical University <sistemdestek@itu.edu.tr>
-Ukrainian               Andriy Kopystyansky <anri@polynet.lviv.ua>
-=====================   ======================================================
diff -pruN 4.2.23-1/kronolith-4.2.23/docs/INSTALL 4.2.27-1/kronolith-4.2.23/docs/INSTALL
--- 4.2.23-1/kronolith-4.2.23/docs/INSTALL	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/docs/INSTALL	1970-01-01 00:00:00.000000000 +0000
@@ -1,219 +0,0 @@
-=========================
- Installing Kronolith H5
-=========================
-
-:Contact: kronolith@lists.horde.org
-
-.. contents:: Contents
-.. section-numbering::
-
-This document contains instructions for installing the Kronolith web-based
-calendar application on your system.
-
-For information on the capabilities and features of Kronolith, see the file
-README_ in the top-level directory of the Kronolith distribution.
-
-
-Prerequisites
-=============
-
-To function properly, Kronolith **requires** the following:
-
-1. A working Horde installation.
-
-   Kronolith runs within the `Horde Application Framework`_, a set of common
-   tools for Web applications written in PHP.  You must install Horde before
-   installing Kronolith.
-
-   .. Important:: Kronolith H5 requires version 5.0+ of the Horde Framework -
-                  earlier versions of Horde will **not** work.
-
-   .. Important:: Be sure to have completed all of the steps in the
-                  `horde/docs/INSTALL`_ file for the Horde Framework before
-                  installing Kronolith. Many of Kronolith's prerequisites are
-                  also Horde prerequisites. Additionally, many of Kronolith's
-                  optional features are configured via the Horde install.
-
-   .. _`Horde Application Framework`: http://www.horde.org/apps/horde
-
-2. SQL support in PHP, or Kolab support in Horde
-
-   Kronolith store its data in a backend - either an SQL database or a
-   Kolab server. Build PHP with whichever SQL driver you require; see
-   the Horde `horde/docs/INSTALL`_ file for more details on using databases
-   with Horde, or the Kolab webclient documentation for how to set up
-   Kronolith for Kolab.
-
-3. Operating System with support for dates before 1970-01-01 [OPTIONAL]
-
-   If you want to use dates earlier the January 1st 1970, for example
-   birthdays, you need an Operating System that supports negative
-   timestamps. Microsoft Windows does **not** support such dates.
-
-4. The following PEAR package:
-   (See `horde/docs/INSTALL`_ for instructions on installing PEAR package)
-
-   .. Important:: If you are going to install Kronolith the recommended way,
-                  i.e. using the PEAR installer, you can skip the remainder of
-                  this section. Installing Kronolith through PEAR will
-                  automatically download and install all required PEAR modules.
-
-   a. Date
-
-      Kronolith uses the Date package for various date calculations.
-
-   b. Date_Holidays 0.21.0 [OPTIONAL]
-
-      Kronolith can use the Date_Holidays package to show several sets of
-      national and religious holidays and memorial days. Since Date_Holidays
-      consists of a number of sub-packages, one for each country, you should
-      install all packages at once::
-
-         pear install Date_Holidays-alpha#all
-
-
-Installing Kronolith
-====================
-
-The **RECOMMENDED** way to install Kronolith is using the PEAR installer.
-Alternatively, if you want to run the latest development code or get the
-latest not yet released fixes, you can install Kronolith from Git.
-
-Installing with PEAR
-~~~~~~~~~~~~~~~~~~~~
-
-First follow the instructions in `horde/docs/INSTALL`_ to prepare a PEAR
-environment for Horde and install the Horde Framework.
-
-When installing Kronolith through PEAR now, the installer will automatically
-install any dependencies of Kronolith too. If you want to install Kronolith
-with all optional dependencies, but without the binary PECL packages that need
-to be compiled, specify both the ``-a`` and the ``-B`` flag::
-
-   pear install -a -B horde/kronolith
-
-By default, only the required dependencies will be installed::
-
-   pear install horde/kronolith
-
-If you want to install Kronolith even with all binary dependencies, you need to
-remove the ``-B`` flag. Please note that this might also try to install PHP
-extensions through PECL that might need further configuration or activation in
-your PHP configuration::
-
-   pear install -a horde/kronolith
-
-Installing from Git
-~~~~~~~~~~~~~~~~~~~
-
-See http://www.horde.org/source/git.php
-
-
-Configuring Kronolith
-=====================
-
-1. Configuring Horde for Kronolith
-
-   Kronolith requires a permanent ``Shares`` backend in Horde to manage
-   calendars and to add events to calendars.  If you didn't setup a Share
-   backend yet, go to the configuration interface, select Horde from the
-   list of applications and select the ``Shares`` tab. Unless you are using
-   Kolab, you should select ``SQL``.
-
-2. Configuring Kronolith
-
-   You must login to Horde as a Horde Administrator to finish the
-   configuration of Kronolith.  Use the Horde ``Administration`` menu item to
-   get to the administration page, and then click on the ``Configuration``
-   icon to get the configuration page.  Select ``Calendar`` from the selection
-   list of applications.  Fill in or change any configuration values as
-   needed.  When done click on ``Generate Calendar Configuration`` to generate
-   the ``conf.php`` file.  If your web server doesn't have write permissions
-   to the Kronolith configuration directory or file, it will not be able to
-   write the file.  In this case, go back to ``Configuration`` and choose one
-   of the other methods to create the configuration file
-   ``kronolith/config/conf.php``.
-
-   Documentation on the format and purpose of the other configuration files in
-   the ``config/`` directory can be found in each file. You may create
-   ``*.local.php`` versions of these files if you wish to customize Kronolith's
-   appearance and behavior. See the header of the configuration files for
-   details and examples. The defaults will be correct for most sites.
-
-3. Creating the database tables
-
-   Once you finished the configuration in the previous step, you can create all
-   database tables by clicking the ``DB schema is out of date.`` link in the
-   Kronolith row of the configuration screen.
-
-   Alternatively you creating the Kronolith database tables can be accomplished
-   with horde's ``horde-db-migrate`` utility.  If your database is properly
-   setup in the Horde configuration, just run the following::
-
-      horde/bin/horde-db-migrate kronolith
-
-4. Setting up reminder emails
-
-   There are two kind of reminders sent to users, reminders on individual
-   events with alarms, and daily agendas. Generally, if you set up cron jobs,
-   you must have the PHP CLI installed (a CGI binary is not supported - ``php
-   -v`` will report what kind of PHP binary you have).
-
-   a. If you have not already set up the Horde Alarm system, you have to set up
-      a cron entry for the Horde alarm script as documented in
-      `horde/docs/INSTALL`_. This will send reminders for individual events.
-
-   b. To send daily agendas, you must create a cron entry for
-      ``kronolith-agenda``, and running the job once a day is recommended,
-      e.g. at 2 a.m.::
-
-         # Kronolith agenda
-         0 2 * * * /usr/bin/kronolith-agenda
-
-   If not installing Kronolith through PEAR of if PEAR's ``bin_dir``
-   configuration doesn't point to ``/usr/bin/``, replace
-   ``/usr/bin/kronolith-agenda`` with the path to the ``kronolith-agenda``
-   script in your Kronolith installation.
-
-5. Testing Kronolith
-
-   Use Kronolith to create, modify, and delete events. Test at least the
-   following:
-
-   - Creating a new event
-   - Creating a recurring event
-   - Modifying an event
-   - Deleting an event
-
-
-Obtaining Support
-=================
-
-If you encounter problems with Kronolith, help is available!
-
-The Horde Frequently Asked Questions List (FAQ), available on the Web at
-
-  http://wiki.horde.org/FAQ
-
-The Horde Project runs a number of mailing lists, for individual applications
-and for issues relating to the project as a whole.  Information, archives, and
-subscription information can be found at
-
-  http://www.horde.org/community/mail
-
-Lastly, Horde developers, contributors and users may also be found on IRC,
-on the channel #horde on the Freenode Network (irc.freenode.net).
-
-Please keep in mind that Kronolith is free software written by volunteers.
-For information on reasonable support expectations, please read
-
-  http://www.horde.org/community/support
-
-Thanks for using Kronolith!
-
-The Horde team
-
-
-.. _README: README
-.. _`horde/docs/INSTALL`: ../../horde/docs/INSTALL
-.. _`horde/docs/TRANSLATIONS`: ../../horde/docs/TRANSLATIONS
diff -pruN 4.2.23-1/kronolith-4.2.23/docs/lighttpd-kronolith.conf 4.2.27-1/kronolith-4.2.23/docs/lighttpd-kronolith.conf
--- 4.2.23-1/kronolith-4.2.23/docs/lighttpd-kronolith.conf	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/docs/lighttpd-kronolith.conf	1970-01-01 00:00:00.000000000 +0000
@@ -1,13 +0,0 @@
-## This file should be reviewed prior to inclusion in your lighttpd
-## configuration.  Specifically, if you have kronolith somewhere other than
-## /horde/kronolith you will need to edit the following rules to match your server
-## configuration.
-
-## This file should be included in your lighttpd.conf file with the "include"
-## directive. Example:
-##   include "path/to/lighttpd-kronolith.conf"
-## The exact path you use will of course depend on your specific configuration.
-
-url.rewrite-once += (
-        "^/horde/kronolith/feed/(index.php\?c=)?(.*)$" => "/horde/kronolith/feed/index.php?c=$2"
-)
diff -pruN 4.2.23-1/kronolith-4.2.23/docs/RELEASE_NOTES 4.2.27-1/kronolith-4.2.23/docs/RELEASE_NOTES
--- 4.2.23-1/kronolith-4.2.23/docs/RELEASE_NOTES	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/docs/RELEASE_NOTES	1970-01-01 00:00:00.000000000 +0000
@@ -1,32 +0,0 @@
-<?php
-/* Security release? */
-$notes['security'] = false;
-
-/* Mailing list release notes. */
-$notes['changes'] = <<<ML
-The Horde Team is pleased to announce the final release of the Kronolith
-Calendar Application version H5 (4.2.23).
-
-Kronolith is the Horde calendar application. It provides web-based calendars
-backed by a SQL database or a Kolab server. Supported features include Ajax and
-mobile interfaces, CalDAV support, shared calendars, remote calendars,
-invitation management (iCalendar/iTip), free/busy management, resource
-management, alarms, recurring events, and a sophisticated day/week view which
-handles arbitrary numbers of overlapping events. For more information on
-Kronolith, visit http://www.horde.org/apps/kronolith.
-
-For upgrading instructions, please see
-http://www.horde.org/apps/kronolith/docs/UPGRADING
-
-For detailed installation and configuration instructions, please see
-http://www.horde.org/apps/kronolith/docs/INSTALL
-
-The major changes compared to Kronolith version H5 (4.2.22) are:
-    * Officially support PHP 7.
-    * Fixed deleting events from remote CalDAV servers.
-    * Small bugfixes.
-ML;
-
-$notes['name'] = 'Kronolith';
-$notes['branch'] = 'Horde 5';
-return $notes;
diff -pruN 4.2.23-1/kronolith-4.2.23/docs/TODO 4.2.27-1/kronolith-4.2.23/docs/TODO
--- 4.2.23-1/kronolith-4.2.23/docs/TODO	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/docs/TODO	1970-01-01 00:00:00.000000000 +0000
@@ -1,42 +0,0 @@
-=================================
- Kronolith Development TODO List
-=================================
-
-:Contact:       kronolith@lists.horde.org
-
-- Drop down list to select from local users or calendars in the attendees view.
-
-- The option to not have any personal calendars displayed, now with remote
-  calendars this would be a desirable option.
-
-- Remove the assumption that each calendar is using the same backend. Ideally
-  it should be possible to have a local calendar using sql or kolab and a
-  remote calendar using another driver (eg wcap or whatever mozilla uses).
-
-- Drivers should handle the permissions checks instead of accessing shares
-  directly (outside drivers) to get the permissions for an event or calendar.
-  One day we might have a calendar backend that knows about permissions.
-
-- Allow publishing calendars to a remote site as iCalendar files.
-
-- Saving of individual events as .ics files.
-
-- Support for searching a local file system for freebusy info.
-
-- Support for searching freebusy.office.microsoft.com for freebusy info.
-
-- Automatically locate a meeting time.
-
-- Add a quick tool for planning meeting agendas.
-
-- Handle resource scheduling/management somehow, either per-calendar or by
-  kinds of calendars, including rules for when overlapping appointments are
-  allowed, etc.
-
-- lib/Driver/ical.php should be the central location for remote calendar
-  support. Needs to be finished.
-
-- RSS or Atom feed for upcoming events.
-
-- Natural language quick event creation? (ex: 3/26 2am Raid on
-  Ironforge [WoW] +thrall +gromhellscream)
diff -pruN 4.2.23-1/kronolith-4.2.23/docs/UPGRADING 4.2.27-1/kronolith-4.2.23/docs/UPGRADING
--- 4.2.23-1/kronolith-4.2.23/docs/UPGRADING	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/docs/UPGRADING	1970-01-01 00:00:00.000000000 +0000
@@ -1,137 +0,0 @@
-=====================
- Upgrading Kronolith
-=====================
-
-:Contact: kronolith@lists.horde.org
-
-.. contents:: Contents
-.. section-numbering::
-
-
-General instructions
-====================
-
-These are instructions to upgrade from earlier Kronolith versions. Please
-backup your existing data before running any of the steps described below. You
-can't use the updated data with your old Kronolith version anymore.
-
-Upgrading Kronolith is as easy as running::
-
-   pear upgrade -a -B horde/kronolith
-
-If you want to upgrade Kronolith with all binary dependencies, you need to
-remove the ``-B`` flag. Please note that this might also try to install PHP
-extensions through PECL that might need further configuration or activation in
-your PHP configuration::
-
-   pear upgrade -a horde/kronolith
-
-If you want to upgrade to an alpha or beta version of Kronolith, you need to
-tell the PEAR installer to prefer non-stable package versions. Please note that
-this might also install pre-release 3rd-party PEAR packages::
-
-   pear -d preferred_state=alpha upgrade -a horde/kronolith
-
-If you want to upgrade from a Kronolith version prior to 3.0, please follow the
-instructions in INSTALL_ to install the most recent Kronolith version using the
-PEAR installer.
-
-After updating to a newer Kronolith version, you **always** need to update
-configurations and database schemes. Log in as an administrator, go to
-Administration => Configuration and update anything that's highlighted as
-outdated.
-
-
-Upgrading Kronolith from 2.3.x to 3.x
-=====================================
-
-Preferences
------------
-
-The last_kronolith_maintenance preference has been removed.
-
-
-UTC timestamps
---------------
-
-Kronolith 3.0 using an SQL backend stores all event dates in UTC by
-default. This allows to share events across different timezones. If you are
-upgrading from earlier Kronolith versions, you can either turn this feature
-off and keep working with your existing event data, or convert the existing
-data to the UTC timezone.
-
-If you want to convert the data, make sure that you have run all other upgrade
-scripts first, so that your database tables are up to date. Then, to convert
-the event times, execute the provided PHP script::
-
-   kronolith-convert-to-utc
-
-
-Daily agendas
--------------
-
-There is a new script in ``kronolith-agenda`` to send out daily agendas to all
-users. It should be run once a day. This replaces the ``scripts/reminders.php``
-script from older versions, but you have to take care yourself now that the
-script isn't run more than once per day.
-
-
-Upgrading Kronolith from 2.3 to 2.3.x
-=====================================
-
-Kronolith requires at least version 0.21.0 of Date_Holidays now, which has
-much better support for translations.
-
-
-Upgrading Kronolith from 2.2 to 2.3.x
-=====================================
-
-Some fields in the SQL share driver tables have been changed. Execute the
-provided SQL script to update your database if you are using the native SQL
-share driver.
-
-   mysql --user=root --password=<MySQL-root-password>  <db name> < scripts/upgrades/2.2_to_2.3.sql
-
-
-Upgrading Kronolith from 2.1.x to 2.2.x
-=======================================
-
-
-SQL Backends
-------------
-
-Two new fields have been added to the default SQL table layout.
-
-Execute the provided SQL script to update your data to the new Kronolith
-version, e.g.::
-
-   mysql --user=root --password=<MySQL-root-password> <db name> < scripts/upgrades/2.1_to_2.2.sql
-
-
-New Beta SQL Share Driver Support
----------------------------------
-
-A new beta-level SQL Horde_Share driver has been added in Horde 3.2. This driver
-offers significant performance improvements over the existing Datatree driver,
-but it has not received the same level of testing, thus the beta designation.
-In order to make use of this driver, you must be using Horde 3.2-RC3 or
-later. The new tables needed for this driver already should have been created
-by the step above.
-
-If you want to use the new SQL Share driver, you must also execute the
-provided PHP script to migrate your existing share data to the new format::
-
-   kronolith-convert-datatree-shares-to-sql
-
-
-Preferences
------------
-
-The preference that stores the address books that are searched for attendees
-with free/busy urls has changed both the name and the format. The preference
-used to be called "search_abook" and contained a serialized PHP array. The new
-preference is called "search_sources", contains a tab-separated list, and goes
-along with the "search_fields" preference.
-
-
-.. _INSTALL: INSTALL
diff -pruN 4.2.23-1/kronolith-4.2.23/edit.php 4.2.27-1/kronolith-4.2.23/edit.php
--- 4.2.23-1/kronolith-4.2.23/edit.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/edit.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,242 +0,0 @@
-<?php
-/**
- * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Kronolith
- */
-
-function _save(&$event)
-{
-    try {
-        $event->save();
-        if (Horde_Util::getFormData('sendupdates', false)) {
-            Kronolith::sendITipNotifications($event, $GLOBALS['notification'], Kronolith::ITIP_REQUEST);
-        }
-    } catch (Exception $e) {
-        $GLOBALS['notification']->push(sprintf(_("There was an error editing the event: %s"), $e->getMessage()), 'horde.error');
-    }
-
-    Kronolith::notifyOfResourceRejection($event);
-}
-
-function _check_max()
-{
-    $perms = $GLOBALS['injector']->getInstance('Horde_Core_Perms');
-    if ($perms->hasAppPermission('max_events') !== true &&
-        $perms->hasAppPermission('max_events') <= Kronolith::countEvents()) {
-        Horde::permissionDeniedError(
-            'kronolith',
-            'max_events',
-            sprintf(_("You are not allowed to create more than %d events."), $perms->hasAppPermission('max_events'))
-        );
-        return false;
-    }
-    return true;
-}
-
-require_once __DIR__ . '/lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->redirect();
-}
-
-do {
-    if ($exception = Horde_Util::getFormData('del_exception')) {
-        /* Deleting recurrence exceptions. */
-        list($type, $calendar) = explode('_', Horde_Util::getFormData('calendar'), 2);
-        try {
-            $kronolith_driver = Kronolith::getDriver($type, $calendar);
-            switch ($type) {
-            case 'internal':
-                $kronolith_calendar = $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $calendar);
-                break;
-            case 'remote':
-                $kronolith_calendar = $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_REMOTE_CALENDARS, $calendar);
-                break;
-            }
-
-            $event = $kronolith_driver->getEvent(Horde_Util::getFormData('eventID'));
-            if (!$kronolith_calendar->hasPermission(Horde_Perms::EDIT, $registry->getAuth(), $event->creator)) {
-                $notification->push(_("You do not have permission to edit this event."), 'horde.warning');
-                break;
-            }
-            $result = sscanf($exception, '%04d%02d%02d', $year, $month, $day);
-            if ($result == 3 && $event->recurs()) {
-                $event->recurrence->deleteException($year, $month, $day);
-                _save($event);
-            }
-            $notification->push(_("The exception has been removed."), 'horde.success');
-        } catch (Exception $e) {
-            $notification->push(sprintf(_("There was an error accessing the calendar: %s"), $e->getMessage()), 'horde.error');
-        }
-        break;
-    }
-
-    if (Horde_Util::getFormData('cancel')) {
-        break;
-    }
-
-    list($sourceType, $source) = explode('_', Horde_Util::getFormData('existingcalendar'), 2);
-    list($targetType, $targetcalendar) = explode('_', Horde_Util::getFormData('targetcalendar'), 2);
-    if (strpos($targetcalendar, '\\')) {
-        list($target, $user) = explode('\\', $targetcalendar, 2);
-    } else {
-        $target = $targetcalendar;
-        $user = $GLOBALS['registry']->getAuth();
-    }
-
-    try {
-        $event = false;
-        if (($edit_recur = Horde_Util::getFormData('edit_recur')) &&
-            $edit_recur != 'all' && $edit_recur != 'copy' &&
-            ($targetType != 'internal' || _check_max())) {
-            /* Edit a recurring exception. */
-
-            /* Get event details. */
-            $kronolith_driver = Kronolith::getDriver($sourceType, $source);
-            switch ($sourceType) {
-            case 'internal':
-                $kronolith_calendar = $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $source);
-                break;
-            case 'remote':
-                $kronolith_calendar = $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_REMOTE_CALENDARS, $source);
-                break;
-            }
-            $event = $kronolith_driver->getEvent(Horde_Util::getFormData('eventID'));
-            if (!$event->hasPermission(Horde_Perms::EDIT)) {
-                $notification->push(_("You do not have permission to edit this event."), 'horde.warning');
-                break;
-            }
-
-            $exception = new Horde_Date(Horde_Util::getFormData('recur_ex'));
-
-            switch ($edit_recur) {
-            case 'current':
-                /* Add exception. */
-                $event->recurrence->addException($exception->year,
-                                                 $exception->month,
-                                                 $exception->mday);
-                $event->save();
-                $uid = $event->uid;
-                $originaltime = $event->start->strftime('%T');
-
-                /* Create one-time event. */
-                $event = $kronolith_driver->getEvent();
-                $event->readForm();
-                $event->baseid = $uid;
-                $event->exceptionoriginaldate = new Horde_Date($exception->strftime('%Y-%m-%d') . 'T' . $originaltime);
-
-                break;
-
-            case 'future':
-                /* Set recurrence end. */
-                $exception->mday--;
-                if ($event->end->compareDate($exception) > 0 &&
-                    $event->hasPermission(Horde_Perms::DELETE)) {
-                    try {
-                        $kronolith_driver->deleteEvent($event->id);
-                    } catch (Exception $e) {
-                        $notification->push($e, 'horde.error');
-                    }
-                } else {
-                    $event->recurrence->setRecurEnd($exception);
-                    $event->save();
-                }
-
-                /* Create new event. */
-                $event = $kronolith_driver->getEvent();
-                $event->readForm();
-
-                break;
-            }
-
-            $event->uid = null;
-            _save($event);
-            break;
-        }
-
-        /* Permission checks on the target calendar . */
-        switch ($targetType) {
-        case 'internal':
-            $kronolith_calendar = $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $target);
-            break;
-        case 'remote':
-            $kronolith_calendar = $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_REMOTE_CALENDARS, $target);
-            break;
-        default:
-            break 2;
-        }
-        if ($user == $GLOBALS['registry']->getAuth() &&
-            !$kronolith_calendar->hasPermission(Horde_Perms::EDIT)) {
-            $notification->push(_("You do not have permission to edit this event."), 'horde.warning');
-            break;
-        }
-        if ($user != $GLOBALS['registry']->getAuth() &&
-            !$kronolith_calendar->hasPermission(Kronolith::PERMS_DELEGATE)) {
-            $notification->push(_("You do not have permission to delegate events to this user."), 'horde.warning');
-            break;
-        }
-
-        if (Horde_Util::getFormData('saveAsNew') || $edit_recur == 'copy') {
-            /* Creating a copy of the event. */
-            if ($targetType == 'internal' && !_check_max()) {
-                break;
-            }
-            $kronolith_driver = Kronolith::getDriver($targetType, $target);
-            $event = $kronolith_driver->getEvent();
-        } else {
-            /* Regular saving of event. */
-            $eventId = Horde_Util::getFormData('eventID');
-            $kronolith_driver = Kronolith::getDriver($sourceType, $source);
-            $event = $kronolith_driver->getEvent($eventId);
-
-            if ($target != $source) {
-                /* Moving the event to a different calendar. Only delete the
-                 * event from the source calendar if this user has permissions
-                 * to do so. */
-                if (!$event->hasPermission(Horde_Perms::DELETE)) {
-                    $notification->push(_("You do not have permission to move this event."), 'horde.warning');
-                } else {
-                    if ($sourceType == 'internal' &&
-                        $targetType == 'internal') {
-                        try {
-                            // TODO: abstract this out.
-                            $kronolith_driver->move($eventId, $target);
-                            $kronolith_driver->open($target);
-                            $event = $kronolith_driver->getEvent($eventId);
-                        } catch (Exception $e) {
-                            $notification->push(sprintf(_("There was an error moving the event: %s"), $e->getMessage()), 'horde.error');
-                        }
-                    } else {
-                        $kronolith_driver->deleteEvent($eventId);
-                        $kronolith_driver = Kronolith::getDriver($targetType, $target);
-                        $event = $kronolith_driver->getEvent();
-                    }
-                }
-            }
-        }
-
-        if ($event) {
-            $event->readForm();
-            _save($event);
-        }
-    } catch (Exception $e) {
-        $notification->push(sprintf(_("There was an error accessing the calendar: %s"), $e->getMessage()), 'horde.error');
-    }
-} while (false);
-
-if ($url = Horde::verifySignedUrl(Horde_Util::getFormData('url'))) {
-    $url = new Horde_Url($url, true);
-} else {
-    $url = Horde::url($prefs->getValue('defaultview') . '.php', true)
-        ->add(array('month' => Horde_Util::getFormData('month'),
-                    'year' => Horde_Util::getFormData('year')));
-}
-
-/* Make sure URL is unique. */
-$url->unique()->redirect();
diff -pruN 4.2.23-1/kronolith-4.2.23/event.php 4.2.27-1/kronolith-4.2.23/event.php
--- 4.2.23-1/kronolith-4.2.23/event.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/event.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,60 +0,0 @@
-<?php
-/**
- * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Kronolith
- */
-
-require_once __DIR__ . '/lib/Application.php';
-Horde_Registry::appInit('kronolith');
-
-$viewName = Horde_Util::getFormData('view', 'Event');
-$view = Kronolith::getView($viewName);
-if (is_string($view->event)) {
-    $notification->push($view->event, 'horde.error');
-    Horde::url($prefs->getValue('defaultview') . '.php', true)->redirect();
-}
-
-if (Kronolith::showAjaxView()) {
-    Horde::url('', true)->setAnchor('event:' . $view->event->calendarType . '|' . $view->event->calendar . ':' . $view->event->id . ':' . Horde_Util::getFormData('datetime', Kronolith::currentDate()->dateString()))->redirect();
-}
-
-switch ($viewName) {
-case 'DeleteEvent':
-    /* Shortcut when we're deleting events and don't want confirmation. */
-    if (!$view->event->recurs() &&
-        !($prefs->getValue('confirm_delete') ||
-          Horde_Util::getFormData('confirm'))) {
-        Horde::url('delete.php?' . $_SERVER['QUERY_STRING'], true)->redirect();
-    }
-    break;
-
-case 'EditEvent':
-    if ($view->event->private &&
-        $view->event->creator != $GLOBALS['registry']->getAuth()) {
-        if ($url = Horde::verifySignedUrl(Horde_Util::getFormData('url'))) {
-            $url = new Horde_Url($url, true);
-        } else {
-            $url = Horde::url($prefs->getValue('defaultview') . '.php', true);
-        }
-        $url->unique()->redirect();
-    }
-    break;
-}
-
-$page_output->header(array(
-    'body_class' => $prefs->getValue('show_panel') ? 'rightPanel' : null,
-    'title' => $view->getTitle()
-));
-require KRONOLITH_TEMPLATES . '/javascript_defs.php';
-$notification->notify(array('listeners' => 'status'));
-
-echo '<div id="page">';
-Kronolith::eventTabs($viewName, $view->event);
-$view->html();
-echo '</div>';
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/fb.php 4.2.27-1/kronolith-4.2.23/fb.php
--- 4.2.23-1/kronolith-4.2.23/fb.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/fb.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,66 +0,0 @@
-<?php
-/**
- * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Kronolith
- */
-
-require_once __DIR__ . '/lib/Application.php';
-Horde_Registry::appInit('kronolith', array('authentication' => 'none', 'session_control' => 'none'));
-
-// Determine the username to show free/busy time for.
-$cal = Horde_Util::getFormData('c');
-$user = Horde_Util::getFormData('u');
-if (!empty($cal)) {
-    if (is_array($cal)) {
-        $cal = implode('|', $cal);
-    }
-} elseif ($pathInfo = Horde_Util::getPathInfo()) {
-    $user = basename($pathInfo);
-}
-
-$cache = $injector->getInstance('Horde_Cache');
-$key = 'kronolith.fb.' . ($user ? 'u.' . $user : 'c.' . $cal);
-$fb = $cache->get($key, 360);
-if (!$fb) {
-    if ($user) {
-        $prefs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Prefs')->create('kronolith', array(
-            'cache' => false,
-            'user' => $user
-        ));
-        $registry->setTimeZone();
-        $cal = @unserialize($prefs->getValue('fb_cals'));
-        if (is_array($cal)) {
-            $cal = implode('|', $cal);
-        }
-
-        // If the free/busy calendars preference is empty, default to
-        // the user's default_share preference, and if that's empty,
-        // to their username.
-        if (!$cal) {
-            $cal = $prefs->getValue('default_share');
-            if (!$cal) {
-                $cal = $user;
-            }
-            $cal = 'internal_' . $cal;
-        }
-    }
-
-    try {
-        $fb = Kronolith_FreeBusy::generate(explode('|', $cal), null, null, false, $user);
-    } catch (Exception $e) {
-        Horde::log($e, 'ERR');
-        exit;
-    }
-    $cache->set($key, $fb);
-}
-
-$browser->downloadHeaders(($user ? $user : $cal) . '.vfb',
-                          'text/calendar; charset=' . 'UTF-8',
-                          true,
-                          strlen($fb));
-echo $fb;
diff -pruN 4.2.23-1/kronolith-4.2.23/feed/.htaccess 4.2.27-1/kronolith-4.2.23/feed/.htaccess
--- 4.2.23-1/kronolith-4.2.23/feed/.htaccess	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/feed/.htaccess	1970-01-01 00:00:00.000000000 +0000
@@ -1,6 +0,0 @@
-<IfModule mod_rewrite.c>
-    RewriteEngine On
-    RewriteCond   %{REQUEST_FILENAME}  !-d
-    RewriteCond   %{REQUEST_FILENAME}  !-f
-    RewriteRule   ^(.*)$ index.php?c=$1 [QSA,L]
-</IfModule>
diff -pruN 4.2.23-1/kronolith-4.2.23/feed/index.php 4.2.27-1/kronolith-4.2.23/feed/index.php
--- 4.2.23-1/kronolith-4.2.23/feed/index.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/feed/index.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,151 +0,0 @@
-<?php
-/**
- * Copyright 2008-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Jan Schneider <jan@horde.org>
- */
-
-function _no_access($status, $reason, $body)
-{
-    header('HTTP/1.0 ' . $status . ' ' . $reason);
-    echo "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">
-<html><head>
-<title>$status $reason</title>
-</head><body>
-<h1>$reason</h1>
-<p>$body</p>
-</body></html>";
-    exit;
-}
-
-require_once __DIR__ . '/../lib/Application.php';
-Horde_Registry::appInit('kronolith', array('authentication' => 'none', 'session_control' => 'readonly'));
-
-$calendar = Horde_Util::getFormData('c');
-try {
-    $share = $injector->getInstance('Kronolith_Shares')->getShare($calendar);
-} catch (Exception $e) {
-    _no_access(404, 'Not Found',
-               sprintf(_("The requested feed (%s) was not found on this server."),
-                       htmlspecialchars($calendar)));
-}
-if (!$share->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::READ)) {
-    if ($GLOBALS['registry']->getAuth()) {
-        _no_access(403, 'Forbidden',
-                   sprintf(_("Permission denied for the requested feed (%s)."),
-                           htmlspecialchars($calendar)));
-    } else {
-        $auth = $injector->getInstance('Horde_Core_Factory_Auth')->create();
-        if (isset($_SERVER['PHP_AUTH_USER'])) {
-            $user = $_SERVER['PHP_AUTH_USER'];
-            $pass = $_SERVER['PHP_AUTH_PW'];
-        } elseif (isset($_SERVER['Authorization'])) {
-            $hash = str_replace('Basic ', '', $_SERVER['Authorization']);
-            $hash = base64_decode($hash);
-            if (strpos($hash, ':') !== false) {
-                list($user, $pass) = explode(':', $hash, 2);
-            }
-        }
-
-        if (!isset($user) ||
-            !$auth->authenticate($user, array('password' => $pass))) {
-            header('WWW-Authenticate: Basic realm="' . $registry->get('name') . ' Feeds"');
-            _no_access(401, 'Unauthorized',
-                       sprintf(_("Login required for the requested feed (%s)."),
-                               htmlspecialchars($calendar)));
-        }
-    }
-}
-
-$feed_type = basename(Horde_Util::getFormData('type'));
-if (empty($feed_type)) {
-    // If not specified, default to Atom.
-    $feed_type = 'atom';
-}
-
-$startDate = new Horde_Date(array('year' => date('Y'),
-                                  'month' => date('n'),
-                                  'mday' => date('j')));
-try {
-    $events = Kronolith::listEvents($startDate,
-                                    new Horde_Date($startDate),
-                                    array($calendar));
-} catch (Exception $e) {
-    Horde::log($e, 'ERR');
-    $events = array();
-}
-
-if (isset($conf['urls']['pretty']) && $conf['urls']['pretty'] == 'rewrite') {
-    $self_url = Horde::url('feed/' . $calendar, true, -1);
-} else {
-    $self_url = Horde::url('feed/index.php', true, -1)
-        ->add('c', $calendar);
-}
-
-$owner = $share->get('owner');
-$identity = $injector->getInstance('Horde_Core_Factory_Identity')->create($owner);
-$history = $injector->getInstance('Horde_History');
-$now = new Horde_Date(time());
-
-$template = $injector->createInstance('Horde_Template');
-$template->set('updated', $now->format(DATE_ATOM));
-$template->set('kronolith_name', 'Kronolith');
-$template->set('kronolith_version', $registry->getVersion());
-$template->set('kronolith_uri', 'http://www.horde.org/kronolith/');
-$template->set('kronolith_icon', Horde::url(Horde_Themes::img('kronolith.png'), true, -1));
-$template->set('xsl', Horde_Themes::getFeedXsl());
-$template->set('calendar_name', htmlspecialchars($share->get('name')));
-$template->set('calendar_desc', htmlspecialchars($share->get('desc')), true);
-$template->set('calendar_owner', htmlspecialchars($identity->getValue('fullname')));
-$template->set('calendar_email', htmlspecialchars($identity->getValue('from_addr')), true);
-$template->set('self_url', $self_url);
-
-$twentyFour = $prefs->getValue('twentyFor');
-$entries = array();
-foreach ($events as $day_events) {
-    foreach ($day_events as $id => $event) {
-        /* Modification date. */
-        $modified = $history->getActionTimestamp('kronolith:' . $calendar . ':'
-                                                 . $event->uid, 'modify');
-        if (!$modified) {
-            $modified = $history->getActionTimestamp('kronolith:' . $calendar . ':'
-                                                     . $event->uid, 'add');
-        }
-        $modified = new Horde_Date($modified);
-        /* Description. */
-        $desc = $event->isPrivate() ? '' : htmlspecialchars($event->description);
-        if (strlen($desc)) {
-            $desc .= '<br /><br />';
-        }
-        /* Time. */
-        $desc .= _("When:") . ' ' . $event->start->strftime($prefs->getValue('date_format')) . ' ' . $event->start->format($twentyFour ? 'H:i' : 'H:ia') . _(" to ");
-        if ($event->start->compareDate($event->end->timestamp()) == 0) {
-            $desc .= $event->end->format($twentyFour ? 'H:i' : 'h:ia');
-        } else {
-            $desc .= $event->end->strftime($prefs->getValue('date_format')) . ' ' . $event->end->format($twentyFor ? 'H:i' : 'h:ia');
-        }
-        /* Attendees. */
-        if (!$event->isPrivate()) {
-            $attendees = Kronolith::getAttendeeEmailList($event->attendees);
-            if (count($attendees)) {
-                $desc .= '<br />' . _("Who:") . ' ' . htmlspecialchars(strval($attendees));
-            }
-            if (strlen($event->location)) {
-                $desc .= '<br />' . _("Where:") . ' ' . htmlspecialchars($event->location);
-            }
-        }
-        $desc .= '<br />' . _("Event Status:") . ' ' . Kronolith::statusToString($event->status);
-
-        $entries[$id]['title'] = htmlspecialchars($event->getTitle());
-        $entries[$id]['desc'] = htmlspecialchars($desc);
-        $entries[$id]['url'] = htmlspecialchars(Horde::url($event->getViewUrl(), true, -1));
-        $entries[$id]['modified'] = $modified->format(DATE_ATOM);
-    }
-}
-$template->set('entries', $entries, true);
-
-$browser->downloadHeaders($calendar . '.xml', 'text/xml', true);
-echo $template->fetch(KRONOLITH_TEMPLATES . '/feeds/' . $feed_type . '.xml');
diff -pruN 4.2.23-1/kronolith-4.2.23/index.php 4.2.27-1/kronolith-4.2.23/index.php
--- 4.2.23-1/kronolith-4.2.23/index.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/index.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,150 +0,0 @@
-<?php
-/**
- * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * not receive such a file, see also http://www.horde.org/licenses/gpl.
- */
-
-require_once __DIR__ . '/lib/Application.php';
-Horde_Registry::appInit('kronolith', array('nodynamicinit' => true));
-
-/* Determine View */
-switch ($registry->getView()) {
-case Horde_Registry::VIEW_MINIMAL:
-case Horde_Registry::VIEW_SMARTMOBILE:
-    include KRONOLITH_BASE . '/smartmobile.php';
-    exit;
-
-case Horde_Registry::VIEW_BASIC:
-case Horde_Registry::VIEW_DYNAMIC:
-    if ($registry->getView() == Horde_Registry::VIEW_DYNAMIC &&
-        $prefs->getValue('dynamic_view')) {
-        break;
-    }
-    include KRONOLITH_BASE . '/' . $prefs->getValue('defaultview') . '.php';
-    exit;
-}
-
-/* Load Ajax interface. */
-$today = new Horde_Date($_SERVER['REQUEST_TIME']);
-$ampm = !$prefs->getValue('twentyFour');
-
-$eventAlarmMethods = $eventAlarmParams = $taskAlarmMethods = $taskAlarmParams = '';
-foreach ($injector->getInstance('Horde_Alarm')->handlers() as $method => $handler) {
-    $eventAlarmMethods .= ' <input type="checkbox" name="event_alarms[]" id="kronolithEventAlarm' . $method . '" value="' . $method . '" /> <label for="kronolithEventAlarm' . $method . '">' . $handler->getDescription() . '</label>';
-    $taskAlarmMethods .= ' <input type="checkbox" name="task[alarm_methods][]" id="kronolithTaskAlarm' . $method . '" value="' . $method . '" /> <label for="kronolithTaskAlarm' . $method . '">' . $handler->getDescription() . '</label>';
-    $params = $handler->getParameters();
-    if (!count($params)) {
-        continue;
-    }
-    $eventAlarmParams .= ' <div id="kronolithEventAlarm' . $method . 'Params" style="display:none">';
-    $taskAlarmParams .= ' <div id="kronolithTaskAlarm' . $method . 'Params" style="display:none">';
-    foreach ($params as $name => $param) {
-        $eventAlarmParams .= ' <label for="kronolithEventAlarmParam' . $name
-            . '">' . $param['desc'] . '</label> ';
-        $eventNameAtt = 'name="event_alarms_' . $name . '"';
-        $eventAtt = 'id="kronolithEventAlarmParam' . $name . '" ' . $eventNameAtt;
-        $taskAlarmParams .= ' <label for="kronolithTaskAlarmParam' . $name
-            . '">' . $param['desc'] . '</label> ';
-        $taskNameAtt = 'name="task[methods][' . $method . '][' . $name . ']"';
-        $taskAtt = 'id="kronolithTaskAlarmParam' . $name . '" ' . $taskNameAtt;
-        switch ($param['type']) {
-        case 'text':
-            $eventAlarmParams .= '<input type="text" ' . $eventAtt . ' />';
-            $taskAlarmParams .= '<input type="text" ' . $taskAtt . ' />';
-            break;
-        case 'boolean':
-            $eventAlarmParams .= '<input type="checkbox" ' . $eventAtt . ' />';
-            $taskAlarmParams .= '<input type="checkbox" ' . $taskAtt . ' />';
-            break;
-        case 'sound':
-            $eventAlarmParams .= '<ul class="sound-list"><li><input type="radio" ' . $eventAtt
-                . ' value="" checked="checked" /> ' . _("No Sound") . '</li>';
-            $taskAlarmParams .= '<ul class="sound-list"><li><input type="radio" ' . $taskAtt
-                . ' value="" checked="checked" /> ' . _("No Sound") . '</li>';
-            foreach (Horde_Themes::soundList() as $key => $val) {
-                $sound = htmlspecialchars($key);
-                $value = sprintf('<li><input type="radio" id="%s%s" %s value="%s" /> <embed autostart="false" src="%s" /> %s</li>',
-                                 '%s',
-                                 $name . str_replace('.wav', '', $sound),
-                                 '%s',
-                                 $sound,
-                                 htmlspecialchars($val->uri),
-                                 $sound);
-                $eventAlarmParams .= sprintf($value,
-                                             'kronolithEventAlarmParam',
-                                             $eventNameAtt);
-                $taskAlarmParams .= sprintf($value,
-                                             'kronolithTaskAlarmParam',
-                                             $taskNameAtt);
-            }
-            $eventAlarmParams .= '</ul>';
-            $taskAlarmParams .= '</ul>';
-            break;
-        }
-        $eventAlarmParams .= '<br />';
-        $taskAlarmParams .= '<br />';
-    }
-    $eventAlarmParams = substr($eventAlarmParams, 0, - 6) . '</div>';
-    $taskAlarmParams = substr($taskAlarmParams, 0, - 6) . '</div>';
-}
-
-$injector->getInstance('Horde_Core_Factory_Imple')->create('Kronolith_Ajax_Imple_TagAutoCompleter', array(
-    'box' => 'kronolithEventACBox',
-    'id' => 'kronolithEventTags',
-    'pretty' => true
-));
-
-$injector->getInstance('Horde_Core_Factory_Imple')->create('Kronolith_Ajax_Imple_TagAutoCompleter', array(
-    'box' => 'kronolithCalendarinternalACBox',
-    'id' => 'kronolithCalendarinternalTags',
-    'pretty' => true,
-    'triggerContainer' => 'kronolithACCalendarTriggerContainer'
-));
-
-$injector->getInstance('Horde_Core_Factory_Imple')->create('Kronolith_Ajax_Imple_TagAutoCompleter', array(
-    'box' => 'kronolithTaskACBox',
-    'id' => 'kronolithTaskTags',
-    'pretty' => true
-));
-
-$injector->getInstance('Horde_Core_Factory_Imple')->create('Kronolith_Ajax_Imple_ContactAutoCompleter', array(
-    'box' => 'kronolithAttendeesACBox',
-    'id' => 'kronolithEventAttendees',
-    'onAdd' => 'function(a) { KronolithCore.addAttendee(a); KronolithCore.checkOrganizerAsAttendee(); }',
-    'onRemove' => 'KronolithCore.removeAttendee.bind(KronolithCore)',
-    'pretty' => true,
-    'triggerContainer' => 'kronolithAttendeesACTriggerContainer',
-    'beforeUpdate' => 'function(a) { return KronolithCore.normalizeAttendee(a); }'
-));
-
-$injector->getInstance('Horde_Core_Factory_Imple')->create('Kronolith_Ajax_Imple_ResourceAutoCompleter', array(
-    'box' => 'kronolithResourceACBox',
-    'id' => 'kronolithEventResources',
-    'onAdd' => 'KronolithCore.addResource.bind(KronolithCore)',
-    'onRemove' => 'KronolithCore.removeResource.bind(KronolithCore)',
-    'pretty' => true,
-    'triggerContainer' => 'kronolithResourceACTriggerContainer'
-));
-
-if ($conf['maps']['driver']) {
-    Horde::initMap();
-}
-
-$time_jobs = $time_clients = false;
-if ($registry->hasMethod('time/listJobTypes')) {
-    try {
-        $time_jobs = $registry->time->listJobTypes(array('enabled' => true));
-        $time_clients = $registry->time->listClients();
-    } catch (Horde_Exception_PushApp $e) {
-    }
-}
-
-$topbar = $injector->getInstance('Horde_View_Topbar');
-$topbar->search = true;
-
-$injector->getInstance('Kronolith_Ajax')->init();
-require KRONOLITH_TEMPLATES . '/dynamic/index.inc';
-echo $injector->getInstance('Kronolith_View_Sidebar');
-$page_output->footer();
diff -pruN 4.2.23-1/kronolith-4.2.23/js/dragdrop2.js 4.2.27-1/kronolith-4.2.23/js/dragdrop2.js
--- 4.2.23-1/kronolith-4.2.23/js/dragdrop2.js	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/js/dragdrop2.js	1970-01-01 00:00:00.000000000 +0000
@@ -1,765 +0,0 @@
-/**
- * dragdrop.js - A minimalist library to handle drag/drop actions.
- * Requires prototype.js 1.6.0.2+
- *
- * Adapted from SkyByte.js/SkyByteDD.js v1.0-beta, May 17 2007
- *   (c) 2007 Aleksandras Ilarionovas (Alex)
- *   http://www.skybyte.net/scripts/
- *
- * Scrolling and ghosting code adapted from script.aculo.us dragdrop.js v1.8.0
- *   (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
- *   (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
- *
- * The original scripts were freely distributable under the terms of an
- * MIT-style license.
- *
- * Usage:
- * ------
- * new Drag(element, {
- *     caption: '',               // Either string or function to set caption
- *                                // on mouse move.
- *     classname: '',             // Class name of the drag element.
- *                                // DEFAULT: 'drag'
- *     constraint: '',            // Constrain movement to 'horizontal' or
- *                                // 'vertical'.
- *     ghosting: false,           // Show ghost outline when dragging.
- *     nodrop: false,             // Don't do drop checking. Optimizes
- *                                // movement speed.
- *     offset: { x:0, y:0 },      // An offset to apply to ghosted elements.
- *     parentElement: function(), // Function returns the parent element.
- *     scroll: element,           // Scroll this element when above/below (
- *                                // only for vertical elements).
- *     snap: null,                // If ghosting, snap allows to specify
- *                                // coords at which the ghosted image will
- *                                // "snap" into place.
- *     snapToParent: false        // Keep image snapped inside the parent
- *                                // element.
- *     threshold: 0               // Move threshold.
- * });
- *
- * Events fired for Drags:
- * -----------------------
- * Custom events are triggered on the drag element. The 'memo' property of
- * the Event object contains the original event object.
- *
- * 'DragDrop2:drag'
- *   Fired on mousemove.
- *
- * 'DragDrop2:end'
- *   Fired when dragging ends.
- *
- * 'DragDrop2:mousedown'
- *   Fired on mousedown.
- *
- * 'DragDrop2:mouseup'
- *   Fored on mouseup *if* the element was not dragged.
- *
- * 'DragDrop2:start'
- *   Fired when first moved more than 'threshold'.
- *
- *
- * new Drop(element, {
- *     accept: [],      // Accept filter by tag name(s) or leave empty to
- *                      // accept all tags.
- *     caption: '',     // Either string or function to set caption on
- *                      // mouseover.
- *     hoverclass: '',  // Change the drag element to this class when hovering
- *                      // over an element.
- *                      // DEFAULT: 'dragdrop'
- *     keypress: false  // If true, will re-render caption if a keypress is
- *                      // detected while a drop is active (useful for
- *                      // CTRL/SHIFT actions).
- * });
- *
- * Events fired for Drops:
- * -----------------------
- * Custom events are triggered on the drop element. The 'memo' property of
- * the Event object contains the Drag object. The dragged element is available
- * in 'memo.element'. The browser event that triggered the custom event is
- * available in 'memo.dragevent'.
- *
- * 'DragDrop2:drop'
- *   Fired when mouse button released (a/k/a a drop event).
- *
- * 'DragDrop2:out'
- *   Fired when mouse leaves the drop zone.
- *
- * 'DragDrop2:over'
- *   Fired when mouse over drop zone.
- *
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *
- * Copyright 2008-2017 Horde LLC (http://www.horde.org/)
- *
- * @author  Michael Slusarz <slusarz@horde.org>
- * @package Horde
- */
-
-var DragDrop = {
-
-    Drags: {
-
-        drags: $H(),
-
-        register: function(obj)
-        {
-            if (!this.div) {
-                this.div = new Element('DIV', { className: obj.options.classname }).setStyle({ position: 'absolute' }).hide();
-                $(document.body).insert(this.div);
-                document.observe('mousedown', this._mouseHandler.bindAsEventListener(this));
-            }
-
-            this.drags.set(obj.element.identify(), obj);
-            obj.element.addClassName('DragElt');
-        },
-
-        unregister: function(obj)
-        {
-            if (this.drag == obj.element) {
-                this.drag.deactivate();
-            }
-
-            this.drags.unset(obj.element.identify());
-            obj.element.removeClassName('DragElt');
-        },
-
-        getDrag: function(el)
-        {
-            return this.drags.get(Object.isElement(el) ? $(el).identify() : el);
-        },
-
-        activate: function(drag)
-        {
-            if (this.drag) {
-                this.deactivate();
-            }
-            this.drag = drag;
-            this.mousemoveE = drag._mouseMove.bindAsEventListener(drag);
-            this.mouseupE = drag._mouseUp.bindAsEventListener(drag);
-            this.keypressE = drag._keyPress.bindAsEventListener(drag);
-            document.observe('mousemove', this.mousemoveE);
-            document.observe('mouseup', this.mouseupE);
-            document.observe('keydown', this.keypressE);
-            document.observe('keyup', this.keypressE);
-        },
-
-        deactivate: function()
-        {
-            if (this.drag) {
-                this.drag = DragDrop.Drops.drop = null;
-                document.stopObserving('mousemove', this.mousemoveE);
-                document.stopObserving('mouseup', this.mouseupE);
-                document.stopObserving('keydown', this.keypressE);
-                document.stopObserving('keyup', this.keypressE);
-            }
-        },
-
-        _mouseHandler: function(e)
-        {
-            var elt = e.findElement('.DragElt');
-            if (this.drags.size() && elt) {
-                this.getDrag(elt).mouseDown(e);
-            }
-        }
-
-    },
-
-    Drops: {
-
-        drops: $H(),
-
-        register: function(obj)
-        {
-            this.drops.set(obj.element.identify(), obj);
-            obj.element.addClassName('DropElt');
-        },
-
-        unregister: function(obj)
-        {
-            if (this.drop == obj.element) {
-                this.drop = null;
-            }
-
-            this.drops.unset(obj.element.identify());
-            obj.element.removeClassName('DropElt');
-        },
-
-        getDrop: function(el)
-        {
-            return this.drops.get(Object.isElement(el) ? $(el).identify() : el);
-        }
-
-    },
-
-    validDrop: function(el)
-    {
-        var d = DragDrop.Drops.drop;
-        return (d &&
-                el &&
-                el != d.element &&
-                (!d.options.accept.size() ||
-                 d.options.accept.include(el.tagName)));
-    }
-
-};
-
-Drag = Class.create({
-
-    initialize: function(el)
-    {
-        this.dragevent = null;
-        this.element = $(el);
-        this.ghostOffset = [ 0, 0 ];
-        this.options = Object.extend({
-            caption: '',
-            classname: 'drag',
-            constraint: null,
-            ghosting: false,
-            nodrop: false,
-            parentElement: null,
-            scroll: null,
-            snap: null,
-            snapToParent: false,
-            threshold: 0
-        }, arguments[1] || {});
-        if (this.options.scroll) {
-            this.options.scroll = $(this.options.scroll);
-        }
-        DragDrop.Drags.register(this);
-
-        // Disable text selection.
-        // See: http://ajaxcookbook.org/disable-text-selection/
-        // Stopping the event on mousedown works on all browsers, but avoid
-        // that if possible because it will prevent any event handlers further
-        // up the DOM tree from firing.
-        if (Prototype.Browser.IE) {
-            this.element.observe('selectstart', Event.stop);
-        } else if (Prototype.Browser.Gecko) {
-            this.element.setStyle({ MozUserSelect: 'none' });
-        }
-    },
-
-    destroy: function()
-    {
-        DragDrop.Drags.unregister(this);
-    },
-
-    mouseDown: function(e)
-    {
-        DragDrop.Drags.activate(this);
-        this.move = 0;
-        this.wasDragged = false;
-        this.wasMoved = false;
-        this.lastcaption = null;
-        this.clickEvent = e;
-
-        this.element.fire('DragDrop2:mousedown', Object.clone(e));
-
-        if (this.options.ghosting || this.options.caption) {
-            if (!DragDrop.Drags.cover) {
-                DragDrop.Drags.cover = new Element('DIV', { id: 'dragdrop2Cover' });
-                $(document.body).insert(DragDrop.Drags.cover);
-                DragDrop.Drags.cover.insert(new Element('DIV').setStyle({ position: 'absolute' }).hide());
-            }
-
-            $$('IFRAME').each(function(i) {
-                var z;
-                if (i.visible()) {
-                    z = parseInt(i.getStyle('zIndex'), 10);
-                    if (isNaN(z)) {
-                        z = 2;
-                    }
-                    DragDrop.Drags.cover.insert(DragDrop.Drags.cover.down().clone(false).setStyle({ zIndex: z }).clonePosition(i).show());
-                }
-            }, this);
-        }
-
-        if (this.options.snapToParent) {
-            this.snap = this.options.parentElement
-                ? this.options.parentElement().getDimensions()
-                : this.element.parentNode.getDimensions();
-        }
-
-        // Stop event to prevent text selection. IE and Gecko are handled in
-        // initialize().
-        if (!Prototype.Browser.IE && !Prototype.Browser.Gecko) {
-            e.stop();
-        }
-    },
-
-    _mouseMove: function(e)
-    {
-        var go, eo, po, xy, p, delta;
-
-        if (++this.move <= this.options.threshold) {
-            return;
-        } else if (!this.wasMoved) {
-            this.element.fire('DragDrop2:start', Object.clone(this.clickEvent));
-            this.wasMoved = true;
-        }
-
-        this.lastCoord = xy = [ e.pointerX(), e.pointerY() ];
-
-        if (!this.options.caption) {
-            if (!this.ghost) {
-                // Use the position of the original click event as the start
-                // coordinate.
-                xy = [ this.clickEvent.pointerX(), this.clickEvent.pointerY() ];
-
-                // Create the "ghost", i.e. the moving element, a clone of the
-                // original element, if it doesn't exist yet.
-                var layout = this.element.getLayout();
-                this.ghost = $(this.element.clone(true))
-                    .writeAttribute('id', null)
-                    .addClassName(this.options.classname)
-                    .setStyle({ position: 'absolute', height: layout.get('height') + 'px', width: layout.get('width') + 'px' });
-
-                p = this.element.viewportOffset();
-                delta = document.body.viewportOffset();
-                delta[0] -= document.body.offsetLeft;
-                delta[1] -= document.body.offsetTop;
-                this.ghost.style.left = (p[0] - delta[0]) + 'px';
-                this.ghost.style.top  = (p[1] - delta[1]) + 'px';
-
-                // eo is the offset of the original element to the body.
-                eo = this.element.cumulativeOffset();
-
-                // Save external dimensions, i.e. height and width including
-                // padding and margins, for later usage.
-                this.dim = {
-                    width: layout.get('margin-box-width'),
-                    height: layout.get('margin-box-height')
-                };
-
-                if (this.options.ghosting) {
-                    var z = parseInt(this.element.getStyle('zIndex'), 10);
-                    if (isNaN(z)) {
-                        z = 1;
-                    }
-                    this.ghost.setOpacity(0.7).setStyle({ zIndex: z + 1 });
-                } else {
-                    this.element.setStyle({ visibility: 'hidden' });
-                }
-
-                // Insert ghost into the parent, either specified by a
-                // function result, or using the original element's parent.
-                if (this.options.parentElement) {
-                    this.options.parentElement().insert(this.ghost);
-                } else {
-                    this.element.insert({ before: this.ghost });
-                }
-
-                // go is the offset of the ghost to the body. This might be
-                // different from the original element's offset because we
-                // used that element's position when cloning the ghost, but
-                // they might have different parents now.
-                go = this.ghost.cumulativeOffset();
-
-                // Calculate the difference between the ghost's offset and the
-                // orginal element's offset.
-                this.ghostOffset = [ go[0] - eo[0], go[1] - eo[1] ];
-
-                // Add the event coordinates to the offset, because we use the
-                // coordinates during later mousemove events as a basis for
-                // the new ghost position. But we don't want to position the
-                // ghost relative to the mouse pointer, but relative to where
-                // the mouse pointer clicked when the ghost was created.
-                // @todo: why do we subtract eo?
-                if (this.options.offset) {
-                    this.mouseOffset = this.ghostOffset;
-                } else {
-                    this.mouseOffset = [ this.ghostOffset[0] + xy[0] - eo[0],
-                                         this.ghostOffset[1] + xy[1] - eo[1] ];
-                }
-
-                if (!this.options.caption && this.options.constraint) {
-                    // Because we later only set the left or top coordinates
-                    // when using constraints, we have to set the correct
-                    // "opposite" coordinates here.
-                    po = this.ghost.getOffsetParent().cumulativeOffset();
-                    switch (this.options.constraint) {
-                    case 'horizontal':
-                        this.ghost.setStyle({ top: (eo[1] - po[1]) + 'px' });
-                        break;
-
-                    case 'vertical':
-                        this.ghost.setStyle({ left: (eo[0] - po[0]) + 'px' });
-                        break;
-                    }
-                }
-            }
-
-            // Subtract the ghost's offset to the original mouse position and
-            // add any scrolling.
-            xy[0] -= this.mouseOffset[0];
-            xy[1] -= this.mouseOffset[1];
-
-            this._setContents(this.ghost, xy[0], xy[1]);
-        }
-
-        if (!this.options.nodrop) {
-            this._onMoveDrag(xy, e);
-        }
-
-        this.wasDragged = true;
-
-        this.element.fire('DragDrop2:drag', Object.clone(e));
-
-        if (this.options.scroll) {
-            this._onMoveScroll();
-        }
-    },
-
-    _mouseUp: function(e)
-    {
-        var d = DragDrop.Drops.drop, tmp;
-
-        this._stopScrolling();
-
-        if (this.ghost) {
-            if (!this.options.ghosting) {
-                this.element.setStyle({ visibility: 'visible' });
-            }
-            try {
-                this.ghost.remove();
-            } catch (ex) {}
-            this.ghost = null;
-        }
-
-        DragDrop.Drags.div.hide();
-
-        if (DragDrop.validDrop(this.element)) {
-            this.dragevent = e;
-            d.element.fire('DragDrop2:drop', this);
-        }
-
-        DragDrop.Drags.deactivate();
-
-        if ((this.options.ghosting || this.options.caption) &&
-            DragDrop.Drags.cover) {
-            DragDrop.Drags.cover.down().siblings().invoke('remove');
-        }
-
-        if (!this.element.parentNode) {
-            tmp = new Element('DIV').insert(this.element);
-        }
-
-        this.element.fire(this.wasMoved ? 'DragDrop2:end' : 'DragDrop2:mouseup', Object.clone(e));
-
-        tmp = null;
-    },
-
-    _onMoveDrag: function(xy, e)
-    {
-        var d = DragDrop.Drops.drop,
-            div = DragDrop.Drags.div,
-            d_update = true,
-            elt = this._findElement(e);
-
-        /* elt will be null if we drag off the browser window. */
-        if (!Object.isElement(elt)) {
-            return;
-        }
-
-        if (this.lastelt == elt) {
-            this._setCaption(div, xy);
-            return;
-        }
-
-        this.lastelt = elt;
-
-        /* Do mouseover/mouseout-like detection here. Saves on observe calls
-         * and handles case where mouse moves over scrollbars. */
-        if (DragDrop.Drops.drops.size()) {
-            if (!elt.hasClassName('DropElt')) {
-                elt = elt.up('.DropElt');
-            }
-
-            if (elt) {
-                /* Ignore if mouse is over an offset ghosted element. */
-                if (elt == this.ghost) {
-                    return;
-                }
-
-                elt = DragDrop.Drops.getDrop(elt);
-                if (elt == d) {
-                    d_update = false;
-                } else {
-                    elt.mouseOver(e);
-                    d = elt;
-                }
-            } else if (d) {
-                d.mouseOut(e);
-                d = null;
-            }
-        }
-
-        if (d_update) {
-            this._updateCaption(d, div, e);
-        }
-
-        this._setCaption(div, xy);
-    },
-
-    _updateCaption: function(d, div, e)
-    {
-        var caption, cname, c_opt;
-
-        if (d && DragDrop.validDrop(this.element)) {
-            d_cap = d.options.caption;
-            if (!d_cap) {
-                return;
-            }
-            caption = Object.isFunction(d_cap) ? d_cap(d.element, this.element, e) : d_cap;
-            if (caption && d.options.hoverclass) {
-                cname = d.options.hoverclass;
-            }
-        }
-
-        if (!caption) {
-            c_opt = this.options.caption;
-            caption = Object.isFunction(c_opt) ? c_opt(this.element) : c_opt;
-        }
-
-        if (caption != this.lastcaption) {
-            this.lastcaption = caption;
-            div.update(caption).writeAttribute({ className: cname || this.options.classname });
-            if (caption.empty()) {
-                div.hide();
-            }
-        }
-    },
-
-    _findElement: function(e)
-    {
-        var drop, x, y;
-
-        if (this.options.caption ||
-            (this.options.offset &&
-             (this.options.offset.x > 0 || this.options.offset.y > 0))) {
-            return e.element();
-        }
-
-        if (!DragDrop.Drops.drops.size()) {
-            return;
-        }
-
-        Position.prepare();
-
-        x = e.pointerX();
-        y = e.pointerY();
-
-        drop = DragDrop.Drops.drops.find(function(drop) {
-            return Position.within(drop.value.element, x, y);
-        });
-
-        if (drop) {
-            return drop.value.element;
-        }
-    },
-
-    _keyPress: function(e)
-    {
-        if (DragDrop.Drops.drop &&
-            DragDrop.Drops.drop.options.keypress) {
-            this._updateCaption(DragDrop.Drops.drop, DragDrop.Drags.div, e);
-        }
-    },
-
-    _setCaption: function(div, xy)
-    {
-        if (this.lastcaption) {
-            this._setContents(div, xy[0] + 15, xy[1] + (this.ghost ? (this.ghost.getHeight() + 5) : 5));
-        }
-    },
-
-    _onMoveScroll: function()
-    {
-        this._stopScrolling();
-
-        var delta, p, speed, vp,
-            s = this.options.scroll,
-            dim = s.getDimensions();
-
-        // No need to scroll if element is not current scrolling.
-        if (s.scrollHeight == dim.height) {
-            return;
-        }
-
-        delta = document.viewport.getScrollOffsets();
-        p = s.viewportOffset();
-        speed = [ 0, 0 ];
-        vp = document.viewport.getDimensions();
-
-        p[0] += s.scrollLeft + delta.left;
-        p[2] = p[0] + dim.width;
-
-        // Only scroll if directly above/below element
-        if (this.lastCoord[0] > p[2] ||
-            this.lastCoord[0] < p[0]) {
-            return;
-        }
-
-        p[1] = vp.height - dim.height;
-        p[3] = vp.height - 10;
-
-        // Left scroll
-        //if (this.lastCoord[0] < p[0]) {
-        //    speed[0] = this.lastCoord[0] - p[0];
-        //}
-
-        // Top scroll
-        if (this.lastCoord[1] < p[1]) {
-            speed[1] = this.lastCoord[1] - p[1];
-        }
-
-        // Scroll right
-        //if (this.lastCoord[0] > p[2]) {
-        //    speed[0] = this.lastCoord[0] - p[2];
-        //}
-
-        // Scroll left
-        if (this.lastCoord[1] > p[3]) {
-            speed[1] = this.lastCoord[1] - p[3];
-        }
-
-        if (speed[0] || speed[1]) {
-            this.lastScrolled = new Date();
-            this.scrollInterval = setInterval(this._scroll.bind(this, speed[0] * 15, speed[1] * 15), 10);
-        }
-    },
-
-    _stopScrolling: function()
-    {
-        if (this.scrollInterval) {
-            clearInterval(this.scrollInterval);
-            this.scrollInterval = null;
-        }
-    },
-
-    _scroll: function(x, y)
-    {
-        var current = new Date(),
-            delta = current - this.lastScrolled,
-            s = this.options.scroll;
-        this.lastScrolled = current;
-
-        //s.scrollLeft += x * delta / 1000;
-        s.scrollTop += y * delta / 1000;
-    },
-
-    _setContents: function(elt, x, y)
-    {
-        var e_pos, vp, so, xy, style;
-
-        if (this.options.offset) {
-            x += this.options.offset.x;
-            y += this.options.offset.y;
-        }
-
-        if (this.options.snapToParent) {
-            if (x < 0) {
-                x = 0;
-            }
-            if (y < 0) {
-                y = 0;
-            }
-            if (x + this.dim.width > this.snap.width) {
-                x = this.snap.width - this.dim.width;
-            }
-            if (y + this.dim.height > this.snap.height) {
-                y = this.snap.height - this.dim.height;
-            }
-        } else if (this.options.snap) {
-            xy = this.options.snap(x, y, this.element);
-            x = xy[0];
-            y = xy[1];
-        } else {
-            e_pos = elt.getDimensions();
-            vp = document.viewport.getDimensions();
-            so = document.viewport.getScrollOffsets();
-            vp.width += so[0];
-            vp.height += so[1];
-            if (x + this.ghostOffset[0] < 0) {
-                x = -this.ghostOffset[0];
-            } else if (x + e_pos.width + this.ghostOffset[0] > vp.width) {
-                x = vp.width - e_pos.width - this.ghostOffset[0];
-            }
-            if (y + this.ghostOffset[1] < 0) {
-                y = -this.ghostOffset[1];
-            } else if (y + e_pos.height + this.ghostOffset[1] > vp.height) {
-                y = vp.height - e_pos.height - this.ghostOffset[1];
-            }
-        }
-
-        if (!this.options.caption) {
-            switch (this.options.constraint) {
-            case 'horizontal':
-                style = { left: x + 'px' };
-                break;
-
-            case 'vertical':
-                style = { top: y + 'px' };
-                break;
-
-            default:
-                style = { left: x + 'px', top: y + 'px' };
-                break;
-            }
-        } else {
-            style = { left: x + 'px', top: y + 'px' };
-        }
-
-        elt.setStyle(style).show();
-    }
-
-}),
-
-Drop = Class.create({
-
-    initialize: function(el)
-    {
-        this.element = $(el);
-        this.options = Object.extend({
-            accept: [],
-            caption: '',
-            hoverclass: 'dragdrop',
-            keypress: false
-        }, arguments[1] || {});
-        DragDrop.Drops.register(this);
-    },
-
-    destroy: function()
-    {
-        DragDrop.Drops.unregister(this);
-    },
-
-    mouseOver: function(e)
-    {
-        DragDrop.Drops.drop = this;
-        DragDrop.Drags.drag.dragevent = e;
-        this.element.fire('DragDrop2:over', DragDrop.Drags.drag);
-    },
-
-    mouseOut: function(e)
-    {
-        this.element.fire('DragDrop2:out', DragDrop.Drags.drag);
-        DragDrop.Drags.drag.dragevent = e;
-        DragDrop.Drops.drop = null;
-    }
-
-});
diff -pruN 4.2.23-1/kronolith-4.2.23/js/edit.js 4.2.27-1/kronolith-4.2.23/js/edit.js
--- 4.2.23-1/kronolith-4.2.23/js/edit.js	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/js/edit.js	1970-01-01 00:00:00.000000000 +0000
@@ -1,531 +0,0 @@
-/**
- * edit.js - Base application logic.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author   Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @package  Kronolith
- */
-
-var KronolithEdit =
-{
-    calendarSelect: function(e)
-    {
-        var prefix;
-
-        switch (e.element().identify()) {
-        case 'end_img':
-            prefix = 'end';
-            break;
-
-        case 'recur_end_img':
-            prefix = 'recur_end';
-            break;
-
-        case 'start_img':
-            prefix = 'start';
-            break;
-
-        default:
-            return;
-        }
-
-        $(prefix + '_year').setValue(e.memo.getFullYear());
-        $(prefix + '_month').setValue(e.memo.getMonth() + 1);
-        $(prefix + '_day').setValue(e.memo.getDate());
-
-        this.doAction(prefix + '_year');
-    },
-
-    updateWday: function(p)
-    {
-        $(p + '_wday').update('(' + Horde_Calendar.fullweekdays[this.getFormDate(p).getDay()] + ')');
-    },
-
-    getFormDate: function(p)
-    {
-        return new Date($F(p + '_year'), $F(p + '_month') - 1, $F(p + '_day'));
-    },
-
-    clickHandler: function(e)
-    {
-        if (e.isRightClick()) {
-            return;
-        }
-
-        var elt = e.element(),
-            id = elt.readAttribute('id');
-
-        switch (id) {
-        case 'allday':
-        case 'allday_label':
-            this.doAction('allday');
-            break;
-
-        case 'am':
-        case 'am_label':
-        case 'pm':
-        case 'pm_label':
-            this.doAction('am');
-            break;
-
-        case 'attendees_button':
-            HordePopup.popup({
-                params: {
-                    startdate: (('000' + $F('start_year')).slice(-4) + ('0' + $F('start_month')).slice(-2) + ('0' + $F('start_day')).slice(-2) + ('0' + $F('start_hour')).slice(-2) + ('0' + $F('start_min')).slice(-2) + '00'),
-                    enddate: (('000' + $F('end_year')).slice(-4) + ('0' + $F('end_month')).slice(-2) + ('0' + $F('end_day')).slice(-2) + ('0' + $F('end_hour')).slice(-2) + ('0' + $F('end_min')).slice(-2) + '00')
-                },
-                url: elt.readAttribute('href')
-            });
-            e.stop();
-            break;
-
-        case 'eam':
-        case 'eam_label':
-        case 'epm':
-        case 'epm_label':
-            this.doAction('eam');
-            break;
-
-        case 'edit_current':
-        case 'edit_future':
-            $('start_year').setValue(parseInt($F('recur_ex').substr(0, 4), 10));
-            $('start_month').selectedIndex = parseInt($F('recur_ex').substr(4, 2), 10) - 1;
-            $('start_day').selectedIndex = parseInt($F('recur_ex').substr(6, 2), 10) - 1;
-
-            this.updateWday('start');
-            this.updateEndDate();
-
-            $('recur_weekly_interval').adjacent('.checkbox').invoke('setValue', 0);
-            break;
-
-        case 'end_img':
-        case 'recur_end_img':
-        case 'start_img':
-            Horde_Calendar.open(elt, this.getFormDate(id.slice(0, -4)));
-            e.stop();
-            break;
-
-        case 'mo':
-        case 'tu':
-        case 'we':
-        case 'th':
-        case 'fr':
-        case 'sa':
-        case 'su':
-            this.setInterval('recurweekly', 'recur_weekly_interval');
-            this.setRecur(2);
-            break;
-
-        case 'nooverwrite':
-        case 'yesoverwrite':
-            if ($F('nooverwrite')) {
-                $('notification_options').hide();
-            } else {
-                $('notification_options').show();
-                $('yesalarm').setValue(1);
-            }
-            break;
-
-        case 'recurdaily':
-        case 'recurdaily_label':
-            this.setInterval('recurdaily', 'recur_daily_interval');
-            break;
-
-        case 'recurmonthday':
-        case 'recurmonthday_label':
-            this.setInterval('recurmonthday', 'recur_day_of_month_interval');
-            break;
-
-        case 'recurmonthweek':
-        case 'recurmonthweek_label':
-            this.setInterval('recurmonthweek', 'recur_week_of_month_interval');
-            break;
-
-        case 'recurnone':
-            this.clearFields(0);
-            break;
-
-        case 'recurweekly':
-        case 'recurweekly_label':
-            this.setInterval('recurweekly', 'recur_weekly_interval');
-            break;
-
-        case 'recuryear':
-        case 'recuryear_label':
-            this.setInterval('recuryear', 'recur_yearly_interval');
-            break;
-
-        case 'recuryearday':
-        case 'recuryearday_label':
-            this.setInterval('recuryearday', 'recur_yearly_day_interval');
-            break;
-
-        case 'recuryearweekday':
-        case 'recuryearweekday_label':
-            this.setInterval('recuryearweekday', 'recur_yearly_weekday_interval');
-            break;
-
-        default:
-            if (elt.readAttribute('name') == 'resetButton') {
-                $('eventform').reset();
-                this.updateWday('start');
-                this.updateWday('end');
-            } else {
-                if (!elt.match('TD')) {
-                    elt = elt.up('TD');
-                }
-                if (elt && elt.hasClassName('toggle')) {
-                    elt.down().toggle().next().toggle();
-                    $('section' + elt.identify().substr(6)).toggle();
-                }
-            }
-            break;
-        }
-    },
-
-    changeHandler: function(e)
-    {
-        this.doAction(e.element().readAttribute('id'));
-    },
-
-    keypressHandler: function(e)
-    {
-        switch (e.element().readAttribute('id')) {
-        case 'recur_daily_interval':
-            this.setRecur(1);
-            break;
-
-        case 'recur_weekly_interval':
-            this.setRecur(2);
-            break;
-
-        case 'recur_day_of_month_interval':
-            this.setRecur(3);
-            break;
-
-        case 'recur_week_of_month_interval':
-            this.setRecur(4);
-            break;
-
-        case 'recur_yearly_interval':
-            this.setRecur(5);
-            break;
-
-        case 'recur_yearly_day_interval':
-            this.setRecur(6);
-            break;
-
-        case 'recur_yearly_weekday_interval':
-            this.setRecur(7);
-            break;
-        }
-    },
-
-    doAction: function(id)
-    {
-        var endDate, endHour, duration, durHour, durMin, failed, startDate,
-            startHour;
-
-        switch (id) {
-        case 'allday':
-            if ($F('allday')) {
-                if (KronolithVar.twentyFour) {
-                    $('start_hour').selectedIndex = 0;
-                } else {
-                    $('start_hour').selectedIndex = 11;
-                    $('am').setValue(1);
-                }
-                $('start_min').setValue(0);
-                $('dur_day').setValue(1);
-                $('dur_hour').setValue(0);
-                $('dur_min').setValue(0);
-            }
-            this.updateEndDate();
-            $('duration').setValue(1);
-            break;
-
-        case 'am':
-            $('allday').setValue(0);
-            this.updateEndDate();
-            break;
-
-        case 'dur_day':
-        case 'dur_hour':
-        case 'dur_min':
-            $('allday').setValue(0);
-            this.updateEndDate();
-            $('end').setValue(1);
-            break;
-
-        case 'eam':
-        case 'epm':
-            break;
-
-        case 'end_year':
-        case 'end_month':
-        case 'end_day':
-            this.updateWday('end');
-            // Fall-through
-
-        case 'end_hour':
-        case 'end_min':
-        case 'pm':
-            $('end').setValue(1);
-
-            startHour = this.convertTo24Hour(parseInt($F('start_hour'), 10), 'pm');
-            endHour = this.convertTo24Hour(parseInt($F('end_hour'), 10), 'epm');
-            startDate = Date.UTC(
-                $F('start_year'),
-                $F('start_month') - 1,
-                $F('start_day'),
-                startHour,
-                $F('start_min')
-            );
-            endDate = Date.UTC(
-                $F('end_year'),
-                $F('end_month') - 1,
-                $F('end_day'),
-                endHour,
-                $F('end_min')
-            );
-
-            if (endDate < startDate) {
-                if (KronolithVar.twentyFour &&
-                    $F('start_year') == $F('end_year') &&
-                    $F('start_month') == $F('end_month') &&
-                    $F('start_day') == $F('end_day') &&
-                    !$F('pm') && !$F('epm')) {
-                    /* If the end hour is marked as the (default) AM, and
-                     * the start hour is also AM, automatically default
-                     * the end hour to PM if the date is otherwise the
-                     * same - assume that the user wants a 9am-2pm event
-                     * (for example), instead of throwing an error. */
-
-                    // Toggle the end date to PM.
-                    $('epm').checked = true;
-
-                    // Recalculate end time
-                    endHour = this.convertTo24Hour(parseInt($F('end_hour'), 10), 'epm');
-                    endDate = Date.UTC(
-                        $F('end_year'),
-                        $F('end_month') - 1,
-                        $F('end_day'),
-                        endHour,
-                        $F('end_min')
-                    );
-                } else {
-                    alert(KronolithText.enddate_error);
-                    endDate = startDate;
-                    failed = true;
-                }
-            }
-
-            duration = (endDate - startDate) / 1000;
-            $('dur_day').setValue(Math.floor(duration / 86400));
-            duration %= 86400;
-
-            durHour = Math.floor(duration / 3600);
-            duration %= 3600;
-
-            durMin = Math.floor(duration / 60 / 5);
-
-            $('dur_hour').selectedIndex = durHour;
-            $('dur_min').selectedIndex = durMin;
-            $('allday').setValue(false);
-
-            if (failed) {
-                this.updateEndDate();
-            }
-            break;
-
-        case 'recur_end_year':
-        case 'recur_end_month':
-        case 'recur_end_day':
-            $('recur_end_type').setValue(1);
-            this.updateWday('recur_end');
-            break;
-
-        case 'recur_daily_interval':
-            this.setRecur(1);
-            break;
-
-        case 'recur_weekly_interval':
-            this.setRecur(2);
-            break;
-
-        case 'recur_day_of_month_interval':
-            this.setRecur(3);
-            break;
-
-        case 'recur_week_of_month_interval':
-            this.setRecur(4);
-            break;
-
-        case 'recur_yearly_interval':
-            this.setRecur(5);
-            break;
-
-        case 'recur_yearly_day_interval':
-            this.setRecur(6);
-            break;
-
-        case 'recur_yearly_weekday_interval':
-            this.setRecur(7);
-            break;
-
-        case 'start_year':
-        case 'start_month':
-        case 'start_day':
-            this.updateWday('start');
-            // Fall-through
-
-        case 'start_hour':
-        case 'start_min':
-            $('allday').setValue(0);
-            this.updateEndDate();
-            break;
-        }
-    },
-
-    updateEndDate: function()
-    {
-        var endHour, endYear, msecs,
-            startHour = this.convertTo24Hour(parseInt($F('start_hour'), 10), 'pm'),
-            startDate = new Date(
-                $F('start_year'),
-                $F('start_month') - 1,
-                $F('start_day'),
-                startHour,
-                $F('start_min')
-            ),
-            endDate = new Date(),
-            minutes = $F('dur_day') * 1440;
-
-        minutes += $F('dur_hour') * 60;
-        minutes += parseInt($F('dur_min'));
-        msecs = minutes * 60000;
-
-        endDate.setTime(startDate.getTime() + msecs);
-
-        endYear = endDate.getFullYear();
-
-        $('end_year').setValue(endYear);
-        $('end_month').selectedIndex = endDate.getMonth();
-        $('end_day').selectedIndex = endDate.getDate() - 1;
-
-        endHour = endDate.getHours();
-        if (!KronolithVar.twentyFour) {
-            if (endHour < 12) {
-                if (endHour === 0) {
-                    endHour = 12;
-                }
-                $('eam').setValue(1);
-            } else {
-                if (endHour > 12) {
-                    endHour -= 12;
-                }
-                $('epm').setValue(1);
-            }
-            endHour -= 1;
-       }
-
-        $('end_hour').selectedIndex = endHour;
-        $('end_min').selectedIndex = endDate.getMinutes() / 5;
-
-        this.updateWday('end');
-    },
-
-    // Converts a 12 hour based number to its 24 hour format
-    convertTo24Hour: function(val, elt)
-    {
-        if (!KronolithVar.twentyFour) {
-            if ($F(elt)) {
-                if (val != 12) {
-                    val += 12;
-                }
-            } else if (val == 12) {
-                val = 0;
-            }
-        }
-
-        return val;
-    },
-
-    setInterval: function(elt, id)
-    {
-        if (!$F(id)) {
-            $(elt).setValue(1);
-        }
-
-        switch (id) {
-        case 'recur_daily_interval':
-            KronolithEdit.clearFields(1);
-            break;
-
-        case 'recur_weekly_interval':
-            KronolithEdit.clearFields(2);
-            break;
-
-        case 'recur_day_of_month_interval':
-            KronolithEdit.clearFields(3);
-            break;
-
-        case 'recur_week_of_month_interval':
-            KronolithEdit.clearFields(4);
-            break;
-
-        case 'recur_yearly_interval':
-            KronolithEdit.clearFields(5);
-            break;
-        }
-    },
-
-    setRecur: function(index)
-    {
-        document.eventform.recur[index].checked = true;
-        KronolithEdit.clearFields(index);
-    },
-
-    clearFields: function(index)
-    {
-        if (index != 1) {
-            $('recur_daily_interval').setValue('');
-        }
-        if (index != 2) {
-            $('recur_weekly_interval').setValue('');
-            $('recur_weekly_interval').adjacent('.checkbox').invoke('setValue', 0);
-        }
-        if (index != 3) {
-            $('recur_day_of_month_interval').setValue('');
-        }
-        if (index != 4) {
-            $('recur_week_of_month_interval').setValue('');
-        }
-        if (index != 5) {
-            $('recur_yearly_interval').setValue('');
-        }
-    },
-
-    onDomLoad: function()
-    {
-        this.updateWday('start');
-        this.updateWday('end');
-        if ($('recur_end_wday')) {
-            this.updateWday('recur_end');
-        }
-        $('eventform').observe('click', this.clickHandler.bindAsEventListener(this));
-        $('eventform').observe('change', this.changeHandler.bindAsEventListener(this));
-        $('eventform').observe('keypress', this.keypressHandler.bindAsEventListener(this));
-
-        $('title').focus();
-    }
-
-};
-
-document.observe('dom:loaded', KronolithEdit.onDomLoad.bind(KronolithEdit));
-document.observe('Horde_Calendar:select', KronolithEdit.calendarSelect.bindAsEventListener(KronolithEdit));
diff -pruN 4.2.23-1/kronolith-4.2.23/js/embed.js 4.2.27-1/kronolith-4.2.23/js/embed.js
--- 4.2.23-1/kronolith-4.2.23/js/embed.js	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/js/embed.js	1970-01-01 00:00:00.000000000 +0000
@@ -1,7 +0,0 @@
-Event.observe(window, 'load', function() {
-    var nodeCount = kronolithNodes.length;
-    for (var n = 0; n < nodeCount; n++) {
-        var j = kronolithNodes[n];
-        $(j).update(kronolith[j]);
-    }
-});
diff -pruN 4.2.23-1/kronolith-4.2.23/js/goto.js 4.2.27-1/kronolith-4.2.23/js/goto.js
--- 4.2.23-1/kronolith-4.2.23/js/goto.js	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/js/goto.js	1970-01-01 00:00:00.000000000 +0000
@@ -1,68 +0,0 @@
-/**
- * goto.js - Menu goto handling.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author   Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @package  Kronolith
- */
-
-var KronolithGoto =
-{
-    // Variables defined externally: dayurl, monthurl, weekurl, yearurl
-
-    calendarSelect: function(e, type)
-    {
-        // Only trigger if this is the goto menu.
-        if (!e.findElement('A.kgotomenu')) {
-            return;
-        }
-
-        var q, url,
-            params = $H({ date: e.memo.getFullYear() + (e.memo.getMonth() + 1).toPaddedString(2) + (e.memo.getDate()).toPaddedString(2) });
-
-        switch (type) {
-        case 'day':
-            url = this.dayurl;
-            break;
-
-        case 'month':
-            url = this.monthurl;
-            break;
-
-        case 'week':
-            url = this.weekurl;
-            break;
-
-        case 'year':
-            url = this.yearurl;
-            break;
-        }
-
-        q = url.indexOf('?');
-        if (q != -1) {
-            params.update(url.toQueryParams());
-            url = url.substring(0, q);
-        }
-
-        window.location = url + '?' + params.toQueryString();
-    },
-
-    onDomLoad: function()
-    {
-        $('horde-sidebar').down('A.kgotomenu').observe('click', function(e) {
-            Horde_Calendar.open(e.element(), Object.isUndefined(window.KronolithDate) ? new Date() : window.KronolithDate);
-        });
-    }
-
-};
-
-document.observe('dom:loaded', KronolithGoto.onDomLoad.bind(KronolithGoto));
-document.observe('Horde_Calendar:select', KronolithGoto.calendarSelect.bindAsEventListener(KronolithGoto, 'day'));
-document.observe('Horde_Calendar:selectMonth', KronolithGoto.calendarSelect.bindAsEventListener(KronolithGoto, 'month'));
-document.observe('Horde_Calendar:selectWeek', KronolithGoto.calendarSelect.bindAsEventListener(KronolithGoto, 'week'));
-document.observe('Horde_Calendar:selectYear', KronolithGoto.calendarSelect.bindAsEventListener(KronolithGoto, 'year'));
diff -pruN 4.2.23-1/kronolith-4.2.23/js/kronolith.js 4.2.27-1/kronolith-4.2.23/js/kronolith.js
--- 4.2.23-1/kronolith-4.2.23/js/kronolith.js	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/js/kronolith.js	1970-01-01 00:00:00.000000000 +0000
@@ -1,7239 +0,0 @@
-/**
- * kronolith.js - Base application logic.
- *
- * TODO: loadingImg()
- *
- * Copyright 2008-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author Jan Schneider <jan@horde.org>
- */
-
-/* Kronolith object. */
-KronolithCore = {
-    // Vars used and defaulting to null/false:
-    //   weekSizes, daySizes,
-    //   groupLoading, colorPicker, duration, timeMarker, monthDays,
-    //   allDays, eventsWeek, initialized
-
-    view: '',
-    ecache: $H(),
-    cacheStart: null,
-    cacheEnd: null,
-    holidays: [],
-    tcache: $H(),
-    eventsLoading: {},
-    loading: 0,
-    viewLoading: [],
-    fbLoading: 0,
-    redBoxLoading: false,
-    date: Date.today(),
-    tasktype: 'incomplete',
-    knl: {},
-    wrongFormat: $H(),
-    mapMarker: null,
-    map: null,
-    mapInitialized: false,
-    freeBusy: $H(),
-    search: 'future',
-    effectDur: 0.4,
-    macos: navigator.appVersion.indexOf('Mac') != -1,
-    orstart: null,
-    orend: null,
-    lastRecurType: 'None',
-    uatts: null,
-    ucb: null,
-    resourceACCache: { choices: [], map: $H() },
-    paramsCache: null,
-    attendees: [],
-    resources: [],
-
-    /**
-     * Flag that indicates if the event currently displayed in the event
-     * properties window is a recurring event.
-     *
-     * @type boolean
-     */
-    recurs: false,
-
-    /**
-     * The location that was open before the current location.
-     *
-     * @var string
-     */
-    lastLocation: '',
-
-    /**
-     * The currently open location.
-     *
-     * @var string
-     */
-    openLocation: '',
-
-    /**
-     * The current (main) location.
-     *
-     * This is different from openLocation as it isn't updated for any
-     * locations that are opened in a popup view, e.g. events.
-     *
-     * @var string
-     */
-    currentLocation: '',
-
-    kronolithBody: $('kronolithBody'),
-
-    onException: function(parentfunc, r, e)
-    {
-        /* Make sure loading images are closed. */
-        this.loading--;
-        if (!this.loading) {
-            $('kronolithLoading').hide();
-        }
-        this.closeRedBox();
-        HordeCore.notify(HordeCore.text.ajax_error, 'horde.error');
-        parentfunc(r, e);
-    },
-
-    setTitle: function(title)
-    {
-        document.title = Kronolith.conf.name + ' :: ' + title;
-        return title;
-    },
-
-    // url = (string) URL to redirect to
-    // hash = (boolean) If true, url is treated as hash information to alter
-    //        on the current page
-    redirect: function(url, hash)
-    {
-        if (hash) {
-            window.location.hash = escape(url);
-            window.location.reload();
-        } else {
-            HordeCore.redirect(url);
-        }
-    },
-
-    go: function(fullloc, data)
-    {
-        if (!this.initialized) {
-            this.go.bind(this, fullloc, data).defer();
-            return;
-        }
-
-        if (this.viewLoading.size()) {
-            this.viewLoading.push([ fullloc, data ]);
-            return;
-        }
-
-        var locParts = fullloc.split(':');
-        var loc = locParts.shift();
-
-        if (this.openLocation == fullloc) {
-            return;
-        }
-
-        this.viewLoading.push([ fullloc, data ]);
-
-        if (loc != 'search') {
-            HordeTopbar.searchGhost.reset();
-        }
-
-        this.switchTaskView(false);
-
-        switch (loc) {
-        case 'day':
-        case 'week':
-        case 'workweek':
-        case 'month':
-        case 'year':
-        case 'agenda':
-        case 'tasks':
-            this.closeView(loc);
-            var locCap = loc.capitalize();
-            $('kronolithNav' + locCap).up().addClassName('horde-active');
-
-            switch (loc) {
-            case 'day':
-            case 'agenda':
-            case 'week':
-            case 'workweek':
-            case 'month':
-            case 'year':
-                var date = locParts.shift();
-                if (date) {
-                    date = this.parseDate(date);
-                } else {
-                    date = this.date;
-                }
-
-                if (this.view != 'agenda' &&
-                    this.view == loc &&
-                    date.getYear() == this.date.getYear() &&
-                    ((loc == 'year') ||
-                     (loc == 'month' && date.getMonth() == this.date.getMonth()) ||
-                     ((loc == 'week' || loc == 'workweek') && date.getRealWeek() == this.date.getRealWeek()) ||
-                     ((loc == 'day'  || loc == 'agenda') && date.dateString() == this.date.dateString()))) {
-                         this.setViewTitle(date, loc);
-                         this.addHistory(fullloc);
-                         this.loadNextView();
-                         return;
-                }
-
-                this.addHistory(fullloc);
-                this.view = loc;
-                this.date = date;
-                this.updateView(date, loc);
-                var dates = this.viewDates(date, loc);
-                this.loadEvents(dates[0], dates[1], loc);
-                $('kronolithView' + locCap).appear({
-                        duration: this.effectDur,
-                        queue: 'end',
-                        afterFinish: function() {
-                            if (loc == 'week' || loc == 'workweek' || loc == 'day') {
-                                this.calculateRowSizes(loc + 'Sizes', 'kronolithView' + locCap);
-                                if ($('kronolithTimeMarker')) {
-                                    this.positionTimeMarker();
-                                }
-                                if ($('kronolithTimeMarker')) {
-                                    $('kronolithTimeMarker').show();
-                                }
-                                // Scroll to the work day start time.
-                                $('kronolithView' + locCap).down('.kronolithViewBody').scrollTop = 9 * this[loc + 'Sizes'].height;
-                            }
-                            this.loadNextView();
-                        }.bind(this)
-                });
-                $('kronolithLoading' + loc).insert($('kronolithLoading').remove());
-                this.updateMinical(date, loc);
-
-                break;
-
-            case 'tasks':
-                var tasktype = locParts.shift() || this.tasktype;
-
-
-                this.switchTaskView(true);
-                $('kronolithCurrent')
-                    .update(this.setTitle(Kronolith.text.tasks));
-                if (this.view == loc && this.tasktype == tasktype) {
-                    this.addHistory(fullloc);
-                    this.loadNextView();
-                    return;
-                }
-                if (!$w('all complete incomplete future').include(tasktype)) {
-                    this.loadNextView();
-                    return;
-                }
-
-                this.addHistory(fullloc);
-                this.view = loc;
-                this.tasktype = tasktype;
-                $w('All Complete Incomplete Future').each(function(tasktype) {
-                    $('kronolithTasks' + tasktype).up().removeClassName('horde-active');
-                });
-                $('kronolithTasks' + this.tasktype.capitalize()).up().addClassName('horde-active');
-                this.loadTasks(this.tasktype);
-                $('kronolithView' + locCap).appear({
-                    duration: this.effectDur,
-                    queue: 'end',
-                    afterFinish: function() {
-                        this.loadNextView();
-                    }.bind(this) });
-                $('kronolithLoading' + loc).insert($('kronolithLoading').remove());
-                this.updateMinical(this.date);
-
-                break;
-
-            default:
-                if (!$('kronolithView' + locCap)) {
-                    break;
-                }
-                this.addHistory(fullloc);
-                this.view = loc;
-                $('kronolithView' + locCap).appear({
-                    duration: this.effectDur,
-                    queue: 'end',
-                    afterFinish: function() {
-                        this.loadNextView();
-                    }.bind(this) });
-                break;
-            }
-
-            break;
-
-        case 'search':
-            var cals = [], time = locParts[0], term = locParts[1],
-                query = Object.toJSON({ title: term });
-
-            if (!($w('all past future').include(time))) {
-                this.loadNextView();
-                return;
-            }
-
-            this.addHistory(fullloc);
-            this.search = time;
-            $w('All Past Future').each(function(time) {
-                $('kronolithSearch' + time).up().removeClassName('horde-active');
-            });
-            $('kronolithSearch' + this.search.capitalize()).up().addClassName('horde-active');
-            this.closeView('agenda');
-            this.view = 'agenda';
-            this.updateView(null, 'search', term);
-            $H(Kronolith.conf.calendars).each(function(type) {
-                $H(type.value).each(function(calendar) {
-                    if (calendar.value.show) {
-                        cals.push(type.key + '|' + calendar.key);
-                    }
-                });
-            });
-            $('kronolithAgendaNoItems').hide();
-            this.startLoading('search', query);
-
-            HordeCore.doAction('searchEvents', {
-                cals: Object.toJSON(cals),
-                query: query,
-                time: this.search
-            }, {
-                callback: function(r) {
-                    // Hide spinner.
-                    this.loading--;
-                    if (!this.loading) {
-                        $('kronolithLoading').hide();
-                    }
-                    if (r.view != 'search' ||
-                        r.query != this.eventsLoading.search) {
-                        return;
-                    }
-                    if (Object.isUndefined(r.events)) {
-                        $('kronolithAgendaNoItems').show();
-                        return;
-                    }
-                    delete this.eventsLoading.search;
-                    $H(r.events).each(function(calendars) {
-                        $H(calendars.value).each(function(events) {
-                            this.createAgendaDay(events.key);
-                            $H(events.value).each(function(event) {
-                                event.value.calendar = calendars.key;
-                                event.value.start = Date.parse(event.value.s);
-                                event.value.end = Date.parse(event.value.e);
-                                this.insertEvent(event, events.key, 'agenda');
-                            }, this);
-                        }, this);
-                    }, this);
-                }.bind(this)
-            });
-
-            $('kronolithViewAgenda').appear({
-                duration: this.effectDur,
-                queue: 'end',
-                afterFinish: function() {
-                    this.loadNextView();
-                }.bind(this) });
-            $('kronolithLoadingagenda').insert($('kronolithLoading').remove());
-            this.updateMinical(this.date);
-            break;
-
-        case 'event':
-            // Load view first if necessary.
-            if (!this.view ) {
-                this.viewLoading.pop();
-                this.go(Kronolith.conf.login_view);
-                this.go.bind(this, fullloc, data).defer();
-                return;
-            }
-
-            if (this.currentLocation == fullloc) {
-                this.loadNextView();
-                return;
-            }
-
-            this.addHistory(fullloc, false);
-            switch (locParts.length) {
-            case 0:
-                // New event.
-                this.editEvent();
-                break;
-            case 1:
-                // New event on a certain date.
-                this.editEvent(null, null, locParts[0]);
-                break;
-            default:
-                // Editing event.
-                var date = locParts.pop(),
-                    event = locParts.pop(),
-                    calendar = locParts.join(':');
-                this.editEvent(calendar, event, date);
-                break;
-            }
-            this.loadNextView();
-            break;
-
-        case 'task':
-            // Load view first if necessary.
-            if (!this.view ) {
-                this.viewLoading.pop();
-                this.go('tasks');
-                this.go.bind(this, fullloc, data).defer();
-                return;
-            }
-
-            this.switchTaskView(true);
-            switch (locParts.length) {
-            case 0:
-                this.addHistory(fullloc, false);
-                this.editTask();
-                break;
-            case 2:
-                this.addHistory(fullloc, false);
-                this.editTask(locParts[0], locParts[1]);
-                break;
-            }
-            this.loadNextView();
-            break;
-
-        case 'calendar':
-            if (!this.view) {
-                this.viewLoading.pop();
-                this.go(Kronolith.conf.login_view);
-                this.go.bind(this, fullloc, data).defer();
-                return;
-            }
-            this.addHistory(fullloc, false);
-            this.editCalendar(locParts.join(':'));
-            this.loadNextView();
-            break;
-
-        default:
-            this.loadNextView();
-            break;
-        }
-    },
-
-    /**
-     * Removes the last loaded view from the stack and loads the last added
-     * view, if the stack is still not empty.
-     *
-     * We want to load views from a LIFO queue, because the queue is only
-     * building up if the user switches to another view while the current view
-     * still loads. In that case we can go directly to the most recently
-     * clicked view and drop the remaining queue.
-     */
-    loadNextView: function()
-    {
-        var current = this.viewLoading.shift();
-        if (this.viewLoading.size()) {
-            var next = this.viewLoading.pop();
-            this.viewLoading = [];
-            if (current[0] != next[0] || current[1] || next[1]) {
-                this.go(next[0], next[1]);
-            }
-        }
-    },
-
-    /**
-     * Rebuilds one of the calendar views for a new date.
-     *
-     * @param Date date    The date to show in the calendar.
-     * @param string view  The view that's rebuilt.
-     * @param mixed data   Any additional data that might be required.
-     */
-    updateView: function(date, view, data)
-    {
-        this.holidays = [];
-
-        switch (view) {
-        case 'day':
-            var today = Date.today();
-            this.dayEvents = [];
-            this.dayGroups = [];
-            this.allDayEvents = [];
-            $('kronolithCurrent')
-                .update(this.setViewTitle(date, view, data));
-            $('kronolithViewDay')
-                .down('.kronolithAllDayContainer')
-                .store('date', date.dateString());
-            $('kronolithEventsDay').store('date', date.dateString());
-            if (date.equals(today)) {
-                this.addTimeMarker('kronolithEventsDay');
-            }
-            break;
-
-        case 'week':
-        case 'workweek':
-            this.dayEvents = [];
-            this.dayGroups = [];
-            this.allDayEvents = [];
-            this.allDays = {};
-            this.eventsWeek = {};
-            var what = view == 'week' ? 'Week' : 'Workweek',
-                div = $('kronolithEvents' + what).down('div'),
-                th = $('kronolithView' + what + 'Head').down('.kronolithWeekDay'),
-                td = $('kronolithView' + what + 'Head').down('tbody td').next('td'),
-                hourRow = $('kronolithView' + what + 'Body').down('tr'),
-                dates = this.viewDates(date, view),
-                today = Date.today(),
-                day, dateString, i, hourCol;
-
-            $('kronolithCurrent')
-                .update(this.setViewTitle(date, view, data));
-
-            for (i = 0; i < 24; i++) {
-                day = dates[0].clone();
-                hourCol = hourRow.down('td').next('td');
-                while (hourCol) {
-                    hourCol.removeClassName('kronolith-today');
-                    if (day.equals(today)) {
-                        hourCol.addClassName('kronolith-today');
-                    }
-                    hourCol = hourCol.next('td');
-                    day.next().day();
-                }
-                hourRow = hourRow.next('tr');
-            }
-            day = dates[0].clone();
-
-            for (i = 0; i < (view == 'week' ? 7 : 5); i++) {
-                dateString = day.dateString();
-                this.allDays['kronolithAllDay' + dateString] = td.down('div');
-                this.eventsWeek['kronolithEvents' + what + dateString] = div;
-                div.store('date', dateString)
-                    .writeAttribute('id', 'kronolithEvents' + what + dateString);
-                th.store('date', dateString)
-                    .down('span').update(day.toString('dddd, d'));
-                td.removeClassName('kronolith-today');
-                this.allDays['kronolithAllDay' + dateString]
-                    .writeAttribute('id', 'kronolithAllDay' + dateString)
-                    .store('date', dateString);
-                if (day.equals(today)) {
-                    td.addClassName('kronolith-today');
-                    this.addTimeMarker('kronolithEvents' + what + dateString);
-                }
-                new Drop(td.down('div'));
-                div = div.next('div');
-                th = th.next('td');
-                td = td.next('td');
-                day.next().day();
-            }
-            break;
-
-        case 'month':
-            var tbody = $('kronolith-month-body'),
-                dates = this.viewDates(date, view),
-                day = dates[0].clone();
-
-            $('kronolithCurrent')
-                .update(this.setViewTitle(date, view, data));
-
-            // Remove old rows. Maybe we should only rebuild the calendars if
-            // necessary.
-            tbody.childElements().each(function(row) {
-                if (row.identify() != 'kronolithRowTemplate') {
-                    row.purge();
-                    row.remove();
-                }
-            });
-
-            // Build new calendar view.
-            this.monthDays = {};
-            while (!day.isAfter(dates[1])) {
-                tbody.insert(this.createWeekRow(day, date.getMonth(), dates).show());
-                day.next().week();
-            }
-            this.equalRowHeights(tbody);
-
-            break;
-
-        case 'year':
-            var month;
-
-            $('kronolithCurrent').update(this.setViewTitle(date, view, data));
-
-            // Build new calendar view.
-            for (month = 0; month < 12; month++) {
-                $('kronolithYear' + month).update(this.createYearMonth(date.getFullYear(), month, 'kronolithYear').show());
-            }
-
-            break;
-
-        case 'agenda':
-        case 'search':
-            // Agenda days are only created on demand, if there are any events
-            // to add.
-            if (view == 'agenda') {
-                var dates = this.viewDates(date, view);
-                $('kronolithCurrent')
-                    .update(this.setViewTitle(date, view, data));
-                $('kronolithSearchNavigation').up().up().hide();
-            } else {
-                $('kronolithCurrent')
-                    .update(this.setViewTitle(date, view, data));
-                $('kronolithSearchNavigation').up().up().show();
-            }
-
-            // Remove old rows. Maybe we should only rebuild the calendars if
-            // necessary.
-            tbody = $('kronolithViewAgendaBody').childElements().each(function(row) {
-                if (row.identify() != 'kronolithAgendaTemplate' &&
-                    row.identify() != 'kronolithAgendaNoItems') {
-                    row.purge();
-                    row.remove();
-                }
-            });
-
-            break;
-        }
-    },
-
-    /**
-     * Sets the browser title of the calendar views.
-     *
-     * @param Date date    The date to show in the calendar.
-     * @param string view  The view that's displayed.
-     * @param mixed data   Any additional data that might be required.
-     */
-    setViewTitle: function(date, view, data)
-    {
-        switch (view) {
-        case 'day':
-            return this.setTitle(date.toString('D'));
-
-        case 'week':
-        case 'workweek':
-            var dates = this.viewDates(date, view);
-            return this.setTitle(dates[0].toString(Kronolith.conf.date_format) + ' - ' + dates[1].toString(Kronolith.conf.date_format));
-
-        case 'month':
-            return this.setTitle(date.toString('MMMM yyyy'));
-
-        case 'year':
-            return this.setTitle(date.toString('yyyy'));
-
-        case 'agenda':
-            var dates = this.viewDates(date, view);
-            return this.setTitle(dates[0].toString(Kronolith.conf.date_format) + ' - ' + dates[1].toString(Kronolith.conf.date_format));
-
-        case 'search':
-            return this.setTitle(Kronolith.text.searching.interpolate({ term: data })).escapeHTML();
-        }
-    },
-
-    /**
-     * Closes the currently active view.
-     */
-    closeView: function(loc)
-    {
-        $w('Day Workweek Week Month Year Tasks Agenda').each(function(a) {
-            a = $('kronolithNav' + a);
-            if (a) {
-                a.up().removeClassName('horde-active');
-            }
-        });
-        if (this.view && this.view != loc) {
-            $('kronolithView' + this.view.capitalize()).fade({
-                duration: this.effectDur,
-                queue: 'end'
-            });
-            this.view = null;
-        }
-    },
-
-    /**
-     * Creates a single row of day cells for usage in the month and multi-week
-     * views.
-     *
-     * @param Date date        The first day to show in the row.
-     * @param integer month    The current month. Days not from the current
-     *                         month get the kronolith-other-month CSS class
-     *                         assigned.
-     * @param array viewDates  Array of Date objects with the start and end
-     *                         dates of the view.
-     *
-     * @return Element  The element rendering a week row.
-     */
-    createWeekRow: function(date, month, viewDates)
-    {
-        var day = date.clone(), today = new Date().dateString(),
-            row, cell, dateString;
-
-        // Create a copy of the row template.
-        row = $('kronolithRowTemplate').clone(true);
-        row.removeAttribute('id');
-
-        // Fill week number and day cells.
-        cell = row.down()
-            .setText(date.getRealWeek())
-            .store('date', date.dateString())
-            .next();
-        while (cell) {
-            dateString = day.dateString();
-            this.monthDays['kronolithMonthDay' + dateString] = cell;
-            cell.id = 'kronolithMonthDay' + dateString;
-            cell.store('date', dateString);
-            cell.removeClassName('kronolith-other-month').removeClassName('kronolith-today');
-            if (day.getMonth() != month) {
-                cell.addClassName('kronolith-other-month');
-            }
-            if (dateString == today) {
-                cell.addClassName('kronolith-today');
-            }
-            new Drop(cell);
-            cell.store('date', dateString)
-                .down('.kronolith-day')
-                .store('date', dateString)
-                .update(day.getDate());
-
-            cell = cell.next();
-            day.add(1).day();
-        }
-
-        return row;
-    },
-
-    /**
-     * Creates a table row for a single day in the agenda view, if it doesn't
-     * exist yet.
-     *
-     * @param string date    The day to show in the row.
-     *
-     * @return Element  The element rendering a week row.
-     */
-    createAgendaDay: function(date)
-    {
-        // Exit if row exists already.
-        if ($('kronolithAgendaDay' + date)) {
-            return;
-        }
-
-        // Create a copy of the row template.
-        var body = $('kronolithViewAgendaBody'),
-            row = $('kronolithAgendaTemplate').clone(true);
-
-        // Fill week number and day cells.
-        row.store('date', date)
-            .down()
-            .setText(this.parseDate(date).toString('D'))
-            .next()
-            .writeAttribute('id', 'kronolithAgendaDay' + date);
-        row.removeAttribute('id');
-
-        // Insert row.
-        var nextRow;
-        body.childElements().each(function(elm) {
-            if (elm.retrieve('date') > date) {
-                nextRow = elm;
-                throw $break;
-            }
-        });
-        if (nextRow) {
-            nextRow.insert({ before: row.show() });
-        } else {
-            body.insert(row.show());
-        }
-
-        return row;
-    },
-
-    /**
-     * Creates a table for a single month in the year view.
-     *
-     * @param integer year     The year.
-     * @param integer month    The month.
-     * @param string idPrefix  If present, each day will get a DOM ID with this
-     *                         prefix
-     *
-     * @return Element  The element rendering a month table.
-     */
-    createYearMonth: function(year, month, idPrefix)
-    {
-        // Create a copy of the month template.
-        var table = $('kronolithYearTemplate').clone(true),
-            tbody = table.down('tbody');
-        table.removeAttribute('id');
-        tbody.writeAttribute('id', 'kronolithYearTable' + month);
-
-        // Set month name.
-        table.down('tr.kronolith-minical-nav th')
-            .store('date', year.toPaddedString(4) + (month + 1).toPaddedString(2) + '01')
-            .update(Date.CultureInfo.monthNames[month]);
-
-        // Build month table.
-        this.buildMinical(tbody, new Date(year, month, 1), null, idPrefix, year);
-
-        return table;
-    },
-
-    equalRowHeights: function(tbody)
-    {
-        var children = tbody.childElements();
-        children.invoke('setStyle', { height: (100 / (children.size() - 1)) + '%' });
-    },
-
-    /**
-     * Calculates some dimensions for the day and week view.
-     *
-     * @param string storage  Property name where the dimensions are stored.
-     * @param string view     DOM node ID of the view.
-     */
-    calculateRowSizes: function(storage, view)
-    {
-        if (!Object.isUndefined(this[storage])) {
-            return;
-        }
-
-        var td = $(view).down('.kronolithViewBody tr td').next('td'),
-            layout = td.getLayout(),
-            spacing = td.up('table').getStyle('borderSpacing');
-
-        // FIXME: spacing is hardcoded for IE 7 because it doesn't know about
-        // border-spacing, but still uses it. WTF?
-        spacing = spacing ? parseInt($w(spacing)[1], 10) : 2;
-        this[storage] = {};
-        this[storage].height = layout.get('margin-box-height') + spacing;
-        this[storage].spacing = this[storage].height - layout.get('padding-box-height') - layout.get('border-bottom');
-    },
-
-    /**
-     * Adds a horizontal ruler representing the current time to the specified
-     * container.
-     *
-     * @param string|Element  The container of the current day.
-     */
-    addTimeMarker: function(container)
-    {
-        if ($('kronolithTimeMarker')) {
-            $('kronolithTimeMarker').remove();
-            this.timeMarker.stop();
-        }
-        $(container).insert(new Element('div', { id: 'kronolithTimeMarker' }).setStyle({ position: 'absolute' }).hide());
-        this.timeMarker = new PeriodicalExecuter(this.positionTimeMarker.bind(this), 60);
-    },
-
-    /**
-     * Updates the horizontal ruler representing the current time.
-     */
-    positionTimeMarker: function()
-    {
-        var today = Date.today(), now;
-
-        switch (this.view) {
-        case 'day':
-            if (!this.date.equals(today)) {
-                $('kronolithTimeMarker').remove();
-                this.timeMarker.stop();
-                return;
-            }
-            break;
-        case 'week':
-        case 'workweek':
-            if ($('kronolithTimeMarker').up().retrieve('date') != today.dateString()) {
-                var newContainer = this.eventsWeek['kronolithEvents' + (this.view == 'week' ? 'Week' : 'Workweek') + today.dateString()];
-                $('kronolithTimeMarker').remove();
-                if (newContainer) {
-                    this.addTimeMarker(newContainer);
-                } else {
-                    this.timeMarker.stop();
-                }
-                return;
-            }
-            break;
-        default:
-            $('kronolithTimeMarker').remove();
-            this.timeMarker.stop();
-            return;
-        }
-
-        now = new Date();
-        $('kronolithTimeMarker').setStyle({ top: ((now.getHours() * 60 + now.getMinutes()) * this[this.view + 'Sizes'].height / 60 | 0) + 'px' });
-    },
-
-    /**
-     * Rebuilds the mini calendar.
-     *
-     * @param Date date    The date to show in the calendar.
-     * @param string view  The view that's displayed, determines which days in
-     *                     the mini calendar are highlighted.
-     */
-    updateMinical: function(date, view)
-    {
-        // Update header.
-        $('kronolithMinicalDate')
-            .store('date', date.dateString())
-            .update(date.toString('MMMM yyyy'));
-
-        this.buildMinical($('kronolith-minical').down('tbody'), date, view);
-    },
-
-    /**
-     * Creates a mini calendar suitable for the navigation calendar and the
-     * year view.
-     *
-     * @param Element tbody    The table body to add the days to.
-     * @param Date date        The date to show in the calendar.
-     * @param string view      The view that's displayed, determines which days
-     *                         in the mini calendar are highlighted.
-     * @param string idPrefix  If present, each day will get a DOM ID with this
-     *                         prefix
-     * @param integer year     If present, generating mini calendars for the
-     *                         year view of this year.
-     */
-    buildMinical: function(tbody, date, view, idPrefix, year)
-    {
-        var dates = this.viewDates(date, 'month'),
-            day = dates[0].clone(),
-            date7 = date.clone().add(1).week(),
-            today = Date.today(),
-            week = this.viewDates(this.date, 'week'),
-            workweek = this.viewDates(this.date, 'workweek'),
-            dateString, td, tr, i;
-
-        // Remove old calendar rows. Maybe we should only rebuild the minical
-        // if necessary.
-        tbody.childElements().invoke('remove');
-
-        for (i = 0; i < 42; i++) {
-            dateString = day.dateString();
-            // Create calendar row and insert week number.
-            if (day.getDay() == Kronolith.conf.week_start) {
-                tr = new Element('tr');
-                tbody.insert(tr);
-                td = new Element('td', { className: 'kronolith-minical-week' })
-                    .store('weekdate', dateString);
-                td.update(day.getRealWeek());
-                tr.insert(td);
-                weekStart = day.clone();
-                weekEnd = day.clone();
-                weekEnd.add(6).days();
-            }
-
-            // Insert day cell.
-            td = new Element('td').store('date', dateString);
-            if (day.getMonth() != date.getMonth()) {
-                td.addClassName('kronolith-other-month');
-            } else if (!Object.isUndefined(idPrefix)) {
-                td.id = idPrefix + dateString;
-            }
-
-            // Highlight days currently being displayed.
-            if (view &&
-                ((view == 'month' && this.date.between(dates[0], dates[1])) ||
-                 (view == 'week' && day.between(week[0], week[1])) ||
-                 (view == 'workweek' && day.between(workweek[0], workweek[1])) ||
-                 (view == 'day' && day.equals(this.date)) ||
-                 (view == 'agenda' && !day.isBefore(date) && day.isBefore(date7)))) {
-                td.addClassName('kronolith-selected');
-            }
-
-            // Highlight today.
-            if (day.equals(today) &&
-                (Object.isUndefined(year) ||
-                 (day.getYear() + 1900 == year &&
-                  date.getMonth() == day.getMonth()))) {
-                td.addClassName('kronolith-today');
-            }
-            td.insert(new Element('a').update(day.getDate()));
-            tr.insert(td);
-            day.next().day();
-        }
-    },
-
-    /**
-     * Inserts a calendar entry in the sidebar menu.
-     *
-     * @param string type  The calendar type.
-     * @param string id    The calendar id.
-     * @param object cal   The calendar object.
-     * @param Element div  Container DIV where to add the entry (optional).
-     */
-    insertCalendarInList: function(type, id, cal, div)
-    {
-        var noItems, calendar, link;
-        if (!div) {
-            div = this.getCalendarList(type, cal.owner);
-        }
-        noItems = div.previous();
-        if (noItems &&
-            noItems.tagName == 'DIV' &&
-            noItems.className == 'horde-info') {
-            noItems.hide();
-        }
-        link = new Element('span', { className: type != 'resourcegroup' ? (cal.show ? 'horde-resource-on' : 'horde-resource-off') : 'horde-resource-none' })
-            .insert(cal.name.escapeHTML());
-        calendar = new Element('div')
-            .store('calendar', id)
-            .store('calendarclass', type)
-            .setStyle({ backgroundColor: cal.bg, color: cal.fg });
-        if (type != 'holiday' && type != 'external') {
-            calendar.insert(
-                new Element('span', { className: 'horde-resource-edit-' + cal.fg.substring(1) })
-                    .setStyle({ backgroundColor: cal.bg, color: cal.fg })
-                    .insert('&#9658;'));
-        }
-        calendar.insert(
-            new Element('div', { className: 'horde-resource-link' })
-                .insert(link));
-        this.addShareIcon(cal, link);
-        div.insert(calendar);
-        if (cal.show) {
-            this.addCalendarLegend(type, id, cal);
-        }
-    },
-
-    /**
-     * Add the share icon after the calendar name in the calendar list.
-     *
-     * @param object cal       A calendar object from Kronolith.conf.calendars.
-     * @param Element element  The calendar element in the list.
-     */
-    addShareIcon: function(cal, element)
-    {
-        if (cal.owner && cal.perms) {
-            $H(cal.perms).each(function(perm) {
-                if (perm.key != 'type' &&
-                    ((Object.isArray(perm.value) && perm.value.size()) ||
-                     (!Object.isArray(perm.value) && perm.value))) {
-                    element.insert(' ').insert(new Element('img', { src: Kronolith.conf.images.attendees.replace(/fff/, cal.fg.substring(1)), title: Kronolith.text.shared }));
-                    throw $break;
-                }
-            });
-        }
-    },
-
-    /**
-     * Rebuilds the list of calendars.
-     */
-    updateCalendarList: function()
-    {
-        var ext = $H(), extNames = $H(),
-            extContainer = $('kronolithExternalCalendars');
-
-        $H(Kronolith.conf.calendars.internal).each(function(cal) {
-            this.insertCalendarInList('internal', cal.key, cal.value);
-        }, this);
-
-        if (Kronolith.conf.tasks) {
-            $H(Kronolith.conf.calendars.tasklists).each(function(cal) {
-                this.insertCalendarInList('tasklists', cal.key, cal.value);
-            }, this);
-        }
-
-        if (Kronolith.conf.calendars.resource) {
-            $H(Kronolith.conf.calendars.resource).each(function(cal) {
-               this.insertCalendarInList('resource', cal.key, cal.value);
-            }, this);
-        }
-
-        if (Kronolith.conf.calendars.resourcegroup) {
-            $H(Kronolith.conf.calendars.resourcegroup).each(function(cal) {
-                this.insertCalendarInList('resourcegroup', cal.key, cal.value);
-            }, this);
-        }
-
-        $H(Kronolith.conf.calendars.external).each(function(cal) {
-            var parts = cal.key.split('/'), api = parts.shift();
-            if (!ext.get(api)) {
-                ext.set(api, $H());
-            }
-            ext.get(api).set(parts.join('/'), cal.value);
-            extNames.set(api, cal.value.api ? cal.value.api : Kronolith.text.external_category);
-        });
-        ext.each(function(api) {
-            extContainer
-                .insert(new Element('div', { className: 'horde-sidebar-split' }))
-                .insert(new Element('div')
-                        .insert(new Element('h3')
-                                .insert(new Element('span', { className: 'horde-expand', title: HordeSidebar.text.expand })
-                                        .insert({ bottom: extNames.get(api.key).escapeHTML() })))
-                        .insert(new Element('div', { id: 'kronolithExternalCalendar' + api.key, className: 'horde-resources', style: 'display:none' })));
-            api.value.each(function(cal) {
-                this.insertCalendarInList('external', api.key + '/' + cal.key, cal.value, $('kronolithExternalCalendar' + api.key));
-            }, this);
-        }, this);
-
-        $H(Kronolith.conf.calendars.remote).each(function(cal) {
-            this.insertCalendarInList('remote', cal.key, cal.value);
-        }, this);
-
-        if (Kronolith.conf.calendars.holiday) {
-            $H(Kronolith.conf.calendars.holiday).each(function(cal) {
-                if (cal.value.show) {
-                   this.insertCalendarInList('holiday', cal.key, cal.value);
-                }
-            }, this);
-        } else {
-            $('kronolithAddholiday').up().hide();
-            $('kronolithHolidayCalendars').hide();
-        }
-    },
-
-    /**
-     * Returns the DIV container that holds all calendars of a certain type.
-     *
-     * @param string type  A calendar type
-     *
-     * @return Element  The container of the calendar type.
-     */
-    getCalendarList: function(type, personal)
-    {
-        switch (type) {
-        case 'internal':
-            return personal
-                ? $('kronolithMyCalendars')
-                : $('kronolithSharedCalendars');
-        case 'resource':
-            return $('kronolithResourceCalendars');
-        case 'resourcegroup':
-            return $('kronolithResourceGroups');
-        case 'tasklists':
-            return personal
-                ? $('kronolithMyTasklists')
-                : $('kronolithSharedTasklists');
-        case 'external':
-            return $('kronolithExternalCalendars');
-        case 'remote':
-            return $('kronolithRemoteCalendars');
-        case 'holiday':
-            return $('kronolithHolidayCalendars');
-        }
-    },
-
-    /**
-     * Loads a certain calendar, if the current view is still a calendar view.
-     *
-     * @param string type      The calendar type.
-     * @param string calendar  The calendar id.
-     */
-    loadCalendar: function(type, calendar)
-    {
-        if (Kronolith.conf.calendars[type][calendar].show &&
-            $w('day workweek week month year agenda').include(this.view)) {
-            var dates = this.viewDates(this.date, this.view);
-            this.deleteCache([type, calendar]);
-            this.loadEvents(dates[0], dates[1], this.view, [[type, calendar]]);
-        }
-    },
-
-    /**
-     * Toggles a calendars visibility.
-     *
-     * @param string type      The calendar type.
-     * @param string calendar  The calendar id.
-     */
-    toggleCalendar: function(type, calendar)
-    {
-        var elt = $('kronolithMenuCalendars').select('div').find(function(div) {
-            return div.retrieve('calendarclass') == type &&
-            div.retrieve('calendar') == calendar;
-        }).down('.horde-resource-link').down('span');
-
-        Kronolith.conf.calendars[type][calendar].show = !Kronolith.conf.calendars[type][calendar].show;
-        elt.toggleClassName('horde-resource-on');
-        elt.toggleClassName('horde-resource-off');
-
-        if (Kronolith.conf.calendars[type][calendar].show) {
-            this.addCalendarLegend(type, calendar, Kronolith.conf.calendars[type][calendar]);
-        } else {
-            this.deleteCalendarLegend(type, calendar);
-        }
-
-        switch (this.view) {
-        case 'month':
-        case 'agenda':
-            if (Object.isUndefined(this.ecache.get(type)) ||
-                Object.isUndefined(this.ecache.get(type).get(calendar))) {
-                this.loadCalendar(type, calendar);
-            } else {
-                var allEvents = this.kronolithBody.select('div').findAll(function(el) {
-                    return el.retrieve('calendar') == type + '|' + calendar;
-                });
-                if (this.view == 'month' && Kronolith.conf.max_events) {
-                    var dates = this.viewDates(this.date, this.view);
-                    if (elt.hasClassName('horde-resource-off')) {
-                        var day, more, events, calendars = [];
-                        $H(Kronolith.conf.calendars).each(function(type) {
-                            $H(type.value).each(function(cal) {
-                                if (cal.value.show) {
-                                    calendars.push(type.key + '|' + cal.key);
-                                }
-                            });
-                        });
-                        allEvents.each(function(el) {
-                            if (el.retrieve('calendar').startsWith('holiday|')) {
-                                this.holidays = this.holidays.without(el.retrieve('eventid'));
-                            }
-                            el.remove();
-                        }, this);
-                        for (var date = dates[0]; !date.isAfter(dates[1]); date.add(1).days()) {
-                            day = this.monthDays['kronolithMonthDay' + date.dateString()];
-                            more = day.select('.kronolithMore');
-                            events = day.select('.kronolith-event');
-                            if (more.size() &&
-                                events.size() < Kronolith.conf.max_events) {
-                                more[0].purge();
-                                more[0].remove();
-                                events.invoke('remove');
-                                calendars.each(function(calendar) {
-                                    this.insertEvents([date, date], 'month', calendar);
-                                }, this);
-                            }
-                        }
-                    } else {
-                        this.insertEvents(dates, 'month', type + '|' + calendar);
-                    }
-                } else {
-                    allEvents.invoke('toggle');
-                }
-            }
-            break;
-
-        case 'year':
-        case 'week':
-        case 'workweek':
-        case 'day':
-            if (Object.isUndefined(this.ecache.get(type)) ||
-                Object.isUndefined(this.ecache.get(type).get(calendar))) {
-                this.loadCalendar(type, calendar);
-            } else {
-                this.insertEvents(this.viewDates(this.date, this.view), this.view);
-            }
-            break;
-
-        case 'tasks':
-            if (type != 'tasklists') {
-                break;
-            }
-            var tasklist = calendar.substr(6);
-            if (elt.hasClassName('horde-resource-off')) {
-                $('kronolithViewTasksBody').select('tr').findAll(function(el) {
-                    return el.retrieve('tasklist') == tasklist;
-                }).invoke('remove');
-            } else {
-                this.loadTasks(this.tasktype, [ tasklist ]);
-            }
-            break;
-        }
-
-        if ($w('tasklists remote external holiday resource').include(type)) {
-            calendar = type + '_' + calendar;
-        }
-        HordeCore.doAction('saveCalPref', { toggle_calendar: calendar });
-    },
-
-    /**
-     * Propagates a SELECT drop down list with the editable calendars.
-     *
-     * @param string id  The id of the SELECT element.
-     */
-    updateCalendarDropDown: function(id)
-    {
-        $(id).update();
-        ['internal', 'remote'].each(function(type) {
-            $H(Kronolith.conf.calendars[type]).each(function(cal) {
-                if (cal.value.edit) {
-                    $(id).insert(new Element('option', { value: type + '|' + cal.key })
-                                 .setStyle({ backgroundColor: cal.value.bg, color: cal.value.fg })
-                                 .update(cal.value.name.escapeHTML()));
-                }
-            });
-        });
-    },
-
-    /**
-     * Adds a calendar entry to the print legend.
-     *
-     * @param string type  The calendar type.
-     * @param string id    The calendar id.
-     * @param object cal   The calendar object.
-     */
-    addCalendarLegend: function(type, id, cal)
-    {
-        $('kronolith-legend').insert(
-            new Element('span')
-                .insert(cal.name.escapeHTML())
-                .store('calendar', id)
-                .store('calendarclass', type)
-                .setStyle({ backgroundColor: cal.bg, color: cal.fg })
-        );
-    },
-
-    /**
-     * Deletes a calendar entry from the print legend.
-     *
-     * @param string type  The calendar type.
-     * @param string id    The calendar id.
-     */
-    deleteCalendarLegend: function(type, id)
-    {
-        var legend = $('kronolith-legend').select('span').find(function(span) {
-            return span.retrieve('calendarclass') == type &&
-                span.retrieve('calendar') == id;
-        });
-        if (legend) {
-            legend.remove();
-        }
-    },
-
-    /**
-     * Opens a tab in a form.
-     *
-     * @param Element  The A element of a tab.
-     */
-    openTab: function(elt)
-    {
-        var dialog = elt.up('form'), tab = $(elt.id.replace(/Link/, 'Tab')),
-            field;
-        dialog.select('.kronolithTabsOption').invoke('hide');
-        dialog.select('.tabset li').invoke('removeClassName', 'horde-active');
-        tab.show();
-        elt.up().addClassName('horde-active');
-        if (elt.id == 'kronolithEventLinkMap') {
-            if (!this.mapInitialized) {
-                this.initializeMap();
-            }
-        }
-        field = tab.down('textarea');
-        if (!field) {
-            field = tab.down('input');
-        }
-        if (field) {
-            try {
-                field.focus();
-            } catch (e) {}
-        }
-        switch (tab.identify()) {
-        case 'kronolithEventTabAttendees':
-            this.attendeeStartDateHandler(this.getFBDate());
-            break;
-        case 'kronolithEventTabResources':
-            this.resourceStartDateHandler(this.getFBDate());
-            break;
-        }
-    },
-
-    /**
-     * Sets the load signature and show the loading spinner.
-     *
-     * @param string resource   The loading resource.
-     * @param string signatrue  The signature for this request.
-     */
-    startLoading: function(resource, signature)
-    {
-        this.eventsLoading[resource] = signature;
-        this.loading++;
-        $('kronolithLoading').show();
-    },
-
-    /**
-     */
-    loadEvents: function(firstDay, lastDay, view, calendars)
-    {
-        var loading = false;
-
-        if (typeof calendars == 'undefined') {
-            calendars = [];
-            $H(Kronolith.conf.calendars).each(function(type) {
-                $H(type.value).each(function(cal) {
-                    if (cal.value.show) {
-                        calendars.push([type.key, cal.key]);
-                    }
-                });
-            });
-        }
-
-        calendars.each(function(cal) {
-            var startDay = firstDay.clone(), endDay = lastDay.clone(),
-                cals = this.ecache.get(cal[0]);
-
-            if (typeof cals != 'undefined' &&
-                typeof cals.get(cal[1]) != 'undefined') {
-                cals = cals.get(cal[1]);
-                while (!Object.isUndefined(cals.get(startDay.dateString())) &&
-                       startDay.isBefore(endDay)) {
-                    if (view != 'year') {
-                        this.insertEvents([startDay, startDay], view, cal.join('|'));
-                    }
-                    startDay.add(1).day();
-                }
-                while (!Object.isUndefined(cals.get(endDay.dateString())) &&
-                       (!startDay.isAfter(endDay))) {
-                    if (view != 'year') {
-                        this.insertEvents([endDay, endDay], view, cal.join('|'));
-                    }
-                    endDay.add(-1).day();
-                }
-                if (startDay.compareTo(endDay) > 0) {
-                    return;
-                }
-            }
-            var start = startDay.dateString(), end = endDay.dateString(),
-                calendar = cal.join('|');
-            loading = true;
-            this.startLoading(calendar, start + end);
-            this.storeCache($H(), calendar, null, true);
-
-            HordeCore.doAction('listEvents', {
-                start: start,
-                end: end,
-                cal: calendar,
-                sig: start + end,
-                view: view
-            }, {
-                callback: function(r) {
-                    this.loadEventsCallback(r, true);
-                }.bind(this)
-            });
-        }, this);
-
-        if (!loading && view == 'year') {
-            this.insertEvents([firstDay, lastDay], 'year');
-        }
-    },
-
-    /**
-     * Callback method for inserting events in the current view.
-     *
-     * @param object r             The ajax response object.
-     * @param boolean createCache  Whether to create a cache list entry for the
-     *                             response, if none exists yet. Useful for
-     *                             (not) adding individual events to the cache
-     *                             if it doesn't match any cached views.
-     */
-    loadEventsCallback: function(r, createCache)
-    {
-        // Hide spinner.
-        this.loading--;
-        if (!this.loading) {
-            $('kronolithLoading').hide();
-        }
-
-        var start = this.parseDate(r.sig.substr(0, 8)),
-            end = this.parseDate(r.sig.substr(8, 8)),
-            dates = [start, end],
-            currentDates;
-
-        this.storeCache(r.events || {}, r.cal, dates, createCache);
-
-        // Check if this is the still the result of the most current request.
-        if (r.sig != this.eventsLoading[r.cal]) {
-            return;
-        }
-        delete this.eventsLoading[r.cal];
-
-        // Check if the calendar is still visible.
-        var calendar = r.cal.split('|');
-        if (!Kronolith.conf.calendars[calendar[0]][calendar[1]].show) {
-            return;
-        }
-
-        // Check if the result is still for the current view.
-        currentDates = this.viewDates(this.date, this.view);
-        if (r.view != this.view ||
-            !start.between(currentDates[0], currentDates[1])) {
-
-            return;
-        }
-
-        if (this.view == 'day' ||
-            this.view == 'week' ||
-            this.view == 'workweek' ||
-            this.view == 'month' ||
-            this.view == 'agenda' ||
-            (this.view == 'year' && !$H(this.eventsLoading).size())) {
-            this.insertEvents(dates, this.view, r.cal);
-        }
-    },
-
-    /**
-     * Reads events from the cache and inserts them into the view.
-     *
-     * If inserting events into day and week views, the calendar parameter is
-     * ignored, and events from all visible calendars are inserted instead.
-     * This is necessary because the complete view has to be re-rendered if
-     * events are not in chronological order.
-     * The year view is specially handled too because there are no individual
-     * events, only a summary of all events per day.
-     *
-     * @param Array dates      Start and end of dates to process.
-     * @param string view      The view to update.
-     * @param string calendar  The calendar to update.
-     */
-    insertEvents: function(dates, view, calendar)
-    {
-        switch (view) {
-        case 'day':
-        case 'week':
-        case 'workweek':
-            // The day and week views require the view to be completely
-            // loaded, to correctly calculate the dimensions.
-            if (this.viewLoading.size() || this.view != view) {
-                this.insertEvents.bind(this, [dates[0].clone(), dates[1].clone()], view, calendar).defer();
-                return;
-            }
-            break;
-        }
-
-        var day = dates[0].clone(),
-                  viewDates = this.viewDates(this.date, this.view),
-                  date, more, title, titles, events, monthDay, busyHours;
-        while (!day.isAfter(dates[1])) {
-            // Skip if somehow events slipped in though the view is gone.
-            if (!day.between(viewDates[0], viewDates[1])) {
-                if (window.console) {
-                    window.console.trace();
-                }
-                day.next().day();
-                continue;
-            }
-
-            date = day.dateString();
-            switch (view) {
-            case 'day':
-            case 'week':
-            case 'workweek':
-                this.dayEvents = [];
-                this.dayGroups = [];
-                this.allDayEvents = [];
-                if (view == 'day') {
-                    $$('.kronolith-event').invoke('remove');
-                } else {
-                    this.eventsWeek['kronolithEvents' + (view == 'week' ? 'Week' : 'Workweek') + date]
-                        .select('.kronolith-event')
-                        .invoke('remove');
-                    this.allDays['kronolithAllDay' + date]
-                        .childElements()
-                        .invoke('remove');
-                }
-                break;
-
-            case 'month':
-                monthDay = this.monthDays['kronolithMonthDay' + date];
-                monthDay.select('div')
-                    .findAll(function(el) { return el.retrieve('calendar') == calendar; })
-                    .invoke('remove');
-                break;
-
-            case 'year':
-                titles = [];
-                busyHours = 0;
-            }
-
-            if (view == 'month' || view == 'agenda') {
-                events = this.getCacheForDate(date, calendar);
-            } else {
-                events = this.getCacheForDate(date);
-            }
-            events.sortBy(this.sortEvents).each(function(event) {
-                var insertBefore;
-                switch (view) {
-                case 'month':
-                case 'agenda':
-                    if (calendar.startsWith('holiday|')) {
-                        if (this.holidays.include(event.key)) {
-                            return;
-                        }
-                        this.holidays.push(event.key);
-                    }
-                    if (view == 'month' && Kronolith.conf.max_events) {
-                        more = monthDay.down('.kronolithMore');
-                        if (more) {
-                            more.purge();
-                            more.remove();
-                        }
-                    }
-                    if (view == 'month') {
-                        if (Kronolith.conf.max_events) {
-                        var events = monthDay.select('.kronolith-event');
-                        if (events.size() >= Kronolith.conf.max_events) {
-                            if (date == (new Date().dateString())) {
-                                // This is today.
-                                if (event.value.al || event.value.end.isBefore()) {
-                                    // No room for all-day or finished events.
-                                    this.insertMore(date);
-                                    return;
-                                }
-                                var remove, max;
-                                // Find an event that is earlier than now or
-                                // later then the current event.
-                                events.each(function(elm) {
-                                    var calendar = elm.retrieve('calendar').split('|'),
-                                        event = this.ecache.get(calendar[0]).get(calendar[1]).get(date).get(elm.retrieve('eventid'));
-                                    if (event.start.isBefore()) {
-                                        remove = elm;
-                                        throw $break;
-                                    }
-                                    if (!max || event.start.isAfter(max)) {
-                                        max = event.start;
-                                        remove = elm;
-                                    }
-                                }, this);
-                                if (remove) {
-                                    remove.purge();
-                                    remove.remove();
-                                    insertBefore = this.findInsertBefore(events.without(remove), event, date);
-                                } else {
-                                    this.insertMore(date);
-                                    return;
-                                }
-                            } else {
-                                // Not today.
-                                var allDays = events.findAll(function(elm) {
-                                    var calendar = elm.retrieve('calendar').split('|');
-                                    return this.ecache.get(calendar[0]).get(calendar[1]).get(date).get(elm.retrieve('eventid')).al;
-                                }.bind(this));
-                                if (event.value.al) {
-                                    // We want one all-day event.
-                                    if (allDays.size()) {
-                                        // There already is an all-day event.
-                                        if (event.value.x == Kronolith.conf.status.confirmed ||
-                                            event.value.x == Kronolith.conf.status.tentative) {
-                                            // But is there a less important
-                                            // one?
-                                            var status = [Kronolith.conf.status.free, Kronolith.conf.status.cancelled];
-                                            if (event.value.x == Kronolith.conf.status.confirmed) {
-                                                status.push(Kronolith.conf.status.tentative);
-                                            }
-                                            var free = allDays.detect(function(elm) {
-                                                var calendar = elm.retrieve('calendar').split('|');
-                                                return status.include(this.ecache.get(calendar[0]).get(calendar[1]).get(date).get(elm.retrieve('eventid')).x);
-                                            }.bind(this));
-                                            if (!free) {
-                                                this.insertMore(date);
-                                                return;
-                                            }
-                                            insertBefore = free.next();
-                                            free.purge();
-                                            free.remove();
-                                        } else {
-                                            // No.
-                                            this.insertMore(date);
-                                            return;
-                                        }
-                                    } else {
-                                        // Remove the last event to make room
-                                        // for this one.
-                                        var elm = events.pop();
-                                        elm.purge();
-                                        elm.remove();
-                                        insertBefore = events.first();
-                                    }
-                                } else {
-                                    if (allDays.size() > 1) {
-                                        // We don't want more than one all-day
-                                        // event.
-                                        var elm = allDays.pop();
-                                        // Remove element from events as well.
-                                        events = events.without(elm);
-                                        elm.purge();
-                                        elm.remove();
-                                        insertBefore = this.findInsertBefore(events, event, date);
-                                    } else {
-                                        // This day is full.
-                                        this.insertMore(date);
-                                        return;
-                                    }
-                                }
-                            }
-                            this.insertMore(date);
-                        } else {
-                            insertBefore = this.findInsertBefore(events, event, date);
-                        }
-                        } else {
-                            var events = monthDay.select('.kronolith-event');
-                            insertBefore = this.findInsertBefore(events, event, date);
-                        }
-                    }
-                    break;
-
-                case 'year':
-                    title = '';
-                    if (event.value.al) {
-                        title += Kronolith.text.allday;
-                    } else {
-                        title += event.value.start.toString('t') + '-' + event.value.end.toString('t');
-                    }
-                    if (event.value.t) {
-                        title += ': ' + event.value.t.escapeHTML();
-                    }
-                    if (event.value.x == Kronolith.conf.status.tentative ||
-                        event.value.x == Kronolith.conf.status.confirmed) {
-                        busyHours += event.value.start.getElapsed(event.value.end) / 3600000;
-                    }
-                    titles.push(title);
-                    return;
-                }
-                this.insertEvent(event, date, view, insertBefore);
-            }, this);
-
-            switch (view) {
-            case 'agenda':
-                if ($('kronolithViewAgendaBody').select('tr').length > 2) {
-                    $('kronolithAgendaNoItems').hide();
-                } else {
-                    $('kronolithAgendaNoItems').show();
-                }
-                break;
-
-            case 'year':
-                var td = $('kronolithYear' + date);
-                if (td.className == 'kronolith-minical-empty') {
-                    continue;
-                }
-                if (td.hasClassName('kronolith-today')) {
-                    td.className = 'kronolith-today';
-                } else {
-                    td.className = '';
-                }
-                if (titles.length) {
-                    td.addClassName('kronolithHasEvents');
-                    if (busyHours > 0) {
-                        td.addClassName(this.getHeatmapClass(busyHours));
-                        busyHours = 0;
-                    }
-                    td.down('a').writeAttribute('nicetitle', Object.toJSON(titles));
-                }
-            }
-
-            day.next().day();
-        }
-        // Workaround Firebug bug.
-        Prototype.emptyFunction();
-    },
-
-    findInsertBefore: function(events, event, date)
-    {
-        var insertBefore, insertSort;
-        events.each(function(elm) {
-            var calendar = elm.retrieve('calendar').split('|'),
-                existing = this.ecache
-                    .get(calendar[0])
-                    .get(calendar[1])
-                    .get(date)
-                    .get(elm.retrieve('eventid'));
-            if (event.value.sort < existing.sort &&
-                (!insertSort || existing.sort < insertSort)) {
-                insertBefore = elm;
-                insertSort = existing.sort;
-            }
-        }, this);
-        return insertBefore;
-    },
-
-    getHeatmapClass: function(hours)
-    {
-        return 'heat' + Math.min(Math.ceil(hours / 2), 6);
-    },
-
-    /**
-     * Creates the DOM node for an event bubble and inserts it into the view.
-     *
-     * @param object event    A Hash member with the event to insert.
-     * @param string date     The day to update.
-     * @param string view     The view to update.
-     * @param Element before  Insert the event before this element (month view).
-     */
-    insertEvent: function(event, date, view, before)
-    {
-        var calendar = event.value.calendar.split('|');
-        event.value.nodeId = ('kronolithEvent' + view + event.value.calendar + date + event.key).replace(new RegExp('[^a-zA-Z0-9]', 'g'), '');
-
-        var _createElement = function(event) {
-            var className ='kronolith-event';
-            switch (event.value.x) {
-            case 3:
-                className += ' kronolith-event-cancelled';
-                break;
-            case 1:
-            case 4:
-                className += ' kronolith-event-tentative';
-                break;
-            }
-            var el = new Element('div', { id: event.value.nodeId, className: className })
-                .store('calendar', event.value.calendar)
-                .store('eventid', event.key);
-            if (!Object.isUndefined(event.value.aj)) {
-                el.store('ajax', event.value.aj);
-            }
-            return el;
-        };
-
-        switch (view) {
-        case 'day':
-        case 'week':
-        case 'workweek':
-            var storage = view + 'Sizes',
-                what = view == 'week' ? 'Week' : 'Workweek',
-                div = _createElement(event),
-                margin = view == 'day' ? 1 : 3,
-                style = { backgroundColor: Kronolith.conf.calendars[calendar[0]][calendar[1]].bg,
-                          color: Kronolith.conf.calendars[calendar[0]][calendar[1]].fg };
-
-            div.writeAttribute('title', event.value.t);
-
-            if (event.value.al) {
-                if (view == 'day') {
-                    $('kronolithViewDay').down('.kronolithAllDayContainer').insert(div.setStyle(style));
-                } else {
-                    var allDay = this.allDays['kronolithAllDay' + date],
-                        existing = allDay.childElements(),
-                        weekHead = $('kronolithView' + what + 'Head');
-                    if (existing.size() == 3) {
-                        if (existing[2].className != 'kronolithMore') {
-                            existing[2].purge();
-                            existing[2].remove();
-                            allDay.insert({ bottom: new Element('span', { className: 'kronolithMore' }).store('date', date).insert(Kronolith.text.more) });
-                        }
-                    } else {
-                        allDay.insert(div.setStyle(style));
-                        if (event.value.pe) {
-                            div.addClassName('kronolithEditable');
-                            var layout = div.getLayout(),
-                                minLeft = weekHead.down('.kronolith-first-col').getWidth() + this[storage].spacing + (parseInt(div.getStyle('marginLeft'), 10) || 0),
-                                minTop = weekHead.down('thead').getHeight() + this[storage].spacing + (parseInt(div.getStyle('marginTop'), 10) || 0),
-                                maxLeft = weekHead.getWidth() - layout.get('margin-box-width'),
-                                maxTop = weekHead.down('thead').getHeight() + weekHead.down('.kronolith-all-day').getHeight(),
-                                opts = {
-                                    threshold: 5,
-                                    parentElement: function() {
-                                        return $('kronolithView' + what).down('.kronolith-view-head');
-                                    },
-                                    snap: function(x, y) {
-                                        return [Math.min(Math.max(x, minLeft), maxLeft),
-                                                Math.min(Math.max(y, minTop), maxTop - div.getHeight())];
-                                    }
-                                };
-                            var d = new Drag(event.value.nodeId, opts);
-                            div.store('drags', []);
-                            Object.extend(d, {
-                                event: event,
-                                innerDiv: new Element('div'),
-                                midnight: this.parseDate(date)
-                            });
-                            div.retrieve('drags').push(d);
-                        }
-                    }
-                }
-                break;
-            }
-
-            var midnight = this.parseDate(date),
-                resizable = event.value.pe && (Object.isUndefined(event.value.vl) || event.value.vl),
-                innerDiv = new Element('div', { className: 'kronolith-event-info' }),
-                minHeight = 0, parentElement, draggerTop, draggerBottom,
-                elapsed = (event.value.start.getHours() - midnight.getHours()) * 60 + (event.value.start.getMinutes() - midnight.getMinutes());
-            switch (view) {
-            case 'day':
-                parentElement = $('kronolithEventsDay');
-                break;
-            case 'week':
-                parentElement = this.eventsWeek['kronolithEventsWeek' + date];
-                break;
-            case 'workweek':
-                parentElement = this.eventsWeek['kronolithEventsWorkweek' + date];
-                break;
-            }
-            if (event.value.fi) {
-                div.addClassName('kronolithFirst');
-                if (resizable) {
-                    draggerTop = new Element('div', { id: event.value.nodeId + 'top', className: 'kronolithDragger kronolithDraggerTop' }).setStyle(style);
-                }
-            } else {
-                innerDiv.setStyle({ top: 0 });
-            }
-            if (event.value.la) {
-                div.addClassName('kronolithLast');
-                if (resizable) {
-                    draggerBottom = new Element('div', { id: event.value.nodeId + 'bottom', className: 'kronolithDragger kronolithDraggerBottom' }).setStyle(style);
-                }
-            } else {
-                innerDiv.setStyle({ bottom: 0 });
-            }
-
-            div.setStyle({
-                top: (elapsed * this[storage].height / 60 | 0) + 'px',
-                width: 100 - margin + '%'
-            })
-                .insert(innerDiv.setStyle(style));
-            if (draggerTop) {
-                div.insert(draggerTop);
-            }
-            if (draggerBottom) {
-                div.insert(draggerBottom);
-            }
-            parentElement.insert(div);
-            if (draggerTop) {
-                minHeight += draggerTop.getHeight();
-            }
-            if (draggerBottom) {
-                minHeight += draggerBottom.getHeight();
-            }
-            if (!minHeight) {
-                minHeight = parseInt(innerDiv.getStyle('lineHeight'), 10)
-                    + (parseInt(innerDiv.getStyle('paddingTop'), 10) || 0)
-                    + (parseInt(innerDiv.getStyle('paddingBottom'), 10) || 0);
-            }
-            div.setStyle({ height: Math.max(Math.round(event.value.start.getElapsed(event.value.end) / 60000) * this[storage].height / 60 - this[storage].spacing | 0, minHeight) + 'px' });
-
-            if (event.value.pe) {
-                div.addClassName('kronolithEditable');
-                div.store('drags', []);
-                // Number of pixels that cover 10 minutes.
-                var step = this[storage].height / 6,
-                    stepX, minLeft, maxLeft, maxTop,
-                    minBottom, maxBottom, dragBottomHeight;
-                if (draggerBottom) {
-                    // Height of bottom dragger
-                    dragBottomHeight = draggerBottom.getHeight();
-                }
-                if (draggerTop) {
-                    // Bottom-most position (maximum y) of top dragger
-                    maxTop = div.offsetTop
-                        - draggerTop.getHeight()
-                        - parseInt(innerDiv.getStyle('lineHeight'), 10);
-                    if (draggerBottom) {
-                        maxTop += draggerBottom.offsetTop;
-                    }
-                }
-                if (draggerBottom) {
-                    // Top-most position (minimum y) of bottom dragger (upper
-                    // edge)
-                    minBottom = div.offsetTop
-                        + parseInt(innerDiv.getStyle('lineHeight'), 10);
-                    // Bottom-most position (maximum y) of bottom dragger
-                    // (upper edge)
-                    maxBottom = 24 * this[storage].height
-                        + dragBottomHeight;
-                    if (draggerTop) {
-                        minBottom += draggerTop.getHeight();
-                    }
-                }
-                    // Height of the whole event div
-                var divHeight = div.getHeight(),
-                    // Maximum height of the whole event div
-                    maxDiv = 24 * this[storage].height - divHeight,
-                    // Whether the top dragger is dragged, vs. the bottom
-                    // dragger
-                    opts = {
-                        threshold: 5,
-                        constraint: 'vertical',
-                        scroll: this.kronolithBody,
-                        nodrop: true,
-                        parentElement: function() {
-                            return parentElement;
-                        }
-                    };
-
-                if (draggerTop) {
-                    opts.snap = function(x, y) {
-                        y = Math.max(0, step * (Math.min(maxTop, y - this.scrollTop) / step | 0));
-                        return [0, y];
-                    }.bind(this);
-                    var d = new Drag(event.value.nodeId + 'top', opts);
-                    Object.extend(d, {
-                        event: event,
-                        innerDiv: innerDiv,
-                        midnight: midnight
-                    });
-                    div.retrieve('drags').push(d);
-                }
-
-                if (draggerBottom) {
-                    opts.snap = function(x, y) {
-                        y = Math.min(maxBottom + dragBottomHeight + KronolithCore[storage].spacing, step * ((Math.max(minBottom, y - this.scrollTop) + dragBottomHeight + KronolithCore[storage].spacing) / step | 0)) - dragBottomHeight - KronolithCore[storage].spacing;
-                        return [0, y];
-                    }.bind(this);
-                    var d = new Drag(event.value.nodeId + 'bottom', opts);
-                    Object.extend(d, {
-                        event: event,
-                        innerDiv: innerDiv,
-                        midnight: midnight
-                    });
-                    div.retrieve('drags').push(d);
-                }
-
-                if (view == 'week' || view == 'workweek') {
-                    var dates = this.viewDates(midnight, view);
-                    minLeft = this.eventsWeek['kronolithEvents' + what + dates[0].dateString()].offsetLeft - this.eventsWeek['kronolithEvents' + what + date].offsetLeft;
-                    maxLeft = this.eventsWeek['kronolithEvents' + what + dates[1].dateString()].offsetLeft - this.eventsWeek['kronolithEvents' + what + date].offsetLeft;
-                    stepX = (maxLeft - minLeft) / (view == 'week' ? 6 : 4);
-                }
-                var d = new Drag(div, {
-                    threshold: 5,
-                    nodrop: true,
-                    parentElement: function() { return parentElement; },
-                    snap: function(x, y) {
-                        x = (view == 'week' || view == 'workweek')
-                            ? Math.max(minLeft, stepX * ((Math.min(maxLeft, x - (x < 0 ? stepX : 0)) + stepX / 2) / stepX | 0))
-                            : 0;
-                        y = Math.max(0, step * (Math.min(maxDiv, y - this.scrollTop) / step | 0));
-                        return [x, y];
-                    }.bind(this)
-                });
-                Object.extend(d, {
-                    divHeight: divHeight,
-                    startTop: div.offsetTop,
-                    event: event,
-                    midnight: midnight,
-                    stepX: stepX
-                });
-                div.retrieve('drags').push(d);
-            }
-
-            var
-                // The current column that we're probing for available space.
-                column = 1,
-                // The number of columns in the current conflict group.
-                columns,
-                // The column width in the current conflict group.
-                width,
-                // The first event that conflict with the current event.
-                conflict = false,
-                // The conflict group where this event should go.
-                pos = this.dayGroups.length,
-                // The event below that the current event fits.
-                placeFound = false,
-                // The minimum (virtual) duration of each event, defined by the
-                // minimum height of an event DIV.
-                minMinutes = (minHeight + this[storage].spacing) * 60 / this[storage].height;
-
-            // this.dayEvents contains all events of the current day.
-            // this.dayGroups contains conflict groups, i.e. all events that
-            // conflict with each other and share a set of columns.
-            //
-            // Go through all events that have been added to this day already.
-            this.dayEvents.each(function(ev) {
-                // Due to the minimum height of an event DIV, events might
-                // visually overlap, even if they physically don't.
-                var minEnd = ev.start.clone().add(minMinutes).minutes(),
-                    end = ev.end.isAfter(minEnd) ? ev.end : minEnd;
-
-                // If it doesn't conflict with the current event, go ahead.
-                if (!end.isAfter(event.value.start)) {
-                    return;
-                }
-
-                // Found a conflicting event, now find its conflict group.
-                for (pos = 0; pos < this.dayGroups.length; pos++) {
-                    if (this.dayGroups[pos].indexOf(ev) != -1) {
-                        // Increase column for each conflicting event in this
-                        // group.
-                        this.dayGroups[pos].each(function(ce) {
-                            var minEnd = ce.start.clone().add(minMinutes).minutes(),
-                                end = ce.end.isAfter(minEnd) ? ce.end : minEnd;
-                            if (end.isAfter(event.value.start)) {
-                                column++;
-                            }
-                        });
-                        throw $break;
-                    }
-                }
-            }, this);
-            event.value.column = event.value.columns = column;
-
-            if (Object.isUndefined(this.dayGroups[pos])) {
-                this.dayGroups[pos] = [];
-            }
-            this.dayGroups[pos].push(event.value);
-
-            // See if the current event had to add yet another column.
-            columns = Math.max(this.dayGroups[pos][0].columns, column);
-
-            // Update the widths of all events in a conflict group.
-            width = 100 / columns;
-            this.dayGroups[pos].each(function(ev) {
-                ev.columns = columns;
-                $(ev.nodeId).setStyle({ width: width - margin + '%', left: (width * (ev.column - 1)) + '%' });
-            });
-            this.dayEvents.push(event.value);
-
-            div = innerDiv;
-            break;
-
-        case 'month':
-            var monthDay = this.monthDays['kronolithMonthDay' + date],
-                div = _createElement(event)
-                .setStyle({ backgroundColor: Kronolith.conf.calendars[calendar[0]][calendar[1]].bg,
-                            color: Kronolith.conf.calendars[calendar[0]][calendar[1]].fg });
-            div.writeAttribute('title', event.value.t);
-            if (before) {
-                before.insert({ before: div });
-            } else {
-                monthDay.insert(div);
-            }
-            if (event.value.pe) {
-                div.setStyle({ cursor: 'move' });
-                new Drag(event.value.nodeId, { threshold: 5, parentElement: function() { return $('kronolith-month-body'); }, snapToParent: true });
-            }
-            if (Kronolith.conf.max_events) {
-                var more = monthDay.down('.kronolithMore');
-                if (more) {
-                    monthDay.insert({ bottom: more.remove() });
-                }
-            }
-            break;
-
-        case 'agenda':
-            var div = _createElement(event)
-                .setStyle({ backgroundColor: Kronolith.conf.calendars[calendar[0]][calendar[1]].bg,
-                            color: Kronolith.conf.calendars[calendar[0]][calendar[1]].fg });
-            this.createAgendaDay(date);
-            $('kronolithAgendaDay' + date).insert(div);
-            break;
-        }
-
-        this.setEventText(div, event.value,
-                          { time: view == 'agenda' || Kronolith.conf.show_time })
-            .observe('mouseover', div.addClassName.curry('kronolith-selected'))
-            .observe('mouseout', div.removeClassName.curry('kronolith-selected'));
-    },
-
-    /**
-     * Re-renders the necessary parts of the current view, if any event changes
-     * in those parts require re-rendering.
-     *
-     * @param Array dates  The date strings of days to re-render.
-     */
-    reRender: function(dates)
-    {
-        switch (this.view) {
-        case 'week':
-        case 'workweek':
-        case 'day':
-            dates.each(function(date) {
-                date = this.parseDate(date);
-                this.insertEvents([ date, date ], this.view);
-            }, this);
-            break;
-        case 'month':
-            dates.each(function(date) {
-                var day = this.monthDays['kronolithMonthDay' + date];
-                day.select('.kronolith-event').each(function(event) {
-                    if (event.retrieve('calendar').startsWith('holiday')) {
-                        delete this.holidays[event.retrieve('eventid')];
-                    }
-                    event.remove();
-                }, this);
-                day.select('.kronolithMore').invoke('remove');
-                date = this.parseDate(date);
-                this.loadEvents(date, date, 'month');
-            }, this);
-            break;
-        }
-    },
-
-    /**
-     * Returns all dates of the current view that contain (recurrences) of a
-     * certain event.
-     *
-     * @param String cal      A calendar string.
-     * @param String eventid  An event id.
-     *
-     * @return Array  A list of date strings that contain a recurrence of the
-     *                event.
-     */
-    findEventDays: function(cal, eventid)
-    {
-        cal = cal.split('|');
-        var cache = this.ecache.get(cal[0]).get(cal[1]),
-            dates = this.viewDates(this.date, this.view),
-            day = dates[0], days = [], dateString;
-        while (!day.isAfter(dates[1])) {
-            dateString = day.dateString();
-            if (cache.get(dateString).get(eventid)) {
-                days.push(dateString);
-            }
-            day.add(1).days();
-        }
-        return days;
-    },
-
-    /**
-     * Adds a "more..." button to the month view cell that links to the days,
-     * or moves it to the buttom.
-     *
-     * @param string date  The date string of the day cell.
-     */
-    insertMore: function(date)
-    {
-        var monthDay = this.monthDays['kronolithMonthDay' + date],
-            more = monthDay.down('.kronolithMore');
-        if (more) {
-            monthDay.insert({ bottom: more.remove() });
-        } else {
-            monthDay.insert({ bottom: new Element('span', { className: 'kronolithMore' }).store('date', date).insert(Kronolith.text.more) });
-        }
-    },
-
-    setEventText: function(div, event, opts)
-    {
-        var calendar = event.calendar.split('|'),
-            span = new Element('span'),
-            time, end;
-        opts = Object.extend({ time: false }, opts || {});
-
-        div.update();
-        if (event.ic) {
-            div.insert(new Element('img', { src: event.ic, className: 'kronolithEventIcon' }));
-        }
-        if (opts.time && !event.al) {
-            time = new Element('span', { className: 'kronolith-time' })
-                .insert(event.start.toString(Kronolith.conf.time_format));
-            if (!event.start.equals(event.end)) {
-                end = event.end.clone();
-                if (end.getHours() == 23 &&
-                    end.getMinutes() == 59 &&
-                    end.getSeconds() == 59) {
-                    end.add(1).second();
-                }
-                time.insert('-' + end.toString(Kronolith.conf.time_format));
-            }
-            div.insert(time).insert(' ');
-        }
-        div.insert(event.t.escapeHTML());
-        div.insert(span);
-        if (event.a) {
-            span.insert(' ')
-                .insert(new Element('img', { src: Kronolith.conf.images.alarm.replace(/fff/, Kronolith.conf.calendars[calendar[0]][calendar[1]].fg.substr(1)), title: Kronolith.text.alarm + ' ' + event.a }));
-        }
-        if (event.r) {
-            span.insert(' ')
-                .insert(new Element('img', { src: Kronolith.conf.images.recur.replace(/fff/, Kronolith.conf.calendars[calendar[0]][calendar[1]].fg.substr(1)), title: Kronolith.text.recur[event.r] }));
-        } else if (event.bid) {
-            div.store('bid', event.bid);
-            span.insert(' ')
-                .insert(new Element('img', { src: Kronolith.conf.images.exception.replace(/fff/, Kronolith.conf.calendars[calendar[0]][calendar[1]].fg.substr(1)), title: Kronolith.text.recur.exception }));
-        }
-        return div;
-    },
-
-    /**
-     * Finally removes events from the DOM and the cache.
-     *
-     * @param string calendar  A calendar name.
-     * @param string event     An event id. If empty, all events from the
-     *                         calendar are deleted.
-     */
-    removeEvent: function(calendar, event)
-    {
-        this.deleteCache(calendar, event);
-        this.kronolithBody.select('div.kronolith-event').findAll(function(el) {
-            return el.retrieve('calendar') == calendar &&
-                (!event || el.retrieve('eventid') == event);
-        }).invoke('remove');
-    },
-
-    /**
-     * Removes all events that reprensent exceptions to the event series
-     * represented by uid.
-     *
-     * @param string calendar  A calendar name.
-     * @param string uid       An event uid.
-     */
-    removeException: function(calendar, uid)
-    {
-        this.kronolithBody.select('div.kronolith-event').findAll(function(el) {
-            if (el.retrieve('calendar') == calendar && el.retrieve('bid') == uid) {
-                this.removeEvent(calendar, el.retrieve('eventid'));
-            }
-        }.bind(this));
-    },
-
-    /**
-     * Calculates the event's start and end dates based on some drag and drop
-     * information.
-     */
-    calculateEventDates: function(event, storage, step, offset, height, start, end)
-    {
-        if (!Object.isUndefined(start)) {
-            event.start = start;
-            event.end = end;
-        }
-        event.start.set({
-            hour: offset / this[storage].height | 0,
-            minute: Math.round(offset % this[storage].height / step) * 10
-        });
-        var hour = (offset + height + this[storage].spacing) / this[storage].height | 0,
-            minute = Math.round((offset + height + this[storage].spacing) % this[storage].height / step) * 10,
-            second = 0;
-        if (hour == 24) {
-            hour = 23;
-            minute = 59;
-            second = 59;
-        }
-        event.end.set({
-            hour: hour,
-            minute: minute,
-            second: second
-        });
-    },
-
-    switchTaskView: function(on)
-    {
-        if (on) {
-            $('kronolithNewEvent', 'kronolithNewTask').compact()[0]
-                .replace(Kronolith.conf.new_task);
-            $('kronolithQuickEvent').addClassName('kronolithNewTask');
-            $('kronolithHeader').down('.kronolithPrev').up().addClassName('disabled');
-            $('kronolithHeader').down('.kronolithNext').up().addClassName('disabled');
-        } else {
-            $('kronolithNewEvent', 'kronolithNewTask').compact()[0]
-                .replace(Kronolith.conf.new_event);
-            $('kronolithQuickEvent').removeClassName('kronolithNewTask');
-            $('kronolithHeader').down('.kronolithPrev').up().removeClassName('disabled');
-            $('kronolithHeader').down('.kronolithNext').up().removeClassName('disabled');
-        }
-    },
-
-    /**
-     * Returns the task cache storage names that hold the tasks of the
-     * requested task type.
-     *
-     * @param string tasktype  The task type.
-     *
-     * @return array  The list of task cache storage names.
-     */
-    getTaskStorage: function(tasktype)
-    {
-        var tasktypes;
-        if (tasktype == 'all' || tasktype == 'future') {
-            tasktypes = [ 'complete', 'incomplete' ];
-        } else {
-            tasktypes = [ tasktype ];
-        }
-        return tasktypes;
-    },
-
-    /**
-     * Loads tasks, either from cache or from the server.
-     *
-     * @param integer tasktype  The tasks type (all, incomplete, complete, or
-     *                          future).
-     * @param Array tasksLists  The lists from where to obtain the tasks.
-     */
-    loadTasks: function(tasktype, tasklists)
-    {
-        var tasktypes = this.getTaskStorage(tasktype), loading = false,
-            spinner = $('kronolithLoading');
-
-        if (Object.isUndefined(tasklists)) {
-            tasklists = [];
-            $H(Kronolith.conf.calendars.tasklists).each(function(tasklist) {
-                if (tasklist.value.show)
-                {
-                    tasklists.push(tasklist.key.substring(6));
-                }
-            });
-        }
-
-        tasktypes.each(function(type) {
-            tasklists.each(function(list) {
-                if (Object.isUndefined(this.tcache.get(type)) ||
-                    Object.isUndefined(this.tcache.get(type).get(list))) {
-                    loading = true;
-                    this.loading++;
-                    spinner.show();
-                    HordeCore.doAction('listTasks', {
-                        type: type,
-                        list: list
-                    }, {
-                        callback: function(r) {
-                            this.loadTasksCallback(r, true);
-                        }.bind(this)
-                    });
-                }
-            }, this);
-        }, this);
-
-        if (!loading) {
-            tasklists.each(function(list) {
-                this.insertTasks(tasktype, list);
-            }, this);
-        }
-    },
-
-    /**
-     * Callback method for inserting tasks in the current view.
-     *
-     * @param object r             The ajax response object.
-     * @param boolean createCache  Whether to create a cache list entry for the
-     *                             response, if none exists yet. Useful for
-     *                             (not) adding individual tasks to the cache
-     *                             without assuming to have all tasks of the
-     *                             list.
-     */
-    loadTasksCallback: function(r, createCache)
-    {
-        // Hide spinner.
-        this.loading--;
-        if (!this.loading) {
-            $('kronolithLoading').hide();
-        }
-
-        this.storeTasksCache(r.tasks || {}, r.type, r.list, createCache);
-
-        // Check if result is still valid for the current view.
-        // There could be a rare race condition where two responses for the
-        // same task(s) arrive in the wrong order. Checking this too, like we
-        // do for events seems not worth it.
-        var tasktypes = this.getTaskStorage(this.tasktype),
-            tasklist = Kronolith.conf.calendars.tasklists['tasks/' + r.list];
-        if (this.view != 'tasks' ||
-            !tasklist || !tasklist.show ||
-            !tasktypes.include(r.type)) {
-            return;
-        }
-        this.insertTasks(this.tasktype, r.list);
-    },
-
-    /**
-     * Reads tasks from the cache and inserts them into the view.
-     *
-     * @param integer tasktype  The tasks type (all, incomplete, complete, or
-     *                          future).
-     * @param string tasksList  The task list to be drawn.
-     */
-    insertTasks: function(tasktype, tasklist)
-    {
-        var tasktypes = this.getTaskStorage(tasktype), now = new Date();
-
-        $('kronolithViewTasksBody').select('tr').findAll(function(el) {
-            return el.retrieve('tasklist') == tasklist;
-        }).invoke('remove');
-
-        tasktypes.each(function(type) {
-            if (!this.tcache.get(type)) {
-                return;
-            }
-            var tasks = this.tcache.get(type).get(tasklist);
-            $H(tasks).each(function(task) {
-                switch (tasktype) {
-                case 'complete':
-                    if (!task.value.cp) {
-                        return;
-                    }
-                    break;
-                case 'incomplete':
-                    if (task.value.cp ||
-                        (!Object.isUndefined(task.value.start) &&
-                         task.value.start.isAfter(now))) {
-                        return;
-                    }
-                    break;
-                case 'future':
-                    if (task.value.cp ||
-                        Object.isUndefined(task.value.start) ||
-                        !task.value.start.isAfter(now)) {
-                        return;
-                    }
-                    break;
-                }
-                this.insertTask(task);
-            }, this);
-        }, this);
-
-        if ($('kronolithViewTasksBody').select('tr').length > 2) {
-            $('kronolithTasksNoItems').hide();
-        } else {
-            $('kronolithTasksNoItems').show();
-        }
-    },
-
-    /**
-     * Creates the DOM node for a task and inserts it into the view.
-     *
-     * @param object task  A Hash with the task to insert
-     */
-    insertTask: function(task)
-    {
-        var row = $('kronolithTasksTemplate').clone(true),
-            col = row.down(), tagc;
-
-
-        row.removeAttribute('id');
-        row.store('tasklist', task.value.l);
-        row.store('taskid', task.key);
-        col.addClassName('kronolithTask' + (!!task.value.cp ? 'Completed' : ''));
-        col.setStyle({
-            backgroundColor: Kronolith.conf.calendars.tasklists['tasks/' + task.value.l].bg,
-            color: Kronolith.conf.calendars.tasklists['tasks/' + task.value.l].fg,
-            textIndent: task.value.i + 'em'
-        });
-        col.insert(task.value.n.escapeHTML());
-        if (!Object.isUndefined(task.value.due)) {
-            var now = new Date();
-            if (!now.isBefore(task.value.due)) {
-                col.addClassName('kronolithTaskDue');
-            }
-            col.insert(new Element('span', { className: 'kronolithSeparator' }).update(' &middot; '));
-            col.insert(new Element('span', { className: 'kronolithDate' }).update(task.value.due.toString(Kronolith.conf.date_format)));
-            if (task.value.r) {
-                col.insert(' ')
-                    .insert(new Element('img', { src: Kronolith.conf.images.recur.replace(/fff/, Kronolith.conf.calendars.tasklists['tasks/' + task.value.l].fg.substr(1)), title: Kronolith.text.recur[task.value.r] }));
-            }
-        }
-
-        if (!Object.isUndefined(task.value.sd)) {
-            col.insert(new Element('span', { className: 'kronolithSeparator' }).update(' &middot; '));
-            col.insert(new Element('span', { className: 'kronolithInfo' }).update(task.value.sd.escapeHTML()));
-        }
-
-        if (task.value.t && task.value.t.size() > 0) {
-            tagc = new Element('ul', { className: 'horde-tags' });
-            task.value.t.each(function(x) {
-                tagc.insert(new Element('li').update(x.escapeHTML()));
-            });
-            col.insert(tagc);
-        }
-        row.insert(col.show());
-        this.insertTaskPosition(row, task);
-    },
-
-    /**
-     * Inserts the task row in the correct position.
-     *
-     * @param Element newRow  The new row to be inserted.
-     * @param object newTask  A Hash with the task being added.
-     */
-    insertTaskPosition: function(newRow, newTask)
-    {
-        var rows = $('kronolithViewTasksBody').select('tr'),
-            rowTasklist, rowTaskId, rowTask, parentFound;
-        // The first row is a template, ignoring.
-        for (var i = 2; i < rows.length; i++) {
-            rowTasklist = rows[i].retrieve('tasklist');
-            rowTaskId = rows[i].retrieve('taskid');
-            if (newTask.value.p) {
-                if (rowTaskId == newTask.value.p) {
-                    parentFound = true;
-                    continue;
-                }
-                if (!parentFound) {
-                    continue;
-                }
-            }
-            rowTask = this.tcache.inject(null, function(acc, list) {
-                if (acc) {
-                    return acc;
-                }
-                if (!Object.isUndefined(list.value.get(rowTasklist))) {
-                    return list.value.get(rowTasklist).get(rowTaskId);
-                }
-            });
-
-            if (Object.isUndefined(rowTask)) {
-                // TODO: Throw error
-                return;
-            }
-            if (!this.isTaskAfter(newTask.value, rowTask)) {
-                break;
-            }
-        }
-        rows[--i].insert({ after: newRow.show() });
-    },
-
-    /**
-     * Analyzes which task should be drawn first.
-     *
-     * TODO: Very incomplete, only a dummy version
-     */
-    isTaskAfter: function(taskA, taskB)
-    {
-        // TODO: Make all ordering system
-        if ((taskA.p || taskB.p) && taskA.p != taskB.p) {
-            return !taskA.p;
-        }
-        return (taskA.pr >= taskB.pr);
-    },
-
-    /**
-     * Completes/uncompletes a task.
-     *
-     * @param string tasklist          The task list to which the tasks belongs.
-     * @param string taskid            The id of the task.
-     * @param boolean|string complete  True if the task is completed, a
-     *                                 due date if there are still
-     *                                 incomplete recurrences.
-     */
-    toggleCompletion: function(tasklist, taskid, complete)
-    {
-        // Update the cache.
-        var task = this.tcache.inject(null, function(acc, list) {
-            if (acc) {
-                return acc;
-            }
-            if (!Object.isUndefined(list.value.get(tasklist))) {
-                return list.value.get(tasklist).get(taskid);
-            }
-        });
-        if (Object.isUndefined(task)) {
-            // This shouldn't happen.
-            this.toggleCompletionClass(taskid);
-            return;
-        }
-        if (Object.isUndefined(complete) || complete === true) {
-            task.cp = !task.cp;
-        }
-
-        if (this.tcache.get(task.cp ? 'complete' : 'incomplete')) {
-            this.tcache.get(task.cp ? 'complete' : 'incomplete').get(tasklist).set(taskid, task);
-        }
-        if (this.tcache.get(task.cp ? 'incomplete' : 'complete')) {
-            this.tcache.get(task.cp ? 'incomplete' : 'complete').get(tasklist).unset(taskid);
-        }
-
-        // Remove row if necessary.
-        var row = this.getTaskRow(taskid);
-        if (!row) {
-            return;
-        }
-        if ((this.tasktype == 'complete' && !task.cp) ||
-            ((this.tasktype == 'incomplete' || this.tasktype == 'future_incomplete') && task.cp) ||
-            ((complete === true) && (this.tasktype == 'future'))) {
-
-            row.fade({
-                duration: this.effectDur,
-                afterFinish: function() {
-                    row.purge();
-                    row.remove();
-
-                    //Check if items remained in interface
-                    if ($('kronolithViewTasksBody').select('tr').length < 3) {
-                        $('kronolithTasksNoItems').show();
-                    }
-                }
-            });
-        }
-
-        // Update due date if necessary.
-        if (!Object.isUndefined(complete) && complete !== true) {
-            var now = new Date(), due = Date.parse(complete);
-            row.down('span.kronolithDate')
-                .update(due.toString(Kronolith.conf.date_format));
-            if (now.isBefore(due)) {
-                row.down('td.kronolithTaskCol')
-                    .removeClassName('kronolithTaskDue');
-            }
-        }
-    },
-
-    /**
-     * Toggles the CSS class to show that a task is completed/uncompleted.
-     *
-     * @param string taskid  The id of the task.
-     */
-    toggleCompletionClass: function(taskid)
-    {
-        var row = this.getTaskRow(taskid);
-        if (!row) {
-            return;
-        }
-        var col = row.down('td.kronolithTaskCol');
-        col.toggleClassName('kronolithTask');
-        col.toggleClassName('kronolithTaskCompleted');
-    },
-
-    /**
-     * Returns the table row of a task.
-     *
-     * @param string taskid  The id of the task.
-     *
-     * @return Element  The table row of the task list, if found.
-     */
-    getTaskRow: function(taskid)
-    {
-        return $('kronolithViewTasksBody').select('tr').find(function(el) {
-            return el.retrieve('taskid') == taskid;
-        });
-    },
-
-    editTask: function(tasklist, id, desc)
-    {
-        if (this.redBoxLoading) {
-            return;
-        }
-
-        if (Object.isUndefined(HordeImple.AutoCompleter.kronolithTaskTags)) {
-            this.editTask.bind(this, tasklist, id, desc).defer();
-            return;
-        }
-
-        this.closeRedBox();
-        this.quickClose();
-        this.redBoxOnDisplay = RedBox.onDisplay;
-        RedBox.onDisplay = function() {
-            if (this.redBoxOnDisplay) {
-                this.redBoxOnDisplay();
-            }
-            try {
-                $('kronolithTaskForm').focusFirstElement();
-            } catch(e) {}
-            RedBox.onDisplay = this.redBoxOnDisplay;
-        }.bind(this);
-
-        this.openTab($('kronolithTaskForm').down('.tabset a.kronolithTabLink'));
-        $('kronolithTaskForm').enable();
-        $('kronolithTaskForm').reset();
-        HordeImple.AutoCompleter.kronolithTaskTags.reset();
-        $('kronolithTaskSave').show().enable();
-        $('kronolithTaskDelete').show().enable();
-        $('kronolithTaskForm').down('.kronolithFormActions .kronolithSeparator').show();
-        this.updateTasklistDropDown();
-        this.disableAlarmMethods('Task');
-        this.knl.kronolithTaskDueTime.markSelected();
-        if (id) {
-            RedBox.loading();
-            this.updateTaskParentDropDown(tasklist);
-            this.updateTaskAssigneeDropDown(tasklist);
-            HordeCore.doAction('getTask', {
-                list: tasklist,
-                id: id
-            }, {
-                callback: this.editTaskCallback.bind(this)
-            });
-            $('kronolithTaskTopTags').update();
-        } else {
-            $('kronolithTaskId').clear();
-            $('kronolithTaskOldList').clear();
-            $('kronolithTaskList').setValue(Kronolith.conf.tasks.default_tasklist);
-            this.updateTaskParentDropDown(Kronolith.conf.tasks.default_tasklist);
-            this.updateTaskAssigneeDropDown(Kronolith.conf.tasks.default_tasklist);
-            $('kronolithTaskParent').setValue('');
-            $('kronolithTaskAssignee').setValue('');
-            //$('kronolithTaskLocation').setValue('http://');
-            HordeCore.doAction('listTopTags', {}, {
-                callback: this.topTagsCallback.curry('kronolithTaskTopTags', 'kronolithTaskTag')
-            });
-            $('kronolithTaskPriority').setValue(3);
-            if (Kronolith.conf.tasks.default_due) {
-                this.setDefaultDue();
-            }
-            if (desc) {
-                $('kronolithTaskDescription').setValue(desc);
-            }
-            this.toggleRecurrence(false, 'None');
-            $('kronolithTaskDelete').hide();
-            this.redBoxLoading = true;
-            RedBox.showHtml($('kronolithTaskDialog').show());
-        }
-    },
-
-    /**
-     * Callback method for showing task forms.
-     *
-     * @param object r  The ajax response object.
-     */
-    editTaskCallback: function(r)
-    {
-        if (!r.task) {
-            RedBox.close();
-            this.go(this.lastLocation);
-            return;
-        }
-
-        var task = r.task;
-
-        /* Basic information */
-        $('kronolithTaskId').setValue(task.id);
-        $('kronolithTaskOldList').setValue(task.l);
-        $('kronolithTaskList').setValue(task.l);
-        $('kronolithTaskTitle').setValue(task.n);
-        $('kronolithTaskParent').setValue(task.p);
-        $('kronolithTaskAssignee').setValue(task.as);
-        //$('kronolithTaskLocation').setValue(task.l);
-        if (task.dd) {
-            $('kronolithTaskDueDate').setValue(task.dd);
-        }
-        if (task.dt) {
-            $('kronolithTaskDueTime').setValue(task.dt);
-            this.knl.kronolithTaskDueTime.setSelected(task.dt);
-        }
-        $('kronolithTaskDescription').setValue(task.de);
-        $('kronolithTaskPriority').setValue(task.pr);
-        $('kronolithTaskCompleted').setValue(task.cp);
-
-        /* Alarm */
-        if (task.a) {
-            this.enableAlarm('Task', task.a);
-            if (task.m) {
-                $('kronolithTaskAlarmDefaultOff').checked = true;
-                $H(task.m).each(function(method) {
-                    if (!$('kronolithTaskAlarm' + method.key)) {
-                        return;
-                    }
-                    $('kronolithTaskAlarm' + method.key).setValue(1);
-                    if ($('kronolithTaskAlarm' + method.key + 'Params')) {
-                        $('kronolithTaskAlarm' + method.key + 'Params').show();
-                    }
-                    $H(method.value).each(function(param) {
-                        var input = $('kronolithTaskAlarmParam' + param.key);
-                        if (!input) {
-                            return;
-                        }
-                        if (input.type == 'radio') {
-                            input.up('form').select('input[type=radio]').each(function(radio) {
-                                if (radio.name == input.name &&
-                                    radio.value == param.value) {
-                                    radio.setValue(1);
-                                    throw $break;
-                                }
-                            });
-                        } else {
-                            input.setValue(param.value);
-                        }
-                    });
-                });
-            }
-        } else {
-            $('kronolithTaskAlarmOff').setValue(true);
-        }
-
-        /* Recurrence */
-        if (task.r) {
-            this.setRecurrenceFields(false, task.r);
-        } else {
-            this.toggleRecurrence(false, 'None');
-        }
-
-        HordeImple.AutoCompleter.kronolithTaskTags.reset(task.t);
-
-        if (!task.pe) {
-            $('kronolithTaskSave').hide();
-            $('kronolithTaskForm').disable();
-        } else {
-            HordeCore.doAction('listTopTags', {}, {
-                callback: this.topTagsCallback.curry('kronolithTaskTopTags', 'kronolithTaskTag')
-            });
-        }
-
-        if (!task.pd) {
-            $('kronolithTaskDelete').show();
-        }
-        if (!task.pe && !task.pd) {
-            $('kronolithTaskForm').down('.kronolithFormActions .kronolithSeparator').hide();
-        }
-
-        this.setTitle(task.n);
-        this.redBoxLoading = true;
-        RedBox.showHtml($('kronolithTaskDialog').show());
-
-        /* Hide alarm message for this task. */
-        if (r.msgs) {
-            r.msgs = r.msgs.reject(function(msg) {
-                if (msg.type != 'horde.alarm') {
-                    return false;
-                }
-                var alarm = msg.flags.alarm;
-                if (alarm.params && alarm.params.notify &&
-                    alarm.params.notify.show &&
-                    alarm.params.notify.show.tasklist &&
-                    alarm.params.notify.show.task &&
-                    alarm.params.notify.show.tasklist == task.l &&
-                    alarm.params.notify.show.task == task.id) {
-                    return true;
-                }
-                return false;
-            });
-        }
-    },
-
-    /**
-     * Propagates a SELECT drop down list with the editable task lists.
-     */
-    updateTasklistDropDown: function()
-    {
-        var tasklist = $('kronolithTaskList');
-        tasklist.update();
-        $H(Kronolith.conf.calendars.tasklists).each(function(cal) {
-            if (cal.value.edit) {
-                tasklist.insert(new Element('option', { value: cal.key.substring(6) })
-                                .setStyle({ backgroundColor: cal.value.bg, color: cal.value.fg })
-                                .update(cal.value.name.escapeHTML()));
-            }
-        });
-    },
-
-    /**
-     * Propagates a SELECT drop down list with the tasks of a task list.
-     *
-     * @param string list  A task list ID.
-     */
-    updateTaskParentDropDown: function(list)
-    {
-        var parents = $('kronolithTaskParent');
-        parents.update(new Element('option', { value: '' })
-                       .update(Kronolith.text.no_parent));
-        HordeCore.doAction('listTasks', {
-            type: 'future_incomplete',
-            list: list
-        }, {
-            ajaxopts: { asynchronuous: false },
-            callback: function(r) {
-                $H(r.tasks).each(function(task) {
-                    parents.insert(new Element('option', { value: task.key })
-                                .setStyle({ textIndent: task.value.i + 'em' })
-                                .update(task.value.n.escapeHTML()));
-                });
-            }.bind(this)
-        });
-    },
-
-    /**
-     * Propagates a SELECT drop down list with the users of a task list.
-     *
-     * @param string list  A task list ID.
-     */
-    updateTaskAssigneeDropDown: function(list)
-    {
-        var assignee = $('kronolithTaskAssignee');
-        assignee.update(new Element('option', { value: '' })
-                       .update(Kronolith.text.no_assignee));
-        $H(Kronolith.conf.calendars.tasklists['tasks/' + list].users).each(function(user) {
-            assignee.insert(new Element('option', { value: user.key })
-                            .update(user.value.escapeHTML()));
-        });
-    },
-
-    /**
-     * Sets the default due date and time for tasks.
-     */
-    setDefaultDue: function()
-    {
-        if ($F('kronolithTaskDueDate') || $F('kronolithTaskDueTime')) {
-            return;
-        }
-        $('kronolithTaskDueDate').setValue(new Date().add(Kronolith.conf.tasks.default_due_days).days().toString(Kronolith.conf.date_format));
-        if (Kronolith.conf.tasks.default_due_time == 'now') {
-            $('kronolithTaskDueTime').setValue(new Date().toString(Kronolith.conf.time_format));
-        } else {
-            var date = new Date();
-            date.setHours(Kronolith.conf.tasks.default_due_time.replace(/:.*$/, ''));
-            date.setMinutes(0);
-            $('kronolithTaskDueTime').setValue(date.toString(Kronolith.conf.time_format));
-        }
-    },
-
-    /**
-     * Finally removes tasks from the DOM and the cache.
-     *
-     * @param string list  A task list name.
-     * @param string task  A task id. If empty, all tasks from the list are
-     *                     deleted.
-     */
-    removeTask: function(list, task)
-    {
-        this.deleteTasksCache(list, task);
-        $('kronolithViewTasksBody').select('tr').findAll(function(el) {
-            return el.retrieve('tasklist') == list &&
-                (!task || el.retrieve('taskid') == task);
-        }).invoke('remove');
-        this.removeEvent('tasklists|tasks/' + list, task ? '_tasks' + task : null);
-        if ($('kronolithViewTasksBody').select('tr').length > 2) {
-            $('kronolithTasksNoItems').hide();
-        } else {
-            $('kronolithTasksNoItems').show();
-        }
-    },
-
-    /**
-     * Submits the task edit form to create or update a task.
-     */
-    saveTask: function()
-    {
-        if (this.wrongFormat.size() ||
-            (($F('kronolithTaskAlarmOn')) && $F('kronolithTaskDueDate').length == 0)) {
-            HordeCore.notify(Kronolith.text.fix_form_values, 'horde.warning');
-            return;
-        }
-
-        var tasklist = $F('kronolithTaskOldList'),
-            target = $F('kronolithTaskList'),
-            taskid = $F('kronolithTaskId'),
-            viewDates = this.viewDates(this.date, this.view),
-            start = viewDates[0].dateString(),
-            end = viewDates[1].dateString();
-
-        HordeImple.AutoCompleter.kronolithTaskTags.shutdown();
-        $('kronolithTaskSave').disable();
-        this.startLoading('tasklists|tasks/' + target, start + end + this.tasktype);
-        this.loading++;
-        HordeCore.doAction(
-            'saveTask',
-            $H($('kronolithTaskForm').serialize({ hash: true })).merge({
-                sig: start + end + this.tasktype,
-                view: this.view,
-                view_start: start,
-                view_end: end
-            }), {
-                callback: function(r) {
-                    if (r.tasks && taskid) {
-                        this.removeTask(tasklist, taskid);
-                    }
-                    this.loadTasksCallback(r, false);
-                    this.loadEventsCallback(r, false);
-                    if (r.tasks) {
-                        this.closeRedBox();
-                        this.go(this.lastLocation);
-                    } else {
-                        $('kronolithTaskSave').enable();
-                    }
-                }.bind(this)
-            }
-        );
-    },
-
-    quickSaveTask: function()
-    {
-        var text = $F('kronolithQuicktaskQ'),
-            viewDates = this.viewDates(this.date, 'tasks'),
-            start = viewDates[0].dateString(),
-            end = viewDates[1].dateString(),
-            params = {
-                sig: start + end + this.tasktype,
-                view: 'tasks',
-                view_start: start,
-                view_end: end,
-                tasklist: Kronolith.conf.tasks.default_tasklist,
-                text: text
-            };
-
-        this.closeRedBox();
-        this.startLoading('tasklists|tasks/' + Kronolith.conf.tasks.default_tasklist,
-                          params.sig);
-        this.loading++;
-        HordeCore.doAction('quickSaveTask', params, {
-            callback: function(r) {
-                this.loadTasksCallback(r, false);
-                this.loadEventsCallback(r, false);
-                if (!r.tasks || !$H(r.tasks).size()) {
-                    this.editTask(null, null, text);
-                } else {
-                    $('kronolithQuicktaskQ').value = '';
-                }
-            }.bind(this)
-         });
-    },
-
-    /**
-     * Opens the form for editing a calendar.
-     *
-     * @param string calendar  Calendar type and calendar id, separated by '|'.
-     */
-    editCalendar: function(calendar)
-    {
-        if (this.redBoxLoading) {
-            return;
-        }
-
-        this.closeRedBox();
-        this.quickClose();
-
-        var type = calendar.split('|')[0], cal = calendar.split('|')[1];
-        if (!$w('internal tasklists remote holiday resource resourcegroup').include(type)) {
-            return;
-        }
-
-        if (cal &&
-            (Object.isUndefined(Kronolith.conf.calendars[type]) ||
-             Object.isUndefined(Kronolith.conf.calendars[type][cal])) &&
-            (type == 'internal' || type == 'tasklists')) {
-            HordeCore.doAction('getCalendar', {
-                cal: cal
-            }, {
-                callback: function(r) {
-                    if (r.calendar) {
-                        Kronolith.conf.calendars[type][cal] = r.calendar;
-                        this.insertCalendarInList(type, cal, r.calendar);
-                        $('kronolithSharedCalendars').show();
-                        this.editCalendar(type + '|' + cal);
-                    } else {
-                        this.go(this.lastLocation);
-                    }
-                }.bind(this)
-            });
-            return;
-        }
-
-        this.redBoxOnDisplay = RedBox.onDisplay;
-        RedBox.onDisplay = function() {
-            if (this.redBoxOnDisplay) {
-                this.redBoxOnDisplay();
-            }
-            try {
-                $('kronolithCalendarForm' + type).focusFirstElement();
-            } catch(e) {}
-            RedBox.onDisplay = this.redBoxOnDisplay;
-        }.bind(this);
-
-        if ($('kronolithCalendarDialog')) {
-            this.redBoxLoading = true;
-            RedBox.showHtml($('kronolithCalendarDialog').show());
-            this.editCalendarCallback(calendar);
-        } else {
-            RedBox.loading();
-            HordeCore.doAction('chunkContent', {
-                chunk: 'calendar'
-            }, {
-                callback: function(r) {
-                    if (r.chunk) {
-                        this.redBoxLoading = true;
-                        RedBox.showHtml(r.chunk);
-                        ['internal', 'tasklists'].each(function(type) {
-                            $('kronolithC' + type + 'PGList').observe('change', function() {
-                                $('kronolithC' + type + 'PG').setValue(1);
-                                this.permsClickHandler(type, 'G');
-                            }.bind(this));
-                        }, this);
-                        this.editCalendarCallback(calendar);
-                    } else {
-                        this.closeRedBox();
-                    }
-                }.bind(this)
-            });
-        }
-    },
-
-    /**
-     * Callback for editing a calendar. Fills the edit form with the correct
-     * values.
-     *
-     * @param string calendar  Calendar type and calendar id, separated by '|'.
-     */
-    editCalendarCallback: function(calendar)
-    {
-        calendar = calendar.split('|');
-        var type = calendar[0];
-        calendar = calendar.length == 1 ? null : calendar[1];
-
-        var form = $('kronolithCalendarForm' + type),
-            firstTab = form.down('.tabset a.kronolithTabLink'),
-            info;
-
-        form.enable();
-        form.reset();
-        if (firstTab) {
-            this.openTab(firstTab);
-        }
-        $('kronolithCalendarDialog').select('.kronolithCalendarDiv').invoke('hide');
-        $('kronolithCalendar' + type + '1').show();
-        form.select('.kronolithCalendarContinue').invoke('enable');
-        $('kronolithC' + type + 'PUNew', 'kronolithC' + type + 'PGNew').compact().each(function(elm) {
-            if (elm.tagName == 'SELECT') {
-                $A(elm.options).each(function(option) {
-                    option.writeAttribute('disabled', false);
-                });
-            }
-        });
-
-        var newCalendar = !calendar;
-        if (calendar &&
-            (Object.isUndefined(Kronolith.conf.calendars[type]) ||
-             Object.isUndefined(Kronolith.conf.calendars[type][calendar]))) {
-            if (type != 'remote') {
-                this.closeRedBox();
-                this.go(this.lastLocation);
-                return;
-            }
-            newCalendar = true;
-        }
-        if (type == 'resourcegroup') {
-            this.updateResourcegroupSelect();
-        }
-        if (newCalendar) {
-            switch (type) {
-            case 'internal':
-                HordeImple.AutoCompleter.kronolithCalendarinternalTags.reset();
-                // Fall through.
-            case 'tasklists':
-                $('kronolithCalendar' + type + 'LinkExport').up('span').hide();
-                break;
-            case 'remote':
-                if (calendar) {
-                    $('kronolithCalendarremoteUrl').setValue(calendar);
-                    $('kronolithCalendarremoteId').setValue(calendar);
-                }
-                break;
-            case 'holiday':
-                $('kronolithCalendarholidayDriver').update();
-                $H(Kronolith.conf.calendars.holiday).each(function(calendar) {
-                    if (calendar.value.show) {
-                        return;
-                    }
-                    $('kronolithCalendarholidayDriver').insert(
-                        new Element('option', { value: calendar.key })
-                            .setStyle({ color: calendar.value.fg, backgroundColor: calendar.value.bg })
-                            .insert(calendar.value.name.escapeHTML())
-                    );
-                });
-                break;
-            }
-            $('kronolithCalendar' + type + 'Id').clear();
-            var color = '#', i;
-            for (i = 0; i < 3; i++) {
-                color += (Math.random() * 256 | 0).toColorPart();
-            }
-            $('kronolithCalendar' + type + 'Color').setValue(color).setStyle({ backgroundColor: color, color: Color.brightness(Color.hex2rgb(color)) < 125 ? '#fff' : '#000' });
-            form.down('.kronolithCalendarDelete').hide();
-            $('kronolithCalendarinternalImportButton').hide();
-        } else {
-            info = Kronolith.conf.calendars[type][calendar];
-
-            $('kronolithCalendar' + type + 'Id').setValue(calendar);
-            $('kronolithCalendar' + type + 'Name').setValue(info.name);
-            $('kronolithCalendar' + type + 'Color').setValue(info.bg).setStyle({ backgroundColor: info.bg, color: info.fg });
-            $('kronolithCalendarinternalImportButton').hide();
-
-            switch (type) {
-            case 'internal':
-                HordeImple.AutoCompleter.kronolithCalendarinternalTags.reset(Kronolith.conf.calendars.internal[calendar].tg);
-                $('kronolithCalendar' + type + 'ImportCal').setValue('internal_' + calendar);
-                if ($('kronolithCalendar' + type + 'LinkImport')) {
-                    if (info.edit) {
-                        $('kronolithCalendar' + type + 'LinkImport').up('li').show();
-                    } else {
-                        $('kronolithCalendar' + type + 'LinkImport').up('li').hide();
-                    }
-                }
-                $('kronolithCalendar' + type + 'UrlFeed').setValue(info.feed);
-                $('kronolithCalendar' + type + 'EmbedUrl').setValue(info.embed);
-                // Fall through.
-            case 'tasklists':
-                $('kronolithCalendar' + type + 'Description').setValue(info.desc);
-                if ($('kronolithCalendar' + type + 'LinkExport')) {
-                    $('kronolithCalendar' + type + 'LinkExport').up('span').show();
-                    $('kronolithCalendar' + type + 'Export').href = type == 'internal'
-                        ? Kronolith.conf.URI_CALENDAR_EXPORT.interpolate({ calendar: calendar })
-                        : Kronolith.conf.tasks.URI_TASKLIST_EXPORT.interpolate({ tasklist: calendar.substring(6) });
-                }
-                $('kronolithCalendar' + type + 'LinkUrls').up().show();
-                if (info.caldav) {
-                    $('kronolithCalendar' + type + 'UrlCaldav').setValue(info.caldav);
-                    $('kronolithCalendar' + type + 'Caldav').show();
-                } else {
-                    $('kronolithCalendar' + type + 'Caldav').hide();
-                }
-                $('kronolithCalendar' + type + 'UrlWebdav').setValue(info.sub);
-                break;
-            case 'remote':
-                $('kronolithCalendarremoteUrl').setValue(calendar);
-                $('kronolithCalendarremoteDescription').setValue(info.desc);
-                $('kronolithCalendarremoteUsername').setValue(info.user);
-                $('kronolithCalendarremotePassword').setValue(info.password);
-                break;
-            case 'resourcegroup':
-                $('kronolithCalendarresourcegroupDescription').setValue(info.desc);
-                $('kronolithCalendarresourcegroupmembers').setValue(info.members);
-                break;
-            case 'resource':
-                $('kronolithCalendarresourceDescription').setValue(info.desc);
-                $('kronolithCalendarresourceResponseType').setValue(info.response_type);
-                $('kronolithCalendarresourceExport').href = Kronolith.conf.URI_RESOURCE_EXPORT.interpolate({ calendar: calendar });
-            }
-        }
-
-        if (newCalendar || info.owner) {
-            if (type == 'internal' || type == 'tasklists') {
-                this.updateGroupDropDown([['kronolithC' + type + 'PGList', this.updateGroupPerms.bind(this, type)],
-                                          ['kronolithC' + type + 'PGNew']]);
-                $('kronolithC' + type + 'PBasic').show();
-                $('kronolithC' + type + 'PAdvanced').hide();
-                $('kronolithC' + type + 'PNone').setValue(1);
-                if ($('kronolithC' + type + 'PAllShow')) {
-                    $('kronolithC' + type + 'PAllShow').disable();
-                }
-                $('kronolithC' + type + 'PGList').disable();
-                $('kronolithC' + type + 'PGPerms').disable();
-                $('kronolithC' + type + 'PUList').disable();
-                $('kronolithC' + type + 'PUPerms').disable();
-                $('kronolithC' + type + 'PAdvanced').select('tr').findAll(function(tr) {
-                    return tr.retrieve('remove');
-                }).invoke('remove');
-                $('kronolithCalendar' + type + 'LinkUrls').up().show();
-                form.down('.kronolithColorPicker').show();
-                if (type == 'internal') {
-                    HordeCore.doAction('listTopTags', {}, {
-                        callback: this.topTagsCallback.curry('kronolithCalendarinternalTopTags', 'kronolithCalendarTag')
-                    });
-                }
-                form.down('.kronolithCalendarSubscribe').hide();
-                form.down('.kronolithCalendarUnsubscribe').hide();
-                if ($('kronolithCalendar' + type + 'LinkPerms')) {
-                    $('kronolithCalendar' + type + 'LinkPerms').up('span').show();
-                }
-                if (!Object.isUndefined(info) && info.owner) {
-                    this.setPermsFields(type, info.perms);
-                }
-            }
-            if (type == 'remote' || type == 'internal' || type == 'tasklists') {
-                if (newCalendar ||
-                    (type == 'internal' && calendar == Kronolith.conf.user) ||
-                    (type == 'tasklists' && calendar == 'tasks/' + Kronolith.conf.user)) {
-                    form.select('.kronolithCalendarDelete').invoke('hide');
-                } else {
-                    form.select('.kronolithCalendarDelete').invoke('show');
-                }
-            }
-            form.down('.kronolithCalendarSave').show();
-            form.down('.kronolithFormActions .kronolithSeparator').show();
-        } else {
-            form.disable();
-            form.down('.kronolithColorPicker').hide();
-            form.down('.kronolithCalendarDelete').hide();
-            form.down('.kronolithCalendarSave').hide();
-            if (type == 'internal' || type == 'tasklists') {
-                $('kronolithCalendar' + type + 'UrlCaldav').enable();
-                $('kronolithCalendar' + type + 'UrlAccount').enable();
-                $('kronolithCalendar' + type + 'UrlWebdav').enable();
-                if (type == 'internal') {
-                    $('kronolithCalendar' + type + 'UrlFeed').enable();
-                    $('kronolithCalendar' + type + 'EmbedUrl').enable();
-                    if (info.edit) {
-                        $('kronolithCalendarinternalImport').enable();
-                        if (info.del) {
-                            $('kronolithCalendarinternalImportOver').enable();
-                        }
-                        $('kronolithCalendarinternalImportButton').show().enable();
-                    }
-                }
-                HordeImple.AutoCompleter.kronolithCalendarinternalTags.disable();
-                if (Kronolith.conf.calendars[type][calendar].show) {
-                    form.down('.kronolithCalendarSubscribe').hide();
-                    form.down('.kronolithCalendarUnsubscribe').show().enable();
-                } else {
-                    form.down('.kronolithCalendarSubscribe').show().enable();
-                    form.down('.kronolithCalendarUnsubscribe').hide();
-                }
-                form.down('.kronolithFormActions .kronolithSeparator').show();
-                if ($('kronolithCalendar' + type + 'LinkPerms')) {
-                    $('kronolithCalendar' + type + 'LinkPerms').up('span').hide();
-                }
-            } else {
-                form.down('.kronolithFormActions .kronolithSeparator').hide();
-            }
-        }
-    },
-
-    /**
-     * Updates the select list in the resourcegroup calendar dialog.
-     */
-    updateResourcegroupSelect: function()
-    {
-        if (!Kronolith.conf.calendars.resource) {
-            return;
-        }
-        $('kronolithCalendarresourcegroupmembers').update();
-        $H(Kronolith.conf.calendars.resource).each(function(r) {
-            var o = new Element('option', { value: r.value.id }).update(r.value.name);
-            $('kronolithCalendarresourcegroupmembers').insert(o);
-        });
-    },
-
-    /**
-     * Handles clicks on the radio boxes of the basic permissions screen.
-     *
-     * @param string type  The calendar type, 'internal' or 'taskslists'.
-     * @param string perm  The permission to activate, 'None', 'All', or
-     *                     'Group'.
-     */
-    permsClickHandler: function(type, perm)
-    {
-        $('kronolithC' + type + 'PAdvanced')
-            .select('input[type=checkbox]')
-            .invoke('setValue', 0);
-        $('kronolithC' + type + 'PAdvanced').select('tr').findAll(function(tr) {
-            return tr.retrieve('remove');
-        }).invoke('remove');
-
-        switch (perm) {
-        case 'None':
-            if ($('kronolithC' + type + 'PAllShow')) {
-                $('kronolithC' + type + 'PAllShow').disable();
-            }
-            $('kronolithC' + type + 'PGList').disable();
-            $('kronolithC' + type + 'PGPerms').disable();
-            $('kronolithC' + type + 'PUList').disable();
-            $('kronolithC' + type + 'PUPerms').disable();
-            break;
-        case 'All':
-            $('kronolithC' + type + 'PAllShow').enable();
-            $('kronolithC' + type + 'PGList').disable();
-            $('kronolithC' + type + 'PGPerms').disable();
-            $('kronolithC' + type + 'PUList').disable();
-            $('kronolithC' + type + 'PUPerms').disable();
-            var perms = {
-                'default': Kronolith.conf.perms.read,
-                'guest': Kronolith.conf.perms.read
-            };
-            if ($F('kronolithC' + type + 'PAllShow')) {
-                perms['default'] |= Kronolith.conf.perms.show;
-                perms['guest'] |= Kronolith.conf.perms.show;
-            }
-            this.setPermsFields(type, perms);
-            break;
-        case 'G':
-            if ($('kronolithC' + type + 'PAllShow')) {
-                $('kronolithC' + type + 'PAllShow').disable();
-            }
-            $('kronolithC' + type + 'PGList').enable();
-            $('kronolithC' + type + 'PGPerms').enable();
-            $('kronolithC' + type + 'PUList').disable();
-            $('kronolithC' + type + 'PUPerms').disable();
-            var group = $F('kronolithC' + type + 'PGSingle')
-                ? $F('kronolithC' + type + 'PGSingle')
-                : $F('kronolithC' + type + 'PGList');
-            this.insertGroupOrUser(type, 'group', group, true);
-            $('kronolithC' + type + 'PGshow_' + group).setValue(1);
-            $('kronolithC' + type + 'PGread_' + group).setValue(1);
-            if ($F('kronolithC' + type + 'PGPerms') == 'edit') {
-                $('kronolithC' + type + 'PGedit_' + group).setValue(1);
-            } else {
-                $('kronolithC' + type + 'PGedit_' + group).setValue(0);
-            }
-            $('kronolithC' + type + 'PGdel_' + group).setValue(0);
-            if ($('kronolithC' + type + 'PGdelegate_' + group)) {
-                $('kronolithC' + type + 'PGdelegate_' + group).setValue(0);
-            }
-            break;
-        case 'U':
-            if ($('kronolithC' + type + 'PAllShow')) {
-                $('kronolithC' + type + 'PAllShow').disable();
-            }
-            $('kronolithC' + type + 'PGList').disable();
-            $('kronolithC' + type + 'PGPerms').disable();
-            $('kronolithC' + type + 'PUList').enable();
-            $('kronolithC' + type + 'PUPerms').enable();
-            var users = $F('kronolithC' + type + 'PUList').strip();
-            users = users ? users.split(/\s*(?:,|\n)\s*/) : [];
-            users.each(function(user) {
-                if (!this.insertGroupOrUser(type, 'user', user, true)) {
-                    return;
-                }
-                $('kronolithC' + type + 'PUshow_' + user).setValue(1);
-                $('kronolithC' + type + 'PUread_' + user).setValue(1);
-                if ($F('kronolithC' + type + 'PUPerms') == 'edit') {
-                    $('kronolithC' + type + 'PUedit_' + user).setValue(1);
-                } else {
-                    $('kronolithC' + type + 'PUedit_' + user).setValue(0);
-                }
-                $('kronolithC' + type + 'PUdel_' + user).setValue(0);
-                if ($('kronolithC' + type + 'PUdelegate_' + user)) {
-                    $('kronolithC' + type + 'PUdelegate_' + user).setValue(0);
-                }
-            }, this);
-            break;
-        }
-    },
-
-    /**
-     * Populates the permissions field matrix.
-     *
-     * @param string type   The calendar type, 'internal' or 'taskslists'.
-     * @param object perms  An object with the resource permissions.
-     */
-    setPermsFields: function(type, perms)
-    {
-        if (this.groupLoading) {
-            this.setPermsFields.bind(this, type, perms).defer();
-            return;
-        }
-
-        var allperms = $H(Kronolith.conf.perms),
-            advanced = false, users = [],
-            basic, same, groupPerms, groupId, userPerms;
-        $H(perms).each(function(perm) {
-            switch (perm.key) {
-            case 'default':
-            case 'guest':
-                if (Object.isUndefined(same)) {
-                    same = perm.value;
-                } else if (Object.isUndefined(basic) &&
-                           same == perm.value &&
-                           (perm.value == Kronolith.conf.perms.read ||
-                            perm.value == (Kronolith.conf.perms.read | Kronolith.conf.perms.show))) {
-                    basic = perm.value == Kronolith.conf.perms.read ? 'all_read' : 'all_show';
-                } else if (perm.value != 0) {
-                    advanced = true;
-                }
-                break;
-            case 'creator':
-                if (perm.value != 0) {
-                    advanced = true;
-                }
-                break;
-            case 'groups':
-                if (!Object.isArray(perm.value)) {
-                    $H(perm.value).each(function(group) {
-                        if (!this.insertGroupOrUser(type, 'group', group.key)) {
-                            return;
-                        }
-                        if (!$('kronolithC' + type + 'PGshow_' + group.key)) {
-                            // Group doesn't exist anymore.
-                            delete perm.value[group.key];
-                            return;
-                        }
-                        groupPerms = group.value;
-                        groupId = group.key;
-                    }, this);
-                    if (Object.isUndefined(basic) &&
-                        $H(perm.value).size() == 1 &&
-                        (groupPerms == (Kronolith.conf.perms.show | Kronolith.conf.perms.read) ||
-                         groupPerms == (Kronolith.conf.perms.show | Kronolith.conf.perms.read | Kronolith.conf.perms.edit))) {
-                        basic = groupPerms == (Kronolith.conf.perms.show | Kronolith.conf.perms.read) ? 'group_read' : 'group_edit';
-                    } else {
-                        advanced = true;
-                    }
-                }
-                break;
-            case 'users':
-                if (!Object.isArray(perm.value)) {
-                    $H(perm.value).each(function(user) {
-                        if (user.key != Kronolith.conf.user) {
-                            if (!this.insertGroupOrUser(type, 'user', user.key)) {
-                                return;
-                            }
-                            if (!$('kronolithC' + type + 'PUshow_' + user.key)) {
-                                // User doesn't exist anymore.
-                                delete perm.value[user.key];
-                                return;
-                            }
-                            // Check if we already have other basic permissions.
-                            if (Object.isUndefined(userPerms) &&
-                                !Object.isUndefined(basic)) {
-                                advanced = true;
-                            }
-                            // Check if all users have the same permissions.
-                            if (!Object.isUndefined(userPerms) &&
-                                userPerms != user.value) {
-                                advanced = true;
-                            }
-                            userPerms = user.value;
-                            if (!advanced &&
-                                (userPerms == (Kronolith.conf.perms.show | Kronolith.conf.perms.read) ||
-                                 userPerms == (Kronolith.conf.perms.show | Kronolith.conf.perms.read | Kronolith.conf.perms.edit))) {
-                                basic = userPerms == (Kronolith.conf.perms.show | Kronolith.conf.perms.read) ? 'user_read' : 'user_edit';
-                                users.push(user.key);
-                            } else {
-                                advanced = true;
-                            }
-                        }
-                    }, this);
-                }
-                break;
-            }
-
-            allperms.each(function(baseperm) {
-                if (baseperm.key == 'all') {
-                    return;
-                }
-                switch (perm.key) {
-                case 'default':
-                case 'guest':
-                case 'creator':
-                    if (baseperm.value & perm.value) {
-                        $('kronolithC' + type + 'P' + perm.key + baseperm.key).setValue(1);
-                    }
-                    break;
-                case 'groups':
-                    $H(perm.value).each(function(group) {
-                        if (baseperm.value & group.value) {
-                            $('kronolithC' + type + 'PG' + baseperm.key + '_' + group.key).setValue(1);
-                        }
-                    });
-                    break;
-                case 'users':
-                    $H(perm.value).each(function(user) {
-                        if (baseperm.value & user.value &&
-                            user.key != Kronolith.conf.user) {
-                            $('kronolithC' + type + 'PU' + baseperm.key + '_' + user.key).setValue(1);
-                        }
-                    });
-                    break;
-                }
-            });
-        }.bind(this));
-
-        if (advanced) {
-            this.activateAdvancedPerms(type);
-        } else {
-            switch (basic) {
-            case 'all_read':
-                $('kronolithC' + type + 'PAll').setValue(1);
-                $('kronolithC' + type + 'PAllShow').setValue(0);
-                $('kronolithC' + type + 'PAllShow').enable();
-                break;
-            case 'all_show':
-                $('kronolithC' + type + 'PAll').setValue(1);
-                $('kronolithC' + type + 'PAllShow').setValue(1);
-                $('kronolithC' + type + 'PAllShow').enable();
-                break;
-            case 'group_read':
-            case 'group_edit':
-                var setGroup = function(group) {
-                    if ($('kronolithC' + type + 'PGList').visible()) {
-                        $('kronolithC' + type + 'PGList').setValue(group);
-                        if ($('kronolithC' + type + 'PGList').getValue() != group) {
-                            // Group no longer exists.
-                            this.permsClickHandler(type, 'None');
-                        }
-                    } else if ($('kronolithC' + type + 'PGSingle').getValue() != group) {
-                        // Group no longer exists.
-                        this.permsClickHandler(type, 'None');
-                    }
-                }.bind(this, groupId);
-                if (this.groupLoading) {
-                    setGroup.defer();
-                } else {
-                    setGroup();
-                }
-                $('kronolithC' + type + 'PG').setValue(1);
-                $('kronolithC' + type + 'PGPerms').setValue(basic.substring(6));
-                $('kronolithC' + type + 'PAdvanced').hide();
-                $('kronolithC' + type + 'PBasic').show();
-                $('kronolithC' + type + 'PGPerms').enable();
-                break;
-            case 'user_read':
-            case 'user_edit':
-                $('kronolithC' + type + 'PUList').enable().setValue(users.join(', '));
-                $('kronolithC' + type + 'PU').setValue(1);
-                $('kronolithC' + type + 'PUPerms').setValue(basic.substring(5));
-                $('kronolithC' + type + 'PAdvanced').hide();
-                $('kronolithC' + type + 'PBasic').show();
-                $('kronolithC' + type + 'PUPerms').enable();
-                break;
-            }
-        }
-   },
-
-    /**
-     * Propagates a SELECT drop down list with the groups.
-     *
-     * @param array params  A two-dimensional array with the following values
-     *                      in each element:
-     *                      - The id of the SELECT element.
-     *                      - A callback method that is invoked with the group
-     *                        list passes as an argument.
-     */
-    updateGroupDropDown: function(params)
-    {
-        this.groupLoading = true;
-        params.each(function(param) {
-            var elm = $(param[0]), options = elm.childElements();
-            options.invoke('remove');
-            elm.up('form').disable();
-        });
-        HordeCore.doAction('listGroups', {}, {
-            callback: function(r) {
-                var groups;
-                if (r.groups) {
-                    groups = $H(r.groups);
-                    params.each(function(param) {
-                        groups.each(function(group) {
-                            $(param[0]).insert(new Element('option', { value: group.key }).update(group.value.escapeHTML()));
-                        });
-                    });
-                }
-                params.each(function(param) {
-                    $(param[0]).up('form').enable();
-                    if (param[1]) {
-                        param[1](groups);
-                    }
-                });
-                this.groupLoading = false;
-            }.bind(this)
-        });
-    },
-
-    /**
-     * Updates the group permission interface after the group list has
-     * been loaded.
-     *
-     * @param string type  The calendar type, 'internal' or 'taskslists'.
-     * @param Hash groups  The list of groups.
-     */
-    updateGroupPerms: function(type, groups)
-    {
-        $('kronolithC' + type + 'PGSingle').clear();
-        if (!groups) {
-            $('kronolithC' + type + 'PGNew').up('div').hide();
-            $('kronolithC' + type + 'PG').up('span').hide();
-        } else {
-            $('kronolithC' + type + 'PGNew').up('div').show();
-            $('kronolithC' + type + 'PG').up('span').show();
-            if (groups.size() == 1) {
-                $('kronolithC' + type + 'PGName')
-                    .update('&quot;' + groups.values()[0].escapeHTML() + '&quot;')
-                    .show();
-                $('kronolithC' + type + 'PGSingle').setValue(groups.keys()[0]);
-                $('kronolithC' + type + 'PGList').hide();
-            } else {
-                $('kronolithC' + type + 'PGName').hide();
-                $('kronolithC' + type + 'PGList').show();
-            }
-        }
-    },
-
-    /**
-     * Inserts a group or user row into the advanced permissions interface.
-     *
-     * @param string type          The calendar type, 'internal' or
-     *                             'taskslists'.
-     * @param what string          Either 'group' or 'user'.
-     * @param group string         The group id or user name to insert.
-     *                             Defaults to the value of the drop down.
-     * @param stay_basic boolean   Enforces to NOT switch to the advanced
-     *                             permissions screen.
-     *
-     * @return boolean  Whether a row has been inserted.
-     */
-    insertGroupOrUser: function(type, what, id, stay_basic)
-    {
-        var elm = $(what == 'user' ? 'kronolithC' + type + 'PUNew' : 'kronolithC' + type + 'PGNew');
-        if (id) {
-            elm.setValue(id);
-        }
-        var value = elm.getValue();
-        if (!value) {
-            if (id) {
-                HordeCore.notify(Kronolith.text.invalid_user + ': ' + id, 'horde.error');
-            }
-            return false;
-        }
-
-        var tr = elm.up('tr'),
-            row = tr.clone(true).store('remove', true),
-            td = row.down('td'),
-            clearName = elm.tagName == 'SELECT' ? elm.options[elm.selectedIndex].text: elm.getValue();
-
-        td.update();
-        td.insert(clearName.escapeHTML())
-            .insert(new Element('input', { type: 'hidden', name: (what == 'user' ? 'u' : 'g') + '_names[' + value + ']', value: value }));
-        row.select('input[type=checkbox]').each(function(input) {
-            input.writeAttribute('name', input.name.replace(/\[.*?$/, '[' + value + ']'))
-                .writeAttribute('id', input.id.replace(/new/, value))
-                .next()
-                .writeAttribute('for', input.id);
-        });
-        tr.insert({ before: row });
-
-        if (elm.tagName == 'SELECT') {
-            elm.options[elm.selectedIndex].writeAttribute('disabled', true);
-            elm.selectedIndex = 0;
-        } else {
-            elm.clear();
-        }
-
-        if (!stay_basic) {
-            this.activateAdvancedPerms(type);
-        }
-
-        return true;
-    },
-
-    /**
-     * Activates the advanced permissions.
-     *
-     * @param string type  The calendar type, 'internal' or 'taskslists'.
-     */
-    activateAdvancedPerms: function(type)
-    {
-        [$('kronolithC' + type + 'PNone'),
-         $('kronolithC' + type + 'PU'),
-         $('kronolithC' + type + 'PG')].each(function(radio) {
-            radio.checked = false;
-        });
-        if ($('kronolithC' + type + 'PAll')) {
-            $('kronolithC' + type + 'PAll').checked = false;
-        }
-        $('kronolithC' + type + 'PBasic').hide();
-        $('kronolithC' + type + 'PAdvanced').show();
-    },
-
-    /**
-     * Opens the next screen of the calendar management wizard.
-     *
-     * @param string type  The calendar type.
-     */
-    calendarNext: function(type)
-    {
-        var i = 1;
-        while (!$('kronolithCalendar' + type + i).visible()) {
-            i++;
-        }
-        $('kronolithCalendar' + type + i).hide();
-        $('kronolithCalendar' + type + (++i)).show();
-        if (this.colorPicker) {
-            this.colorPicker.hide();
-        }
-    },
-
-    /**
-     * Submits the calendar form to save the calendar data.
-     *
-     * @param Element form  The form node.
-     *
-     * @return boolean  Whether the save request was successfully sent.
-     */
-    saveCalendar: function(form)
-    {
-        if (this.colorPicker) {
-            this.colorPicker.hide();
-        }
-        var data = form.serialize({ hash: true });
-
-        if (data.type == 'holiday') {
-            this.insertCalendarInList('holiday', data.driver, Kronolith.conf.calendars.holiday[data.driver]);
-            this.toggleCalendar('holiday', data.driver);
-            form.down('.kronolithCalendarSave').enable();
-            this.closeRedBox();
-            this.go(this.lastLocation);
-            return;
-        }
-
-        if (data.name.empty()) {
-            HordeCore.notify(data.type == 'tasklists' ? Kronolith.text.no_tasklist_title : Kronolith.text.no_calendar_title, 'horde.warning');
-            $('kronolithCalendar' + data.type + 'Name').focus();
-            return false;
-        }
-        HordeCore.doAction('saveCalendar', data, {
-            callback: this.saveCalendarCallback.bind(this, form, data)
-        });
-        return true;
-    },
-
-    calendarImport: function(form, disableForm)
-    {
-        if ($F('kronolithCalendarinternalImport')) {
-            HordeCore.notify(Kronolith.text.import_warning, 'horde.message');
-            this.loading++;
-            $('kronolithLoading').show();
-            var name = 'kronolithIframe' + Math.round(Math.random() * 1000),
-                iframe = new Element('iframe', { src: 'about:blank', name: name, id: name }).setStyle({ display: 'none' });
-            document.body.insert(iframe);
-            form.enable();
-            form.target = name;
-            form.submit();
-            if (disableForm) {
-                form.disable();
-            }
-        }
-    },
-
-    /**
-     * Callback method after saving a calendar.
-     *
-     * @param Element form  The form node.
-     * @param object data   The serialized form data.
-     * @param object r      The ajax response object.
-     */
-    saveCalendarCallback: function(form, data, r)
-    {
-        var type = form.id.replace(/kronolithCalendarForm/, '');
-
-        // If saving the calendar changed the owner, we need to delete
-        // and re-insert the calendar.
-        if (r.deleted) {
-            this.deleteCalendar(type, data.calendar);
-            delete data.calendar;
-        }
-        if (r.saved) {
-            this.calendarImport(form, false);
-            var cal = r.calendar, id;
-            if (data.calendar) {
-                var color = {
-                    backgroundColor: cal.bg,
-                    color: cal.fg
-                },
-                legendSpan;
-                id = data.calendar;
-                this.getCalendarList(type, cal.owner).select('div').each(function(element) {
-                    if (element.retrieve('calendar') == id) {
-                        var link = element.down('.horde-resource-link span');
-                        element.setStyle(color);
-                        link.update(cal.name.escapeHTML());
-                        this.addShareIcon(cal, link);
-                        throw $break;
-                    }
-                }, this);
-                this.kronolithBody.select('div').each(function(el) {
-                    if (el.retrieve('calendar') == type + '|' + id) {
-                        el.setStyle(color);
-                    }
-                });
-                legendSpan = $('kronolith-legend').select('span')
-                    .find(function(span) {
-                        return span.retrieve('calendarclass') == type &&
-                            span.retrieve('calendar') == id;
-                    });
-                if (legendSpan) {
-                    legendSpan.setStyle(color).update(cal.name.escapeHTML());
-                }
-                Kronolith.conf.calendars[type][id] = cal;
-            } else {
-                id = r.id;
-                if (!Kronolith.conf.calendars[type]) {
-                    Kronolith.conf.calendars[type] = [];
-                }
-                Kronolith.conf.calendars[type][id] = cal;
-                this.insertCalendarInList(type, id, cal);
-                this.storeCache($H(), [type, id], this.viewDates(this.date, this.view), true);
-                if (type == 'tasklists') {
-                    this.storeTasksCache($H(), this.tasktype, id.replace(/^tasks\//, ''), true);
-                }
-            }
-            if (type == 'remote') {
-                this.loadCalendar(type, id);
-            }
-        }
-        form.down('.kronolithCalendarSave').enable();
-        this.closeRedBox();
-        this.go(this.lastLocation);
-    },
-
-    /**
-     * Deletes a calendar and all its events from the interface and cache.
-     *
-     * @param string type      The calendar type.
-     * @param string calendar  The calendar id.
-     */
-    deleteCalendar: function(type, calendar)
-    {
-        var container = this.getCalendarList(type, Kronolith.conf.calendars[type][calendar].owner),
-            noItems = container.previous(),
-            div = container.select('div').find(function(element) {
-                return element.retrieve('calendar') == calendar;
-            }),
-            arrow = div.down('span');
-        arrow.purge();
-        arrow.remove();
-        div.purge();
-        div.remove();
-        if (noItems &&
-            noItems.tagName == 'DIV' &&
-            noItems.className == 'horde-info' &&
-            !container.childElements().size()) {
-            noItems.show();
-        }
-        this.deleteCalendarLegend(type, calendar);
-        this.removeEvent(type + '|' + calendar);
-        this.deleteCache([type, calendar]);
-        if (type == 'tasklists' && this.view == 'tasks') {
-            this.removeTask(calendar.replace(/^tasks\//, ''));
-        }
-        delete Kronolith.conf.calendars[type][calendar];
-    },
-
-    /**
-     * Parses a date attribute string into a Date object.
-     *
-     * For other strings use Date.parse().
-     *
-     * @param string date  A yyyyMMdd date string.
-     *
-     * @return Date  A date object.
-     */
-    parseDate: function(date)
-    {
-        var d = new Date(date.substr(0, 4), date.substr(4, 2) - 1, date.substr(6, 2));
-        if (date.length == 12) {
-            d.setHours(date.substr(8, 2));
-            d.setMinutes(date.substr(10, 2));
-        }
-        return d;
-    },
-
-    /**
-     * Calculates first and last days being displayed.
-     *
-     * @var Date date    The date of the view.
-     * @var string view  A view name.
-     *
-     * @return array  Array with first and last day of the view.
-     */
-    viewDates: function(date, view)
-    {
-        var start = date.clone(), end = date.clone();
-
-        switch (view) {
-        case 'week':
-        case 'workweek':
-            if (view == 'workweek') {
-                start.add(1).days();
-            }
-            start.moveToBeginOfWeek(view == 'week' ? Kronolith.conf.week_start : 1);
-            end = start.clone();
-            end.moveToEndOfWeek(Kronolith.conf.week_start);
-            if (view == 'workweek') {
-                end.add(Kronolith.conf.week_start == 0 ? -1 : -2).days();
-            }
-            break;
-        case 'month':
-            start.setDate(1);
-            start.moveToBeginOfWeek(Kronolith.conf.week_start);
-            end.moveToLastDayOfMonth();
-            end.moveToEndOfWeek(Kronolith.conf.week_start);
-            break;
-        case 'year':
-            start.setDate(1);
-            start.setMonth(0);
-            end.setMonth(11);
-            end.moveToLastDayOfMonth();
-            break;
-        case 'agenda':
-            end.add(6).days();
-            break;
-        }
-
-        return [start, end];
-    },
-
-    /**
-     * Stores a set of events in the cache.
-     *
-     * For dates in the specified date ranges that don't contain any events,
-     * empty cache entries are created so that those dates aren't re-fetched
-     * each time.
-     *
-     * @param object events        A list of calendars and events as returned
-     *                             from an ajax request.
-     * @param string calendar      A calendar string or array.
-     * @param string dates         A date range in the format yyyymmddyyyymmdd
-     *                             as used in the ajax response signature.
-     * @param boolean createCache  Whether to create a cache list entry for the
-     *                             response, if none exists yet.
-     */
-    storeCache: function(events, calendar, dates, createCache)
-    {
-        if (Object.isString(calendar)) {
-            calendar = calendar.split('|');
-        }
-
-        // Create cache entry for the calendar.
-        if (!this.ecache.get(calendar[0])) {
-            if (!createCache) {
-                return;
-            }
-            this.ecache.set(calendar[0], $H());
-        }
-        if (!this.ecache.get(calendar[0]).get(calendar[1])) {
-            if (!createCache) {
-                return;
-            }
-            this.ecache.get(calendar[0]).set(calendar[1], $H());
-        }
-        var calHash = this.ecache.get(calendar[0]).get(calendar[1]);
-
-        // Create empty cache entries for all dates.
-        if (!!dates) {
-            var day = dates[0].clone(), date;
-            while (!day.isAfter(dates[1])) {
-                date = day.dateString();
-                if (!calHash.get(date)) {
-                    if (!createCache) {
-                        return;
-                    }
-                    if (!this.cacheStart || this.cacheStart.isAfter(day)) {
-                        this.cacheStart = day.clone();
-                    }
-                    if (!this.cacheEnd || this.cacheEnd.isBefore(day)) {
-                        this.cacheEnd = day.clone();
-                    }
-                    calHash.set(date, $H());
-                }
-                day.add(1).day();
-            }
-        }
-
-        var cal = calendar.join('|');
-        $H(events).each(function(date) {
-            // We might not have a cache for this date if the event lasts
-            // longer than the current view
-            if (!calHash.get(date.key)) {
-                return;
-            }
-
-            // Store calendar string and other useful information in event
-            // objects.
-            $H(date.value).each(function(event) {
-                event.value.calendar = cal;
-                event.value.start = Date.parse(event.value.s);
-                event.value.end = Date.parse(event.value.e);
-            });
-
-            // Store events in cache.
-            calHash.set(date.key, calHash.get(date.key).merge(date.value));
-        });
-    },
-
-    /**
-     * Stores a set of tasks in the cache.
-     *
-     * @param Hash tasks           The tasks to be stored.
-     * @param string tasktypes     The task type that's being stored.
-     * @param string tasklist      The task list to which the tasks belong.
-     * @param boolean createCache  Whether to create a cache list entry for the
-     *                             response, if none exists yet.
-     */
-    storeTasksCache: function(tasks, tasktypes, tasklist, createCache)
-    {
-        var taskHashes = {}, cacheExists = {};
-
-        if (tasktypes == 'all' || tasktypes == 'future') {
-            tasktypes = [ 'complete', 'incomplete' ];
-        } else {
-            tasktypes = [ tasktypes ];
-        }
-
-        tasktypes.each(function(tasktype) {
-            cacheExists[tasktype] = false;
-            if (!this.tcache.get(tasktype)) {
-                if (!createCache) {
-                    return;
-                }
-                this.tcache.set(tasktype, $H());
-            }
-            if (!tasklist) {
-                return;
-            }
-            if (!this.tcache.get(tasktype).get(tasklist)) {
-                if (!createCache) {
-                    return;
-                }
-                this.tcache.get(tasktype).set(tasklist, $H());
-                cacheExists[tasktype] = true;
-            } else {
-                cacheExists[tasktype] = true;
-            }
-            taskHashes[tasktype] = this.tcache.get(tasktype).get(tasklist);
-        }, this);
-
-        $H(tasks).each(function(task) {
-            var tasktype = task.value.cp ? 'complete' : 'incomplete';
-            if (!cacheExists[tasktype]) {
-                return;
-            }
-            if (!Object.isUndefined(task.value.s)) {
-                task.value.start = Date.parse(task.value.s);
-            }
-            if (!Object.isUndefined(task.value.du)) {
-                task.value.due = Date.parse(task.value.du);
-            }
-            taskHashes[tasktype].set(task.key, task.value);
-        });
-    },
-
-    /**
-     * Deletes an event or a complete calendar from the cache.
-     *
-     * @param string calendar  A calendar string or array.
-     * @param string event     An event ID or empty if deleting the calendar.
-     * @param string day       A specific day to delete in yyyyMMdd form.
-     */
-    deleteCache: function(calendar, event, day)
-    {
-        if (Object.isString(calendar)) {
-            calendar = calendar.split('|');
-        }
-        if (!this.ecache.get(calendar[0]) ||
-            !this.ecache.get(calendar[0]).get(calendar[1])) {
-            return;
-        }
-        if (event) {
-            this.ecache.get(calendar[0]).get(calendar[1]).each(function(day) {
-                day.value.unset(event);
-            });
-        } else if (day) {
-            this.ecache.get(calendar[0]).get(calendar[1]).unset(day);
-        } else {
-            this.ecache.get(calendar[0]).unset(calendar[1]);
-        }
-    },
-
-    /**
-     * Deletes tasks from the cache.
-     *
-     * @param string list  A task list string.
-     * @param string task  A task ID. If empty, all tasks from the list are
-     *                     deleted.
-     */
-    deleteTasksCache: function(list, task)
-    {
-        this.deleteCache([ 'external', 'tasks/' + list ], task);
-        $w('complete incomplete').each(function(type) {
-            if (!Object.isUndefined(this.tcache.get(type)) &&
-                !Object.isUndefined(this.tcache.get(type).get(list))) {
-                if (task) {
-                    this.tcache.get(type).get(list).unset(task);
-                } else {
-                    this.tcache.get(type).unset(list);
-                }
-            }
-        }, this);
-    },
-
-    /**
-     * Return all events for a single day from all displayed calendars merged
-     * into a single hash.
-     *
-     * @param string date  A yyyymmdd date string.
-     *
-     * @return Hash  An event hash which event ids as keys and event objects as
-     *               values.
-     */
-    getCacheForDate: function(date, calendar)
-    {
-        if (calendar) {
-            var cals = calendar.split('|');
-            if (!this.ecache.get(cals[0]) ||
-                !this.ecache.get(cals[0]).get(cals[1])) {
-                return $H();
-            }
-            var x = this.ecache.get(cals[0]).get(cals[1]).get(date);
-            return this.ecache.get(cals[0]).get(cals[1]).get(date);
-        }
-
-        var events = $H();
-        this.ecache.each(function(type) {
-            type.value.each(function(cal) {
-                if (!Kronolith.conf.calendars[type.key][cal.key].show) {
-                    return;
-                }
-                events = events.merge(cal.value.get(date));
-            });
-        });
-        return events;
-    },
-
-    /**
-     * Helper method for Enumerable.sortBy to sort events first by start time,
-     * second by end time reversed.
-     *
-     * @param Hash event  A hash entry with the event object as the value.
-     *
-     * @return string  A comparable string.
-     */
-    sortEvents: function(event)
-    {
-        return event.value.sort;
-    },
-
-    /**
-     * Adds a new location to the history and displays it in the URL hash.
-     *
-     * This is not really a history, because only the current and the last
-     * location are stored.
-     *
-     * @param string loc    The location to save.
-     * @param boolean save  Whether to actually save the location. This should
-     *                      be false for any location that are displayed on top
-     *                      of another location, i.e. in a popup view.
-     */
-    addHistory: function(loc, save)
-    {
-        location.hash = encodeURIComponent(loc);
-        this.lastLocation = this.currentLocation;
-        if (Object.isUndefined(save) || save) {
-            this.currentLocation = loc;
-        }
-        this.openLocation = loc;
-    },
-
-    /**
-     * Loads an external page.
-     *
-     * @param string loc  The URL of the page to load.
-     */
-    loadPage: function(loc)
-    {
-        window.location.assign(loc);
-    },
-
-    searchSubmit: function(e)
-    {
-        this.go('search:' + this.search + ':' + $F('horde-search-input'));
-    },
-
-    searchReset: function(e)
-    {
-        HordeTopbar.searchGhost.reset();
-    },
-
-    /**
-     * Event handler for HordeCore:showNotifications events.
-     */
-    showNotification: function(e)
-    {
-        if (!e.memo.flags ||
-            !e.memo.flags.alarm ||
-            !e.memo.flags.growl ||
-            !e.memo.flags.alarm.params.notify.ajax) {
-            return;
-        }
-
-        var growl = e.memo.flags.growl, link = growl.down('A');
-
-        if (link) {
-            link.observe('click', function(ee) {
-                ee.stop();
-                HordeCore.Growler.ungrowl(growl);
-                this.go(e.memo.flags.alarm.params.notify.ajax);
-            }.bind(this));
-        }
-    },
-
-    /* Keydown event handler */
-    keydownHandler: function(e)
-    {
-        if (e.stopped) {
-            return;
-        }
-
-        var kc = e.keyCode || e.charCode,
-            form = e.findElement('FORM'), trigger = e.findElement();
-
-        switch (trigger.id) {
-        case 'kronolithEventLocation':
-            if (kc == Event.KEY_RETURN && $F('kronolithEventLocation')) {
-                this.initializeMap(true);
-                this.geocode($F('kronolithEventLocation'));
-                e.stop();
-                return;
-            }
-            break;
-
-        case 'kronolithCalendarinternalUrlCaldav':
-        case 'kronolithCalendarinternalUrlWebdav':
-        case 'kronolithCalendarinternalUrlAccount':
-        case 'kronolithCalendarinternalUrlFeed':
-        case 'kronolithCalendartasklistsUrlCaldav':
-        case 'kronolithCalendartasklistsUrlWebdav':
-        case 'kronolithCalendartasklistsUrlAccount':
-            if (String.fromCharCode(kc) != 'C' ||
-                (this.macos && !e.metaKey) ||
-                (!this.macos && !e.ctrlKey)) {
-                e.stop();
-                return;
-            }
-            break;
-        }
-
-        if (form) {
-            switch (kc) {
-            case Event.KEY_RETURN:
-                switch (form.identify()) {
-                case 'kronolithEventForm':
-                    if (e.element().tagName != 'TEXTAREA') {
-                        this.saveEvent();
-                        e.stop();
-                    }
-                    break;
-
-                case 'kronolithTaskForm':
-                    if (e.element().tagName != 'TEXTAREA') {
-                        this.saveTask();
-                        e.stop();
-                    }
-                    break;
-
-                case 'kronolithQuickinsertForm':
-                    this.quickSaveEvent();
-                    e.stop();
-                    break;
-
-                case 'kronolithCalendarForminternal':
-                case 'kronolithCalendarFormtasklists':
-                case 'kronolithCalendarFormremote':
-                    // Disabled for now, we have to also catch Continue buttons.
-                    //var saveButton = form.down('.kronolithCalendarSave');
-                    //saveButton.disable();
-                    //if (!this.saveCalendar(form)) {
-                    //    saveButton.enable();
-                    //}
-                    //e.stop();
-                    break;
-                }
-                break;
-
-            case Event.KEY_ESC:
-                switch (form.identify()) {
-                case 'kronolithQuickinsertForm':
-                case 'kronolithQuicktaskForm':
-                    this.quickClose();
-                    break;
-                case 'kronolithEventForm':
-                case 'kronolithTaskForm':
-                    Horde_Calendar.hideCal();
-                    this.closeRedBox();
-                    this.go(this.lastLocation);
-                    break;
-                }
-                break;
-            }
-
-            return;
-        }
-
-        switch (kc) {
-        case Event.KEY_ESC:
-            Horde_Calendar.hideCal();
-            this.closeRedBox();
-            break;
-        }
-    },
-
-    keyupHandler: function(e)
-    {
-        switch (e.element().readAttribute('id')) {
-        case 'kronolithEventLocation':
-            if ($F('kronolithEventLocation') && Kronolith.conf.maps.driver) {
-                $('kronolithEventMapLink').show();
-            } else if (Kronolith.conf.maps.driver) {
-                $('kronolithEventMapLink').hide();
-                this.removeMapMarker();
-            }
-            return;
-
-        case 'kronolithEventStartTime':
-        case 'kronolithEventEndTime':
-            var field = $(e.element().readAttribute('id')), kc = e.keyCode;
-
-            switch(e.keyCode) {
-            case Event.KEY_UP:
-            case Event.KEY_DOWN:
-            case Event.KEY_RIGHT:
-            case Event.KEY_LEFT:
-                return;
-            default:
-                if ($F(field) !== this.knl[field.identify()].getCurrentEntry()) {
-                    this.knl[field.identify()].markSelected(null);
-                }
-                return;
-            }
-        }
-
-    },
-
-    clickHandler: function(e, dblclick)
-    {
-        if (e.isRightClick() || typeof e.element != 'function') {
-            return;
-        }
-
-        var elt = e.element(),
-            orig = e.element(),
-            id, tmp, calendar;
-
-        while (Object.isElement(elt)) {
-            id = elt.readAttribute('id');
-
-            switch (id) {
-            case 'kronolithNewEvent':
-                this.go('event');
-                e.stop();
-                return;
-
-            case 'kronolithNewTask':
-                this.go('task');
-                e.stop();
-                return;
-
-            case 'kronolithQuickEvent':
-                if (this.view == 'tasks') {
-                    RedBox.showHtml($('kronolithQuicktask').show());
-                } else {
-                    this.updateCalendarDropDown('kronolithQuickinsertCalendars');
-                    $('kronolithQuickinsertCalendars').setValue(Kronolith.conf.default_calendar);
-                    RedBox.showHtml($('kronolithQuickinsert').show());
-                }
-                e.stop();
-                return;
-
-            case 'kronolithQuickinsertSave':
-                this.quickSaveEvent();
-                e.stop();
-                return;
-
-            case 'kronolithQuicktaskSave':
-                this.quickSaveTask();
-                e.stop();
-                return;
-
-            case 'kronolithQuickinsertCancel':
-            case 'kronolithQuicktaskCancel':
-                this.quickClose();
-                e.stop();
-                return;
-
-            case 'kronolithGotoToday':
-                var view = this.view;
-                if (!$w('day workweek week month year agenda').include(view)) {
-                    view = Kronolith.conf.login_view;
-                }
-                this.go(view + ':' + new Date().dateString());
-                e.stop();
-                return;
-
-            case 'kronolithEventAllday':
-                this.toggleAllDay();
-                break;
-
-            case 'kronolithEventAlarmDefaultOn':
-                this.disableAlarmMethods('Event');
-                break;
-
-            case 'kronolithTaskAlarmDefaultOn':
-                this.disableAlarmMethods('Task');
-                break;
-
-            case 'kronolithEventAlarmPrefs':
-                HordeCore.redirect(HordeCore.addURLParam(
-                    Kronolith.conf.prefs_url,
-                    {
-                        group: 'notification'
-                    }
-                ));
-                e.stop();
-                break;
-
-            case 'kronolithTaskAlarmPrefs':
-                if (Kronolith.conf.tasks.prefs_url) {
-                    HordeCore.redirect(HordeCore.addURLParam(
-                        Kronolith.conf.tasks.prefs_url,
-                        {
-                            group: 'notification'
-                        }
-                    ));
-                }
-                e.stop();
-                break;
-
-            case 'kronolithEventLinkNone':
-            case 'kronolithEventLinkDaily':
-            case 'kronolithEventLinkWeekly':
-            case 'kronolithEventLinkMonthly':
-            case 'kronolithEventLinkYearly':
-            case 'kronolithEventLinkLength':
-            case 'kronolithTaskLinkNone':
-            case 'kronolithTaskLinkDaily':
-            case 'kronolithTaskLinkWeekly':
-            case 'kronolithTaskLinkMonthly':
-            case 'kronolithTaskLinkYearly':
-            case 'kronolithTaskLinkLength':
-                this.toggleRecurrence(
-                    id.startsWith('kronolithEvent'),
-                    id.substring(id.startsWith('kronolithEvent') ? 18 : 17));
-                break;
-
-            case 'kronolithEventRepeatDaily':
-            case 'kronolithEventRepeatWeekly':
-            case 'kronolithEventRepeatMonthly':
-            case 'kronolithEventRepeatYearly':
-            case 'kronolithEventRepeatLength':
-            case 'kronolithTaskRepeatDaily':
-            case 'kronolithTaskRepeatWeekly':
-            case 'kronolithTaskRepeatMonthly':
-            case 'kronolithTaskRepeatYearly':
-            case 'kronolithTaskRepeatLength':
-                this.toggleRecurrence(
-                    id.startsWith('kronolithEvent'),
-                    id.substring(id.startsWith('kronolithEvent') ? 20 : 19));
-                break;
-
-            case 'kronolithEventSave':
-                if (!elt.disabled) {
-                    this._checkDate($('kronolithEventStartDate'));
-                    this._checkDate($('kronolithEventEndDate'));
-                    if ($F('kronolithEventAttendees') && $F('kronolithEventId')) {
-                        $('kronolithEventSendUpdates').setValue(0);
-                        $('kronolithEventDiv').hide();
-                        $('kronolithUpdateDiv').show();
-                        e.stop();
-                        break;
-                    }
-                }
-            case 'kronolithEventSendUpdateYes':
-                if (this.uatts) {
-                    this.uatts.u = true;
-                } else {
-                    $('kronolithEventSendUpdates').setValue(1);
-                }
-            case 'kronolithEventSendUpdateNo':
-                if (this.uatts) {
-                    this.doDragDropUpdate(this.uatts, this.ucb);
-                    this.uatts = null;
-                    this.ucb = null;
-                    this.closeRedBox();
-                    $('kronolithUpdateDiv').hide();
-                    $('kronolithEventDiv').show();
-                } else if (!elt.disabled) {
-                    this.saveEvent();
-                }
-                e.stop();
-                break;
-            case 'kronolithEventConflictYes':
-                this.doSaveEvent();
-                e.stop();
-                break;
-            case 'kronolithEventConflictNo':
-                $('kronolithConflictDiv').hide();
-                $('kronolithEventDiv').show();
-                e.stop();
-                break;
-            case 'kronolithEventSaveAsNew':
-                if (!elt.disabled) {
-                    $('kronolithEventSendUpdates').setValue(1);
-                    this.saveEvent(true);
-                }
-                e.stop();
-                break;
-
-            case 'kronolithTaskSave':
-                if (!elt.disabled) {
-                    this.saveTask();
-                }
-                e.stop();
-                break;
-
-            case 'kronolithEventDeleteCancel':
-                $('kronolithDeleteDiv').hide();
-                $('kronolithEventDiv').show();
-                e.stop();
-                return;
-
-            case 'kronolithEventSendCancellationYes':
-                $('kronolithRecurDeleteAll').enable();
-                $('kronolithRecurDeleteCurrent').enable();
-                $('kronolithRecurDeleteFuture').enable();
-                this.paramsCache.sendupdates = 1;
-            case 'kronolithEventSendCancellationNo':
-                $('kronolithRecurDeleteAll').enable();
-                $('kronolithRecurDeleteCurrent').enable();
-                $('kronolithRecurDeleteFuture').enable();
-                $('kronolithCancellationDiv').hide();
-                this.delete_verified = true;
-            case 'kronolithEventDelete':
-                if ((Kronolith.conf.confirm_delete || this.recurs) && !this.delete_verified) {
-                    $('kronolithEventDiv').hide();
-                    $('kronolithDeleteDiv').show();
-                    e.stop();
-                    break;
-                } else {
-                    $('kronolithEventDiv').hide();
-                    this.delete_verified = false;
-                }
-                // Fallthrough
-            case 'kronolithRecurDeleteAll':
-            case 'kronolithRecurDeleteCurrent':
-            case 'kronolithRecurDeleteFuture':
-            case 'kronolithEventDeleteConfirm':
-                if (elt.disabled) {
-                    e.stop();
-                    break;
-                }
-                elt.disable();
-                var cal = $F('kronolithEventCalendar'),
-                    eventid = $F('kronolithEventId');
-                if (id != 'kronolithEventSendCancellationNo' &&
-                    id != 'kronolithEventSendCancellationYes') {
-                    this.paramsCache = {
-                        cal: cal,
-                        id: eventid,
-                        rstart: $F('kronolithEventRecurOStart'),
-                        cstart: this.cacheStart.toISOString(),
-                        cend: this.cacheEnd.toISOString()
-                    };
-                    switch (id) {
-                    case 'kronolithRecurDeleteAll':
-                        this.paramsCache.r = 'all';
-                        break;
-                    case 'kronolithRecurDeleteCurrent':
-                        this.paramsCache.r = 'current';
-                        break;
-                    case 'kronolithRecurDeleteFuture':
-                        this.paramsCache.r = 'future';
-                        break;
-                    }
-                }
-
-                if (id != 'kronolithEventSendCancellationNo'
-                    && id != 'kronolithEventSendCancellationYes'
-                    && $F('kronolithEventAttendees')) {
-
-                    $('kronolithDeleteDiv').hide();
-                    $('kronolithCancellationDiv').show();
-                    e.stop();
-                    break;
-                }
-
-                this.kronolithBody.select('div').findAll(function(el) {
-                    return el.retrieve('calendar') == cal &&
-                        el.retrieve('eventid') == eventid;
-                }).invoke('hide');
-                var viewDates = this.viewDates(this.date, this.view),
-                start = viewDates[0].toString('yyyyMMdd'),
-                end = viewDates[1].toString('yyyyMMdd');
-                this.paramsCache.sig = start + end + (Math.random() + '').slice(2);
-                this.paramsCache.view_start = start;
-                this.paramsCache.view_end = end;
-
-                HordeCore.doAction('deleteEvent', this.paramsCache, {
-                    callback: function(elt,r) {
-                        if (r.deleted) {
-                            var days;
-                            if (this.view == 'month' ||
-                                this.view == 'week' ||
-                                this.view == 'workweek' ||
-                                this.view == 'day') {
-                                days = this.findEventDays(cal, eventid);
-                                days.each(function(day) {
-                                    this.refreshResources(day, cal, eventid);
-                                }.bind(this));
-                            }
-                            this.removeEvent(cal, eventid);
-                            if (r.uid) {
-                                this.removeException(cal, r.uid);
-                            }
-                            this.loadEventsCallback(r, false);
-                            if (days && days.length) {
-                                this.reRender(days);
-                            }
-                        } else {
-                            this.kronolithBody.select('div').findAll(function(el) {
-                                return el.retrieve('calendar') == cal &&
-                                       el.retrieve('eventid') == eventid;
-                            }).invoke('show');
-                        }
-                        elt.enable();
-                    }.curry(elt).bind(this)
-                });
-
-                $('kronolithDeleteDiv').hide();
-                $('kronolithEventDiv').show();
-                this.closeRedBox();
-                this.go(this.lastLocation);
-                e.stop();
-                break;
-
-            case 'kronolithTaskDelete':
-                if (elt.disabled) {
-                    e.stop();
-                    break;
-                }
-
-                elt.disable();
-                var tasklist = $F('kronolithTaskOldList'),
-                    taskid = $F('kronolithTaskId');
-
-                HordeCore.doAction('deleteTask', {
-                    list: tasklist,
-                    id: taskid
-                }, {
-                    callback: function(r) {
-                        if (r.deleted) {
-                            this.removeTask(tasklist, taskid);
-                        } else {
-                            elt.enable();
-                            $('kronolithViewTasksBody').select('tr').find(function(el) {
-                                return el.retrieve('tasklist') == tasklist &&
-                                       el.retrieve('taskid') == taskid;
-                            }).toggle();
-                        }
-                    }.bind(this)
-                });
-
-                var taskrow = $('kronolithViewTasksBody').select('tr').find(function(el) {
-                    return el.retrieve('tasklist') == tasklist &&
-                        el.retrieve('taskid') == taskid;
-                });
-                if (taskrow) {
-                    taskrow.hide();
-                }
-                this.closeRedBox();
-                this.go(this.lastLocation);
-                e.stop();
-                break;
-
-            case 'kronolithCinternalPMore':
-            case 'kronolithCinternalPLess':
-            case 'kronolithCtasklistsPMore':
-            case 'kronolithCtasklistsPLess':
-                var type = id.match(/kronolithC(.*)P/)[1];
-                $('kronolithC' + type + 'PBasic').toggle();
-                $('kronolithC' + type + 'PAdvanced').toggle();
-                e.stop();
-                break;
-
-            case 'kronolithCinternalPNone':
-            case 'kronolithCinternalPAll':
-            case 'kronolithCinternalPG':
-            case 'kronolithCinternalPU':
-            case 'kronolithCtasklistsPNone':
-            case 'kronolithCtasklistsPAll':
-            case 'kronolithCtasklistsPG':
-            case 'kronolithCtasklistsPU':
-                var info = id.match(/kronolithC(.*)P(.*)/);
-                this.permsClickHandler(info[1], info[2]);
-                break;
-
-            case 'kronolithCinternalPAllShow':
-            case 'kronolithCtasklistsPAllShow':
-                var type = id.match(/kronolithC(.*)P/)[1];
-                this.permsClickHandler(type, 'All');
-                break;
-
-            case 'kronolithCinternalPAdvanced':
-            case 'kronolithCtasklistsPAdvanced':
-                var type = id.match(/kronolithC(.*)P/)[1];
-                if (orig.tagName != 'INPUT') {
-                    break;
-                }
-                this.activateAdvancedPerms(type);
-                if (orig.name.match(/u_.*||new/)) {
-                    this.insertGroupOrUser(type, 'user');
-                }
-                break;
-
-            case 'kronolithCinternalPUAdd':
-            case 'kronolithCinternalPGAdd':
-            case 'kronolithCtasklistsPUAdd':
-            case 'kronolithCtasklistsPGAdd':
-                var info = id.match(/kronolithC(.*)P(.)/);
-                this.insertGroupOrUser(info[1], info[2] == 'U' ? 'user' : 'group');
-                break;
-
-            case 'kronolithNavDay':
-            case 'kronolithNavWeek':
-            case 'kronolithNavWorkweek':
-            case 'kronolithNavMonth':
-            case 'kronolithNavYear':
-            case 'kronolithNavAgenda':
-                this.go(id.substring(12).toLowerCase() + ':' + this.date.dateString());
-                e.stop();
-                return;
-
-            case 'kronolithNavTasks':
-                this.go('tasks');
-                e.stop();
-                return;
-
-            case 'kronolithTasksAll':
-            case 'kronolithTasksComplete':
-            case 'kronolithTasksIncomplete':
-            case 'kronolithTasksFuture':
-                this.go('tasks:' + id.substring(14).toLowerCase());
-                e.stop();
-                return;
-
-            case 'kronolithMinicalDate':
-                this.go('month:' + orig.retrieve('date'));
-                e.stop();
-                return;
-
-            case 'kronolith-minical':
-                if (orig.id == 'kronolith-minical-prev') {
-                    var date = this.parseDate($('kronolithMinicalDate').retrieve('date'));
-                    date.previous().month();
-                    this.updateMinical(date, date.getMonth() == this.date.getMonth() ? this.view : undefined);
-                    e.stop();
-                    return;
-                }
-                if (orig.id == 'kronolith-minical-next') {
-                    var date = this.parseDate($('kronolithMinicalDate').retrieve('date'));
-                    date.next().month();
-                    this.updateMinical(date, date.getMonth() == this.date.getMonth() ? this.view : null);
-                    e.stop();
-                    return;
-                }
-
-                var tmp = orig;
-                if (tmp.tagName.toLowerCase() != 'td') {
-                    tmp = tmp.up('td');
-                }
-                if (tmp) {
-                    if (tmp.retrieve('weekdate') &&
-                        tmp.hasClassName('kronolith-minical-week')) {
-                        this.go('week:' + tmp.retrieve('weekdate'));
-                    } else if (tmp.retrieve('date') &&
-                               !tmp.hasClassName('empty')) {
-                        this.go('day:' + tmp.retrieve('date'));
-                    }
-                }
-                e.stop();
-                return;
-
-            case 'kronolithEventsDay':
-                var date = this.date.clone();
-                date.add(Math.round((e.pointerY() - elt.cumulativeOffset().top + elt.up('.kronolithViewBody').scrollTop) / this.daySizes.height * 2) * 30).minutes();
-                this.go('event:' + date.toString('yyyyMMddHHmm'));
-                e.stop();
-                return;
-
-            case 'kronolithViewMonth':
-                if (orig.hasClassName('kronolith-first-col')) {
-                    var date = orig.retrieve('date');
-                    if (date) {
-                        this.go('week:' + date);
-                        e.stop();
-                        return;
-                    }
-                }
-                e.stop();
-                return;
-
-            case 'kronolithViewYear':
-                var tmp = orig;
-                if (tmp.tagName.toLowerCase() != 'td' && tmp.tagName.toLowerCase() != 'th') {
-                    tmp = tmp.up('td');
-                }
-                if (tmp) {
-                    if (tmp.retrieve('weekdate') &&
-                        tmp.hasClassName('kronolith-minical-week')) {
-                        this.go('week:' + tmp.retrieve('weekdate'));
-                    } else if (tmp.hasClassName('kronolithMinicalDate')) {
-                        this.go('month:' + tmp.retrieve('date'));
-                    } else if (tmp.retrieve('date') &&
-                               !tmp.hasClassName('empty')) {
-                        this.go('day:' + tmp.retrieve('date'));
-                    }
-                }
-                e.stop();
-                return;
-
-            case 'kronolithViewAgendaBody':
-                var tmp = orig;
-                if (tmp.tagName != 'TR') {
-                    tmp = tmp.up('tr');
-                }
-                if (tmp && tmp.retrieve('date')) {
-                    this.go('day:' + tmp.retrieve('date'));
-                }
-                e.stop();
-                return;
-
-            case 'kronolithSearchButton':
-                this.go('search:' + this.search + ':' + $F('horde-search-input'));
-                e.stop();
-                break;
-
-            case 'kronolithSearchFuture':
-                if (this.search != 'future') {
-                    this.go('search:future:' + $F('horde-search-input'));
-                }
-                e.stop();
-                break;
-
-            case 'kronolithSearchPast':
-                if (this.search != 'past') {
-                    this.go('search:past:' + $F('horde-search-input'));
-                }
-                e.stop();
-                break;
-
-            case 'kronolithSearchAll':
-                if (this.search != 'all') {
-                    this.go('search:all:' + $F('horde-search-input'));
-                }
-                e.stop();
-                break;
-            case 'kronolithEventToTimeslice':
-                var params = $H();
-                params.set('e', $('kronolithEventId').value);
-                params.set('cal', $('kronolithEventCalendar').value);
-                params.set('t', $('kronolithEventTimesliceType').value);
-                params.set('c', $('kronolithEventTimesliceClient').value);
-                HordeCore.doAction('toTimeslice', params);
-                break;
-            case 'kronolithEventDialog':
-            case 'kronolithTaskDialog':
-                Horde_Calendar.hideCal();
-                return;
-
-            case 'kronolithCalendarDialog':
-                if (this.colorPicker) {
-                    this.colorPicker.hide();
-                }
-                return;
-
-            case 'kronolithEditRecurCurrent':
-            case 'kronolithEditRecurFuture':
-                $('kronolithEventStartDate').setValue(this.orstart);
-                $('kronolithEventEndDate').setValue(this.orend);
-                if (id == 'kronolithEditRecurCurrent') {
-                    this.toggleRecurrence('Exception');
-                } else {
-                    this.toggleRecurrence(this.lastRecurType);
-                }
-                return;
-            case 'kronolithEditRecurAll':
-                this.toggleRecurrence(this.lastRecurType);
-                break;
-            case 'kronolithEventUrlToggle':
-                $('kronolithEventUrlDisplay').hide();
-                $('kronolithEventUrl').show();
-                e.stop();
-                return;
-            case 'kronolithCalendarinternalImportButton':
-                // Used when user has edit perms to a shared calendar.
-                this.calendarImport(elt.up('form'), true);
-                break;
-            }
-
-            // Caution, this only works if the element has definitely only a
-            // single CSS class.
-            switch (elt.className) {
-            case 'kronolithPrev':
-            case 'kronolithNext':
-                var newDate = this.date.clone(),
-                    offset = elt.className == 'kronolithPrev' ? -1 : 1;
-                switch (this.view) {
-                case 'day':
-                case 'agenda':
-                    newDate.add(offset).day();
-                    break;
-                case 'week':
-                case 'workweek':
-                    newDate.add(offset).week();
-                    break;
-                case 'month':
-                    newDate.add(offset).month();
-                    break;
-                case 'year':
-                    newDate.add(offset).year();
-                    break;
-                }
-                this.go(this.view + ':' + newDate.dateString());
-                e.stop();
-                return;
-
-            case 'horde-add':
-                this.go('calendar:' + id.replace(/kronolithAdd/, ''));
-                e.stop();
-                return;
-
-            case 'kronolithTabLink':
-                this.openTab(elt);
-                e.stop();
-                break;
-
-            case 'horde-cancel':
-                this.closeRedBox();
-                this.resetMap();
-                this.go(this.lastLocation);
-                e.stop();
-                break;
-
-            case 'kronolithEventTag':
-                HordeImple.AutoCompleter.kronolithEventTags.addNewItemNode(elt.getText());
-                e.stop();
-                break;
-
-            case 'kronolithCalendarTag':
-                HordeImple.AutoCompleter.kronolithCalendarinternalTags.addNewItemNode(elt.getText());
-                e.stop();
-                break;
-
-            case 'kronolithTaskTag':
-                HordeImple.AutoCompleter.kronolithTaskTags.addNewItemNode(elt.getText());
-                e.stop();
-                break;
-
-            case 'kronolithEventGeo':
-                this.initializeMap(true);
-                this.geocode($F('kronolithEventLocation'));
-                e.stop();
-                break;
-
-            case 'kronolithTaskRow':
-                if (elt.retrieve('taskid')) {
-                    this.go('task:' + elt.retrieve('tasklist') + ':' + elt.retrieve('taskid'));
-                }
-                e.stop();
-                return;
-
-            case 'horde-resource-edit-000':
-            case 'horde-resource-edit-fff':
-                this.go('calendar:' + elt.up().retrieve('calendarclass') + '|' + elt.up().retrieve('calendar'));
-                e.stop();
-                return;
-
-            case 'kronolithMore':
-                this.go('day:' + elt.retrieve('date'));
-                e.stop();
-                return;
-
-            case 'kronolithDatePicker':
-                id = elt.readAttribute('id');
-                Horde_Calendar.open(id, Date.parseExact($F(id.replace(/Picker$/, 'Date')), Kronolith.conf.date_format));
-                e.stop();
-                return;
-
-            case 'kronolithColorPicker':
-                var input = elt.previous();
-                this.colorPicker = new ColorPicker({
-                    color: $F(input),
-                    offsetParent: elt,
-                    update: [[input, 'value'],
-                             [input, 'background']]
-                });
-                e.stop();
-                return;
-            }
-
-            if (elt.hasClassName('kronolith-event')) {
-                if (!Object.isUndefined(elt.retrieve('ajax'))) {
-                    this.go(elt.retrieve('ajax'));
-                } else {
-                    this.go('event:' + elt.retrieve('calendar') + ':' + elt.retrieve('eventid') + ':' + elt.up().retrieve('date'));
-                }
-                e.stop();
-                return;
-            } else if (elt.hasClassName('kronolithMonthDay')) {
-                if (orig.hasClassName('kronolith-day')) {
-                    var date = orig.retrieve('date');
-                    if (date) {
-                        this.go('day:' + date);
-                        e.stop();
-                        return;
-                    }
-                }
-                this.go('event:' + elt.retrieve('date'));
-                e.stop();
-                return;
-            } else if (elt.hasClassName('kronolithWeekDay')) {
-                this.go('day:' + elt.retrieve('date'));
-                e.stop();
-                return;
-            } else if (elt.hasClassName('kronolithEventsWeek') ||
-                       elt.hasClassName('kronolithEventsWorkweek') ||
-                       elt.hasClassName('kronolithAllDayContainer')) {
-                var date = elt.retrieve('date');
-                if (elt.hasClassName('kronolithAllDayContainer')) {
-                    date += 'all';
-                } else {
-                    date = this.parseDate(date);
-                    date.add(Math.round((e.pointerY() - elt.cumulativeOffset().top + elt.up('.kronolithViewBody').scrollTop) / (elt.hasClassName('kronolithEventsWeek') ? this.weekSizes.height : this.workweekSizes.height) * 2) * 30).minutes();
-                    date = date.toString('yyyyMMddHHmm');
-                }
-                this.go('event:' + date);
-                e.stop();
-                return;
-            } else if (elt.hasClassName('kronolithTaskCheckbox')) {
-                var taskid = elt.up('tr.kronolithTaskRow', 0).retrieve('taskid'),
-                    tasklist = elt.up('tr.kronolithTaskRow', 0).retrieve('tasklist');
-                this.toggleCompletionClass(taskid);
-
-                HordeCore.doAction('toggleCompletion', {
-                    list: tasklist,
-                    id: taskid
-                }, {
-                    callback: function(r) {
-                        if (r.toggled) {
-                            this.toggleCompletion(tasklist, taskid, r.toggled);
-                            if (r.toggled !== true) {
-                                this.toggleCompletionClass(taskid);
-                            }
-                        } else {
-                            this.toggleCompletionClass(taskid);
-                        }
-                    }.bind(this)
-                });
-
-                e.stop();
-                return;
-            } else if (elt.hasClassName('kronolithCalendarSave')) {
-                if (!elt.disabled) {
-                    elt.disable();
-                    if (!this.saveCalendar(elt.up('form'))) {
-                        elt.enable();
-                    }
-                }
-                e.stop();
-                break;
-            } else if (elt.hasClassName('kronolithCalendarContinue')) {
-                if (elt.disabled) {
-                    e.stop();
-                    break;
-                }
-
-                elt.disable();
-                var form = elt.up('form'),
-                    type = form.id.replace(/kronolithCalendarForm/, ''),
-                    i = 1;
-                while (!$('kronolithCalendar' + type + i).visible()) {
-                    i++;
-                }
-                if (type == 'remote') {
-                    var params = { url: $F('kronolithCalendarremoteUrl') };
-                    if (i == 1) {
-                        if (!$F('kronolithCalendarremoteUrl')) {
-                            HordeCore.notify(Kronolith.text.no_url, 'horde.warning');
-                            e.stop();
-                            break;
-                        }
-
-                        HordeCore.doAction('getRemoteInfo', params, {
-                            asynchronous: false,
-                            callback: function(r) {
-                                if (r.success) {
-                                    if (r.name) {
-                                        $('kronolithCalendarremoteName').setValue(r.name);
-                                    }
-                                    if (r.desc) {
-                                        $('kronolithCalendarremoteDescription').setValue(r.desc);
-                                    }
-                                    this.calendarNext(type);
-                                    this.calendarNext(type);
-                                } else if (r.auth) {
-                                    this.calendarNext(type);
-                                } else {
-                                    elt.enable();
-                                }
-                            }.bind(this)
-                        });
-
-                    }
-                    if (i == 2) {
-                        if ($F('kronolithCalendarremoteUsername')) {
-                            params.user = $F('kronolithCalendarremoteUsername');
-                            params.password =  $F('kronolithCalendarremotePassword');
-                        }
-
-                        HordeCore.doAction('getRemoteInfo', params, {
-                            callback: function(r) {
-                                if (r.success) {
-                                    if (r.name &&
-                                        !$F('kronolithCalendarremoteName')) {
-                                        $('kronolithCalendarremoteName').setValue(r.name);
-                                    }
-                                    if (r.desc &&
-                                        !$F('kronolithCalendarremoteDescription')) {
-                                        $('kronolithCalendarremoteDescription').setValue(r.desc);
-                                    }
-                                    this.calendarNext(type);
-                                } else {
-                                    if (r.auth) {
-                                        HordeCore.notify(Kronolith.text.wrong_auth, 'horde.warning');
-                                    }
-                                    elt.enable();
-                                }
-                            }.bind(this)
-                        });
-                    }
-                    e.stop();
-                    break;
-                }
-                this.calendarNext(type);
-                e.stop();
-                break;
-            } else if (elt.hasClassName('kronolithCalendarDelete')) {
-                var form = elt.up('form'),
-                    type = form.id.replace(/kronolithCalendarForm/, ''),
-                    calendar = $F('kronolithCalendar' + type + 'Id');
-
-                if ((type == 'tasklists' &&
-                     !window.confirm(Kronolith.text.delete_tasklist)) ||
-                    (type != 'tasklists' &&
-                     !window.confirm(Kronolith.text.delete_calendar))) {
-                    e.stop();
-                    break;
-                }
-
-                if (!elt.disabled) {
-                    elt.disable();
-
-                    HordeCore.doAction('deleteCalendar', {
-                        type: type,
-                        calendar: calendar
-                    }, {
-                        callback: function(r) {
-                            if (r.deleted) {
-                                this.deleteCalendar(type, calendar);
-                            }
-                            this.closeRedBox();
-                            this.go(this.lastLocation);
-                        }.bind(this)
-                    });
-                }
-                e.stop();
-                break;
-            } else if (elt.hasClassName('kronolithCalendarSubscribe') ||
-                       elt.hasClassName('kronolithCalendarUnsubscribe')) {
-                var form = elt.up('form');
-                this.toggleCalendar($F(form.down('input[name=type]')),
-                                    $F(form.down('input[name=calendar]')));
-                this.closeRedBox();
-                this.go(this.lastLocation);
-                e.stop();
-                break;
-            } else if (elt.tagName == 'INPUT' &&
-                       (elt.name == 'event_alarms[]' ||
-                        elt.name == 'task[alarm_methods][]')) {
-                if (elt.name == 'event_alarms[]') {
-                    $('kronolithEventAlarmOn').setValue(1);
-                    $('kronolithEventAlarmDefaultOff').setValue(1);
-                } else {
-                    $('kronolithTaskAlarmOn').setValue(1);
-                    $('kronolithTaskAlarmDefaultOff').setValue(1);
-                }
-                if ($(elt.id + 'Params')) {
-                    if (elt.getValue()) {
-                        $(elt.id + 'Params').show();
-                    } else {
-                        $(elt.id + 'Params').hide();
-                    }
-                }
-                break;
-            }
-
-            var calClass = elt.retrieve('calendarclass');
-            if (calClass) {
-                this.toggleCalendar(calClass, elt.retrieve('calendar'));
-                e.stop();
-                return;
-            }
-
-            elt = elt.up();
-        }
-        // Workaround Firebug bug.
-        Prototype.emptyFunction();
-    },
-
-    /**
-     * Handles date selections from a date picker.
-     */
-    datePickerHandler: function(e)
-    {
-        var field = e.element().previous();
-        field.setValue(e.memo.toString(Kronolith.conf.date_format));
-        this.updateTimeFields(field.identify());
-    },
-
-    /**
-     * Handles moving an event to a different day in month view and all day
-     * events in weekly/daily views.
-     */
-    onDrop: function(e)
-    {
-        var drop = e.element(),
-            el = e.memo.element;
-
-        if (drop == el.up()) {
-            return;
-        }
-
-        var lastDate = this.parseDate(el.up().retrieve('date')),
-            newDate = this.parseDate(drop.retrieve('date')),
-            diff = newDate.subtract(lastDate),
-            eventid = el.retrieve('eventid'),
-            cal = el.retrieve('calendar'),
-            viewDates = this.viewDates(this.date, this.view),
-            start = viewDates[0].toString('yyyyMMdd'),
-            end = viewDates[1].toString('yyyyMMdd'),
-            sig = start + end + (Math.random() + '').slice(2),
-            events = this.getCacheForDate(lastDate.toString('yyyyMMdd'), cal),
-            attributes = $H({ offDays: diff }),
-            event = events.find(function(e) { return e.key == eventid; });
-
-        drop.insert(el);
-        this.startLoading(cal, sig);
-        if (event.value.r) {
-            attributes.set('rday', lastDate);
-            attributes.set('cstart', this.cacheStart);
-            attributes.set('cend', this.cacheEnd);
-        }
-        var uatts = {
-            cal: cal,
-            id: eventid,
-            view: this.view,
-            sig: sig,
-            view_start: start,
-            view_end: end,
-            att: Object.toJSON(attributes)
-        },
-        callback = function(r) {
-          if (r.events) {
-              // Check if this is the still the result of the
-              // most current request.
-              if (r.sig == this.eventsLoading[r.cal]) {
-                  var days;
-                  if ((this.view == 'month' &&
-                       Kronolith.conf.max_events) ||
-                      this.view == 'week' ||
-                      this.view == 'workweek' ||
-                      this.view == 'day') {
-                      days = this.findEventDays(cal, eventid);
-                  }
-                  this.removeEvent(cal, eventid);
-                  if (days && days.length) {
-                      this.reRender(days);
-                  }
-              }
-              $H(r.events).each(function(days) {
-                  $H(days.value).each(function(event) {
-                      if (event.value.c.startsWith('tasks/')) {
-                          var tasklist = event.value.c.substr(6),
-                              task = event.key.substr(6),
-                              taskObject;
-                          if (this.tcache.get('incomplete') &&
-                              this.tcache.get('incomplete').get(tasklist) &&
-                              this.tcache.get('incomplete').get(tasklist).get(task)) {
-                              taskObject = this.tcache.get('incomplete').get(tasklist).get(task);
-                              taskObject.due = Date.parse(event.value.s);
-                              this.tcache.get('incomplete').get(tasklist).set(task, taskObject);
-                          }
-                      }
-                  }, this);
-              }, this);
-          }
-          this.loadEventsCallback(r, false);
-          $H(r.events).each(function(days) {
-              $H(days.value).each(function(event) {
-                  if (event.key == eventid) {
-                      this.refreshResources(days.key, cal, eventid, lastDate.toString('yyyyMMdd'), event);
-                  }
-              }.bind(this))
-          }.bind(this));
-      }.bind(this);
-
-      if (event.value.mt) {
-          $('kronolithEventDiv').hide();
-          $('kronolithUpdateDiv').show();
-          RedBox.showHtml($('kronolithEventDialog').show());
-          this.uatts = uatts;
-          this.ucb = callback;
-      } else {
-          this.doDragDropUpdate(uatts, callback);
-      }
-    },
-
-    onDragStart: function(e)
-    {
-        if (this.view == 'month') {
-            return;
-        }
-
-        var elt = e.element();
-
-        if (elt.hasClassName('kronolithDragger')) {
-            elt.up().addClassName('kronolith-selected');
-            DragDrop.Drags.getDrag(elt).top = elt.cumulativeOffset().top;
-        } else if (elt.hasClassName('kronolithEditable')) {
-            elt.addClassName('kronolith-selected').setStyle({ left: 0, width: (this.view == 'week' || this.view == 'workweek') ? '90%' : '95%', zIndex: 1 });
-        }
-
-        this.scrollTop = $('kronolithView' + this.view.capitalize())
-            .down('.kronolithViewBody')
-            .scrollTop;
-        this.scrollLast = this.scrollTop;
-    },
-
-    onDrag: function(e)
-    {
-        if (this.view == 'month') {
-            return;
-        }
-
-        var elt = e.element(),
-            drag = DragDrop.Drags.getDrag(elt);
-            storage = this.view + 'Sizes',
-            step = this[storage].height / 6;
-
-            if (!drag.event) {
-                return;
-            }
-
-        var event = drag.event.value;
-
-        if (elt.hasClassName('kronolithDragger')) {
-            // Resizing the event.
-            var div = elt.up(),
-                top = drag.ghost.cumulativeOffset().top,
-                scrollTop = $('kronolithView' + this.view.capitalize()).down('.kronolithViewBody').scrollTop,
-                offset = 0,
-                height;
-
-            // Check if view has scrolled since last call.
-            if (scrollTop != this.scrollLast) {
-                offset = scrollTop - this.scrollLast;
-                this.scrollLast = scrollTop;
-            }
-            if (elt.hasClassName('kronolithDraggerTop')) {
-                offset += top - drag.top;
-                height = div.offsetHeight - offset;
-                div.setStyle({
-                    top: (div.offsetTop + offset) + 'px'
-                });
-                offset = drag.ghost.offsetTop;
-                drag.top = top;
-            } else {
-                offset += top - drag.top;
-                height = div.offsetHeight + offset;
-                offset = div.offsetTop;
-                drag.top = top;
-            }
-            div.setStyle({
-                height: height + 'px'
-            });
-
-            this.calculateEventDates(event, storage, step, offset, height);
-            drag.innerDiv.update('(' + event.start.toString(Kronolith.conf.time_format) + ' - ' + event.end.toString(Kronolith.conf.time_format) + ') ' + event.t.escapeHTML());
-        } else if (elt.hasClassName('kronolithEditable')) {
-            // Moving the event.
-            if (Object.isUndefined(drag.innerDiv)) {
-                drag.innerDiv = drag.ghost.down('.kronolith-event-info');
-            }
-            if ((this.view == 'week') || (this.view == 'workweek')) {
-                var offsetX = Math.round(drag.ghost.offsetLeft / drag.stepX);
-                event.offsetDays = offsetX;
-                this.calculateEventDates(event, storage, step, drag.ghost.offsetTop, drag.divHeight, event.start.clone().addDays(offsetX), event.end.clone().addDays(offsetX));
-            } else {
-                event.offsetDays = 0;
-                this.calculateEventDates(event, storage, step, drag.ghost.offsetTop, drag.divHeight);
-            }
-            event.offsetTop = drag.ghost.offsetTop - drag.startTop;
-            drag.innerDiv.update('(' + event.start.toString(Kronolith.conf.time_format) + ' - ' + event.end.toString(Kronolith.conf.time_format) + ') ' + event.t.escapeHTML());
-            elt.clonePosition(drag.ghost, { offsetLeft: (this.view == 'week' || this.view == 'workweek') ? -2 : 0 });
-        }
-    },
-
-    onDragEnd: function(e)
-    {
-        if (this.view == 'month') {
-            return;
-        }
-
-        if (!e.element().hasClassName('kronolithDragger') &&
-            !e.element().hasClassName('kronolithEditable')) {
-            return;
-        }
-
-        var div = e.element(),
-            drag = DragDrop.Drags.getDrag(div),
-            event = drag.event;
-
-
-        if (event.value.al) {
-            return;
-        }
-        var date = drag.midnight,
-            storage = this.view + 'Sizes',
-            step = this[storage].height / 6,
-            dates = this.viewDates(date, this.view),
-            start = dates[0].dateString(),
-            end = dates[1].dateString(),
-            sig = start + end + (Math.random() + '').slice(2),
-            element, attributes;
-
-        div.removeClassName('kronolith-selected');
-        if (!Object.isUndefined(drag.innerDiv)) {
-            this.setEventText(drag.innerDiv, event.value);
-        }
-        this.startLoading(event.value.calendar, sig);
-        if (!Object.isUndefined(event.value.offsetTop)) {
-            attributes = $H({ offDays: event.value.offsetDays,
-                              offMins: Math.round(event.value.offsetTop / step) * 10 });
-            element = div;
-        } else if (div.hasClassName('kronolithDraggerTop')) {
-            attributes = $H({ start: event.value.start });
-            element = div.up();
-        } else if (div.hasClassName('kronolithDraggerBottom')) {
-            attributes = $H({ end: event.value.end });
-            element = div.up();
-        } else {
-            attributes = $H({ start: event.value.start,
-                              end: event.value.end });
-            element = div;
-        }
-        if (event.value.r) {
-            attributes.set('rstart', event.value.s);
-            attributes.set('rend', event.value.e);
-            attributes.set('cstart', this.cacheStart);
-            attributes.set('cend', this.cacheEnd);
-        }
-        element.retrieve('drags').invoke('destroy');
-        var uatts = {
-            cal: event.value.calendar,
-            id: event.key,
-            view: this.view,
-            sig: sig,
-            view_start: start,
-            view_end: end,
-            att: Object.toJSON(attributes)
-        },
-        callback = function(r) {
-            // Check if this is the still the result of the most current
-            // request.
-            if (r.events &&
-                r.sig == this.eventsLoading[r.cal]) {
-                if (event.value.rs) {
-                    var d = new Date(event.value.s);
-                    this.refreshResources(d.toString('yyyyMMdd'), event.value.calendar, event.key)
-                }
-                this.removeEvent(event.value.calendar, event.key);
-            }
-            this.loadEventsCallback(r, false);
-        }.bind(this);
-
-        if (event.value.mt) {
-            $('kronolithEventDiv').hide();
-            $('kronolithUpdateDiv').show();
-            RedBox.showHtml($('kronolithEventDialog').show());
-            this.uatts = uatts;
-            this.ucb = callback;
-        } else {
-            this.doDragDropUpdate(uatts, callback);
-        }
-    },
-
-    doDragDropUpdate: function(att, cb)
-    {
-        HordeCore.doAction('updateEvent', att, {
-            callback: cb
-        });
-    },
-
-    /**
-     * Refresh any resource calendars bound to the given just-updated event.
-     * Clears the old resource event from UI and cache, and clears the cache
-     * for the days of the new event, in order to allow listEvents to refresh
-     * the UI.
-     *
-     * @param  string dt       The current/new date for the event (yyyyMMdd).
-     * @param  string cal      The calendar the event exists in.
-     * @param  string eventid  The eventid that is changing.
-     * @param  string last_dt  The previous date for the event, if known. (yyyyMMdd).
-     * @param  object event    The event object (if a new event) dt is ignored.
-     *
-     */
-    refreshResources: function(dt, cal, eventid, last_dt, event)
-    {
-        var events = this.getCacheForDate(dt, cal),
-            update_cals = [], r_dates;
-
-        if (!event) {
-            event = events.find(function(e) { return e.key == eventid; });
-        }
-        if (!dt) {
-            dt = new Date(event.value.s);
-        } else {
-            dt = this.parseDate(dt);
-        }
-        if (event) {
-            $H(event.value.rs).each(function(r) {
-                var r_cal = ['resource', r.value.calendar],
-                    r_events = this.getCacheForDate(last_dt, r_cal.join('|')),
-                    r_event, day, end;
-
-                if (r_events) {
-                    r_event = r_events.find(function(e) { return e.value.uid == event.value.uid });
-                    if (r_event) {
-                        this.removeEvent(r_cal, r_event.key);
-                        day = new Date(r_event.value.s);
-                        end = new Date(r_event.value.s);
-                        while (!day.isAfter(end)) {
-                            this.deleteCache(r_cal, null, day.toString('yyyyMMdd'));
-                            day.add(1).day();
-                        }
-                        day = new Date(event.value.s);
-                        end = new Date(event.value.e);
-
-                        while (!day.isAfter(end)) {
-                            this.deleteCache(r_cal, null, day.toString('yyyyMMdd'));
-                            day.add(1).day();
-                        }
-                    } else {
-                        // Don't know the previous date/time so just nuke the cache.
-                       this.deleteCache(r_cal);
-                    }
-                } else {
-                    this.deleteCache(r_cal);
-                }
-                update_cals.push(r_cal);
-            }.bind(this));
-
-            if (update_cals.length) {
-                dates = this.viewDates(dt, this.view);
-                // Ensure we also grab the full length of the events.
-                if (dates[0].isAfter(dt)) {
-                    dates[0] = dt;
-                }
-                var dt_end = new Date(event.value.e);
-                if (dt_end.isAfter(dates[1])) {
-                    dates[1] = dt_end;
-                }
-                this.loadEvents(dates[0], dates[1], this.view, update_cals);
-            }
-        }
-    },
-
-    editEvent: function(calendar, id, date, title)
-    {
-        if (this.redBoxLoading) {
-            return;
-        }
-        if (Object.isUndefined(HordeImple.AutoCompleter.kronolithEventTags)) {
-            this.editEvent.bind(this, calendar, id, date).defer();
-            return;
-        }
-
-        this.closeRedBox();
-        this.quickClose();
-        this.redBoxOnDisplay = RedBox.onDisplay;
-        RedBox.onDisplay = function() {
-            if (this.redBoxOnDisplay) {
-                this.redBoxOnDisplay();
-            }
-            try {
-                $('kronolithEventForm').focusFirstElement();
-            } catch(e) {}
-            if (Kronolith.conf.maps.driver &&
-                $('kronolithEventLinkMap').up().hasClassName('horde-active') &&
-                !this.mapInitialized) {
-
-                this.initializeMap();
-            }
-            RedBox.onDisplay = this.redBoxOnDisplay;
-        }.bind(this);
-        this.attendees = [];
-        this.resources = [];
-        this.updateCalendarDropDown('kronolithEventTarget');
-        this.toggleAllDay(false);
-        this.openTab($('kronolithEventForm').down('.tabset a.kronolithTabLink'));
-        this.disableAlarmMethods('Event');
-        this.knl.kronolithEventStartTime.markSelected();
-        this.knl.kronolithEventEndTime.markSelected();
-        $('kronolithEventForm').reset();
-        this.resetMap();
-        HordeImple.AutoCompleter.kronolithEventAttendees.reset();
-        HordeImple.AutoCompleter.kronolithEventTags.reset();
-        HordeImple.AutoCompleter.kronolithEventResources.reset();
-        if (Kronolith.conf.maps.driver) {
-            $('kronolithEventMapLink').hide();
-        }
-        $('kronolithEventSave').show().enable();
-        $('kronolithEventSaveAsNew').show().enable();
-        $('kronolithEventDelete').show().enable();
-        $('kronolithEventDeleteConfirm').enable();
-        $('kronolithEventTarget').show();
-        $('kronolithEventTargetRO').hide();
-        $('kronolithEventForm').down('.kronolithFormActions .kronolithSeparator').show();
-        $('kronolithEventExceptions').clear();
-        if (id) {
-            // An id passed to this function indicates we are editing an event.
-            RedBox.loading();
-            var attributes = { cal: calendar, id: id, date: date };
-            // Need the current st and et of this instance.
-            var events = this.getCacheForDate(date.toString('yyyyMMdd'), calendar);
-            if (events) {
-                var ev = events.find(function(e) { return e.key == id; });
-                if (ev[1].r) {
-                    attributes.rsd = ev[1].start.dateString();
-                    attributes.red = ev[1].end.dateString();
-                }
-            }
-            HordeCore.doAction('getEvent', attributes, {
-                callback: this.editEventCallback.bind(this)
-            });
-            $('kronolithEventTopTags').update();
-        } else {
-            // This is a new event.
-            HordeCore.doAction('listTopTags', {}, {
-                callback: this.topTagsCallback.curry('kronolithEventTopTags', 'kronolithEventTag')
-            });
-            var d;
-            if (date) {
-                if (date.endsWith('all')) {
-                    date = date.substring(0, date.length - 3);
-                    $('kronolithEventAllday').setValue(true);
-                    this.toggleAllDay(true);
-                }
-                d = this.parseDate(date);
-            } else {
-                d = new Date();
-            }
-            if (title) {
-                $('kronolithEventTitle').setValue(title);
-            }
-            $('kronolithEventId').clear();
-            $('kronolithEventCalendar').clear();
-            $('kronolithEventTarget').setValue(Kronolith.conf.default_calendar);
-            $('kronolithEventDelete').hide();
-            $('kronolithEventStartDate').setValue(d.toString(Kronolith.conf.date_format));
-            $('kronolithEventStartTime').setValue(d.toString(Kronolith.conf.time_format));
-            this.updateFBDate(d);
-            d.add(1).hour();
-            this.duration = 60;
-            $('kronolithEventEndDate').setValue(d.toString(Kronolith.conf.date_format));
-            $('kronolithEventEndTime').setValue(d.toString(Kronolith.conf.time_format));
-            $('kronolithEventLinkExport').up('span').hide();
-            $('kronolithEventSaveAsNew').hide();
-            $('kronolithEventUrlDisplay').hide();
-            $('kronolithEventUrl').show();
-            this.toggleRecurrence(true, 'None');
-            $('kronolithEventEditRecur').hide();
-            this.enableAlarm('Event', Kronolith.conf.default_alarm);
-            this.redBoxLoading = true;
-            RedBox.showHtml($('kronolithEventDialog').show());
-        }
-    },
-
-    /**
-     * Generates ajax request parameters for requests to save events.
-     *
-     * @return object  An object with request parameters.
-     */
-    saveEventParams: function()
-    {
-        var viewDates = this.viewDates(this.date, this.view),
-            params = {
-                sig: viewDates[0].dateString() + viewDates[1].dateString(),
-                view: this.view
-            };
-        if (this.cacheStart) {
-            params.view_start = this.cacheStart.dateString();
-            params.view_end = this.cacheEnd.dateString();
-        }
-        return params;
-    },
-
-    /**
-     * Submits the event edit form to create or update an event.
-     */
-    saveEvent: function(asnew)
-    {
-        this.validateEvent(asnew);
-    },
-
-    /**
-     * Perform any preliminary checks necessary. doSaveEvent will be called from
-     * the callback if checks are successful.
-     *
-     */
-    validateEvent: function(asnew)
-    {
-        if (this.wrongFormat.size()) {
-            HordeCore.notify(Kronolith.text.fix_form_values, 'horde.warning');
-            return;
-        }
-
-        // Check that there are no conflicts.
-        if (Kronolith.conf.has_resources && $F('kronolithEventResourceIds')) {
-            HordeCore.doAction(
-                'checkResources',
-                {
-                    s: this.getDate('start').toISOString(),
-                    e: this.getDate('end').toISOString(),
-                    i: $F('kronolithEventId'),
-                    c: $F('kronolithEventCalendar'),
-                    r: $F('kronolithEventResourceIds')
-                },
-                {
-                    callback: this.validateEventCallback.curry(asnew).bind(this)
-                }
-            );
-        } else {
-            this.validateEventCallback(asnew, {});
-        }
-    },
-
-    validateEventCallback: function(asnew, r)
-    {
-        var conflict = false;
-
-        $H(r).each(function(a) {
-            // 3 == Kronolith::RESPONSE_DECLINED
-            if (a.value == 3) {
-                $('kronolithEventDiv').hide();
-                $('kronolithConflictDiv').show();
-                conflict = true;
-                return;
-            }
-        });
-        if (!conflict) {
-            this.doSaveEvent(asnew);
-        }
-    },
-
-    doSaveEvent: function(asnew)
-    {
-        var cal = $F('kronolithEventCalendar'),
-            target = $F('kronolithEventTarget'),
-            eventid = $F('kronolithEventId'),
-            params;
-
-        if (this.mapInitialized) {
-            $('kronolithEventMapZoom').value = this.map.getZoom();
-        }
-
-        params = $H($('kronolithEventForm').serialize({ hash: true }))
-            .merge(this.saveEventParams());
-        params.set('as_new', asnew ? 1 : 0);
-        if (this.cacheStart) {
-            params.set('cstart', this.cacheStart.toISOString());
-            params.set('cend', this.cacheEnd.toISOString());
-        }
-        HordeImple.AutoCompleter.kronolithEventTags.shutdown();
-        $('kronolithEventSave').disable();
-        $('kronolithEventSaveAsNew').disable();
-        $('kronolithEventDelete').disable();
-        this.startLoading(target, params.get('sig'));
-        HordeCore.doAction('saveEvent', params, {
-            callback: function(r) {
-                if (!asnew && r.events && eventid) {
-                    this.removeEvent(cal, eventid);
-                }
-                this.loadEventsCallback(r, false);
-
-                // Refresh bound exceptions
-                var calendar = cal.split('|'), refreshed = false;
-                $H(r.events).each(function(d) {
-                    $H(d.value).each(function(evt) {
-                        if (evt.value.bid) {
-                            var cache = this.getCacheForDate(this.findEventDays(cal, evt.key, cal));
-                            cache.each(function(entry) {
-                                if (entry.value.bid == evt.value.bid && evt.value.c != calendar[1]) {
-                                    this.removeEvent(cal, entry.key);
-                                }
-                            }.bind(this));
-                        }
-                        if (!refreshed && ((evt.key == eventid) || !eventid) && evt.value.rs) {
-                            this.refreshResources(null, cal, eventid, false, evt);
-                            refreshed = true;
-                        }
-                    }.bind(this))
-                }.bind(this));
-
-                if (r.events) {
-                    this.resetMap();
-                    this.closeRedBox();
-                    this.go(this.lastLocation);
-                } else {
-                    $('kronolithEventSave').enable();
-                    $('kronolithEventSaveAsNew').enable();
-                    $('kronolithEventDelete').enable();
-                }
-                $('kronolithUpdateDiv').hide();
-                $('kronolithConflictDiv').hide();
-                $('kronolithEventDiv').show();
-            }.bind(this)
-        });
-    },
-
-    quickSaveEvent: function()
-    {
-        var text = $F('kronolithQuickinsertQ'),
-            cal = $F('kronolithQuickinsertCalendars'),
-            params;
-
-        params = $H($('kronolithEventForm').serialize({ hash: true }))
-            .merge(this.saveEventParams());
-        params.set('text', text);
-        params.set('cal', cal);
-
-        this.closeRedBox();
-        this.startLoading(cal, params.get('sig'));
-        HordeCore.doAction('quickSaveEvent', params, {
-            callback: function(r) {
-                this.loadEventsCallback(r, false);
-                if (r.error) {
-                    this.editEvent(null, null, null, text);
-                } else {
-                    $('kronolithQuickinsertQ').value = '';
-                }
-             }.bind(this)
-         });
-    },
-
-    /**
-     * Closes and resets the quick event form.
-     */
-    quickClose: function()
-    {
-        $('kronolithQuickinsertQ').value = '';
-        if ($('kronolithQuicktaskQ')) {
-            $('kronolithQuicktaskQ').value = '';
-        }
-        this.closeRedBox();
-    },
-
-    topTagsCallback: function(update, tagclass, r)
-    {
-        $('kronolithEventTabTags').select('label').invoke('show');
-        if (!r.tags) {
-            $(update).update();
-            return;
-        }
-
-        var t = new Element('ul', { className: 'horde-tags' });
-        r.tags.each(function(tag) {
-            if (tag == null) {
-                return;
-            }
-            t.insert(new Element('li', { className: tagclass }).update(tag.escapeHTML()));
-        });
-        $(update).update(t);
-    },
-
-    /**
-     * Callback method for showing event forms.
-     *
-     * @param object r  The ajax response object.
-     */
-    editEventCallback: function(r)
-    {
-        if (!r.event) {
-            RedBox.close();
-            this.go(this.lastLocation);
-            return;
-        }
-
-        var ev = r.event;
-
-        if (!Object.isUndefined(ev.ln)) {
-            this.loadPage(ev.ln);
-            this.closeRedBox();
-            return;
-        }
-
-        /* Basic information */
-        $('kronolithEventId').setValue(ev.id);
-        $('kronolithEventCalendar').setValue(ev.ty + '|' + ev.c);
-        $('kronolithEventTarget').setValue(ev.ty + '|' + ev.c);
-        $('kronolithEventTargetRO').update(Kronolith.conf.calendars[ev.ty][ev.c].name.escapeHTML());
-        $('kronolithEventTitle').setValue(ev.t);
-        $('kronolithEventLocation').setValue(ev.l);
-        $('kronolithEventTimezone').setValue(ev.tz);
-        if (ev.l && Kronolith.conf.maps.driver) {
-            $('kronolithEventMapLink').show();
-        }
-        if (ev.uhl) {
-            $('kronolithEventUrlDisplay').down().update(ev.uhl);
-            $('kronolithEventUrlDisplay').show();
-            $('kronolithEventUrl').hide();
-        }
-        else {
-            $('kronolithEventUrlDisplay').hide();
-            $('kronolithEventUrl').show();
-        }
-
-        if (ev.u) {
-            $('kronolithEventUrl').setValue(ev.u);
-        }
-
-        $('kronolithEventAllday').setValue(ev.al);
-
-        if (ev.r && ev.rsd && ev.red) {
-            // Save the original datetime, so we can properly create the
-            // exception.
-            var osd = Date.parse(ev.rsd + ' ' + ev.st);
-            var oed = Date.parse(ev.red + ' ' + ev.et);
-
-            $('kronolithEventRecurOStart').setValue(osd.toString('s'));
-            $('kronolithEventRecurOEnd').setValue(oed.toString('s'));
-
-            // ...and put the same value in the form field to replace the
-            // date of the initial series.
-            $('kronolithEventStartDate').setValue(ev.sd);
-            $('kronolithEventEndDate').setValue(ev.ed);
-            // Save the current datetime in case we are not editing 'all'
-            this.orstart = ev.rsd;
-            this.orend = ev.red;
-        } else {
-            $('kronolithEventStartDate').setValue(ev.sd);
-            $('kronolithEventEndDate').setValue(ev.ed);
-            $('kronolithEventRecurEnd').clear();
-            $('kronolithEventRecurOStart').clear();
-            $('kronolithEventRecurOEnd').clear();
-            this.orstart = null;
-            this.orend = null;
-        }
-
-        $('kronolithEventStartTime').setValue(ev.st);
-        this.knl.kronolithEventStartTime.setSelected(ev.st);
-        this.updateFBDate(Date.parseExact(ev.sd, Kronolith.conf.date_format));
-        $('kronolithEventEndTime').setValue(ev.et);
-        this.knl.kronolithEventEndTime.setSelected(ev.et);
-        this.duration = Math.abs(Date.parse(ev.e).getTime() - Date.parse(ev.s).getTime()) / 60000;
-        this.toggleAllDay(ev.al);
-        $('kronolithEventStatus').setValue(ev.x);
-        $('kronolithEventDescription').setValue(ev.d);
-        $('kronolithEventPrivate').setValue(ev.pv);
-        $('kronolithEventLinkExport').up('span').show();
-        $('kronolithEventExport').href = Kronolith.conf.URI_EVENT_EXPORT.interpolate({ id: ev.id, calendar: ev.c, type: ev.ty });
-
-        /* Alarm */
-        if (ev.a) {
-            this.enableAlarm('Event', ev.a);
-            if (ev.m) {
-                $('kronolithEventAlarmDefaultOff').checked = true;
-                $H(ev.m).each(function(method) {
-                    $('kronolithEventAlarm' + method.key).setValue(1);
-                    if ($('kronolithEventAlarm' + method.key + 'Params')) {
-                        $('kronolithEventAlarm' + method.key + 'Params').show();
-                        $H(method.value).each(function(param) {
-                            var input = $('kronolithEventAlarmParam' + param.key);
-                            if (input.type == 'radio') {
-                                input.up('form').select('input[type=radio]').each(function(radio) {
-                                    if (radio.name == input.name &&
-                                        radio.value == param.value) {
-                                        radio.setValue(1);
-                                        throw $break;
-                                    }
-                                });
-                            } else {
-                                input.setValue(param.value);
-                            }
-                        });
-                    }
-                });
-            }
-        } else {
-            $('kronolithEventAlarmOff').setValue(true);
-        }
-
-        /* Recurrence */
-        if (ev.r) {
-            this.setRecurrenceFields(true, ev.r);
-            $('kronolithRecurDelete').show();
-            $('kronolithNoRecurDelete').hide();
-            $('kronolithEventEditRecur').show();
-            this.recurs = true;
-        } else if (ev.bid) {
-            $('kronolithRecurDelete').hide();
-            $('kronolithNoRecurDelete').show();
-            $('kronolithEventEditRecur').hide();
-            var div = $('kronolithEventRepeatException');
-            div.down('span').update(ev.eod);
-            this.toggleRecurrence(true, 'Exception');
-            this.recurs = false;
-        } else {
-            $('kronolithRecurDelete').hide();
-            $('kronolithNoRecurDelete').show();
-            $('kronolithEventEditRecur').hide();
-            this.toggleRecurrence(true, 'None');
-            this.recurs = false;
-        }
-
-        /* Attendees */
-        if (!Object.isUndefined(ev.at)) {
-            HordeImple.AutoCompleter.kronolithEventAttendees.reset(ev.at.pluck('l'));
-            ev.at.each(this.addAttendee.bind(this));
-            if (this.fbLoading) {
-                $('kronolithFBLoading').show();
-            }
-        }
-
-        /* Resources */
-        if (!Object.isUndefined(ev.rs)) {
-            var rs = $H(ev.rs);
-            HordeImple.AutoCompleter.kronolithEventResources.reset(rs.values().pluck('name'));
-            rs.each(function(r) { this.addResource(r.value, r.key); }.bind(this));
-            if (this.fbLoading) {
-                $('kronolithResourceFBLoading').show();
-            }
-        }
-
-        /* Tags */
-        HordeImple.AutoCompleter.kronolithEventTags.reset(ev.tg);
-
-        /* Geo */
-        if (ev.gl) {
-            $('kronolithEventLocationLat').value = ev.gl.lat;
-            $('kronolithEventLocationLon').value = ev.gl.lon;
-            $('kronolithEventMapZoom').value = Math.max(1, ev.gl.zoom);
-        }
-
-        if (!ev.pe) {
-            $('kronolithEventSave').hide();
-            HordeImple.AutoCompleter.kronolithEventTags.disable();
-            $('kronolithEventTabTags').select('label').invoke('hide');
-        } else {
-            HordeCore.doAction('listTopTags', {}, {
-                callback: this.topTagsCallback.curry('kronolithEventTopTags', 'kronolithEventTag')
-            });
-        }
-        if (!ev.pd) {
-            $('kronolithEventDelete').hide();
-            $('kronolithEventTarget').hide();
-            $('kronolithEventTargetRO').show();
-        }
-
-        this.setTitle(ev.t);
-        this.redBoxLoading = true;
-        RedBox.showHtml($('kronolithEventDialog').show());
-
-        /* Hide alarm message for this event. */
-        if (r.msgs) {
-            r.msgs = r.msgs.reject(function(msg) {
-                if (msg.type != 'horde.alarm') {
-                    return false;
-                }
-                var alarm = msg.flags.alarm;
-                if (alarm.params && alarm.params.notify &&
-                    alarm.params.notify.show &&
-                    alarm.params.notify.show.calendar &&
-                    alarm.params.notify.show.event &&
-                    alarm.params.notify.show.calendar == ev.c &&
-                    alarm.params.notify.show.event == ev.id) {
-                    return true;
-                }
-                return false;
-            });
-        }
-    },
-
-    /**
-     * Adds an attendee row to the free/busy table.
-     *
-     * @param object attendee  An attendee object with the properties:
-     *                         - e: email address
-     *                         - l: the display name of the attendee
-     */
-    addAttendee: function(attendee)
-    {
-        if (typeof attendee == 'string') {
-            if (attendee.include('@')) {
-                HordeCore.doAction('parseEmailAddress', {
-                    email: attendee
-                }, {
-                    callback: function (r) {
-                        if (r.email) {
-                            this.addAttendee({ e: r.email, l: attendee });
-                        }
-                    }.bind(this)
-                });
-                return;
-            } else {
-                attendee = { l: attendee };
-            }
-        }
-
-        if (attendee.e) {
-            this.attendees.push(attendee);
-            this.fbLoading++;
-            HordeCore.doAction('getFreeBusy', {
-                email: attendee.e
-            }, {
-                callback: function(r) {
-                    this.fbLoading--;
-                    if (!this.fbLoading) {
-                        $('kronolithFBLoading').hide();
-                    }
-                    if (!Object.isUndefined(r.fb)) {
-                        this.freeBusy.get(attendee.l)[1] = r.fb;
-                        this.insertFreeBusy(attendee.l, this.getFBDate());
-                    }
-                }.bind(this)
-            });
-        }
-
-        var tr = new Element('tr'), response, i;
-        this.freeBusy.set(attendee.l, [ tr ]);
-        attendee.r = attendee.r || 1;
-        switch (attendee.r) {
-            case 1: response = 'None'; break;
-            case 2: response = 'Accepted'; break;
-            case 3: response = 'Declined'; break;
-            case 4: response = 'Tentative'; break;
-        }
-        tr.insert(new Element('td')
-                  .writeAttribute('title', attendee.l)
-                  .addClassName('kronolithAttendee' + response)
-                  .insert(attendee.e ? attendee.e.escapeHTML() : attendee.l.escapeHTML()));
-        for (i = 0; i < 24; i++) {
-            tr.insert(new Element('td', { className: 'kronolithFBUnknown' }));
-        }
-        $('kronolithEventAttendeesList').down('tbody').insert(tr);
-    },
-
-    resetFBRows: function()
-    {
-        this.attendees.each(function(attendee) {
-            var row = this.freeBusy.get(attendee.l)[0];
-            row.update();
-
-            attendee.r = attendee.r || 1;
-            switch (attendee.r) {
-                case 1: response = 'None'; break;
-                case 2: response = 'Accepted'; break;
-                case 3: response = 'Declined'; break;
-                case 4: response = 'Tentative'; break;
-            }
-            row.insert(new Element('td')
-                      .writeAttribute('title', attendee.l)
-                      .addClassName('kronolithAttendee' + response)
-                      .insert(attendee.e ? attendee.e.escapeHTML() : attendee.l.escapeHTML()));
-            for (i = 0; i < 24; i++) {
-                row.insert(new Element('td', { className: 'kronolithFBUnknown' }));
-            }
-        }.bind(this));
-        this.resources.each(function(resource) {
-            var row = this.freeBusy.get(resource)[0],
-                tdone = row.down('td');
-            row.update();
-            row.update(tdone);
-            for (i = 0; i < 24; i++) {
-                row.insert(new Element('td', { className: 'kronolithFBUnknown' }));
-            }
-        }.bind(this));
-    },
-
-    addResource: function(resource, id)
-    {
-        var v, response = 1;
-        if (!id) {
-            // User entered
-            this.resourceACCache.choices.each(function(i) {
-                if (i.name == resource) {
-                    v = i.code;
-                    throw $break;
-                } else {
-                    v = false;
-                }
-            }.bind(this));
-        } else {
-            // Populating from an edit event action
-            v = id;
-            response = resource.response;
-            resource = resource.name;
-        }
-
-        switch (response) {
-            case 1: response = 'None'; break;
-            case 2: response = 'Accepted'; break;
-            case 3: response = 'Declined'; break;
-            case 4: response = 'Tentative'; break;
-        }
-        var att = {
-            'resource': v
-        },
-        tr, i;
-        if (att.resource) {
-            this.fbLoading++;
-            HordeCore.doAction('getFreeBusy', att, {
-                callback: this.addResourceCallback.curry(resource).bind(this)
-            });
-            tr = new Element('tr');
-            this.freeBusy.set(resource, [ tr ]);
-            tr.insert(new Element('td')
-                .writeAttribute('title', resource)
-                .addClassName('kronolithAttendee' + response)
-                .insert(resource.escapeHTML()));
-            for (i = 0; i < 24; i++) {
-                tr.insert(new Element('td', { className: 'kronolithFBUnknown' }));
-            }
-            $('kronolithEventResourcesList').down('tbody').insert(tr);
-            this.resourceACCache.map.set(resource, v);
-            $('kronolithEventResourceIds').value = this.resourceACCache.map.values();
-        } else {
-            HordeCore.notify(Kronolith.text.unknown_resource + ': ' + resource, 'horde.error');
-        }
-    },
-
-    removeResource: function(resource)
-    {
-        var row = this.freeBusy.get(resource)[0];
-        row.purge();
-        row.remove();
-        this.resourceACCache.map.unset(resource);
-        $('kronolithEventResourceIds').value = this.resourceACCache.map.values();
-    },
-
-    addResourceCallback: function(resource, r)
-    {
-        this.fbLoading--;
-        if (!this.fbLoading) {
-            $('kronolithResourceFBLoading').hide();
-        }
-        if (Object.isUndefined(r.fb)) {
-            return;
-        }
-        this.resources.push(resource);
-        this.freeBusy.get(resource)[1] = r.fb;
-        this.insertFreeBusy(resource);
-    },
-
-    /**
-     * Removes an attendee row from the free/busy table.
-     *
-     * @param string attendee  The display name of the attendee.
-     */
-    removeAttendee: function(attendee)
-    {
-        var row = this.freeBusy.get(attendee)[0];
-        row.purge();
-        row.remove();
-    },
-
-    normalizeAttendee: function(attendee)
-    {
-        var pattern = /:(.*);/;
-        var match = pattern.exec(attendee);
-        if (match) {
-           return match[1].split(',');
-        }
-        return [attendee];
-    },
-
-    checkOrganizerAsAttendee: function()
-    {
-        if (HordeImple.AutoCompleter.kronolithEventAttendees.selectedItems.length == 1 &&
-            HordeImple.AutoCompleter.kronolithEventAttendees.selectedItems.first().rawValue != Kronolith.conf.email) {
-            // Invite the organizer of this event to the new event.
-            HordeImple.AutoCompleter.kronolithEventAttendees.addNewItemNode(Kronolith.conf.email);
-            this.addAttendee(Kronolith.conf.email);
-        }
-    },
-
-    getFBDate: function ()
-    {
-        var startDate = $('kronolithFBDate').innerHTML.split(' ');
-        if (startDate.length > 1) {
-            startDate = startDate[1];
-        } else {
-            startDate = startDate[0];
-        }
-        return Date.parseExact(startDate, Kronolith.conf.date_format);
-    },
-
-    /**
-     * Updates rows with free/busy information in the attendees table.
-     *
-     * @param string attendee  An attendee display name as the free/busy
-     *                         identifier.
-     * @param date   start     An optinal start date for f/b info. If omitted,
-     *                         $('kronolithEventStartDate') is used.
-     */
-    insertFreeBusy: function(attendee, start)
-    {
-        if (!$('kronolithEventDialog').visible() ||
-            !this.freeBusy.get(attendee)) {
-            return;
-        }
-        var fb = this.freeBusy.get(attendee)[1],
-            tr = this.freeBusy.get(attendee)[0],
-            td = tr.select('td')[1],
-            div = td.down('div'), start;
-        if (!fb) {
-            return;
-        }
-
-        if (!td.getWidth()) {
-            this.insertFreeBusy.bind(this, attendee, start).defer();
-            return;
-        }
-
-        if (div) {
-            div.purge();
-            div.remove();
-        }
-        if (!start) {
-            start = Date.parseExact($F('kronolithEventStartDate'), Kronolith.conf.date_format);
-        }
-        var end = start.clone().add(1).days(),
-            width = td.getWidth(),
-            fbs = this.parseDate(fb.s),
-            fbe = this.parseDate(fb.e);
-
-
-        if (start.isBefore(fbs) || end.isBefore(fbs) || start.isAfter(fbe)) {
-            return;
-        }
-
-        tr.select('td').each(function(td, i) {
-            if (i != 0) {
-                td.className = 'kronolithFBFree';
-            }
-            i++;
-        });
-        div = new Element('div').setStyle({ position: 'relative', height: td.offsetHeight + 'px' });
-        td.insert(div);
-        $H(fb.b).each(function(busy) {
-            var left, from = Date.parse(busy.key).addSeconds(1),
-            to = Date.parse(busy.value).addSeconds(1);
-            if (!end.isAfter(from) || to.isBefore(start)) {
-                return;
-            }
-            if (from.isBefore(start)) {
-                from = start.clone();
-            }
-            if (to.isAfter(end)) {
-                to = end.clone();
-            }
-            if (to.getHours() === 0 && to.getMinutes() === 0) {
-                to.add(-1).minutes();
-            }
-            left = from.getHours() + from.getMinutes() / 60;
-            div.insert(new Element('div', { className: 'kronolithFBBusy' }).setStyle({ zIndex: 1, top: 0, left: (left * width) + 'px', width: (((to.getHours() + to.getMinutes() / 60) - left) * width) + 'px' }));
-        });
-
-    },
-
-    fbStartDateOnChange: function()
-    {
-        if (!$F('kronolithEventStartDate')) {
-          this._checkDate($('kronolithEventStartDate'));
-          return;
-        }
-        this.fbStartDateHandler(Date.parseExact($F('kronolithEventStartDate'), Kronolith.conf.date_format));
-    },
-
-    /**
-     * @param Date start  The start date.
-     */
-    fbStartDateHandler: function(start)
-    {
-        this.updateFBDate(start);
-        this.resetFBRows();
-        // Need to check visisbility - multiple changes will break the display
-        // due to the use of .defer() in insertFreeBusy().
-        if ($('kronolithEventTabAttendees').visible()) {
-            this.attendeeStartDateHandler(start);
-        }
-        if ($('kronolithEventTabResources').visible()) {
-            this.resourceStartDateHandler(start);
-        }
-    },
-
-    attendeeStartDateHandler: function(start)
-    {
-        this.attendees.each(function(attendee) {
-            this.insertFreeBusy(attendee.l, start);
-        }, this);
-    },
-
-    resourceStartDateHandler: function(start)
-    {
-        this.resources.each(function(resource) {
-            this.insertFreeBusy(resource, start);
-        }, this);
-    },
-
-    nextFreebusy: function()
-    {
-        this.fbStartDateHandler(this.getFBDate().addDays(1));
-    },
-
-    prevFreebusy: function()
-    {
-        this.fbStartDateHandler(this.getFBDate().addDays(-1));
-    },
-
-    /**
-     * @start Date object
-     */
-    updateFBDate: function(start)
-    {
-        $('kronolithFBDate').update(start.toString('dddd') + ' ' + start.toString(Kronolith.conf.date_format));
-        $('kronolithResourceFBDate').update(start.toString('dddd') + ' ' + start.toString(Kronolith.conf.date_format));
-    },
-
-    /**
-     * Toggles the start and end time fields of the event edit form on and off.
-     *
-     * @param boolean on  Whether the event is an all-day event, i.e. the time
-     *                    fields should be turned off. If not specified, the
-     *                    current state is toggled.
-     */
-    toggleAllDay: function(on)
-    {
-        var end = this.getDate('end'),
-            old = $('kronolithEventStartTimeLabel').getStyle('visibility') == 'hidden';
-        if (Object.isUndefined(on)) {
-            on = $('kronolithEventStartTimeLabel').getStyle('visibility') == 'visible';
-        }
-        if (end) {
-            if (on) {
-                if (end.getHours() == 0 && end.getMinutes() == 0) {
-                    end.add(-1).minute();
-                }
-            } else if (old) {
-                end.setHours(23);
-                end.setMinutes(59);
-            }
-            $('kronolithEventEndDate').setValue(end.toString(Kronolith.conf.date_format));
-            $('kronolithEventEndTime').setValue(end.toString(Kronolith.conf.time_format));
-        }
-        $('kronolithEventStartTimeLabel').setStyle({ visibility: on ? 'hidden' : 'visible' });
-        $('kronolithEventEndTimeLabel').setStyle({ visibility: on ? 'hidden' : 'visible' });
-    },
-
-    /**
-     * Enables the alarm in the event or task form and sets the correct value
-     * and unit.
-     *
-     * @param string type    The object type, either 'Event' or 'Task'.
-     * @param integer alarm  The alarm time in seconds.
-     */
-    enableAlarm: function(type, alarm) {
-        if (!alarm) {
-            return;
-        }
-        type = 'kronolith' + type + 'Alarm';
-        $(type + 'On').setValue(true);
-        [10080, 1440, 60, 1].each(function(unit) {
-            if (alarm % unit === 0) {
-                $(type + 'Value').setValue(alarm / unit);
-                $(type + 'Unit').setValue(unit);
-                throw $break;
-            }
-        });
-    },
-
-    /**
-     * Disables all custom alarm methods in the event form.
-     */
-    disableAlarmMethods: function(type) {
-        $('kronolith' + type + 'TabReminder').select('input').each(function(input) {
-            if (input.name == (type == 'Event' ? 'event_alarms[]' : 'task[alarm_methods][]')) {
-                input.setValue(0);
-                if ($(input.id + 'Params')) {
-                    $(input.id + 'Params').hide();
-                }
-            }
-        });
-    },
-
-    /**
-     * Toggles the recurrence fields of the event and task edit forms.
-     *
-     * @param boolean event  Whether to use the event form.
-     * @param string recur   The recurrence part of the field name, i.e. 'None',
-     *                       'Daily', etc.
-     */
-    toggleRecurrence: function(event, recur)
-    {
-        var prefix = 'kronolith' + (event ? 'Event' : 'Task');
-        if (recur == 'Exception') {
-            if (!$(prefix + 'RepeatException').visible()) {
-                $(prefix + 'TabRecur').select('div').invoke('hide');
-                $(prefix + 'RepeatException').show();
-            }
-        } else if (recur != 'None') {
-            var div = $(prefix + 'Repeat' + recur),
-                length = $(prefix + 'RepeatLength');
-            this.lastRecurType = recur;
-            if (!div.visible()) {
-                $(prefix + 'TabRecur').select('div').invoke('hide');
-                div.show();
-                length.show();
-                $(prefix + 'RepeatType').show();
-            }
-            switch (recur) {
-            case 'Daily':
-            case 'Weekly':
-            case 'Monthly':
-            case 'Yearly':
-                var recurLower = recur.toLowerCase();
-                if (div.down('input[name=recur_' + recurLower + '][value=1]').checked) {
-                    div.down('input[name=recur_' + recurLower + '_interval]').disable();
-                } else {
-                    div.down('input[name=recur_' + recurLower + '_interval]').enable();
-                }
-                break;
-            }
-
-            if (length.down('input[name=recur_end_type][value=date]').checked) {
-                $(prefix + 'RecurDate').enable();
-                $(prefix + 'RecurPicker').setStyle({ visibility: 'visible' });
-            } else {
-                $(prefix + 'RecurDate').disable();
-                $(prefix + 'RecurPicker').setStyle({ visibility: 'hidden' });
-            }
-            if (length.down('input[name=recur_end_type][value=count]').checked) {
-                $(prefix + 'RecurCount').enable();
-            } else {
-                $(prefix + 'RecurCount').disable();
-            }
-        } else {
-            $(prefix + 'TabRecur').select('div').invoke('hide');
-            $(prefix + 'RepeatType').show();
-        }
-    },
-
-    /**
-     * Fills the recurrence fields of the event and task edit forms.
-     *
-     * @param boolean event  Whether to use the event form.
-     * @param object recur   The recurrence object from the ajax response.
-     */
-    setRecurrenceFields: function(event, recur)
-    {
-        var scheme = Kronolith.conf.recur[recur.t],
-            schemeLower = scheme.toLowerCase(),
-            prefix = 'kronolith' + (event ? 'Event' : 'Task'),
-            div = $(prefix + 'Repeat' + scheme);
-        $(prefix + 'Link' + scheme).setValue(true);
-        if (scheme == 'Monthly' || scheme == 'Yearly') {
-            div.down('input[name=recur_' + schemeLower + '_scheme][value=' + recur.t + ']').setValue(true);
-        }
-        if (scheme == 'Weekly') {
-            div.select('input[type=checkbox]').each(function(input) {
-                if (input.name == 'weekly[]' &&
-                    input.value & recur.d) {
-                    input.setValue(true);
-                }
-            });
-        }
-        if (recur.i == 1) {
-            div.down('input[name=recur_' + schemeLower + '][value=1]').setValue(true);
-        } else {
-            div.down('input[name=recur_' + schemeLower + '][value=0]').setValue(true);
-            div.down('input[name=recur_' + schemeLower + '_interval]').setValue(recur.i);
-        }
-        if (!Object.isUndefined(recur.e)) {
-            $(prefix + 'RepeatLength').down('input[name=recur_end_type][value=date]').setValue(true);
-            $(prefix + 'RecurDate').setValue(Date.parse(recur.e).toString(Kronolith.conf.date_format));
-        } else if (!Object.isUndefined(recur.c)) {
-            $(prefix + 'RepeatLength').down('input[name=recur_end_type][value=count]').setValue(true);
-            $(prefix + 'RecurCount').setValue(recur.c);
-        } else {
-            $(prefix + 'RepeatLength').down('input[name=recur_end_type][value=none]').setValue(true);
-        }
-        $(prefix + 'Exceptions').setValue(recur.ex || '');
-        if ($(prefix + 'Completions')) {
-            $(prefix + 'Completions').setValue(recur.co || '');
-        }
-        this.toggleRecurrence(event, scheme);
-    },
-
-    /**
-     * Returns the Date object representing the date and time specified in the
-     * event form's start or end fields.
-     *
-     * @param string what  Which fields to parse, either 'start' or 'end'.
-     *
-     * @return Date  The date object or null if the fields can't be parsed.
-     */
-    getDate: function(what) {
-        var dateElm, timeElm, date, time;
-        if (what == 'start') {
-            dateElm = 'kronolithEventStartDate';
-            timeElm = 'kronolithEventStartTime';
-        } else {
-            dateElm = 'kronolithEventEndDate';
-            timeElm = 'kronolithEventEndTime';
-        }
-        date = Date.parseExact($F(dateElm), Kronolith.conf.date_format)
-            || Date.parse($F(dateElm));
-        if (date) {
-            time = Date.parseExact($F(timeElm), Kronolith.conf.time_format);
-            if (!time) {
-                time = Date.parse($F(timeElm));
-            }
-            if (time) {
-                date.setHours(time.getHours());
-                date.setMinutes(time.getMinutes());
-            }
-        }
-        return date;
-    },
-
-    checkDate: function(e) {
-        this._checkDate(e.element());
-    },
-
-    _checkDate: function(elm)
-    {
-        if ($F(elm)) {
-            var date = Date.parseExact($F(elm), Kronolith.conf.date_format) || Date.parse($F(elm));
-            if (date) {
-                elm.setValue(date.toString(Kronolith.conf.date_format));
-                this.wrongFormat.unset(elm.id);
-            } else {
-                HordeCore.notify(Kronolith.text.wrong_date_format.interpolate({ wrong: $F(elm), right: new Date().toString(Kronolith.conf.date_format) }), 'horde.warning');
-                this.wrongFormat.set(elm.id, true);
-            }
-        } else {
-            HordeCore.notify(Kronolith.text.wrong_date_format.interpolate({ wrong: $F(elm), right: new Date().toString(Kronolith.conf.date_format) }), 'horde.warning');
-            this.wrongFormat.set(elm.id, true);
-        }
-    },
-
-    /**
-     * Attaches a KeyNavList drop down to one of the time fields.
-     *
-     * @param string|Element field  A time field (id).
-     *
-     * @return KeyNavList  The drop down list object.
-     */
-    attachTimeDropDown: function(field)
-    {
-        var list = [], d = new Date(), time, opts;
-
-        d.setHours(0);
-        d.setMinutes(0);
-        do {
-            time = d.toString(Kronolith.conf.time_format);
-            list.push({ l: time, v: time });
-            d.add(30).minutes();
-        } while (d.getHours() !== 0 || d.getMinutes() !== 0);
-
-        field = $(field);
-        opts = {
-            list: list,
-            domParent: field.up('.kronolithDialog'),
-            onChoose: function(value) {
-                if (value) {
-                    field.setValue(value);
-                }
-                this.updateTimeFields(field.identify());
-            }.bind(this)
-        };
-
-        this.knl[field.id] = new KeyNavList(field, opts);
-
-        return this.knl[field.id];
-    },
-
-    checkTime: function(e) {
-        var elm = e.element();
-        if ($F(elm)) {
-            var time = Date.parseExact(new Date().toString(Kronolith.conf.date_format) + ' ' + $F(elm), Kronolith.conf.date_format + ' ' + Kronolith.conf.time_format) || Date.parse(new Date().toString('yyyy-MM-dd ') + $F(elm));
-            if (time) {
-                elm.setValue(time.toString(Kronolith.conf.time_format));
-                this.wrongFormat.unset(elm.id);
-            } else {
-                HordeCore.notify(Kronolith.text.wrong_time_format.interpolate({ wrong: $F(elm), right: new Date().toString(Kronolith.conf.time_format) }), 'horde.warning');
-                this.wrongFormat.set(elm.id, true);
-            }
-        }
-    },
-
-    /**
-     * Updates the start time in the event form after changing the end time.
-     */
-    updateStartTime: function(date) {
-        var start = this.getDate('start'), end = this.getDate('end');
-        if (!start) {
-            return;
-        }
-        if (!date) {
-            date = end;
-        }
-        if (!date) {
-            return;
-        }
-        if (start.isAfter(end)) {
-            $('kronolithEventStartDate').setValue(date.toString(Kronolith.conf.date_format));
-            $('kronolithEventStartTime').setValue($F('kronolithEventEndTime'));
-        }
-        this.duration = Math.abs(date.getTime() - start.getTime()) / 60000;
-    },
-
-    /**
-     * Updates the end time in the event form after changing the start time.
-     */
-    updateEndTime: function() {
-        var date = this.getDate('start');
-        if (!date) {
-            return;
-        }
-        date.add(this.duration).minutes();
-        $('kronolithEventEndDate').setValue(date.toString(Kronolith.conf.date_format));
-        $('kronolithEventEndTime').setValue(date.toString(Kronolith.conf.time_format));
-    },
-
-    /**
-     * Event handler for scrolling the mouse over the date field.
-     *
-     * @param Event e       The mouse event.
-     * @param string field  The field name.
-     */
-    scrollDateField: function(e, field) {
-        var date = Date.parseExact($F(field), Kronolith.conf.date_format);
-        if (!date || (!e.wheelData && !e.detail)) {
-            return;
-        }
-        date.add(e.wheelData > 0 || e.detail < 0 ? 1 : -1).days();
-        $(field).setValue(date.toString(Kronolith.conf.date_format));
-        switch (field) {
-        case 'kronolithEventStartDate':
-            this.updateEndTime();
-            break;
-        case 'kronolithEventEndDate':
-            this.updateStartTime(date);
-            break;
-        }
-    },
-
-    /**
-     * Event handler for scrolling the mouse over the time field.
-     *
-     * @param Event e       The mouse event.
-     * @param string field  The field name.
-     */
-    scrollTimeField: function(e, field) {
-        var time = Date.parseExact($F(field), Kronolith.conf.time_format) || Date.parse($F(field)),
-            newTime, minute;
-        if (!time || (!e.wheelData && !e.detail)) {
-            return;
-        }
-
-        newTime = time.clone();
-        newTime.add(e.wheelData > 0 || e.detail < 0 ? 10 : -10).minutes();
-        minute = newTime.getMinutes();
-        if (minute % 10) {
-            if (e.wheelData > 0 || e.detail < 0) {
-                minute = minute / 10 | 0;
-            } else {
-                minute = (minute - 10) / 10 | 0;
-            }
-            minute *= 10;
-            newTime.setMinutes(minute);
-        }
-        if (newTime.getDate() != time.getDate()) {
-            if (newTime.isAfter(time)) {
-                newTime = time.clone().set({ hour: 23, minute: 59 });
-            } else {
-                newTime = time.clone().set({ hour: 0, minute: 0 });
-            }
-        }
-
-        $(field).setValue(newTime.toString(Kronolith.conf.time_format));
-        this.updateTimeFields(field);
-
-        /* Mozilla bug https://bugzilla.mozilla.org/show_bug.cgi?id=502818
-         * Need to stop or else multiple scroll events may be fired. We
-         * lose the ability to have the mousescroll bubble up, but that is
-         * more desirable than having the wrong scrolling behavior. */
-        if (Prototype.Browser.Gecko && !e.stop) {
-            Event.stop(e);
-        }
-    },
-
-    /**
-     * Updates the time fields of the event dialog after either has been
-     * changed.
-     *
-     * @param string field  The id of the field that has been changed.
-     */
-    updateTimeFields: function(field)
-    {
-        switch (field) {
-        case 'kronolithEventStartDate':
-            this.fbStartDateHandler(Date.parseExact($F(field), Kronolith.conf.date_format));
-        case 'kronolithEventStartTime':
-            this.updateEndTime();
-            break;
-        case 'kronolithEventEndDate':
-        case 'kronolithEventEndTime':
-            this.updateStartTime();
-            this.fbStartDateHandler(Date.parseExact($F('kronolithEventStartDate'), Kronolith.conf.date_format));
-            break;
-        }
-    },
-
-    /**
-     * Closes a RedBox overlay, after saving its content to the body.
-     */
-    closeRedBox: function()
-    {
-        if (!RedBox.getWindow()) {
-            return;
-        }
-        var content = RedBox.getWindowContents();
-        if (content) {
-            document.body.insert(content.hide());
-        }
-        RedBox.close();
-    },
-
-    // By default, no context onShow action
-    contextOnShow: Prototype.emptyFunction,
-
-    // By default, no context onClick action
-    contextOnClick: Prototype.emptyFunction,
-
-    // Map
-    initializeMap: function(ignoreLL)
-    {
-        if (this.mapInitialized) {
-            return;
-        }
-        var layers = [];
-        if (Kronolith.conf.maps.providers) {
-            Kronolith.conf.maps.providers.each(function(l) {
-                var p = new HordeMap[l]();
-                $H(p.getLayers()).values().each(function(e) {layers.push(e);});
-            });
-        }
-
-        this.map = new HordeMap.Map[Kronolith.conf.maps.driver]({
-            elt: 'kronolithEventMap',
-            delayed: true,
-            layers: layers,
-            markerDragEnd: this.onMarkerDragEnd.bind(this),
-            mapClick: this.afterClickMap.bind(this)
-        });
-
-        if ($('kronolithEventLocationLat').value && !ignoreLL) {
-            var ll = { lat:$('kronolithEventLocationLat').value, lon: $('kronolithEventLocationLon').value };
-            // Note that we need to cast the value of zoom to an integer here,
-            // otherwise the map display breaks.
-            this.placeMapMarker(ll, true, $('kronolithEventMapZoom').value - 0);
-        }
-        //@TODO: check for Location field - and if present, but no lat/lon value, attempt to
-        // geocode it.
-        this.map.display();
-        this.mapInitialized = true;
-    },
-
-    resetMap: function()
-    {
-        this.mapInitialized = false;
-        $('kronolithEventLocationLat').value = null;
-        $('kronolithEventLocationLon').value = null;
-        $('kronolithEventMapZoom').value = null;
-        if (this.mapMarker) {
-            this.map.removeMarker(this.mapMarker, {});
-            this.mapMarker = null;
-        }
-        if (this.map) {
-            this.map.destroy();
-            this.map = null;
-        }
-    },
-
-    /**
-     * Callback for handling marker drag end.
-     *
-     * @param object r  An object that implenents a getLonLat() method to obtain
-     *                  the new location of the marker.
-     */
-    onMarkerDragEnd: function(r)
-    {
-        var ll = r.getLonLat();
-        $('kronolithEventLocationLon').value = ll.lon;
-        $('kronolithEventLocationLat').value = ll.lat;
-        var gc = new HordeMap.Geocoder[Kronolith.conf.maps.geocoder](this.map.map, 'kronolithEventMap');
-        gc.reverseGeocode(ll, this.onReverseGeocode.bind(this), this.onGeocodeError.bind(this) );
-    },
-
-    /**
-     * Callback for handling a reverse geocode request.
-     *
-     * @param array r  An array of objects containing the results. Each object in
-     *                 the array is {lat:, lon:, address}
-     */
-    onReverseGeocode: function(r)
-    {
-        if (!r.length) {
-            return;
-        }
-        $('kronolithEventLocation').value = r[0].address;
-    },
-
-    onGeocodeError: function(r)
-    {
-        $('kronolithEventGeo_loading_img').toggle();
-        HordeCore.notify(Kronolith.text.geocode_error + ' ' + r, 'horde.error');
-    },
-
-    /**
-     * Callback for geocoding calls.
-     */
-    onGeocode: function(r)
-    {
-        $('kronolithEventGeo_loading_img').toggle();
-        r = r.shift();
-        if (r.precision) {
-            zoom = r.precision * 2;
-        } else {
-            zoom = null;
-        }
-        this.ensureMap(true);
-        this.placeMapMarker({ lat: r.lat, lon: r.lon }, true, zoom);
-    },
-
-    geocode: function(a) {
-        if (!a) {
-            return;
-        }
-        $('kronolithEventGeo_loading_img').toggle();
-        var gc = new HordeMap.Geocoder[Kronolith.conf.maps.geocoder](this.map.map, 'kronolithEventMap');
-        gc.geocode(a, this.onGeocode.bind(this), this.onGeocodeError);
-    },
-
-    /**
-     * Place the event marker on the map, at point ll, ensuring it exists.
-     * Optionally center the map on the marker and zoom. Zoom only honored if
-     * center is set, and if center is set, but zoom is null, we zoomToFit().
-     *
-     */
-    placeMapMarker: function(ll, center, zoom)
-    {
-        if (!this.mapMarker) {
-            this.mapMarker = this.map.addMarker(
-                    ll,
-                    { draggable: true },
-                    {
-                        context: this,
-                        dragend: this.onMarkerDragEnd
-                    });
-        } else {
-            this.map.moveMarker(this.mapMarker, ll);
-        }
-
-        if (center) {
-            this.map.setCenter(ll, zoom);
-            if (!zoom) {
-                this.map.zoomToFit();
-            }
-        }
-        $('kronolithEventLocationLon').value = ll.lon;
-        $('kronolithEventLocationLat').value = ll.lat;
-    },
-
-    /**
-     * Remove the event marker from the map. Called after clearing the location
-     * field.
-     */
-    removeMapMarker: function()
-    {
-        if (this.mapMarker) {
-            this.map.removeMarker(this.mapMarker, {});
-            $('kronolithEventLocationLon').value = null;
-            $('kronolithEventLocationLat').value = null;
-        }
-
-        this.mapMarker = false;
-    },
-
-    /**
-     * Ensures the map tab is visible and sets UI elements accordingly.
-     */
-    ensureMap: function(ignoreLL)
-    {
-        if (!this.mapInitialized) {
-            this.initializeMap(ignoreLL);
-        }
-        var dialog = $('kronolithEventForm');
-        dialog.select('.kronolithTabsOption').invoke('hide');
-        dialog.select('.tabset li').invoke('removeClassName', 'horde-active');
-        $('kronolithEventTabMap').show();
-        $('kronolithEventLinkMap').up().addClassName('horde-active');
-    },
-
-    /**
-     * Callback that gets called after a new marker has been placed on the map
-     * due to a single click on the map.
-     *
-     * @return object o  { lonlat: }
-     */
-    afterClickMap: function(o)
-    {
-        this.placeMapMarker(o.lonlat, false);
-        var gc = new HordeMap.Geocoder[Kronolith.conf.maps.geocoder](this.map.map, 'kronolithEventMap');
-        gc.reverseGeocode(o.lonlat, this.onReverseGeocode.bind(this), this.onGeocodeError.bind(this) );
-    },
-
-    /* Onload function. */
-    onDomLoad: function()
-    {
-        var dateFields, timeFields;
-
-        /* Initialize the starting page. */
-        var tmp = location.hash;
-        if (!tmp.empty() && tmp.startsWith('#')) {
-            tmp = (tmp.length == 1) ? '' : tmp.substring(1);
-        }
-        if (tmp.empty()) {
-            this.updateView(this.date, Kronolith.conf.login_view);
-            $('kronolithView' + Kronolith.conf.login_view.capitalize()).show();
-        }
-        HordeCore.doAction('listCalendars', {}, { callback: this.initialize.bind(this, tmp) });
-
-        RedBox.onDisplay = function() {
-            this.redBoxLoading = false;
-        }.bind(this);
-        RedBox.duration = this.effectDur;
-
-        $('kronolithEventStartDate', 'kronolithEventEndDate', 'kronolithTaskDueDate').compact().invoke('observe', 'blur', this.checkDate.bind(this));
-        var timeFields = $('kronolithEventStartTime', 'kronolithEventEndTime', 'kronolithTaskDueTime').compact();
-        timeFields.invoke('observe', 'blur', this.checkTime.bind(this));
-        timeFields.each(function(field) {
-            var dropDown = this.attachTimeDropDown(field);
-            field.observe('click', function() { dropDown.show(); });
-        }, this);
-        $('kronolithEventStartDate', 'kronolithEventStartTime').invoke('observe', 'change', this.updateEndTime.bind(this));
-        $('kronolithEventEndDate', 'kronolithEventEndTime').invoke('observe', 'change', function() { this.updateStartTime(); }.bind(this));
-
-        if (Kronolith.conf.has_tasks) {
-            $('kronolithTaskDueDate', 'kronolithTaskDueTime').compact().invoke('observe', 'focus', this.setDefaultDue.bind(this));
-            $('kronolithTaskList').observe('change', function() {
-                this.updateTaskParentDropDown($F('kronolithTaskList'));
-                this.updateTaskAssigneeDropDown($F('kronolithTaskList'));
-            }.bind(this));
-        }
-
-        document.observe('keydown', KronolithCore.keydownHandler.bindAsEventListener(KronolithCore));
-        document.observe('keyup', KronolithCore.keyupHandler.bindAsEventListener(KronolithCore));
-        document.observe('click', KronolithCore.clickHandler.bindAsEventListener(KronolithCore));
-        document.observe('dblclick', KronolithCore.clickHandler.bindAsEventListener(KronolithCore, true));
-
-        // Mouse wheel handler.
-        dateFields = [ 'kronolithEventStartDate', 'kronolithEventEndDate' ];
-        timeFields = [ 'kronolithEventStartTime', 'kronolithEventEndTime' ];
-        if (Kronolith.conf.has_tasks) {
-            dateFields.push('kronolithTaskDueDate');
-            timeFields.push('kronolithTaskDueTime');
-        }
-        dateFields.each(function(field) {
-            $(field).observe(Prototype.Browser.Gecko ? 'DOMMouseScroll' : 'mousewheel', this.scrollDateField.bindAsEventListener(this, field));
-        }, this);
-        timeFields.each(function(field) {
-            $(field).observe(Prototype.Browser.Gecko ? 'DOMMouseScroll' : 'mousewheel', this.scrollTimeField.bindAsEventListener(this, field));
-        }, this);
-
-        $('kronolithEventStartDate').observe('change', this.fbStartDateOnChange.bind(this));
-        $('kronolithFBDatePrev').observe('click', this.prevFreebusy.bind(this));
-        $('kronolithFBDateNext').observe('click', this.nextFreebusy.bind(this));
-        $('kronolithResourceFBDatePrev').observe('click', this.prevFreebusy.bind(this));
-        $('kronolithResourceFBDateNext').observe('click', this.nextFreebusy.bind(this));
-
-        this.updateMinical(this.date);
-    },
-
-    initialize: function(location, r)
-    {
-        Kronolith.conf.calendars = r.calendars;
-        this.updateCalendarList();
-        HordeSidebar.refreshEvents();
-        $('kronolithLoadingCalendars').hide();
-        $('kronolithMenuCalendars').show();
-        this.initialized = true;
-
-        /* Initialize the starting page. */
-        if (!location.empty()) {
-            this.go(decodeURIComponent(location));
-        } else {
-            this.go(Kronolith.conf.login_view);
-        }
-
-        /* Start polling. */
-        new PeriodicalExecuter(function()
-            {
-                HordeCore.doAction('poll');
-                $(kronolithGotoToday).update(Date.today().toString(Kronolith.conf.date_format));
-            },
-            60
-        );
-    }
-
-};
-
-/* Initialize global event handlers. */
-document.observe('dom:loaded', KronolithCore.onDomLoad.bind(KronolithCore));
-document.observe('DragDrop2:drag', KronolithCore.onDrag.bindAsEventListener(KronolithCore));
-document.observe('DragDrop2:drop', KronolithCore.onDrop.bindAsEventListener(KronolithCore));
-document.observe('DragDrop2:end', KronolithCore.onDragEnd.bindAsEventListener(KronolithCore));
-document.observe('DragDrop2:start', KronolithCore.onDragStart.bindAsEventListener(KronolithCore));
-document.observe('Horde_Calendar:select', KronolithCore.datePickerHandler.bindAsEventListener(KronolithCore));
-document.observe('FormGhost:reset', KronolithCore.searchReset.bindAsEventListener(KronolithCore));
-document.observe('FormGhost:submit', KronolithCore.searchSubmit.bindAsEventListener(KronolithCore));
-document.observe('HordeCore:showNotifications', KronolithCore.showNotification.bindAsEventListener(KronolithCore));
-if (Prototype.Browser.IE) {
-    $('kronolithBody').observe('selectstart', Event.stop);
-}
-
-/* Extend AJAX exception handling. */
-HordeCore.onException = HordeCore.onException.wrap(KronolithCore.onException.bind(KronolithCore));
diff -pruN 4.2.23-1/kronolith-4.2.23/js/smartmobile.js 4.2.27-1/kronolith-4.2.23/js/smartmobile.js
--- 4.2.23-1/kronolith-4.2.23/js/smartmobile.js	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/js/smartmobile.js	1970-01-01 00:00:00.000000000 +0000
@@ -1,797 +0,0 @@
-/**
- * mobile.js - Base mobile application logic.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author   Michael J. Rubinsky <mrubinsk@horde.org>
- * @author   Jan Schneider <jan@horde.org>
- * @category Horde
- * @license  http://www.horde.org/licenses/gpl GPL
- * @package  Kronolith
- */
-var KronolithMobile = {
-
-    /**
-     * List of calendars we are displaying
-     */
-    calendars:  [],
-
-    /**
-     * List of calendars that are currently loaded for the current view
-     */
-    loadedCalendars: [],
-
-    /**
-     * Event cache
-     */
-    ecache: {},
-    cacheStart: null,
-    cacheEnd: null,
-
-    deferHash: {},
-    viewRan: false,
-
-    /**
-     * The currently displayed view
-     */
-    view: 'day',
-
-    /**
-     * The currently selected date
-     */
-    date: null,
-
-    /**
-     * Temporary fix for pages not firing pagebeforecreate events properly
-     */
-    haveOverview: false,
-
-    /**
-     * Load all events between start and end time.
-     *
-     * @param Date firstDay
-     * @param Date lastDay
-     * @param string view    The view we are loading for (month, day)
-     */
-    loadEvents: function(firstDay, lastDay, view)
-    {
-        var dates = [firstDay, lastDay], loading = false;
-
-        // Clear out the loaded cal cache
-        KronolithMobile.loadedCalendars = [];
-        KronolithMobile.clearView(view);
-        KronolithMobile.viewRan = false;
-        $.each(KronolithMobile.calendars, function(key, cal) {
-            var startDay = dates[0].clone(), endDay = dates[1].clone(),
-            cals = KronolithMobile.ecache[cal[0]], c;
-            if (typeof cals != 'undefined' &&
-                typeof cals[cal[1]] != 'undefined') {
-
-                cals = cals[cal[1]];
-                c = cals[startDay.dateString()];
-                while (typeof c != 'undefined' && startDay.isBefore(endDay)) {
-                    if (view == 'day') {
-                        KronolithMobile.insertEvents([startDay, startDay], view, cal.join('|'));
-                    }
-                    startDay.addDays(1);
-                    c = cals[startDay.dateString()];
-                }
-
-                c = cals[endDay.dateString()];
-                while (typeof c != 'undefined' && !startDay.isAfter(endDay)) {
-                    if (view == 'day') {
-                        KronolithMobile.insertEvents([endDay, endDay], view, cal.join('|'));
-                    }
-                    endDay.addDays(-1);
-                    c = cals[endDay.dateString()];
-                }
-                if (startDay.compareTo(endDay) > 0) {
-                    KronolithMobile.loadedCalendars.push(cal.join('|'));
-                    return;
-                }
-            }
-
-            var start = startDay.dateString(), end = endDay.dateString();
-                loading = true;
-            HordeMobile.doAction('listEvents',
-                                 {
-                                   'start': start,
-                                   'end': end,
-                                   'cal': cal.join('|'),
-                                   'view': view,
-                                   'sig': start + end + (Math.random() + '').slice(2)
-                                 },
-                                 KronolithMobile.loadEventsCallback
-            );
-        });
-
-        if (!loading && view == 'overview') {
-            KronolithMobile.insertEvents([firstDay, lastDay], view);
-        }
-    },
-
-    /**
-     * Sort a collection of events as returned from the ecache
-     */
-    sortEvents: function(events)
-    {
-        var e = [];
-
-        // Need a native array to sort.
-        $.each(events, function(id, event) {
-            e.push(event);
-        });
-        return  e.sort(function(a, b) {
-           sortA = a.sort;
-           sortB = b.sort;
-           return (sortA < sortB) ? -1 : (sortA > sortB) ? 1 : 0;
-         });
-    },
-
-    /**
-     * Callback for the loadEvents AJAX request.
-     *
-     * @param object data  The ajax response.
-     */
-    loadEventsCallback: function(data)
-    {
-        var start = KronolithMobile.parseDate(data.sig.substr(0, 8)),
-            end = KronolithMobile.parseDate(data.sig.substr(8, 8)),
-            dates = [start, end], view = data.view;
-
-        KronolithMobile.storeCache(data.events, data.cal, dates, true);
-        KronolithMobile.loadedCalendars.push(data.cal);
-        KronolithMobile.insertEvents(dates, view, data.cal);
-    },
-
-    /**
-     * Inserts events into current view.
-     * For Day view, builds a new listview and attaches to the DOM.
-     * For Month view, hightlights dates with events.
-     */
-    insertEvents: function(dates, view, cal)
-    {
-        var key = dates[0].dateString() + dates[1].dateString() + view + cal,
-        d = [dates[0].clone(), dates[1].clone()], date, events, list, day;
-
-        // Make sure all calendars are loaded before rendering the view.
-        // @TODO: Implement LIFO queue as in kronolith.js
-        if (KronolithMobile.loadedCalendars.length != KronolithMobile.calendars.length) {
-            if (KronolithMobile.deferHash[key]) {
-                return;
-            } else {
-                KronolithMobile.deferHash[key] = window.setTimeout(function() { KronolithMobile.insertEvents(d, view, cal); }, 0);
-                return;
-            }
-        }
-        if (KronolithMobile.deferHash[key]) {
-            window.clearTimeout(KronolithMobile.deferHash[key]);
-            KronolithMobile.deferHash[key] = false;
-        }
-
-        KronolithMobile.running = true;
-        switch (view) {
-            case 'day':
-                if (!KronolithMobile.viewRan) {
-                    KronolithMobile.viewRan = true;
-                    date = d[0].dateString();
-                    events = KronolithMobile.getCacheForDate(date);
-                    events = KronolithMobile.sortEvents(events);
-                    list = $('<ul>').attr({'data-role': 'listview'});
-                    $.each(events, function(index, event) {
-                        list.append(KronolithMobile.buildDayEvent(event));
-                    });
-                    if (!list.children().length) {
-                        list.append($('<li>').text(Kronolith.text.noevents));
-                    }
-                    $("#dayview :jqmData(role='content')").append(list).trigger('create');
-                }
-                break;
-
-            case 'month':
-                day = d[0].clone();
-                while (!day.isAfter(d[1])) {
-                    date = day.dateString();
-                    events = KronolithMobile.getCacheForDate(date);
-                    $.each(events, function(key, event) {
-                        $('#kronolithMonth' + date).addClass('kronolithContainsEvents');
-                    });
-                    day.next().day();
-                }
-                // Select current date.
-                $('#kronolithMonth'+ KronolithMobile.date.dateString()).addClass('kronolith-selected');
-                KronolithMobile.selectMonthDay(KronolithMobile.date.dateString());
-                break;
-
-            case 'overview':
-                day = d[0].clone(), haveEvent = false;
-                list = $('<ul>').attr({'data-role': 'listview'});
-                while (!day.isAfter(d[1])) {
-                    list.append($('<li>').attr({ 'data-role': 'list-divider' }).text(day.toString('ddd') + ' ' + day.toString('d')));
-                    events = KronolithMobile.sortEvents(KronolithMobile.getCacheForDate(day.dateString())) ;
-                    $.each(events, function(index, event) {
-                        list.append(KronolithMobile.buildDayEvent(event));
-                        haveEvent = true;
-                    });
-                    if (!haveEvent) {
-                        list.append($('<li>').text(Kronolith.text.noevents));
-                    }
-                    haveEvent = false;
-                    day.next().day();
-                }
-                $("#overview :jqmData(role='content')").append(list).trigger('create');
-                break;
-        }
-        KronolithMobile.running = false;
-    },
-
-    /**
-     * Build the dom element for an event to insert into the day view.
-     *
-     * @param object event  The event object returned from the ajax request.
-     */
-    buildDayEvent: function(event)
-    {
-        var id;
-        if ($.isEmptyObject(event)) {
-          return;
-        }
-
-        var cal = event.calendar, type = cal.split('|')[0], c = cal.split('|')[1],
-        d = $('<div>'), item = $('<li>'), a;
-
-        // Time
-        var timeWrapper = $('<div>').addClass('kronolithTimeWrapper');
-        if (event.al) {
-            timeWrapper.append(Kronolith.text.allday).html();
-        } else {
-            var startTime = Date.parse(event.s).toString(Kronolith.conf.time_format);
-            var endTime = '- ' + Date.parse(event.e).toString(Kronolith.conf.time_format);
-            timeWrapper
-              .append($('<div>').addClass('kronolithStartTime').append(startTime))
-              .append($('<div>').addClass('kronolithEndTime').append(endTime));
-        }
-
-        e = $('<h2>').text(event.t);
-        l = $('<p>').addClass('kronolithDayLocation' + Kronolith.conf.calendars[type][c].fg.substring(1)).text(event.l);
-        d.append(timeWrapper).append(e).append(l);
-
-        // Add the link to view the event detail.
-        a = $('<a>')
-            .css({ backgroundColor: Kronolith.conf.calendars[type][c].bg })
-            .attr({ href: '#eventview',
-                    'class': 'kronolith-event kronolith-event-' + Kronolith.conf.calendars[type][c].fg.substring(1) })
-            .click(function(ev) {
-                $("#eventview :jqmData(role='content') ul").detach();
-                KronolithMobile.loadEvent(cal, event.id, Date.parse(event.e));
-            })
-            .append(d);
-
-        return item.append(a);
-    },
-
-    /**
-     * Retrieve a single event from the server and show it.
-     *
-     * @param string cal  The calendar identifier.
-     * @param string id   The event identifier.
-     * @param Date   d    The date the event occurs.
-     */
-    loadEvent: function(cal, id, d)
-    {
-        HordeMobile.doAction('getEvent',
-                             {'cal': cal, 'id': id, 'date': d.toString('yyyyMMdd')},
-                             KronolithMobile.loadEventCallback);
-    },
-
-    /**
-     * Callback for loadEvent call.  Assume we are in Event view for now, build
-     * the event view structure and attach to DOM.
-     *
-     * @param object data  The ajax response.
-     */
-    loadEventCallback: function(data)
-    {
-         if (!data.event) {
-             // @TODO: Error handling.
-             return;
-         }
-
-         var event = data.event;
-         var ul = KronolithMobile.buildEventView(event);
-         $("#eventview :jqmData(role='content')").append(ul).trigger('create');
-    },
-
-    /**
-     * Build event view DOM structure and return the top event element.
-     *
-     * @param object e  The event structure returned from the ajax call.
-     */
-    buildEventView: function(e)
-    {
-         var list = $('<ul>')
-            .addClass('kronolithEventDetail')
-            .attr({'data-role': 'listview', 'data-inset': true});
-
-         var loc = false;
-
-         // Title and calendar
-         var title = $('<div>').addClass('kronolithEventDetailTitle').append($('<h2>').text(e.t));
-         var calendar = $('<p>').addClass('kronolithEventDetailCalendar').text(Kronolith.conf.calendars[e.ty][e.c]['name']);
-         list.append($('<li>').append(title).append(calendar));
-
-         // Time
-         var item = $('<div>');
-         if (e.r) {
-             var recurText = Kronolith.text.recur.desc[e.r.t][(e.r.i > 1) ? 1 : 0],
-                 date = Date.parse(e.s);
-             recurText = recurText.replace('#{interval}', e.r.i);
-             switch (e.r.t) {
-             case 2:
-                 // WEEKLY
-                 recurText = recurText.replace('#{weekday}', Kronolith.text.weekday[e.r.d]);
-                 break;
-             case 3:
-                 // MONTHLY_DATE
-                 recurText = recurText.replace('#{date}', date.toString('d') + '.');
-                 break;
-             case 5:
-                 // YEARLY_DATE
-                 recurText = recurText.replace('#{date}', date.toString('m'));
-                 break;
-             }
-             item.append($('<div>').addClass('kronolithEventDetailRecurring').append(recurText));
-             item.append($('<div>').addClass('kronolithEventDetailRecurring').text(Kronolith.text.recur[e.r.t]));
-         }
-         if (e.al) {
-             item.append($('<div>').addClass('kronolithEventDetailAllDay').text(Kronolith.text.allday))
-                .append($('<div>').addClass('kronolithEventDetailDate').text(Date.parse(e.s).toString('D')));
-         } else {
-             item.append($('<div>')
-                .append($('<div>').addClass('kronolithEventDetailDate').text(Date.parse(e.s).toString('D'))
-                .append($('<div>').addClass('kronolithEventDetailTime').text(Date.parse(e.s).toString(Kronolith.conf.time_format) + ' - ' + Date.parse(e.e).toString(Kronolith.conf.time_format))))
-             );
-         }
-         list.append($('<li>').append(item));
-
-         // Location
-         if (e.gl) {
-             loc = $('<div>').addClass('kronolithEventDetailLocation')
-                .append($('<a>').attr({'data-style': 'b', 'href': 'http://maps.google.com?q=' + encodeURIComponent(e.gl.lat + ',' + e.gl.lon)}).text(e.l));
-         } else if (e.l) {
-             loc = $('<div>').addClass('kronolithEventDetailLocation')
-                .append($('<a>').attr({'href': 'http://maps.google.com?q=' + encodeURIComponent(e.l)}).text(e.l));
-         }
-         if (loc) {
-             list.append($('<li>').append(loc));
-         }
-
-         // Description
-         if (e.d) {
-           list.append($('<li>').append($('<div>').addClass('kronolithEventDetailDesc').text(e.d)));
-         }
-
-         // url
-         if (e.u) {
-           list.append($('<li>').append($('<a>').attr({'rel': 'external', 'href': e.u}).text(e.u)));
-         }
-
-         return list;
-    },
-
-    clearView: function(view)
-    {
-        switch (view) {
-        case 'month':
-            $('#kronolithDayDetail ul').detach();
-            break;
-        case 'day':
-            $("#dayview :jqmData(role='content') ul").detach();
-            break;
-        case 'overview':
-            $("#overview :jqmData(role='content') ul").detach();
-        }
-    },
-
-    /**
-     * Advance the day view by one day
-     */
-    showNextDay: function()
-    {
-        KronolithMobile.moveToDay(KronolithMobile.date.clone().addDays(1));
-    },
-
-    /**
-     * Move the day view back by one day
-     */
-    showPrevDay: function()
-    {
-        KronolithMobile.moveToDay(KronolithMobile.date.clone().addDays(-1));
-    },
-
-    /**
-     * Move the day view to a specific day
-     *
-     * @param Date date  The date to set the day view to.
-     */
-    moveToDay: function(date)
-    {
-        $('#kronolithDayDate').text(date.toString('ddd') + ' ' + date.toString('d'));
-        KronolithMobile.date = date.clone();
-        KronolithMobile.loadEvents(KronolithMobile.date, KronolithMobile.date, 'day');
-    },
-
-    /**
-     * Advance the month view ahead one month.
-     */
-    showPrevMonth: function()
-    {
-        KronolithMobile.moveToMonth(KronolithMobile.date.clone().addMonths(-1));
-    },
-
-    /**
-     * Move the month view back one month
-     */
-    showNextMonth: function()
-    {
-        KronolithMobile.moveToMonth(KronolithMobile.date.clone().addMonths(1));
-    },
-
-    /**
-     * Move the month view to the month containing the specified date.
-     *
-     * @params Date date  The date to move to.
-     */
-    moveToMonth: function(date)
-    {
-        var dates = KronolithMobile.viewDates(date, 'month');
-        KronolithMobile.date = date;
-        KronolithMobile.loadEvents(dates[0], dates[1], 'month');
-        KronolithMobile.buildCal(date);
-        KronolithMobile.insertEvents(dates, 'month');
-    },
-
-    /**
-     * Selects a day in the month view, and displays any events it may contain.
-     * Also sets the dayview to the same date, so navigating back to it is
-     * smooth.
-     *
-     * @param string date  A date string in the form of yyyyMMdd.
-     */
-    selectMonthDay: function(date)
-    {
-        var ul = $('<ul>').attr({ 'data-role': 'listview' }),
-        d = KronolithMobile.parseDate(date), today = new Date(), text;
-        $('#kronolithDayDetail ul').detach();
-        if (today.dateString() == d.dateString()) {
-          text = Kronolith.text.today;
-        } else if (today.clone().addDays(-1).dateString() == d.dateString()) {
-          text = Kronolith.text.yesterday;
-        } else if (today.clone().addDays(1).dateString() == d.dateString()) {
-          text = Kronolith.text.tomorrow;
-        } else {
-          text = d.toString('ddd') + ' ' + d.toString('d');
-        }
-        $('#kronolithDayDetailHeader h3').text(text);
-        $('.kronolith-selected').removeClass('kronolith-selected');
-        $('#kronolithMonth' + date).addClass('kronolith-selected');
-        if ($('#kronolithMonth' + date).hasClass('kronolithContainsEvents')) {
-            var events = KronolithMobile.getCacheForDate(date);
-            events = KronolithMobile.sortEvents(events);
-            $.each(events, function(k, e) {
-                ul.append(KronolithMobile.buildDayEvent(e));
-            });
-        }
-        $('#kronolithDayDetail').append(ul).trigger('create');
-        KronolithMobile.moveToDay(d);
-    },
-
-    /**
-     * Calculates first and last days being displayed.
-     *
-     * @var Date date    The date of the view.
-     * @var string view  A view name.
-     *
-     * @return array  Array with first and last day of the view.
-     */
-    viewDates: function(date, view)
-    {
-        var start = date.clone(), end = date.clone();
-
-        switch (view) {
-        case 'month':
-            start.setDate(1);
-            start.moveToBeginOfWeek(Kronolith.conf.week_start);
-            end.moveToLastDayOfMonth();
-            end.moveToEndOfWeek(Kronolith.conf.week_start);
-            break;
-        case 'summary':
-            end.add(6).days();
-            break;
-        }
-
-        return [start, end];
-    },
-
-    /**
-     * Creates the month view calendar.
-     *
-     * @param Date date        The date to show in the calendar.
-     */
-    buildCal: function(date)
-    {
-        var tbody = $('.kronolith-minical table tbody');
-        var dates = KronolithMobile.viewDates(date, 'month'), day = dates[0].clone(),
-        today = Date.today(), dateString, td, tr, i;
-
-        // Remove old calendar rows.
-        tbody.children().remove();
-
-        // Update title
-        $('#kronolithMinicalDate').html(date.toString('MMMM yyyy'));
-
-        for (i = 0; i < 42; i++) {
-            dateString = day.dateString();
-
-            // Create calendar row .
-            if (day.getDay() == Kronolith.conf.week_start) {
-                tr = $('<tr>');
-                tbody.append(tr);
-            }
-
-            // Insert day cell.
-            td = $('<td>').attr({ id: 'kronolithMonth' + dateString })
-                .addClass('kronolithMonthDay')
-                .jqmData('date', dateString);
-            if (day.getMonth() != date.getMonth()) {
-                td.addClass('kronolithMinicalEmpty');
-            }
-
-            // Highlight today.
-            if (day.dateString() == today.dateString()) {
-                td.addClass('kronolithToday');
-            }
-            td.html(day.getDate());
-            tr.append(td);
-            day.next().day();
-        }
-    },
-
-    /**
-     * Parses a date attribute string into a Date object.
-     *
-     * For other strings use Date.parse().
-     *
-     * @param string date  A yyyyMMdd date string.
-     *
-     * @return Date  A date object.
-     */
-    parseDate: function(date)
-    {
-        var d = new Date(date.substr(0, 4), date.substr(4, 2) - 1, date.substr(6, 2));
-        if (date.length == 12) {
-            d.setHours(date.substr(8, 2));
-            d.setMinutes(date.substr(10, 2));
-        }
-        return d;
-    },
-
-    storeCache: function(events, calendar, dates, createCache)
-    {
-        events = events || {};
-
-        //calendar[0] == type, calendar[1] == calendar name
-        calendar = calendar.split('|');
-        if (!KronolithMobile.ecache[calendar[0]]) {
-            if (!createCache) {
-                return;
-            }
-            KronolithMobile.ecache[calendar[0]] = {};
-        }
-        if (!KronolithMobile.ecache[calendar[0]][calendar[1]]) {
-            if (!createCache) {
-                return;
-            }
-            KronolithMobile.ecache[calendar[0]][calendar[1]] = {};
-        }
-        var calHash = KronolithMobile.ecache[calendar[0]][calendar[1]];
-
-        // Create empty cache entries for all dates.
-        if (!!dates) {
-            var day = dates[0].clone(), date;
-            while (!day.isAfter(dates[1])) {
-                date = day.dateString();
-                if (!calHash[date]) {
-                    if (!createCache) {
-                        return;
-                    }
-                    if (!KronolithMobile.cacheStart || KronolithMobile.cacheStart.isAfter(day)) {
-                        KronolithMobile.cacheStart = day.clone();
-                    }
-                    if (!KronolithMobile.cacheEnd || KronolithMobile.cacheEnd.isBefore(day)) {
-                        KronolithMobile.cacheEnd = day.clone();
-                    }
-                    calHash[date] = {};
-                }
-                day.add(1).day();
-            }
-        }
-
-        var cal = calendar.join('|');
-        $.each(events, function(key, date) {
-            // We might not have a cache for this date if the event lasts
-            // longer than the current view
-            if (typeof calHash[key] == 'undefined') {
-                return;
-            }
-
-            // Store useful information in event objects.
-            $.each(date, function(k, event) {
-                event.calendar = cal;
-                event.start = Date.parse(event.s);
-                event.end = Date.parse(event.e);
-                event.sort = event.start.toString('HHmmss')
-                    + (240000 - parseInt(event.end.toString('HHmmss'), 10)).toPaddedString(6);
-                event.id = k;
-            });
-
-            // Store events in cache.
-            $.extend(calHash[key], date);
-        });
-    },
-
-    /**
-     * Return all events for a single day from all displayed calendars merged
-     * into a single hash.
-     *
-     * @param string date  A yyyymmdd date string.
-     *
-     * @return Hash  An event hash which event ids as keys and event objects as
-     *               values.
-     */
-    getCacheForDate: function(date, calendar)
-    {
-        if (calendar) {
-            var cals = calendar.split('|');
-            return KronolithMobile.ecache[cals[0]][cals[1]][date];
-        }
-
-        var events = {};
-        $.each(KronolithMobile.ecache, function(key, type) {
-            $.each(type, function(id, cal) {
-                if (!Kronolith.conf.calendars[key][id].show) {
-                    return;
-                }
-                if (typeof cal[date] != 'undefined') {
-                    $.extend(events, cal[date]);
-                }
-           });
-        });
-
-        return events;
-    },
-
-    /**
-     * Handle swipe events for the current view.
-     */
-    handleSwipe: function(map)
-    {
-        switch (KronolithMobile.view) {
-        case 'day':
-            if (map.type == 'swipeleft') {
-                KronolithMobile.showNextDay();
-            } else {
-                KronolithMobile.showPrevDay();
-            }
-            break;
-
-        case 'month':
-            if (map.type == 'swipeleft') {
-                KronolithMobile.showNextMonth();
-            } else {
-                KronolithMobile.showPrevMonth();
-            }
-        }
-    },
-
-    /**
-     * Event handler for the pagebeforechange event that implements loading of
-     * deep-linked pages.
-     *
-     * @param object e     Event object.
-     * @param object data  Event data.
-     */
-    toPage: function(e, data)
-    {
-        switch (data.options.parsedUrl.view) {
-        case 'minical-next':
-            KronolithMobile.showNextMonth();
-            e.preventDefault();
-            break;
-
-        case 'minical-prev':
-            KronolithMobile.showPrevMonth();
-            e.preventDefault();
-            break;
-
-        case 'nextday':
-            KronolithMobile.showNextDay();
-            e.preventDefault();
-            break;
-
-        case 'prevday':
-            KronolithMobile.showPrevDay();
-            e.preventDefault();
-            break;
-        }
-    },
-
-    /**
-     */
-    loadPage: function()
-    {
-        switch (HordeMobile.currentPage()) {
-        case 'monthview':
-            KronolithMobile.view = "month";
-            // (re)build the minical only if we need to
-            if (!$("#kronolithMinicalDate").jqmData("date") ||
-                ($("#kronolithMinicalDate").jqmData("date").toString("M") != KronolithMobile.date.toString("M"))) {
-                KronolithMobile.moveToMonth(KronolithMobile.date);
-            }
-            break;
-
-        case 'overview':
-            KronolithMobile.view = "overview";
-            if (!KronolithMobile.haveOverview) {
-                KronolithMobile.loadEvents(KronolithMobile.date, KronolithMobile.date.clone().addDays(7), "overview");
-                KronolithMobile.haveOverview = true;
-            }
-            break;
-
-        case null:
-            break;
-
-        case 'dayview':
-        default:
-            KronolithMobile.view = "day";
-            $("#kronolithDayDate").html(KronolithMobile.date.toString("ddd") + " " + KronolithMobile.date.toString("d"));
-            KronolithMobile.loadEvents(KronolithMobile.date, KronolithMobile.date, "day");
-            break;
-        }
-    },
-
-    onDocumentReady: function()
-    {
-        KronolithMobile.date = new Date();
-
-        // Build list of calendars we want.
-        $.each(Kronolith.conf.calendars, function(key, value) {
-            $.each(value, function(cal, info) {
-                if (info.show) {
-                    KronolithMobile.calendars.push([key, cal]);
-                }
-            });
-        });
-
-        // Bind click and swipe events
-        $('body').bind('swipeleft', KronolithMobile.handleSwipe)
-            .bind('swiperight', KronolithMobile.handleSwipe);
-        $(document).bind('pageshow', KronolithMobile.loadPage)
-            .bind('pagebeforechange', KronolithMobile.toPage)
-            .on("pageshow", "#eventview", function(event, ui) {
-                KronolithMobile.view = "event";
-            });
-
-        $('#kronolith-minical').on('click', 'td', function(e) {
-            KronolithMobile.selectMonthDay($(e.target).jqmData('date'));
-        });
-
-        // Load initial view.
-        KronolithMobile.loadPage();
-    }
-};
-
-$(KronolithMobile.onDocumentReady);
diff -pruN 4.2.23-1/kronolith-4.2.23/js/views.js 4.2.27-1/kronolith-4.2.23/js/views.js
--- 4.2.23-1/kronolith-4.2.23/js/views.js	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/js/views.js	1970-01-01 00:00:00.000000000 +0000
@@ -1,24 +0,0 @@
-var eventTabs = null;
-
-function ShowTab(tab)
-{
-    if (eventTabs === null) {
-        eventTabs = $('page').select('.tabset ul li');
-    }
-
-    eventTabs.each(function(c) {
-        var t = $(c.id.substring(3));
-        if (!t) {
-            return;
-        }
-        if (c.id == 'tab' + tab) {
-            c.addClassName('horde-active');
-            t.show();
-        } else {
-            c.removeClassName('horde-active');
-            t.hide();
-        }
-    });
-
-    return false;
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Ajax/Application/Handler.php 4.2.27-1/kronolith-4.2.23/lib/Ajax/Application/Handler.php
--- 4.2.23-1/kronolith-4.2.23/lib/Ajax/Application/Handler.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Ajax/Application/Handler.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,1695 +0,0 @@
-<?php
-/**
- * Defines the AJAX actions used in Kronolith.
- *
- * Copyright 2012-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author   Michael Slusarz <slusarz@horde.org>
- * @author   Jan Schneider <jan@horde.org>
- * @author   Gonçalo Queirós <mail@goncaloqueiros.net>
- * @category Horde
- * @license  http://www.horde.org/licenses/gpl GPL
- * @package  Kronolith
- */
-class Kronolith_Ajax_Application_Handler extends Horde_Core_Ajax_Application_Handler
-{
-    protected $_external = array('embed');
-
-    /**
-     * Just polls for alarm messages and keeps session fresh for now.
-     */
-    public function poll()
-    {
-        return false;
-    }
-
-    /**
-     * Returns a list of all calendars.
-     */
-    public function listCalendars()
-    {
-        Kronolith::initialize();
-        $all_external_calendars = $GLOBALS['calendar_manager']->get(Kronolith::ALL_EXTERNAL_CALENDARS);
-        $result = new stdClass;
-        $auth_name = $GLOBALS['registry']->getAuth();
-
-        // Calendars. Do some twisting to sort own calendar before shared
-        // calendars.
-        foreach (array(true, false) as $my) {
-            foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_CALENDARS) as $id => $calendar) {
-                $owner = ($auth_name && ($calendar->owner() == $auth_name));
-                if (($my && $owner) || (!$my && !$owner)) {
-                    $result->calendars['internal'][$id] = $calendar->toHash();
-                }
-            }
-
-            // Tasklists
-            if (Kronolith::hasApiPermission('tasks')) {
-                foreach ($GLOBALS['registry']->tasks->listTasklists($my, Horde_Perms::SHOW, false) as $id => $tasklist) {
-                    if (isset($all_external_calendars['tasks/' . $id])) {
-                        $owner = ($auth_name &&
-                                  ($tasklist->get('owner') == $auth_name));
-                        if (($my && $owner) || (!$my && !$owner)) {
-                            $result->calendars['tasklists']['tasks/' . $id] =
-                                $all_external_calendars['tasks/' . $id]->toHash();
-                        }
-                    }
-                }
-            }
-        }
-
-        // Resources
-        if (!empty($GLOBALS['conf']['resource']['driver'])) {
-            foreach (Kronolith::getDriver('Resource')->listResources() as $resource) {
-                if ($resource->get('type') != Kronolith_Resource::TYPE_GROUP) {
-                    $rcal = new Kronolith_Calendar_Resource(array(
-                        'resource' => $resource
-                    ));
-                    $result->calendars['resource'][$resource->get('calendar')] = $rcal->toHash();
-                } else {
-                    $rcal = new Kronolith_Calendar_ResourceGroup(array(
-                        'resource' => $resource
-                    ));
-                    $result->calendars['resourcegroup'][$resource->getId()] = $rcal->toHash();
-                }
-            }
-        }
-
-        // Timeobjects
-        foreach ($all_external_calendars as $id => $calendar) {
-            if ($calendar->api() != 'tasks' && $calendar->display()) {
-                $result->calendars['external'][$id] = $calendar->toHash();
-            }
-        }
-
-        // Remote calendars
-        foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_REMOTE_CALENDARS) as $url => $calendar) {
-            $result->calendars['remote'][$url] = $calendar->toHash();
-        }
-
-        // Holidays
-        foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_HOLIDAYS) as $id => $calendar) {
-            $result->calendars['holiday'][$id] = $calendar->toHash();
-        }
-
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function listEvents()
-    {
-        global $session;
-
-        $start = new Horde_Date($this->vars->start);
-        $end   = new Horde_Date($this->vars->end);
-        $result = $this->_signedResponse($this->vars->cal);
-        if (!($kronolith_driver = $this->_getDriver($this->vars->cal))) {
-            return $result;
-        }
-        try {
-            $session->close();
-            $events = $kronolith_driver->listEvents($start, $end, array(
-                'show_recurrence' => true,
-                'json' => true)
-            );
-            $session->start();
-            if (count($events)) {
-                $result->events = $events;
-            }
-        } catch (Exception $e) {
-            $session->start();
-            $GLOBALS['notification']->push($e, 'horde.error');
-        }
-        return $result;
-    }
-
-    /**
-     * Returns a JSON object representing the requested event.
-     *
-     * Request variables used:
-     *  - cal:  The calendar id
-     *  - id:   The event id
-     *  - date: The date of the event we are requesting [OPTIONAL]
-     *  - rsd:  The event start date of the instance of a recurring event, if
-     *          requesting a specific instance.
-     *  - red:  The event end date of the instance of a recurring event, if
-     *          requesting a specific instance.
-     */
-    public function getEvent()
-    {
-        $result = new stdClass;
-
-        if (!($kronolith_driver = $this->_getDriver($this->vars->cal)) ||
-            !isset($this->vars->id)) {
-            return $result;
-        }
-
-        try {
-            $event = $kronolith_driver->getEvent($this->vars->id, $this->vars->date);
-            $event->setTimezone(true);
-            $result->event = $event->toJson(null, true, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A');
-            // If recurring, we need to format the dates of this instance, since
-            // Kronolith_Driver#getEvent will return the start/end dates of the
-            // original event in the series.
-            if ($event->recurs() && $this->vars->rsd) {
-                $rs = new Horde_Date($this->vars->rsd);
-                $result->event->rsd = $rs->strftime('%x');
-                $re = new Horde_Date($this->vars->red);
-                $result->event->red = $re->strftime('%x');
-            }
-        } catch (Horde_Exception_NotFound $e) {
-            $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error');
-        } catch (Exception $e) {
-            $GLOBALS['notification']->push($e, 'horde.error');
-        }
-
-        return $result;
-    }
-
-    /**
-     * Save a new or update an existing event from the AJAX event detail view.
-     *
-     * Request parameters used:
-     * - event:          The event id.
-     * - cal:            The calendar id.
-     * - targetcalendar: If moving events, the targetcalendar to move to.
-     * - as_new:         Save an existing event as a new event.
-     * - recur_edit:     If editing an instance of a recurring event series,
-     *                   how to apply the edit [current|future|all].
-     * - rstart:         If editing an instance of a recurring event series,
-     *                   the original start datetime of this instance.
-     * - rend:           If editing an instance of a recurring event series,
-     *                   the original ending datetime of this instance.
-     * - sendupdates:    Should updates be sent to attendees?
-     * - cstart:         Start time of the client cache.
-     * - cend:           End time of the client cache.
-     */
-    public function saveEvent()
-    {
-        global $injector, $notification, $registry;
-
-        $result = $this->_signedResponse($this->vars->targetcalendar);
-
-        if (!($kronolith_driver = $this->_getDriver($this->vars->targetcalendar))) {
-            return $result;
-        }
-
-        if ($this->vars->as_new) {
-            unset($this->vars->event);
-        }
-        if (!$this->vars->event) {
-            $perms = $injector->getInstance('Horde_Core_Perms');
-            if ($perms->hasAppPermission('max_events') !== true &&
-                $perms->hasAppPermission('max_events') <= Kronolith::countEvents()) {
-                Horde::permissionDeniedError(
-                    'kronolith',
-                    'max_events',
-                    sprintf(
-                        _("You are not allowed to create more than %d events."),
-                        $perms->hasAppPermission('max_events')
-                    )
-                );
-                return $result;
-            }
-        }
-
-        if ($this->vars->event &&
-            $this->vars->cal &&
-            $this->vars->cal != $this->vars->targetcalendar) {
-            if (strpos($kronolith_driver->calendar, '\\')) {
-                list($target, $user) = explode(
-                    '\\', $kronolith_driver->calendar, 2
-                );
-            } else {
-                $target = $kronolith_driver->calendar;
-                $user = $registry->getAuth();
-            }
-            $kronolith_driver = $this->_getDriver($this->vars->cal);
-            // Only delete the event from the source calendar if this user has
-            // permissions to do so.
-            try {
-                $sourceShare = Kronolith::getInternalCalendar(
-                    $kronolith_driver->calendar
-                );
-                $share = Kronolith::getInternalCalendar($target);
-                if ($sourceShare->hasPermission($registry->getAuth(), Horde_Perms::DELETE) &&
-                    (($user == $registry->getAuth() &&
-                      $share->hasPermission($registry->getAuth(), Horde_Perms::EDIT)) ||
-                     ($user != $registry->getAuth() &&
-                      $share->hasPermission($registry->getAuth(), Kronolith::PERMS_DELEGATE)))) {
-                    $kronolith_driver->move($this->vars->event, $target);
-                    $kronolith_driver = $this->_getDriver($this->vars->targetcalendar);
-                }
-            } catch (Exception $e) {
-                $notification->push(
-                    sprintf(
-                        _("There was an error moving the event: %s"),
-                        $e->getMessage()
-                    ),
-                    'horde.error'
-                );
-                return $result;
-            }
-        }
-
-        if ($this->vars->as_new) {
-            $event = $kronolith_driver->getEvent();
-        } else {
-            try {
-                $event = $kronolith_driver->getEvent($this->vars->event);
-            } catch (Horde_Exception_NotFound $e) {
-                $notification->push(
-                    _("The requested event was not found."),
-                    'horde.error'
-                );
-                return $result;
-            } catch (Exception $e) {
-                $notification->push($e);
-                return $result;
-            }
-        }
-
-        if (!$event->hasPermission(Horde_Perms::EDIT)) {
-            $notification->push(
-                _("You do not have permission to edit this event."),
-                'horde.warning'
-            );
-            return $result;
-        }
-
-        $removed_attendees = $old_attendees = array();
-        if ($this->vars->recur_edit && $this->vars->recur_edit != 'all') {
-            switch ($this->vars->recur_edit) {
-            case 'current':
-                $attributes = new stdClass();
-                $attributes->rstart = $this->vars->rstart;
-                $attributes->rend = $this->vars->rend;
-                $this->_addException($event, $attributes);
-
-                // Create a copy of the original event so we can read in the
-                // new form values for the exception. We also MUST reset the
-                // recurrence property even though we won't be using it, since
-                // clone() does not do a deep copy. Otherwise, the original
-                // event's recurrence will become corrupt.
-                $newEvent = clone($event);
-                $newEvent->recurrence = new Horde_Date_Recurrence($event->start);
-                $newEvent->readForm($event);
-
-                // Create an exception event from the new properties.
-                $exception = $this->_copyEvent($event, $newEvent, $attributes);
-                $exception->start = $newEvent->start;
-                $exception->end = $newEvent->end;
-
-                // Save the new exception.
-                $attributes->cstart = $this->vars->cstart;
-                $attributes->cend = $this->vars->cend;
-                $result = $this->_saveEvent(
-                    $exception,
-                    $event,
-                    $attributes);
-                break;
-            case 'future':
-                $instance = new Horde_Date($this->vars->rstart, $event->timezone);
-                $exception = clone($instance);
-                $exception->mday--;
-                if ($event->end->compareDate($exception) > 0) {
-                    // Same as 'all' since this is the first recurrence.
-                    $this->vars->recur_edit = 'all';
-                    return $this->saveEvent();
-                } else {
-                    $event->recurrence->setRecurEnd($exception);
-                    $newEvent = $kronolith_driver->getEvent();
-                    $newEvent->readForm();
-                    $newEvent->uid = null;
-                    $result = $this->_saveEvent(
-                        $newEvent, $event, $this->vars, true
-                    );
-                }
-
-            }
-        } else {
-            try {
-                $old_attendees = $event->attendees;
-                $event->readForm();
-                $removed_attendees = array_diff(
-                    array_keys($old_attendees),
-                    array_keys($event->attendees)
-                );
-                $result = $this->_saveEvent($event);
-            } catch (Exception $e) {
-                $notification->push($e);
-                return $result;
-            }
-        }
-
-        if (($result !== true) && $this->vars->sendupdates) {
-            $type = $event->status == Kronolith::STATUS_CANCELLED
-                ? Kronolith::ITIP_CANCEL
-                : Kronolith::ITIP_REQUEST;
-            Kronolith::sendITipNotifications($event, $notification, $type);
-        }
-
-        // Send a CANCEL iTip for attendees that have been removed, but only if
-        // the entire event isn't being marked as cancelled (which would be
-        // caught above).
-        if (!empty($removed_attendees)) {
-            $to_cancel = array();
-            foreach ($removed_attendees as $email) {
-                $to_cancel[$email] = $old_attendees[$email];
-            }
-            $cancelEvent = clone $event;
-            Kronolith::sendITipNotifications(
-                $cancelEvent, $notification, Kronolith::ITIP_CANCEL, null, null, $to_cancel
-            );
-        }
-        Kronolith::notifyOfResourceRejection($event);
-
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function quickSaveEvent()
-    {
-        $cal = explode('|', $this->vars->cal, 2);
-        try {
-            $event = Kronolith::quickAdd($this->vars->text, $cal[1]);
-            return $this->_saveEvent($event);
-        } catch (Horde_Exception $e) {
-            $GLOBALS['notification']->push($e);
-            $result = $this->_signedResponse($this->vars->cal);
-            $result->error = true;
-            return $result;
-        }
-    }
-
-    /**
-     * Update event details as a result of a drag/drop operation (which would
-     * only affect the event's start/end times).
-     *
-     * Uses the following request variables:
-     *<pre>
-     *   -cal:  The calendar id.
-     *   -id:   The event id.
-     *   -att:  Attribute hash of changed values. Can contain:
-     *      -start:     A new start datetime for the event.
-     *      -end:       A new end datetime for the event.
-     *      -offDays:   An offset of days to apply to the event.
-     *      -offMins:   An offset of minutes to apply to the event.
-     *      -rstart:    The orginal start datetime of a series instance.
-     *      -rend:      The original end datetime of a series instance.
-     *      -rday:      A new start value for a series instance (used when
-     *                  dragging on the month view where only the date can
-     *                  change, and not the start/end times).
-     *      -u:         Send update to attendees.
-     *</pre>
-     */
-    public function updateEvent()
-    {
-        $result = $this->_signedResponse($this->vars->cal);
-
-        if (!($kronolith_driver = $this->_getDriver($this->vars->cal)) ||
-            !isset($this->vars->id)) {
-            return $result;
-        }
-
-        try {
-            $oevent = $kronolith_driver->getEvent($this->vars->id);
-        } catch (Exception $e) {
-            $GLOBALS['notification']->push($e, 'horde.error');
-            return $result;
-        }
-        if (!$oevent) {
-            $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error');
-            return $result;
-        } elseif (!$oevent->hasPermission(Horde_Perms::EDIT)) {
-            $GLOBALS['notification']->push(_("You do not have permission to edit this event."), 'horde.warning');
-            return $result;
-        }
-
-        $attributes = Horde_Serialize::unserialize($this->vars->att, Horde_Serialize::JSON);
-
-        // If this is a recurring event, need to create an exception.
-        if ($oevent->recurs()) {
-            $this->_addException($oevent, $attributes);
-            $event = $this->_copyEvent($oevent, null, $attributes);
-        } else {
-            $event = clone($oevent);
-        }
-
-        foreach ($attributes as $attribute => $value) {
-            switch ($attribute) {
-            case 'start':
-                $newDate = new Horde_Date($value);
-                $newDate->setTimezone($event->start->timezone);
-                $event->start = clone($newDate);
-                break;
-
-            case 'end':
-                $newDate = new Horde_Date($value);
-                $newDate->setTimezone($event->end->timezone);
-                $event->end = clone($newDate);
-                if ($event->end->hour == 23 &&
-                    $event->end->min == 59 &&
-                    $event->end->sec == 59) {
-                    $event->end->mday++;
-                    $event->end->hour = $event->end->min = $event->end->sec = 0;
-                }
-                break;
-
-            case 'offDays':
-                $event->start->mday += $value;
-                $event->end->mday += $value;
-                break;
-
-            case 'offMins':
-                $event->start->min += $value;
-                $event->end->min += $value;
-                break;
-            }
-        }
-
-        $result = $this->_saveEvent($event, ($oevent->recurs() ? $oevent : null), $attributes);
-        if ($this->vars->u) {
-            Kronolith::sendITipNotifications($event, $GLOBALS['notification'], Kronolith::ITIP_REQUEST);
-        }
-
-        return $result;
-    }
-
-    /**
-     * Deletes an event, or an instance of an event series from the backend.
-     *
-     * Uses the following request variables:
-     *<pre>
-     *   - cal:          The calendar id.
-     *   - id:           The event id.
-     *   - r:            If this is an event series, what type of deletion to
-     *                   perform [future | current | all].
-     *   - rstart:       The start time of the event instance being removed, if
-     *                   this is a series instance.
-     *   - cstart:       The start date of the client event cache.
-     *   - cend:         The end date of the client event cache.
-     *   - sendupdates:  Send cancellation notice to attendees?
-     * </pre>
-     */
-    public function deleteEvent()
-    {
-        $result = new stdClass;
-        $instance = null;
-
-        if (!($kronolith_driver = $this->_getDriver($this->vars->cal)) ||
-            !isset($this->vars->id)) {
-            return $result;
-        }
-
-        try {
-            $event = $kronolith_driver->getEvent($this->vars->id);
-            if (!$event->hasPermission(Horde_Perms::DELETE)) {
-                $GLOBALS['notification']->push(_("You do not have permission to delete this event."), 'horde.warning');
-                return $result;
-            }
-            $range = null;
-            if ($event->recurs() && $this->vars->r != 'all') {
-                switch ($this->vars->r) {
-                case 'future':
-                    // Deleting all future instances.
-                    // @TODO: Check if we need to find future exceptions
-                    //        that are after $recurEnd and remove those as well.
-                    $instance = new Horde_Date($this->vars->rstart, $event->timezone);
-                    $recurEnd = clone($instance);
-                    $recurEnd->hour = 0;
-                    $recurEnd->min = 0;
-                    $recurEnd->sec = 0;
-                    $recurEnd->mday--;
-                    if ($event->end->compareDate($recurEnd) > 0) {
-                        $kronolith_driver->deleteEvent($event->id);
-                        $result = $this->_signedResponse($this->vars->cal);
-                        $result->events = array();
-                    } else {
-                        $event->recurrence->setRecurEnd($recurEnd);
-                        $result = $this->_saveEvent($event, $event, $this->vars);
-                    }
-                    $range = Kronolith::RANGE_THISANDFUTURE;
-                    break;
-                case 'current':
-                    // Deleting only the current instance.
-                    $instance = new Horde_Date($this->vars->rstart, $event->timezone);
-                    $event->recurrence->addException(
-                        $instance->year, $instance->month, $instance->mday);
-                    $result = $this->_saveEvent($event, $event, $this->vars);
-                }
-            } else {
-                // Deleting an entire series, or this is a single event only.
-                $kronolith_driver->deleteEvent($event->id);
-                $result = $this->_signedResponse($this->vars->cal);
-                $result->events = array();
-                $result->uid = $event->uid;
-            }
-
-            if ($this->vars->sendupdates) {
-                Kronolith::sendITipNotifications(
-                    $event, $GLOBALS['notification'], Kronolith::ITIP_CANCEL, $instance, $range);
-            }
-            $result->deleted = true;
-        } catch (Horde_Exception_NotFound $e) {
-            $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error');
-        } catch (Exception $e) {
-            $GLOBALS['notification']->push($e, 'horde.error');
-        }
-
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function searchEvents()
-    {
-        $query = Horde_Serialize::unserialize($this->vars->query, Horde_Serialize::JSON);
-        if (!isset($query->start)) {
-            $query->start = new Horde_Date($_SERVER['REQUEST_TIME']);
-        }
-        if (!isset($query->end)) {
-            $query->end = null;
-        }
-        switch ($this->vars->time) {
-        case 'all':
-            $query->start = null;
-            $query->end = null;
-            break;
-        case 'future':
-            $query->start = new Horde_Date($_SERVER['REQUEST_TIME']);
-            $query->end = null;
-            break;
-        case 'past':
-            $query->start = null;
-            $query->end = new Horde_Date($_SERVER['REQUEST_TIME']);
-            break;
-        }
-
-        $tagger = new Kronolith_Tagger();
-        $cals = Horde_Serialize::unserialize($this->vars->cals, Horde_Serialize::JSON);
-        $events = array();
-        foreach ($cals as $cal) {
-            if (!($kronolith_driver = $this->_getDriver($cal))) {
-                continue;
-            }
-            try {
-                $result = $kronolith_driver->search($query, true);
-                if ($result) {
-                    $events[$cal] = $result;
-                }
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push($e, 'horde.error');
-            }
-            $split = explode('|', $cal);
-            if ($split[0] == 'internal') {
-                $result = $tagger->search($query->title, array('type' => 'event', 'calendar' => $split[1]));
-                foreach ($result['events'] as $uid) {
-                    Kronolith::addSearchEvents($events[$cal], $kronolith_driver->getByUID($uid), $query, true);
-                }
-            }
-        }
-
-        $result = new stdClass;
-        $result->view = 'search';
-        $result->query = $this->vars->query;
-        if ($events) {
-            $result->events = $events;
-        }
-
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function listTasks()
-    {
-        if (!$GLOBALS['registry']->hasMethod('tasks/listTasks')) {
-            return false;
-        }
-
-        $result = new stdClass;
-        $result->list = $this->vars->list;
-        $result->type = $this->vars->type;
-        try {
-            $tasks = $GLOBALS['registry']->tasks
-                ->listTasks(array(
-                    'tasklists' => $this->vars->list,
-                    'completed' => $this->vars->type == 'incomplete' ? 'future_incomplete' : $this->vars->type,
-                    'include_tags' => true,
-                    'external' => false,
-                    'json' => true
-                ));
-            if (count($tasks)) {
-                $result->tasks = $tasks;
-            }
-        } catch (Exception $e) {
-            $GLOBALS['notification']->push($e, 'horde.error');
-        }
-
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function getTask()
-    {
-        if (!$GLOBALS['registry']->hasMethod('tasks/getTask') ||
-            !isset($this->vars->id) ||
-            !isset($this->vars->list)) {
-            return false;
-        }
-
-        $result = new stdClass;
-        try {
-            $task = $GLOBALS['registry']->tasks->getTask($this->vars->list, $this->vars->id);
-            if ($task) {
-                $result->task = $task->toJson(true, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A');
-            } else {
-                $GLOBALS['notification']->push(_("The requested task was not found."), 'horde.error');
-            }
-        } catch (Exception $e) {
-            $GLOBALS['notification']->push($e, 'horde.error');
-        }
-
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function saveTask()
-    {
-        if (!$GLOBALS['registry']->hasMethod('tasks/updateTask') ||
-            !$GLOBALS['registry']->hasMethod('tasks/addTask')) {
-            return false;
-        }
-
-        $id = $this->vars->task_id;
-        $list = $this->vars->old_tasklist;
-        $task = $this->vars->task;
-        $result = $this->_signedResponse('tasklists|tasks/' . $task['tasklist']);
-
-        $due = trim($task['due_date'] . ' ' . $task['due_time']);
-        if (!empty($due)) {
-            try {
-                $due = Kronolith::parseDate($due);
-                $task['due'] = $due->timestamp();
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push($e, 'horde.error');
-                return $result;
-            }
-        }
-
-        if ($task['alarm']['on']) {
-            $value = $task['alarm']['value'];
-            $unit = $task['alarm']['unit'];
-            if ($value == 0) {
-                $value = $unit = 1;
-            }
-            $task['alarm'] = $value * $unit;
-            if (isset($task['alarm_methods']) && isset($task['methods'])) {
-                foreach (array_keys($task['methods']) as $method) {
-                    if (!in_array($method, $task['alarm_methods'])) {
-                        unset($task['methods'][$method]);
-                    }
-                }
-                foreach ($task['alarm_methods'] as $method) {
-                    if (!isset($task['methods'][$method])) {
-                        $task['methods'][$method] = array();
-                    }
-                }
-            } else {
-                $task['methods'] = array();
-            }
-        } else {
-            $task['alarm'] = 0;
-            $task['methods'] = array();
-        }
-        unset($task['alarm_methods']);
-
-        if (!isset($task['completed'])) {
-            $task['completed'] = false;
-        }
-
-        if ($this->vars->recur && !empty($due)) {
-            $task['recurrence'] = Kronolith_Event::readRecurrenceForm($due, 'UTC');
-        }
-
-        $task['tags'] = Horde_Util::getFormData('tags');
-
-        try {
-            $ids = ($id && $list)
-                ? $GLOBALS['registry']->tasks->updateTask($list, $id, $task)
-                : $GLOBALS['registry']->tasks->addTask($task);
-            if (!$id) {
-                $id = $ids[0];
-            }
-            $task = $GLOBALS['registry']->tasks->getTask($task['tasklist'], $id);
-            $result->tasks = array($id => $task->toJson(false, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A'));
-            $result->type = $task->completed ? 'complete' : 'incomplete';
-            $result->list = $task->tasklist;
-        } catch (Exception $e) {
-            $GLOBALS['notification']->push($e, 'horde.error');
-            return $result;
-        }
-
-        if ($due &&
-            $kronolith_driver = $this->_getDriver('tasklists|tasks/' . $task->tasklist)) {
-            try {
-                $event = $kronolith_driver->getEvent('_tasks' . $id);
-                $end = clone $due;
-                $end->hour = 23;
-                $end->min = $end->sec = 59;
-                $start = clone $due;
-                $start->hour = $start->min = $start->sec = 0;
-                $events = array();
-                Kronolith::addEvents($events, $event, $start, $end, true, true);
-                if (count($events)) {
-                    $result->events = $events;
-                }
-            } catch (Horde_Exception_NotFound $e) {
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push($e, 'horde.error');
-            }
-        }
-
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function quickSaveTask()
-    {
-        if (!$GLOBALS['registry']->hasMethod('tasks/quickAdd')) {
-            return false;
-        }
-
-        $result = $this->_signedResponse(
-            'tasklists|tasks/' . $this->vars->tasklist);
-
-        try {
-            $ids = $GLOBALS['registry']->tasks->quickAdd($this->vars->text);
-            $result->type = 'incomplete';
-            $result->list = $this->vars->tasklist;
-            $result->tasks = array();
-            foreach ($ids as $uid) {
-                $task = $GLOBALS['registry']->tasks->export($uid, 'raw');
-                $result->tasks[$task->id] = $task->toJson(
-                    false,
-                    $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A'
-                );
-            }
-        } catch (Exception $e) {
-            $GLOBALS['notification']->push($e, 'horde.error');
-        }
-
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function deleteTask()
-    {
-        $result = new stdClass;
-
-        if (!$GLOBALS['registry']->hasMethod('tasks/deleteTask') ||
-            !isset($this->vars->id) ||
-            !isset($this->vars->list)) {
-            return $result;
-        }
-
-        try {
-            $GLOBALS['registry']->tasks->deleteTask($this->vars->list, $this->vars->id);
-            $result->deleted = true;
-        } catch (Exception $e) {
-            $GLOBALS['notification']->push($e, 'horde.error');
-        }
-
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function toggleCompletion()
-    {
-        $result = new stdClass;
-
-        if (!$GLOBALS['registry']->hasMethod('tasks/toggleCompletion')) {
-            return $result;
-        }
-
-        try {
-            $result->toggled = $GLOBALS['registry']->tasks->toggleCompletion($this->vars->id, $this->vars->list);
-        } catch (Exception $e) {
-            $GLOBALS['notification']->push($e, 'horde.error');
-        }
-
-        return $result;
-    }
-
-    /**
-     * Generate a list of most frequently used tags for the current user.
-     */
-    public function listTopTags()
-    {
-        $tagger = new Kronolith_Tagger();
-        $result = new stdClass;
-        $result->tags = array();
-        $tags = $tagger->getCloud($GLOBALS['registry']->getAuth(), 10, true);
-        foreach ($tags as $tag) {
-            $result->tags[] = $tag['tag_name'];
-        }
-        return $result;
-    }
-
-    /**
-     * Return fb information for the requested attendee or resource.
-     *
-     * Uses the following request parameters:
-     *<pre>
-     *  -email:    The attendee's email address.
-     *  -resource: The resource id.
-     *</pre>
-     */
-    public function getFreeBusy()
-    {
-        $result = new stdClass;
-        if ($this->vars->email) {
-            try {
-                $result->fb = Kronolith_FreeBusy::get($this->vars->email, true);
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push($e->getMessage(), 'horde.warning');
-            }
-        } elseif ($this->vars->resource) {
-            try {
-                $resource = Kronolith::getDriver('Resource')
-                    ->getResource($this->vars->resource);
-                try {
-                    $result->fb = $resource->getFreeBusy(null, null, true, true);
-                } catch (Horde_Exception $e) {
-                    // Resource groups can't provide FB information.
-                    $result->fb = null;
-                }
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push($e->getMessage(), 'horde.warning');
-            }
-        }
-
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function searchCalendars()
-    {
-        $result = new stdClass;
-        $result->events = 'Searched for calendars: ' . $this->vars->title;
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function saveCalendar()
-    {
-        $calendar_id = $this->vars->calendar;
-        $result = new stdClass;
-
-        switch ($this->vars->type) {
-        case 'internal':
-            $info = array();
-            foreach (array('name', 'color', 'description', 'tags') as $key) {
-                $info[$key] = $this->vars->$key;
-            }
-
-            // Create a calendar.
-            if (!$calendar_id) {
-                if (!$GLOBALS['registry']->getAuth() ||
-                    $GLOBALS['prefs']->isLocked('default_share')) {
-                    return $result;
-                }
-                try {
-                    $calendar = Kronolith::addShare($info);
-                    Kronolith::readPermsForm($calendar);
-                    if ($calendar->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW)) {
-                        $wrapper = new Kronolith_Calendar_Internal(array('share' => $calendar));
-                        $result->saved = true;
-                        $result->id = $calendar->getName();
-                        $result->calendar = $wrapper->toHash();
-                    }
-                } catch (Exception $e) {
-                    $GLOBALS['notification']->push($e, 'horde.error');
-                    return $result;
-                }
-                $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been created."), $info['name']), 'horde.success');
-                break;
-            }
-
-            // Update a calendar.
-            try {
-                $calendar = $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar_id);
-                $original_name = $calendar->get('name');
-                $original_owner = $calendar->get('owner');
-                Kronolith::updateShare($calendar, $info);
-                Kronolith::readPermsForm($calendar);
-                if ($calendar->get('owner') != $original_owner) {
-                    $result->deleted = true;
-                }
-                if ($calendar->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW)) {
-                    $wrapper = new Kronolith_Calendar_Internal(array('share' => $calendar));
-                    $result->saved = true;
-                    $result->calendar = $wrapper->toHash();
-                }
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push($e, 'horde.error');
-                return $result;
-
-            }
-            if ($calendar->get('name') != $original_name) {
-                $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been renamed to \"%s\"."), $original_name, $calendar->get('name')), 'horde.success');
-            } else {
-                $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been saved."), $original_name), 'horde.success');
-            }
-            break;
-
-        case 'tasklists':
-            $calendar = array();
-            foreach (array('name', 'color', 'description') as $key) {
-                $calendar[$key] = $this->vars->$key;
-            }
-
-            // Create a task list.
-            if (!$calendar_id) {
-                if (!$GLOBALS['registry']->getAuth() ||
-                    $GLOBALS['prefs']->isLocked('default_share')) {
-                    return $result;
-                }
-                try {
-                    $tasklistId = $GLOBALS['registry']->tasks->addTasklist($calendar['name'], $calendar['description'], $calendar['color']);
-                    $tasklists = $GLOBALS['registry']->tasks->listTasklists(true);
-                    if (!isset($tasklists[$tasklistId])) {
-                        $GLOBALS['notification']->push(_("Added task list not found."), 'horde.error');
-                        return $result;
-                    }
-                    $tasklist = $tasklists[$tasklistId];
-                    Kronolith::readPermsForm($tasklist);
-                    if ($tasklist->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW)) {
-                        $wrapper = new Kronolith_Calendar_External_Tasks(array('api' => 'tasks', 'name' => $tasklistId, 'share' => $tasklist));
-
-                        // Update external calendars caches.
-                        $all_external = $GLOBALS['session']->get('kronolith', 'all_external_calendars');
-                        $all_external[] = array('a' => 'tasks', 'n' => $tasklistId, 'd' => $tasklist->get('name'));
-                        $GLOBALS['session']->set('kronolith', 'all_external_calendars', $all_external);
-                        $display_external = $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_EXTERNAL_CALENDARS);
-                        $display_external[] = 'tasks/' . $tasklistId;
-                        $GLOBALS['calendar_manager']->set(Kronolith::DISPLAY_EXTERNAL_CALENDARS, $display_external);
-                        $GLOBALS['prefs']->setValue('display_external_cals', serialize($display_external));
-                        $all_external = $GLOBALS['calendar_manager']->get(Kronolith::ALL_EXTERNAL_CALENDARS);
-                        $all_external['tasks/' . $tasklistId] = $wrapper;
-                        $GLOBALS['calendar_manager']->set(Kronolith::ALL_EXTERNAL_CALENDARS, $all_external);
-
-                        $result->saved = true;
-                        $result->id = 'tasks/' . $tasklistId;
-                        $result->calendar = $wrapper->toHash();
-                    }
-                } catch (Exception $e) {
-                    $GLOBALS['notification']->push($e, 'horde.error');
-                    return $result;
-                }
-                $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been created."), $calendar['name']), 'horde.success');
-                break;
-            }
-
-            // Update a task list.
-            $calendar_id = substr($calendar_id, 6);
-            try {
-                $GLOBALS['registry']->tasks->updateTasklist($calendar_id, $calendar);
-                $tasklists = $GLOBALS['registry']->tasks->listTasklists(true, Horde_Perms::EDIT);
-                $tasklist = $tasklists[$calendar_id];
-                $original_owner = $tasklist->get('owner');
-                Kronolith::readPermsForm($tasklist);
-                if ($tasklist->get('owner') != $original_owner) {
-                    $result->deleted = true;
-                }
-                if ($tasklist->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW)) {
-                    $wrapper = new Kronolith_Calendar_External_Tasks(array('api' => 'tasks', 'name' => $calendar_id, 'share' => $tasklist));
-                    $result->saved = true;
-                    $result->calendar = $wrapper->toHash();
-                }
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push($e, 'horde.error');
-                return $result;
-            }
-            if ($tasklist->get('name') != $calendar['name']) {
-                $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been renamed to \"%s\"."), $tasklist->get('name'), $calendar['name']), 'horde.success');
-            } else {
-                $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been saved."), $tasklist->get('name')), 'horde.success');
-            }
-            break;
-
-        case 'remote':
-            $calendar = array();
-            foreach (array('name', 'desc', 'url', 'color', 'user', 'password') as $key) {
-                $calendar[$key] = $this->vars->$key;
-            }
-            try {
-                Kronolith::subscribeRemoteCalendar($calendar, $calendar_id);
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push($e, 'horde.error');
-                return $result;
-            }
-            if ($calendar_id) {
-                $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been saved."), $calendar['name']), 'horde.success');
-            } else {
-                $GLOBALS['notification']->push(sprintf(_("You have been subscribed to \"%s\" (%s)."), $calendar['name'], $calendar['url']), 'horde.success');
-                $result->id = $calendar['url'];
-            }
-            $wrapper = new Kronolith_Calendar_Remote($calendar);
-            $result->saved = true;
-            $result->calendar = $wrapper->toHash();
-            break;
-
-        case 'resource':
-            foreach (array('name', 'description', 'response_type') as $key) {
-                $info[$key] = $this->vars->$key;
-            }
-
-            if (!$calendar_id) {
-                // New resource
-                // @TODO: Groups.
-                if (!$GLOBALS['registry']->isAdmin() &&
-                    !$GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission('resource_management')) {
-                    $GLOBALS['notification']->push(_("You are not allowed to create new resources."), 'horde.error');
-                    return $result;
-                }
-                $resource = Kronolith_Resource::addResource(new Kronolith_Resource_Single($info));
-            } else {
-                try {
-                    $rdriver = Kronolith::getDriver('Resource');
-                    $resource = $rdriver->getResource($rdriver->getResourceIdByCalendar($calendar_id));
-                    if (!($resource->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::EDIT))) {
-                        $GLOBALS['notification']->push(_("You are not allowed to edit this resource."), 'horde.error');
-                        return $result;
-                    }
-                    foreach (array('name', 'description', 'response_type', 'email') as $key) {
-                        $resource->set($key, $this->vars->$key);
-                    }
-                    $resource->save();
-                } catch (Kronolith_Exception $e) {
-                    $GLOBALS['notification']->push($e->getMessage(), 'horde.error');
-                    return $result;
-                }
-            }
-            $wrapper = new Kronolith_Calendar_Resource(array('resource' => $resource));
-            $result->calendar = $wrapper->toHash();
-            $result->saved = true;
-            $result->id = $resource->get('calendar');
-            $GLOBALS['notification']->push(sprintf(_("The resource \"%s\" has been saved."), $resource->get('name'), 'horde.success'));
-            break;
-
-        case 'resourcegroup':
-            if (empty($calendar_id)) {
-                // New resource group.
-                $resource = Kronolith_Resource::addResource(
-                    new Kronolith_Resource_Group(array(
-                        'name' => $this->vars->name,
-                        'description' => $this->vars->description,
-                        'members' => $this->vars->members)
-                    )
-                );
-            } else {
-                $driver = Kronolith::getDriver('Resource');
-                $resource = $driver->getResource($calendar_id);
-                $resource->set('name', $this->vars->name);
-                $resource->set('description', $this->vars->description);
-                $resource->set('members', $this->vars->members);
-                $resource->save();
-            }
-
-            $wrapper = new Kronolith_Calendar_ResourceGroup(array('resource' => $resource));
-            $result->calendar = $wrapper->toHash();
-            $result->saved = true;
-            $result->id = $resource->get('calendar');
-            $GLOBALS['notification']->push(sprintf(_("The resource group \"%s\" has been saved."), $resource->get('name'), 'horde.success'));
-            break;
-        }
-
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function deleteCalendar()
-    {
-        $calendar_id = $this->vars->calendar;
-        $result = new stdClass;
-
-        switch ($this->vars->type) {
-        case 'internal':
-            try {
-                $calendar = $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar_id);
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push($e, 'horde.error');
-                return $result;
-            }
-            try {
-                Kronolith::deleteShare($calendar);
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push(sprintf(_("Unable to delete \"%s\": %s"), $calendar->get('name'), $e->getMessage()), 'horde.error');
-                return $result;
-            }
-            $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been deleted."), $calendar->get('name')), 'horde.success');
-            break;
-
-        case 'tasklists':
-            $calendar_id = substr($calendar_id, 6);
-            $tasklists = $GLOBALS['registry']->tasks->listTasklists(true);
-            if (!isset($tasklists[$calendar_id])) {
-                $GLOBALS['notification']->push(_("You are not allowed to delete this task list."), 'horde.error');
-                return $result;
-            }
-            try {
-                $GLOBALS['registry']->tasks->deleteTasklist($calendar_id);
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push(sprintf(_("Unable to delete \"%s\": %s"), $tasklists[$calendar_id]->get('name'), $e->getMessage()), 'horde.error');
-                return $result;
-            }
-            $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been deleted."), $tasklists[$calendar_id]->get('name')), 'horde.success');
-            break;
-
-        case 'remote':
-            try {
-                $deleted = Kronolith::unsubscribeRemoteCalendar($calendar_id);
-            } catch (Exception $e) {
-                $GLOBALS['notification']->push($e, 'horde.error');
-                return $result;
-            }
-            $GLOBALS['notification']->push(sprintf(_("You have been unsubscribed from \"%s\" (%s)."), $deleted['name'], $deleted['url']), 'horde.success');
-            break;
-
-        case 'resource':
-            try {
-                $rdriver = Kronolith::getDriver('Resource');
-                $resource = $rdriver->getResource($rdriver->getResourceIdByCalendar($calendar_id));
-                if (!($resource->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::DELETE))) {
-                    $GLOBALS['notification']->push(_("You are not allowed to delete this resource."), 'horde.error');
-                    return $result;
-                }
-                $name = $resource->get('name');
-                $rdriver->delete($resource);
-            } catch (Kronolith_Exception $e) {
-                $GLOBALS['notification']->push($e->getMessage(), 'horde.error');
-                return $result;
-            }
-
-            $GLOBALS['notification']->push(sprintf(_("The resource \"%s\" has been deleted."), $name), 'horde.success');
-            break;
-
-        case 'resourcegroup':
-            try {
-                $rdriver = Kronolith::getDriver('Resource');
-                $resource = $rdriver->getResource($calendar_id);
-                if (!($resource->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::DELETE))) {
-                    $GLOBALS['notification']->push(_("You are not allowed to delete this resource."), 'horde.error');
-                    return $result;
-                }
-                $name = $resource->get('name');
-                $rdriver->delete($resource);
-            } catch (Kronolith_Exception $e) {
-                $GLOBALS['notification']->push($e->getMessage(), 'horde.error');
-                return $result;
-            }
-            $GLOBALS['notification']->push(sprintf(_("The resource \"%s\" has been deleted."), $name), 'horde.success');
-
-        }
-        $result->deleted = true;
-
-        return $result;
-    }
-
-    /**
-     * Returns the information for a shared internal calendar.
-     */
-    public function getCalendar()
-    {
-        $result = new stdClass;
-        $all_calendars = $GLOBALS['calendar_manager']->get(Kronolith::ALL_CALENDARS);
-        if (!isset($all_calendars[$this->vars->cal]) && !$GLOBALS['conf']['share']['hidden']) {
-                $GLOBALS['notification']->push(_("You are not allowed to view this calendar."), 'horde.error');
-                return $result;
-        } elseif (!isset($all_calendars[$this->vars->cal])) {
-            // Subscribing to a "hidden" share, check perms.
-            $kronolith_shares = $GLOBALS['injector']->getInstance('Kronolith_Shares');
-            $share = $kronolith_shares->getShare($this->vars->cal);
-            if (!$share->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::READ)) {
-                $GLOBALS['notification']->push(_("You are not allowed to view this calendar."), 'horde.error');
-                return $result;
-            }
-            $calendar = new Kronolith_Calendar_Internal(array('share' => $share));
-        } else {
-            $calendar = $all_calendars[$this->vars->cal];
-        }
-
-        $result->calendar = $calendar->toHash();
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function getRemoteInfo()
-    {
-        $params = array('timeout' => 15);
-        if ($user = $this->vars->user) {
-            $params['user'] = $user;
-            $params['password'] = $this->vars->password;
-        }
-        if (!empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) {
-            $params['proxy'] = $GLOBALS['conf']['http']['proxy'];
-        }
-
-        $result = new stdClass;
-        try {
-            $driver = $GLOBALS['injector']->getInstance('Kronolith_Factory_Driver')->create('Ical', $params);
-            $driver->open($this->vars->url);
-            if ($driver->isCalDAV()) {
-                $result->success = true;
-                // TODO: find out how to retrieve calendar information via CalDAV.
-            } else {
-                $ical = $driver->getRemoteCalendar(false);
-                $result->success = true;
-                try {
-                    $name = $ical->getAttribute('X-WR-CALNAME');
-                    $result->name = $name;
-                } catch (Horde_Icalendar_Exception $e) {}
-                try {
-                    $desc = $ical->getAttribute('X-WR-CALDESC');
-                    $result->desc = $desc;
-                } catch (Horde_Icalendar_Exception $e) {}
-            }
-        } catch (Exception $e) {
-            if ($e->getCode() == 401) {
-                $result->auth = true;
-            } else {
-                $GLOBALS['notification']->push($e, 'horde.error');
-            }
-        }
-
-        return $result;
-    }
-
-    /**
-     * TODO
-     */
-    public function saveCalPref()
-    {
-        return false;
-    }
-
-    /**
-     * Return a list of available resources.
-     *
-     * @return array  A hash of resource_id => resource sorted by resource name.
-     */
-    public function getResourceList()
-    {
-        $data = array();
-        $resources = Kronolith::getDriver('Resource')
-            ->listResources(Horde_Perms::READ, array(), 'name');
-        foreach ($resources as $resource) {
-            $data[] = $resource->toJson();
-        }
-
-        return $data;
-    }
-
-    /**
-     * Handle output of the embedded widget: allows embedding calendar widgets
-     * in external websites.
-     *
-     * The following arguments are required:
-     *   - calendar: The share_name for the requested calendar.
-     *   - container: The DOM node to populate with the widget.
-     *   - view: The view (block) we want.
-     *
-     * The following are optional (and are not used for all views)
-     *   - css
-     *   - days
-     *   - maxevents: The maximum number of events to show.
-     *   - months: The number of months to include.
-     */
-    public function embed()
-    {
-        global $page_output, $registry;
-
-        /* First, determine the type of view we are asking for */
-        $view = $this->vars->view;
-
-        /* The DOM container to put the HTML in on the remote site */
-        $container = $this->vars->container;
-
-        /* The share_name of the calendar to display */
-        $calendar = $this->vars->calendar;
-
-        /* Deault to showing only 1 month when we have a choice */
-        $count_month = $this->vars->get('months', 1);
-
-        /* Default to no limit for the number of events */
-        $max_events = $this->vars->get('maxevents', 0);
-
-        /* Default to one week */
-        $count_days = $this->vars->get('days', 7);
-
-        if ($this->vars->css == 'none') {
-            $nocss = true;
-        }
-
-        /* Build the block parameters */
-        $params = array(
-            'calendar' => $calendar,
-            'maxevents' => $max_events,
-            'months' => $count_month,
-            'days' => $count_days
-        );
-
-        /* Call the Horde_Block api to get the calendar HTML */
-        $title = $registry->call('horde/blockTitle', array('kronolith', $view, $params));
-        $results = $registry->call('horde/blockContent', array('kronolith', $view, $params));
-
-        /* Some needed paths */
-        $js_path = $registry->get('jsuri', 'kronolith');
-
-        /* Local js */
-        $jsurl = Horde::url($js_path . '/embed.js', true);
-
-        /* Horde's js */
-        $hjs_path = $registry->get('jsuri', 'horde');
-        $hjsurl = Horde::url($hjs_path . '/tooltips.js', true);
-        $pturl = Horde::url($hjs_path . '/prototype.js', true);
-
-        /* CSS */
-        if (empty($nocss)) {
-            $page_output->addThemeStylesheet('embed.css');
-
-            Horde::startBuffer();
-            $page_output->includeStylesheetFiles(array('nobase' => true), true);
-            $css = Horde::endBuffer();
-        } else {
-            $css = '';
-        }
-
-        /* Escape the text and put together the javascript to send back */
-        $container = Horde_Serialize::serialize($container, Horde_Serialize::JSON);
-        $results = Horde_Serialize::serialize('<div class="kronolith_embedded"><div class="title">' . $title . '</div>' . $results . '</div>', Horde_Serialize::JSON);
-
-        $js = <<<EOT
-if (typeof kronolith == 'undefined') {
-    if (typeof Prototype == 'undefined') {
-        document.write('<script type="text/javascript" src="$pturl"></script>');
-    }
-    if (typeof Horde_ToolTips == 'undefined') {
-        Horde_ToolTips_Autoload = false;
-        document.write('<script type="text/javascript" src="$hjsurl"></script>');
-    }
-    kronolith = new Object();
-    kronolithNodes = new Array();
-    document.write('<script type="text/javascript" src="$jsurl"></script>');
-    document.write('$css');
-}
-kronolithNodes[kronolithNodes.length] = $container;
-kronolith[$container] = $results;
-EOT;
-
-        return new Horde_Core_Ajax_Response_Raw($js, 'text/javascript');
-    }
-
-    public function toTimeslice()
-    {
-        $driver = $this->_getDriver($this->vars->cal);
-        $event = $driver->getEvent($this->vars->e);
-
-        try {
-            Kronolith::toTimeslice($event, $this->vars->t, $this->vars->c);
-        } catch (Kronolith_Exception $e) {
-            $GLOBALS['notification']->push(sprintf(_("Error saving timeslice: %s"), $e->getMessage()), 'horde.error');
-            return false;
-        }
-        $GLOBALS['notification']->push(_("Successfully saved timeslice."), 'horde.success');
-
-        return true;
-    }
-
-    /**
-     * Check reply status of any resources and report back. Used as a check
-     * before saving an event to give the user feedback.
-     *
-     * The following arguments are expected:
-     *   - r:  A comma separated string of resource identifiers.
-     *   - s:  The event start time to check.
-     *   - e:  The event end time to check.
-     *   - u:  The event uid, if not a new event.
-     *   - c:  The event's calendar.
-     */
-    public function checkResources()
-    {
-        if (empty($GLOBALS['conf']['resource']['driver'])) {
-            return array();
-        }
-
-        if ($this->vars->i) {
-            $event = $this->_getDriver($this->vars->c)->getEvent($this->vars->i);
-        } else {
-            $event = Kronolith::getDriver()->getEvent();
-        }
-        // Overrite start/end times since we may be checking before we edit
-        // an existing event with new times.
-        $event->start = new Horde_Date($this->vars->s);
-        $event->end = new Horde_Date($this->vars->e);
-        $event->start->setTimezone(date_default_timezone_get());
-        $event->end->setTimezone(date_default_timezone_get());
-        $results = array();
-        foreach (explode(',', $this->vars->r) as $id) {
-            $resource = Kronolith::getDriver('Resource')->getResource($id);
-            $results[$id] = $resource->getResponse($event);
-        }
-
-        return $results;
-    }
-
-    /**
-     * Returns the driver object for a calendar.
-     *
-     * @param string $cal  A calendar string in the format "type|name".
-     *
-     * @return Kronolith_Driver|boolean  A driver instance or false on failure.
-     */
-    protected function _getDriver($cal)
-    {
-        list($driver, $calendar) = explode('|', $cal);
-        if ($driver == 'internal' &&
-            !Kronolith::hasPermission($calendar, Horde_Perms::SHOW)) {
-            $GLOBALS['notification']->push(_("Permission Denied"), 'horde.error');
-            return false;
-        }
-        try {
-            $kronolith_driver = Kronolith::getDriver($driver, $calendar);
-        } catch (Exception $e) {
-            $GLOBALS['notification']->push($e, 'horde.error');
-            return false;
-        }
-        if ($driver == 'remote') {
-            $kronolith_driver->setParam('timeout', 15);
-        }
-        return $kronolith_driver;
-    }
-
-    /**
-     * Saves an event and returns a signed result object including the saved
-     * event.
-     *
-     * @param Kronolith_Event $event     An event object.
-     * @param Kronolith_Event $original  If $event is an exception, this should
-     *                                   be set to the original event.
-     * @param object $attributes         The attributes sent by the client.
-     *                                   Expected to contain cstart and cend.
-     * @param boolean $saveOriginal      Commit any changes in $original to
-     *                                   storage also.
-     *
-     * @return object  The result object.
-     */
-    protected function _saveEvent(Kronolith_Event $event,
-                                  Kronolith_Event $original = null,
-                                  $attributes = null,
-                                  $saveOriginal = false)
-    {
-        if ($this->vars->targetcalendar) {
-            $cal = $this->vars->targetcalendar;
-        } elseif ($this->vars->cal) {
-            $cal = $this->vars->cal;
-        } else {
-            $cal = $event->calendarType . '|' . $event->calendar;
-        }
-        $result = $this->_signedResponse($cal);
-        $events = array();
-        try {
-            $event->save();
-            if (!$this->vars->view_start || !$this->vars->view_end) {
-              $result->events = array();
-              return $result;
-            }
-            $end = new Horde_Date($this->vars->view_end);
-            $end->hour = 23;
-            $end->min = $end->sec = 59;
-            Kronolith::addEvents(
-                $events, $event,
-                new Horde_Date($this->vars->view_start),
-                $end, true, true);
-            // If this is an exception, we re-add the original event also;
-            // cstart and cend are the cacheStart and cacheEnd dates from the
-            // client.
-            if (!empty($original)) {
-                Kronolith::addEvents(
-                    $events, $original,
-                    new Horde_Date($attributes->cstart),
-                    new Horde_Date($attributes->cend),
-                    true, true);
-                if ($saveOriginal) {
-                    $original->save();
-                }
-            }
-
-            // If this event recurs, we must add any bound exceptions to the
-            // results
-            if ($event->recurs()) {
-                $bound = $event->boundExceptions(false);
-                foreach ($bound as $day => &$exceptions) {
-                    foreach ($exceptions as &$exception) {
-                        $exception = $exception->toJson();
-                    }
-                }
-                Kronolith::mergeEvents($events, $bound);
-            }
-            $result->events = count($events) ? $events : array();
-        } catch (Exception $e) {
-            $GLOBALS['notification']->push($e, 'horde.error');
-        }
-        return $result;
-    }
-
-    /**
-     * Creates a result object with the signature of the current request.
-     *
-     * @param string $calendar  A calendar id.
-     *
-     * @return object  The result object.
-     */
-    protected function _signedResponse($calendar)
-    {
-        $result = new stdClass;
-        $result->cal = $calendar;
-        $result->view = $this->vars->view;
-        $result->sig = $this->vars->sig;
-        return $result;
-    }
-
-    /**
-     * Add an exception to the original event.
-     *
-     * @param Kronolith_Event $event  The recurring event.
-     * @param object $attributes      The attributes passed from the client.
-     *                                Expected to contain either rstart or rday.
-     *
-     * @return Kronolith_Event  The event representing the exception, with
-     *                          the start/end times set the same as the original
-     *                          occurence.
-     */
-    protected function _addException(Kronolith_Event $event, $attributes)
-    {
-        if ($attributes->rstart) {
-            $rstart = new Horde_Date($attributes->rstart);
-            $rstart->setTimezone($event->start->timezone);
-        } else {
-            $rstart = new Horde_Date($attributes->rday);
-            $rstart->setTimezone($event->start->timezone);
-            $rstart->hour = $event->start->hour;
-            $rstart->min = $event->start->min;
-        }
-        $event->recurrence->addException($rstart->year, $rstart->month, $rstart->mday);
-        $event->save();
-    }
-
-    /**
-     * Creates a new event that represents an exception to a recurring event.
-     *
-     * @param Kronolith_Event $event  The original recurring event.
-     * @param Kronolith_Event $copy   If present, contains a copy of $event, but
-     *                                with changes from edited event form.
-     * @param stdClass $attributes    The attributes passed from the client.
-     *                                Expected to contain rstart and rend or
-     *                                rday that represents the original
-     *                                starting/ending date of the instance.
-     *
-     * @return Kronolith_Event  The event representing the exception
-     */
-    protected function _copyEvent(Kronolith_Event $event, Kronolith_Event $copy = null, $attributes = null)
-    {
-        if (empty($copy)) {
-            $copy = clone($event);
-        }
-
-        if ($attributes->rstart) {
-            $rstart = new Horde_Date($attributes->rstart);
-            $rstart->setTimezone($event->start->timezone);
-            $rend = new Horde_Date($attributes->rend);
-            $rend->setTimezone($event->end->timezone);
-        } else {
-            $rstart = new Horde_Date($attributes->rday);
-            $rstart->setTimezone($event->start->timezone);
-            $rstart->hour = $event->start->hour;
-            $rstart->min = $event->start->min;
-            $rend = $rstart->add($event->getDuration);
-            $rend->setTimezone($event->end->timezone);
-            $rend->hour = $event->end->hour;
-            $rend->min = $event->end->min;
-        }
-        $uid = $event->uid;
-        $otime = $event->start->strftime('%T');
-
-        // Create new event for the exception
-        $nevent = $event->getDriver()->getEvent();
-        $nevent->baseid = $uid;
-        $nevent->exceptionoriginaldate = new Horde_Date($rstart->strftime('%Y-%m-%d') . 'T' . $otime);
-        $nevent->exceptionoriginaldate->setTimezone($event->start->timezone);
-        $nevent->creator = $event->creator;
-        $nevent->title = $copy->title;
-        $nevent->description = $copy->description;
-        $nevent->location = $copy->location;
-        $nevent->private = $copy->private;
-        $nevent->url = $copy->url;
-        $nevent->status = $copy->status;
-        $nevent->attendees = $copy->attendees;
-        $nevent->setResources($copy->getResources());
-        $nevent->start = $rstart;
-        $nevent->end = $rend;
-        $nevent->initialized = true;
-
-        return $nevent;
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Ajax/Application.php 4.2.27-1/kronolith-4.2.23/lib/Ajax/Application.php
--- 4.2.23-1/kronolith-4.2.23/lib/Ajax/Application.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Ajax/Application.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,36 +0,0 @@
-<?php
-/**
- * Defines the AJAX interface for Kronolith.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author   Michael Slusarz <slusarz@horde.org>
- * @author   Jan Schneider <jan@horde.org>
- * @author   Gonçalo Queirós <mail@goncaloqueiros.net>
- * @category Horde
- * @license  http://www.horde.org/licenses/gpl GPL
- * @package  Kronolith
- */
-class Kronolith_Ajax_Application extends Horde_Core_Ajax_Application
-{
-    /**
-     */
-    protected function _init()
-    {
-        $this->addHandler('Kronolith_Ajax_Application_Handler');
-
-        $this->addHandler('Horde_Core_Ajax_Application_Handler_Chunk');
-        $this->addHandler('Horde_Core_Ajax_Application_Handler_Groups');
-        $this->addHandler('Horde_Core_Ajax_Application_Handler_Imple');
-        $this->addHandler('Horde_Core_Ajax_Application_Handler_Prefs');
-
-        $email = $this->addHandler('Horde_Core_Ajax_Application_Handler_Email');
-        $email->defaultDomain = empty($GLOBALS['conf']['storage']['default_domain'])
-            ? null
-            : $GLOBALS['conf']['storage']['default_domain'];
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Ajax/Imple/ContactAutoCompleter.php 4.2.27-1/kronolith-4.2.23/lib/Ajax/Imple/ContactAutoCompleter.php
--- 4.2.23-1/kronolith-4.2.23/lib/Ajax/Imple/ContactAutoCompleter.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Ajax/Imple/ContactAutoCompleter.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,47 +0,0 @@
-<?php
-/**
- * Imple to attach the contact autocompleter to a HTML element.
- *
- * Copyright 2005-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author   Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @license  http://www.horde.org/licenses/gpl GPL
- * @package  Kronolith
- */
-class Kronolith_Ajax_Imple_ContactAutoCompleter extends Horde_Core_Ajax_Imple_ContactAutoCompleter
-{
-    /**
-     */
-    protected function _getAutoCompleter()
-    {
-        $opts = array();
-
-        foreach (array('box', 'onAdd', 'onRemove', 'triggerContainer', 'beforeUpdate') as $val) {
-            if (isset($this->_params[$val])) {
-                $opts[$val] = $this->_params[$val];
-            }
-        }
-
-        return empty($this->_params['pretty'])
-            ? new Horde_Core_Ajax_Imple_AutoCompleter_Ajax($opts)
-            : new Horde_Core_Ajax_Imple_AutoCompleter_Pretty($opts);
-    }
-
-    /**
-     */
-    protected function _getAddressbookSearchParams()
-    {
-        $params = Kronolith::getAddressbookSearchParams();
-
-        $ob = new stdClass;
-        $ob->fields = $params['fields'];
-        $ob->sources = $params['sources'];
-
-        return $ob;
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Ajax/Imple/ResourceAutoCompleter.php 4.2.27-1/kronolith-4.2.23/lib/Ajax/Imple/ResourceAutoCompleter.php
--- 4.2.23-1/kronolith-4.2.23/lib/Ajax/Imple/ResourceAutoCompleter.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Ajax/Imple/ResourceAutoCompleter.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,71 +0,0 @@
-<?php
-/**
- * Imple to attach the resource autocompleter to a HTML element.
- *
- * Copyright 2005-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author   Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @license  http://www.horde.org/licenses/gpl GPL
- * @package  Kronolith
- */
-class Kronolith_Ajax_Imple_ResourceAutoCompleter extends Horde_Core_Ajax_Imple_AutoCompleter
-{
-    /**
-     */
-    protected function _getAutoCompleter()
-    {
-        $opts = array();
-
-        foreach (array('box', 'onAdd', 'onRemove', 'triggerContainer') as $val) {
-            if (isset($this->_params[$val])) {
-                $opts[$val] = $this->_params[$val];
-            }
-        }
-
-        if (empty($this->_params['pretty'])) {
-            return new Horde_Core_Ajax_Imple_AutoCompleter_Ajax($opts);
-        }
-
-        $opts['filterCallback'] = <<<EOT
-function(c) {
-   if (!c) {
-       return [];
-   }
-
-   var r = [];
-   KronolithCore.resourceACCache.choices = c;
-   c.each(function(i) {
-       r.push(i.name);
-   });
-   return r;
-}
-EOT;
-        $opts['requireSelection'] = true;
-
-        return new Horde_Core_Ajax_Imple_AutoCompleter_Pretty($opts);
-    }
-
-    /**
-     */
-    protected function _handleAutoCompleter($input)
-    {
-        $ret = array();
-
-        // For now, return all resources.
-        $resources = Kronolith::getDriver('Resource')->listResources(Horde_Perms::READ, array(), 'name');
-        foreach ($resources as $r) {
-            if (strpos(Horde_String::lower($r->get('name')), Horde_String::lower($input)) !== false) {
-                $ret[] = array(
-                    'name' => $r->get('name'),
-                    'code' => $r->getId());
-            }
-        }
-
-        return $ret;
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Ajax/Imple/TagAutoCompleter.php 4.2.27-1/kronolith-4.2.23/lib/Ajax/Imple/TagAutoCompleter.php
--- 4.2.23-1/kronolith-4.2.23/lib/Ajax/Imple/TagAutoCompleter.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Ajax/Imple/TagAutoCompleter.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,37 +0,0 @@
-<?php
-/**
- * Copyright 2009-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Michael Slusarz <slusarz@horde.org>
- * @package Kronolith
- */
-class Kronolith_Ajax_Imple_TagAutoCompleter extends Horde_Core_Ajax_Imple_AutoCompleter
-{
-    /**
-     */
-    protected function _getAutoCompleter()
-    {
-        $opts = array();
-
-        foreach (array('box', 'triggerContainer') as $val) {
-            if (isset($this->_params[$val])) {
-                $opts[$val] = $this->_params[$val];
-            }
-        }
-
-        return empty($this->_params['pretty'])
-            ? new Horde_Core_Ajax_Imple_AutoCompleter_Ajax($opts)
-            : new Horde_Core_Ajax_Imple_AutoCompleter_Pretty($opts);
-    }
-
-    /**
-     */
-    protected function _handleAutoCompleter($input)
-    {
-        return array_values(Kronolith::getTagger()->listTags($input));
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Ajax.php 4.2.27-1/kronolith-4.2.23/lib/Ajax.php
--- 4.2.23-1/kronolith-4.2.23/lib/Ajax.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Ajax.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,181 +0,0 @@
-<?php
-/**
- * Kronolith wrapper for the base AJAX framework handler.
- *
- * Copyright 2012-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author   Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @license  http://www.horde.org/licenses/gpl GPL
- * @package  Kronolith
- */
-class Kronolith_Ajax
-{
-    /**
-     */
-    public function init()
-    {
-        global $page_output;
-
-        $page_output->addScriptFile('dragdrop2.js');
-        $page_output->addScriptFile('redbox.js', 'horde');
-        $page_output->addScriptFile('tooltips.js', 'horde');
-        $page_output->addScriptFile('colorpicker.js', 'horde');
-        $page_output->addScriptPackage('Horde_Core_Script_Package_Datejs');
-        $page_output->addScriptFile('kronolith.js');
-        Horde_Core_Ui_JsCalendar::init(array('short_weekdays' => true));
-
-        $page_output->addInlineJsVars(array(
-            'var Kronolith' => $this->_addBaseVars()
-        ), array('top' => true));
-
-        $page_output->header(array(
-            'body_class' => 'horde-ajax',
-            'growler_log' => true
-        ));
-    }
-
-    /**
-     * Add base javascript variables to the page.
-     */
-    protected function _addBaseVars()
-    {
-        global $conf, $injector, $prefs, $registry;
-
-        $auth_name = $registry->getAuth();
-        $has_tasks = Kronolith::hasApiPermission('tasks');
-        $identity = $injector->getInstance('Horde_Core_Factory_Identity')->create();
-
-        $app_urls = $js_vars = array();
-        if (isset($conf['menu']['apps']) &&
-            is_array($conf['menu']['apps'])) {
-            foreach ($conf['menu']['apps'] as $app) {
-                $app_urls[$app] = strval(Horde::url($registry->getInitialPage($app), true));
-            }
-        }
-
-        /* Variables used in core javascript files. */
-        $js_vars['conf'] = array_filter(array(
-            'URI_CALENDAR_EXPORT' => str_replace(
-                array('%23', '%2523', '%7B', '%257B', '%7D', '%257D'),
-                array('#', '#', '{', '{', '}', '}'),
-                strval($registry->downloadUrl('#{calendar}.ics', array('actionID' => 'export', 'all_events' => 1, 'exportID' => Horde_Data::EXPORT_ICALENDAR, 'exportCal' => 'internal_#{calendar}'))->setRaw(true))),
-            'URI_RESOURCE_EXPORT' => str_replace(
-                array('%23', '%2523', '%7B', '%257B', '%7D', '%257D'),
-                array('#', '#', '{', '{', '}', '}'),
-                strval($registry->downloadUrl('#{calendar}.ics', array('actionID' => 'export', 'all_events' => 1, 'exportID' => Horde_Data::EXPORT_ICALENDAR, 'exportCal' => 'resource_#{calendar}'))->setRaw(true))),
-            'URI_EVENT_EXPORT' => str_replace(array('%23', '%7B', '%7D'), array('#', '{', '}'), Horde::url('event.php', true)->add(array('view' => 'ExportEvent', 'eventID' => '#{id}', 'calendar' => '#{calendar}', 'type' => '#{type}'))),
-            'images' => array(
-                'alarm'     => strval(Horde_Themes::img('alarm-fff.png')),
-                'attendees' => strval(Horde_Themes::img('attendees-fff.png')),
-                'exception' => strval(Horde_Themes::img('exception-fff.png')),
-                'new_event' => strval(Horde_Themes::img('new.png')),
-                'new_task'  => strval(Horde_Themes::img('new_task.png')),
-                'recur'     => strval(Horde_Themes::img('recur-fff.png')),
-            ),
-            'new_event' => $injector->getInstance('Kronolith_View_Sidebar')->newLink
-                . $injector->getInstance('Kronolith_View_Sidebar')->newText
-                . '</a>',
-            'new_task' => $injector->getInstance('Kronolith_View_SidebarTasks')->newLink
-                . $injector->getInstance('Kronolith_View_SidebarTasks')->newText
-                . '</a>',
-            'user' => $registry->convertUsername($auth_name, false),
-            'name' => $identity->getName(),
-            'email' => strval($identity->getDefaultFromAddress()),
-            'prefs_url' => strval($registry->getServiceLink('prefs', 'kronolith')->setRaw(true)),
-            'app_urls' => $app_urls,
-            'name' => $registry->get('name'),
-            'has_tasks' => intval($has_tasks),
-            'has_resources' => intval(!empty($conf['resource']['driver'])),
-            'login_view' => $prefs->getValue('defaultview'),
-            'default_calendar' => 'internal|' . Kronolith::getDefaultCalendar(Horde_Perms::EDIT),
-            'max_events' => intval($prefs->getValue('max_events')),
-            'date_format' => Horde_Core_Script_Package_Datejs::translateFormat(
-                Horde_Nls::getLangInfo(D_FMT)
-            ),
-            'time_format' => $prefs->getValue('twentyFour') ? 'HH:mm' : 'hh:mm tt',
-            'show_time' => Kronolith::viewShowTime(),
-            'default_alarm' => intval($prefs->getValue('default_alarm')),
-            'status' => array(
-                'cancelled' => Kronolith::STATUS_CANCELLED,
-                'confirmed' => Kronolith::STATUS_CONFIRMED,
-                'free' => Kronolith::STATUS_FREE,
-                'tentative' => Kronolith::STATUS_TENTATIVE
-            ),
-            'recur' => array(
-                Horde_Date_Recurrence::RECUR_NONE => 'None',
-                Horde_Date_Recurrence::RECUR_DAILY => 'Daily',
-                Horde_Date_Recurrence::RECUR_WEEKLY => 'Weekly',
-                Horde_Date_Recurrence::RECUR_MONTHLY_DATE => 'Monthly',
-                Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY => 'Monthly',
-                Horde_Date_Recurrence::RECUR_YEARLY_DATE => 'Yearly',
-                Horde_Date_Recurrence::RECUR_YEARLY_DAY => 'Yearly',
-                Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY => 'Yearly'
-            ),
-            'perms' => array(
-                'all' => Horde_Perms::ALL,
-                'show' => Horde_Perms::SHOW,
-                'read' => Horde_Perms::READ,
-                'edit' => Horde_Perms::EDIT,
-                'del'  => Horde_Perms::DELETE,
-                'delegate' => Kronolith::PERMS_DELEGATE
-            ),
-            'tasks' => $has_tasks ? $registry->tasks->ajaxDefaults() : null,
-            'confirm_delete' => $prefs->getValue('confirm_delete')
-        ));
-
-        /* Make sure this value is not optimized out by array_filter(). */
-        $js_vars['conf']['week_start'] = intval($prefs->getValue('week_start_monday'));
-
-        /* Gettext strings. */
-        $js_vars['text'] = array(
-            'alarm' => _("Alarm:"),
-            'alerts' => _("Notifications"),
-            'allday' => _("All day"),
-            'delete_calendar' => _("Are you sure you want to delete this calendar and all the events in it?"),
-            'delete_tasklist' => _("Are you sure you want to delete this task list and all the tasks in it?"),
-            'external_category' => _("Other events"),
-            'fix_form_values' => _("Please enter correct values in the form first."),
-            'geocode_error' => _("Unable to locate requested address"),
-            'hidelog' => _("Hide Notifications"),
-            'import_warning' => _("Importing calendar data. This may take a while..."),
-            'invalid_user' => _("User does not exist"),
-            'more' => _("more..."),
-            'no_assignee' => _("None"),
-            'no_calendar_title' => _("The calendar title must not be empty."),
-            'no_parent' => _("No parent task"),
-            'no_tasklist_title' => _("The task list title must not be empty."),
-            'no_url' => _("You must specify a URL."),
-            'prefs' => _("Preferences"),
-            'searching' => sprintf(_("Events matching \"%s\""), '#{term}'),
-            'shared' => _("Shared"),
-            'tasks' => _("Tasks"),
-            'unknown_resource' => _("Unknown resource."),
-            'wrong_auth' => _("The authentication information you specified wasn't accepted."),
-            'wrong_date_format' => sprintf(_("You used an unknown date format \"%s\". Please try something like \"%s\"."), '#{wrong}', '#{right}'),
-            'wrong_time_format' => sprintf(_("You used an unknown time format \"%s\". Please try something like \"%s\"."), '#{wrong}', '#{right}'),
-        );
-
-        for ($i = 1; $i <= 12; ++$i) {
-            $js_vars['text']['month'][$i - 1] = Horde_Nls::getLangInfo(constant('MON_' . $i));
-        }
-
-        for ($i = 1; $i <= 7; ++$i) {
-            $js_vars['text']['weekday'][$i] = Horde_Nls::getLangInfo(constant('DAY_' . $i));
-        }
-
-        foreach (array_diff(array_keys($js_vars['conf']['recur']), array(Horde_Date_Recurrence::RECUR_NONE)) as $recurType) {
-            $js_vars['text']['recur'][$recurType] = Kronolith::recurToString($recurType);
-        }
-        $js_vars['text']['recur']['exception'] = _("Exception");
-
-        // Maps
-        $js_vars['conf']['maps'] = $conf['maps'];
-
-        return $js_vars;
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Api.php 4.2.27-1/kronolith-4.2.23/lib/Api.php
--- 4.2.23-1/kronolith-4.2.23/lib/Api.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Api.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,1595 +0,0 @@
-<?php
-/**
- * Kronolith external API interface.
- *
- * This file defines Kronolith's external API interface. Other applications
- * can interact with Kronolith through this API.
- *
- * @package Kronolith
- */
-class Kronolith_Api extends Horde_Registry_Api
-{
-    /**
-     * Links.
-     *
-     * @var array
-     */
-    protected $_links = array(
-        'show' => '%application%/event.php?calendar=|calendar|&eventID=|event|&uid=|uid|'
-    );
-
-    /**
-     * Returns the share helper prefix
-     *
-     * @return string
-     */
-    public function shareHelp()
-    {
-        return 'shares';
-    }
-
-    /**
-     * Returns the last modification timestamp for the given uid.
-     *
-     * @param string $uid      The uid to look for.
-     * @param string $calendar The calendar to search in.
-     *
-     * @return integer  The timestamp for the last modification of $uid.
-     */
-    public function modified($uid, $calendar = null)
-    {
-        $modified = $this->getActionTimestamp($uid, 'modify', $calendar);
-        if (empty($modified)) {
-            $modified = $this->getActionTimestamp($uid, 'add', $calendar);
-        }
-        return $modified;
-    }
-
-    /**
-     * Browse through Kronolith's object tree.
-     *
-     * @param string $path       The level of the tree to browse.
-     * @param array $properties  The item properties to return. Defaults to 'name',
-     *                           'icon', and 'browseable'.
-     *
-     * @return array  The contents of $path
-     * @throws Kronolith_Exception
-     */
-    public function browse($path = '', $properties = array())
-    {
-        global $injector, $registry;
-
-        // Default properties.
-        if (!$properties) {
-            $properties = array('name', 'icon', 'browseable');
-        }
-
-        if (substr($path, 0, 9) == 'kronolith') {
-            $path = substr($path, 9);
-        }
-        $path = trim($path, '/');
-        $parts = explode('/', $path);
-        $currentUser = $registry->getAuth();
-
-        if (empty($path)) {
-            // This request is for a list of all users who have calendars
-            // visible to the requesting user.
-            $calendars = Kronolith::listInternalCalendars(false, Horde_Perms::READ);
-            $owners = array();
-            foreach ($calendars as $calendar) {
-                $owners[$calendar->get('owner') ? $calendar->get('owner') : '-system-'] = true;
-            }
-
-            $results = array();
-            foreach (array_keys($owners) as $owner) {
-                $path = 'kronolith/' . $registry->convertUsername($owner, false);
-                if (in_array('name', $properties)) {
-                    $results[$path]['name'] = $injector
-                        ->getInstance('Horde_Core_Factory_Identity')
-                        ->create($owner)
-                        ->getName();
-                }
-                if (in_array('icon', $properties)) {
-                    $results[$path]['icon'] = Horde_Themes::img('user.png');
-                }
-                if (in_array('browseable', $properties)) {
-                    $results[$path]['browseable'] = true;
-                }
-            }
-            return $results;
-
-        } elseif (count($parts) == 1) {
-            // This request is for all calendars owned by the requested user
-            $owner = $parts[0] == '-system-' ? '' : $registry->convertUsername($parts[0], true);
-            $calendars = $injector->getInstance('Kronolith_Shares')
-                ->listShares(
-                    $currentUser,
-                    array('perm' => Horde_Perms::SHOW,
-                          'attributes' => $owner)
-                );
-            $results = array();
-            foreach ($calendars as $calendarId => $calendar) {
-                if ($parts[0] == '-system-' && $calendar->get('owner')) {
-                    continue;
-                }
-                $retpath = 'kronolith/' . $parts[0] . '/' . $calendarId;
-                if (in_array('name', $properties)) {
-                    $results[$retpath]['name'] = sprintf(_("Events from %s"), Kronolith::getLabel($calendar));
-                    $results[$retpath . '.ics']['name'] = Kronolith::getLabel($calendar);
-                }
-                if (in_array('displayname', $properties)) {
-                    $results[$retpath]['displayname'] = Kronolith::getLabel($calendar);
-                    $results[$retpath . '.ics']['displayname'] = Kronolith::getLabel($calendar) . '.ics';
-                }
-                if (in_array('owner', $properties)) {
-                    $results[$retpath]['owner'] = $results[$retpath . '.ics']['owner'] = $calendar->get('owner')
-                        ? $registry->convertUsername($calendar->get('owner'), false)
-                        : '-system-';
-                }
-                if (in_array('icon', $properties)) {
-                    $results[$retpath]['icon'] = Horde_Themes::img('kronolith.png');
-                    $results[$retpath . '.ics']['icon'] = Horde_Themes::img('mime/icalendar.png');
-                }
-                if (in_array('browseable', $properties)) {
-                    $results[$retpath]['browseable'] = $calendar->hasPermission($currentUser, Horde_Perms::READ);
-                    $results[$retpath . '.ics']['browseable'] = false;
-                }
-                if (in_array('read-only', $properties)) {
-                    $results[$retpath]['read-only'] = $results[$retpath . '.ics']['read-only'] = !$calendar->hasPermission($currentUser, Horde_Perms::EDIT);
-                }
-                if (in_array('contenttype', $properties)) {
-                    $results[$retpath . '.ics']['contenttype'] = 'text/calendar';
-                }
-            }
-            return $results;
-
-        } elseif (count($parts) == 2 &&
-                  array_key_exists($parts[1], Kronolith::listInternalCalendars(false, Horde_Perms::READ))) {
-            // This request is browsing into a specific calendar.  Generate
-            // the list of items and represent them as files within the
-            // directory.
-            try {
-                $calendar = $injector->getInstance('Kronolith_Shares')
-                    ->getShare($parts[1]);
-            } catch (Horde_Exception_NotFound $e) {
-                throw new Kronolith_Exception(_("Invalid calendar requested."), 404);
-            } catch (Horde_Share_Exception $e) {
-                throw new Kronolith_Exception($e->getMessage, 500);
-            }
-            $kronolith_driver = Kronolith::getDriver(null, $parts[1]);
-            $events = $kronolith_driver->listEvents();
-            $icon = Horde_Themes::img('mime/icalendar.png');
-            $owner = $calendar->get('owner')
-                ? $registry->convertUsername($calendar->get('owner'), false)
-                : '-system-';
-            $results = array();
-            foreach ($events as $dayevents) {
-                foreach ($dayevents as $event) {
-                    $key = 'kronolith/' . $path . '/' . $event->id;
-                    if (in_array('name', $properties)) {
-                        $results[$key]['name'] = $event->getTitle();
-                    }
-                    if (in_array('owner', $properties)) {
-                        $results[$key]['owner'] = $owner;
-                    }
-                    if (in_array('icon', $properties)) {
-                        $results[$key]['icon'] = $icon;
-                    }
-                    if (in_array('browseable', $properties)) {
-                        $results[$key]['browseable'] = false;
-                    }
-                    if (in_array('contenttype', $properties)) {
-                        $results[$key]['contenttype'] = 'text/calendar';
-                    }
-                    if (in_array('modified', $properties)) {
-                        $results[$key]['modified'] = $this->modified($event->uid, $parts[1]);
-                    }
-                    if (in_array('created', $properties)) {
-                        $results[$key]['created'] = $this->getActionTimestamp($event->uid, 'add');
-                    }
-                }
-            }
-            return $results;
-        } else {
-            // The only valid request left is for either a specific event or
-            // for the entire calendar.
-            if (count($parts) == 3 &&
-                array_key_exists($parts[1], Kronolith::listInternalCalendars(false, Horde_Perms::READ))) {
-                // This request is for a specific item within a given calendar.
-                $event = Kronolith::getDriver(null, $parts[1])->getEvent($parts[2]);
-
-                $result = array(
-                    'data' => $this->export($event->uid, 'text/calendar'),
-                    'mimetype' => 'text/calendar');
-                $modified = $this->modified($event->uid, $parts[1]);
-                if (!empty($modified)) {
-                    $result['mtime'] = $modified;
-                }
-                return $result;
-            } elseif (count($parts) == 2 &&
-                      substr($parts[1], -4, 4) == '.ics' &&
-                      array_key_exists(substr($parts[1], 0, -4), Kronolith::listInternalCalendars(false, Horde_Perms::READ))) {
-                // This request is for an entire calendar (calendar.ics).
-                $ical_data = $this->exportCalendar(substr($parts[1], 0, -4), 'text/calendar');
-                return array(
-                    'data'          => $ical_data,
-                    'mimetype'      => 'text/calendar',
-                    'contentlength' => strlen($ical_data),
-                    'mtime'         => $_SERVER['REQUEST_TIME']
-                );
-            } else {
-                // All other requests are a 404: Not Found
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Saves a file into the Kronolith tree.
-     *
-     * @param string $path          The path where to PUT the file.
-     * @param string $content       The file content.
-     * @param string $content_type  The file's content type.
-     *
-     * @return array  The event UIDs.
-     * @throws Kronolith_Exception
-     */
-    public function put($path, $content, $content_type)
-    {
-        if (substr($path, 0, 9) == 'kronolith') {
-            $path = substr($path, 9);
-        }
-        $path = trim($path, '/');
-        $parts = explode('/', $path);
-
-        if (count($parts) == 2 && substr($parts[1], -4) == '.ics') {
-            // Workaround for WebDAV clients that are not smart enough to send
-            // the right content type.  Assume text/calendar.
-            if ($content_type == 'application/octet-stream') {
-                $content_type = 'text/calendar';
-            }
-            $calendar = substr($parts[1], 0, -4);
-        } elseif (count($parts) == 3) {
-            $calendar = $parts[1];
-            // Workaround for WebDAV clients that are not smart enough to send
-            // the right content type.  Assume text/calendar.
-            if ($content_type == 'application/octet-stream') {
-                $content_type = 'text/calendar';
-            }
-        } else {
-            throw new Kronolith_Exception(_("Invalid calendar data supplied."));
-        }
-
-        if (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) {
-            // FIXME: Should we attempt to create a calendar based on the
-            // filename in the case that the requested calendar does not
-            // exist?
-            throw new Kronolith_Exception(_("Calendar does not exist or no permission to edit"));
-        }
-
-        // Store all currently existings UIDs. Use this info to delete UIDs not
-        // present in $content after processing.
-        $ids = array();
-        if (count($parts) == 2) {
-            $uids_remove = array_flip($this->listUids($calendar));
-        } else {
-            $uids_remove = array();
-        }
-
-        switch ($content_type) {
-        case 'text/calendar':
-        case 'text/x-vcalendar':
-            $iCal = new Horde_Icalendar();
-            if (!($content instanceof Horde_Icalendar_Vevent)) {
-                if (!$iCal->parsevCalendar($content)) {
-                    throw new Kronolith_Exception(_("There was an error importing the iCalendar data."));
-                }
-            } else {
-                $iCal->addComponent($content);
-            }
-
-            $kronolith_driver = Kronolith::getDriver();
-            $kronolith_driver->open($parts[1]);
-            $history = $GLOBALS['injector']->getInstance('Horde_History');
-            foreach ($iCal->getComponents() as $content) {
-                if ($content instanceof Horde_Icalendar_Vevent) {
-                    $event = $kronolith_driver->getEvent();
-                    $event->fromiCalendar($content);
-                    $uid = $event->uid;
-                    if ($uid) {
-                        // Remove from uids_remove list so we won't delete in
-                        // the end.
-                        unset($uids_remove[$uid]);
-                        try {
-                            $existing_event = $kronolith_driver->getByUID(
-                                $uid, array($calendar)
-                            );
-                            // Check if our event is newer then the existing -
-                            // get the event's history.
-                            $created = $modified = null;
-                            try {
-                                $created = $history->getActionTimestamp(
-                                    'kronolith:' . $calendar . ':' . $uid,
-                                    'add'
-                                );
-                                $modified = $history->getActionTimestamp(
-                                    'kronolith:' . $calendar . ':' . $uid,
-                                    'modify'
-                                );
-                                /* The history driver returns 0 for not
-                                 * found. If 0 or null does not matter, strip
-                                 * this */
-                                if ($created == 0) {
-                                    $created = null;
-                                }
-                                if ($modified == 0) {
-                                    $modified = null;
-                                }
-                            } catch (Horde_Exception $e) {
-                            }
-                            if (empty($modified) && !empty($created)) {
-                                $modified = $created;
-                            }
-                            try {
-                                if (!empty($modified) &&
-                                    $modified >= $content->getAttribute('LAST-MODIFIED')) {
-                                    // LAST-MODIFIED timestamp of existing
-                                    // entry is newer: don't replace it.
-                                    continue;
-                                }
-                            } catch (Horde_Icalendar_Exception $e) {
-                            }
-
-                            // Don't change creator/owner.
-                            $event->creator = $existing_event->creator;
-                        } catch (Horde_Exception_NotFound $e) {
-                        }
-                    }
-
-                    // Save entry.
-                    $event->save();
-                    $ids[] = $event->uid;
-                }
-            }
-            break;
-
-        default:
-            throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $content_type));
-        }
-
-        if (Kronolith::hasPermission($calendar, Horde_Perms::DELETE)) {
-            foreach (array_keys($uids_remove) as $uid) {
-                $this->delete($uid);
-            }
-        }
-
-        return $ids;
-    }
-
-    /**
-     * Deletes a file from the Kronolith tree.
-     *
-     * @param string $path  The path to the file.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function path_delete($path)
-    {
-        if (substr($path, 0, 9) == 'kronolith') {
-            $path = substr($path, 9);
-        }
-        $path = trim($path, '/');
-        $parts = explode('/', $path);
-
-        if (substr($parts[1], -4) == '.ics') {
-            $calendarId = substr($parts[1], 0, -4);
-        } else {
-            $calendarId = $parts[1];
-        }
-
-        if (!(count($parts) == 2 || count($parts) == 3) ||
-            !Kronolith::hasPermission($calendarId, Horde_Perms::DELETE)) {
-                throw new Kronolith_Exception(_("Calendar does not exist or no permission to delete"));
-            }
-
-        if (count($parts) == 3) {
-            // Delete just a single entry
-            Kronolith::getDriver(null, $calendarId)->deleteEvent($parts[2]);
-        } else {
-            // Delete the entire calendar
-            try {
-                Kronolith::getDriver()->delete($calendarId);
-                // Remove share and all groups/permissions.
-                $kronolith_shares = $GLOBALS['injector']->getInstance('Kronolith_Shares');
-                $share = $kronolith_shares->getShare($calendarId);
-                $kronolith_shares->removeShare($share);
-            } catch (Exception $e) {
-                throw new Kronolith_Exception(sprintf(_("Unable to delete calendar \"%s\": %s"), $calendarId, $e->getMessage()));
-            }
-        }
-    }
-
-    /**
-     * Returns all calendars a user has access to, according to several
-     * parameters/permission levels.
-     *
-     * @param boolean $owneronly   Only return calendars that this user owns?
-     *                             Defaults to false.
-     * @param integer $permission  The permission to filter calendars by.
-     *
-     * @return array  The calendar list.
-     */
-    public function listCalendars($owneronly = false, $permission = null)
-    {
-        if (is_null($permission)) {
-            $permission = Horde_Perms::SHOW;
-        }
-        return array_keys(Kronolith::listInternalCalendars($owneronly, $permission));
-    }
-
-    /**
-     * Returns a list of available sources.
-     *
-     * @param boolean $writeable  If true, limits to writeable sources.
-     * @param boolean $sync_only  Only include syncable sources.
-     *
-     * @return array  An array of the available sources. Keys are source IDs,
-     *                values are source titles.
-     * @since 4.2.0
-     */
-    public function sources($writeable = false, $sync_only = false)
-    {
-        $out = array();
-
-        foreach (Kronolith::listInternalCalendars(false, $writeable ? Horde_Perms::EDIT : Horde_Perms::READ) as $id => $data) {
-            $out[$id] = $data->get('name');
-        }
-
-        if ($sync_only) {
-            $syncable = Kronolith::getSyncCalendars();
-            $out = array_intersect_key($out, array_flip($syncable));
-        }
-
-        return $out;
-    }
-
-    /**
-     * Retrieve the UID for the current user's default calendar.
-     *
-     * @return string  UID.
-     * @since 4.2.0
-     */
-    public function getDefaultShare()
-    {
-        return Kronolith::getDefaultCalendar(Horde_Perms::EDIT, true);
-    }
-
-    /**
-     * Returns the ids of all the events that happen within a time period.
-     * Only includes recurring events once per time period, and does not include
-     * events that represent exceptions, making this method useful for syncing
-     * purposes. For more control, use the listEvents method.
-     *
-     * @param string|array $calendars    The calendar to check for events.
-     * @param object $startstamp         The start of the time range.
-     * @param object $endstamp           The end of the time range.
-     *
-     * @return array  The event ids happening in this time period.
-     * @throws Kronolith_Exception
-     */
-    public function listUids($calendars = null, $startstamp = 0, $endstamp = 0)
-    {
-        if (empty($calendars)) {
-            $calendars = Kronolith::getSyncCalendars();
-        } elseif (!is_array($calendars)) {
-            $calendars = array($calendars);
-        }
-
-        $driver = Kronolith::getDriver();
-        $results = array();
-        foreach ($calendars as $calendar) {
-            if (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) {
-                Horde::log(sprintf(
-                    _("Permission Denied or Calendar Not Found: %s - skipping."),
-                    $calendar));
-                continue;
-            }
-            try {
-                $driver->open($calendar);
-                $events = $driver->listEvents(
-                    $startstamp ? new Horde_Date($startstamp) : null,
-                    $endstamp   ? new Horde_Date($endstamp)   : null,
-                    array('cover_dates' => false,
-                          'hide_exceptions' => true)
-                );
-                Kronolith::mergeEvents($results, $events);
-            } catch (Kronolith_Exception $e) {
-                Horde::log($e);
-            }
-        }
-        $uids = array();
-        foreach ($results as $dayevents) {
-            foreach ($dayevents as $event) {
-                $uids[] = $event->uid;
-            }
-        }
-
-        return $uids;
-    }
-
-    /**
-     * Returns an array of UIDs for events that have had $action happen since
-     * $timestamp.
-     *
-     * @param string  $action     The action to check for - add, modify, or delete.
-     * @param integer $timestamp  The time to start the search.
-     * @param string  $calendar   The calendar to search in.
-     * @param integer $end        The optional ending timestamp
-     * @param boolean $isModSeq   If true, $timestamp and $end are modification
-     *                            sequences and not timestamps. @since 4.1.1
-     *
-     * @return array  An array of UIDs matching the action and time criteria.
-     *
-     * @throws Kronolith_Exception
-     * @throws Horde_History_Exception
-     * @throws InvalidArgumentException
-     */
-    public function listBy($action, $timestamp, $calendar = null, $end = null, $isModSeq = false)
-    {
-        if (empty($calendar)) {
-            $cs = Kronolith::getSyncCalendars($action == 'delete');
-            $results = array();
-            foreach ($cs as $c) {
-                $results = array_merge(
-                    $results, $this->listBy($action, $timestamp, $c, $end, $isModSeq));
-            }
-            return $results;
-        }
-        $filter = array(array('op' => '=', 'field' => 'action', 'value' => $action));
-        if (!empty($end) && !$isModSeq) {
-            $filter[] = array('op' => '<', 'field' => 'ts', 'value' => $end);
-        }
-
-        if (!$isModSeq) {
-            $histories = $GLOBALS['injector']
-                ->getInstance('Horde_History')
-                ->getByTimestamp('>', $timestamp, $filter, 'kronolith:' . $calendar);
-        } else {
-            $histories = $GLOBALS['injector']
-                ->getInstance('Horde_History')
-                ->getByModSeq($timestamp, $end, $filter, 'kronolith:' . $calendar);
-        }
-
-        // Strip leading kronolith:username:.
-        return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
-    }
-
-    /**
-     * Method for obtaining all server changes between two timestamps. Basically
-     * a wrapper around listBy(), but returns an array containing all adds,
-     * edits and deletions. If $ignoreExceptions is true, events representing
-     * recurring event exceptions will not be included in the results.
-     *
-     * @param integer $start             The starting timestamp
-     * @param integer $end               The ending timestamp.
-     * @param boolean $ignoreExceptions  Do not include exceptions in results.
-     * @param boolean $isModSeq          If true, $timestamp and $end are
-     *                                   modification sequences and not
-     *                                   timestamps. @since 4.1.1
-     * @param string|array $calendars    The sources to check. @since 4.2.0
-     *
-     * @return array  An hash with 'add', 'modify' and 'delete' arrays.
-     * @throws Horde_Exception_PermissionDenied
-     * @throws Kronolith_Exception
-     */
-    public function getChanges(
-        $start, $end, $ignoreExceptions = true, $isModSeq = false, $calendars = null)
-    {
-        // Only get the calendar once
-        if (is_null($calendars)) {
-            $cs = Kronolith::getSyncCalendars();
-        } else {
-            if (!is_array($calendars)) {
-                $calendars = array($calendars);
-            }
-            $cs = $calendars;
-        }
-
-        $changes = array(
-            'add' => array(),
-            'modify' => array(),
-            'delete' => array());
-
-        foreach ($cs as $c) {
-             // New events
-            $uids = $this->listBy('add', $start, $c, $end, $isModSeq);
-            if ($ignoreExceptions) {
-                foreach ($uids as $uid) {
-                    try {
-                        $event = Kronolith::getDriver()->getByUID($uid, array($c));
-                    } catch (Exception $e) {
-                        continue;
-                    }
-                    if (empty($event->baseid)) {
-                        $changes['add'][] = $uid;
-                    }
-                }
-            } else {
-                $changes['add'] = array_keys(array_flip(array_merge($changes['add'], $uids)));
-            }
-
-            // Edits
-            $uids = $this->listBy('modify', $start, $c, $end, $isModSeq);
-            if ($ignoreExceptions) {
-                foreach ($uids as $uid) {
-                    try {
-                        $event = Kronolith::getDriver()->getByUID($uid, array($c));
-                    } catch (Exception $e) {
-                        continue;
-                    }
-                    if (empty($event->baseid)) {
-                        $changes['modify'][] = $uid;
-                    }
-                }
-            } else {
-                $changes['modify'] = array_keys(array_flip(array_merge($changes['modify'], $uids)));
-            }
-            // No way to figure out if this was an exception, so we must include all
-            $changes['delete'] = array_keys(
-                array_flip(array_merge($changes['delete'], $this->listBy('delete', $start, $c, $end, $isModSeq))));
-        }
-
-        return $changes;
-    }
-
-    /**
-     * Return all changes occuring between the specified modification
-     * sequences.
-     *
-     * @param integer $start             The starting modseq.
-     * @param integer $end               The ending modseq.
-     * @param string|array $calendars    The sources to check. @since 4.2.0
-     *
-     * @return array  The changes @see getChanges()
-     * @since 4.1.1
-     */
-    public function getChangesByModSeq($start, $end, $calendars = null)
-    {
-        return $this->getChanges($start, $end, true, true, $calendars);
-    }
-
-    /**
-     * Returns the timestamp of an operation for a given uid an action
-     *
-     * @param string $uid      The uid to look for.
-     * @param string $action   The action to check for - add, modify, or delete.
-     * @param string $calendar The calendar to search in.
-     * @param boolean $modSeq  Request a modification sequence instead of a
-     *                         timestamp. @since 4.1.1
-     *
-     * @return integer  The timestamp or modseq for this action.
-     *
-     * @throws Kronolith_Exception
-     * @throws InvalidArgumentException
-     */
-    public function getActionTimestamp($uid, $action, $calendar = null, $modSeq = false)
-    {
-        if (empty($calendar)) {
-            $calendar = Kronolith::getDefaultCalendar();
-        } elseif (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-
-        if (!$modSeq) {
-            return $GLOBALS['injector']->getInstance('Horde_History')->getActionTimestamp('kronolith:' . $calendar . ':' . $uid, $action);
-        }
-
-        return $GLOBALS['injector']->getInstance('Horde_History')->getActionModSeq('kronolith:' . $calendar . ':' . $uid, $action);
-    }
-
-    /**
-     * Return the largest modification sequence from the history backend.
-     *
-     * @param string $id  The calendar id to return the hightest MDOSEQ for. If
-     *                    null, the highest MODSEQ across all calendars is
-     *                    returned. @since 4.2.0
-     *
-     * @return integer  The MODSEQ value.
-     * @since 4.1.1
-     */
-    public function getHighestModSeq($id = null)
-    {
-        $parent = 'kronolith';
-        if (!empty($id)) {
-            $parent .= ':' . $id;
-        }
-        return $GLOBALS['injector']->getInstance('Horde_History')->getHighestModSeq($parent);
-    }
-
-    /**
-     * Imports an event represented in the specified content type.
-     *
-     * @param string $content      The content of the event.
-     * @param string $contentType  What format is the data in? Currently supports:
-     *                             <pre>
-     *                             text/calendar
-     *                             text/x-vcalendar
-     *                             activesync
-     *                             </pre>
-     * @param string $calendar     What calendar should the event be added to?
-     *
-     * @return array  The event's UID.
-     * @throws Kronolith_Exception
-     */
-    public function import($content, $contentType, $calendar = null)
-    {
-        if (!isset($calendar)) {
-            $calendar = Kronolith::getDefaultCalendar(Horde_Perms::EDIT);
-        } elseif (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-
-        $kronolith_driver = Kronolith::getDriver(null, $calendar);
-
-        switch ($contentType) {
-        case 'text/calendar':
-        case 'text/x-vcalendar':
-            $iCal = new Horde_Icalendar();
-            if (!($content instanceof Horde_Icalendar_Vevent)) {
-                if (!$iCal->parsevCalendar($content)) {
-                    throw new Kronolith_Exception(_("There was an error importing the iCalendar data."));
-                }
-            } else {
-                $iCal->addComponent($content);
-            }
-
-            $ical_importer = new Kronolith_Icalendar_Handler_Base($iCal, $kronolith_driver);
-            $result = array_flip($ical_importer->process());
-            return current($result);
-
-            case 'activesync':
-                $event = $kronolith_driver->getEvent();
-                $event->fromASAppointment($content);
-                $event->save();
-                return $event->uid;
-        }
-
-        throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
-    }
-
-    /**
-     * Imports a single vEvent part to storage.
-     *
-     * @param Horde_Icalendar_Vevent $content  The vEvent part
-     * @param Kronolith_Driver $driver         The kronolith driver
-     * @param boolean $exception               Content represents an exception
-     *                                         in a recurrence series.
-     *
-     * @return string  The new event's uid
-     */
-    protected function _addiCalEvent($content, $driver, $exception = false)
-    {
-        $event = $driver->getEvent();
-        $event->fromiCalendar($content, true);
-        // Check if the entry already exists in the data source, first by UID.
-        if (!$exception) {
-            try {
-                $driver->getByUID($event->uid, array($driver->calendar));
-                throw new Kronolith_Exception(sprintf(_("%s Already Exists"), $event->uid));
-            } catch (Horde_Exception $e) {}
-        }
-
-        $result = $driver->search($event);
-        // Check if the match really is an exact match:
-        foreach ($result as $days) {
-            foreach ($days as $match) {
-                if ($match->start == $event->start &&
-                    $match->end == $event->end &&
-                    $match->title == $event->title &&
-                    $match->location == $event->location &&
-                    $match->hasPermission(Horde_Perms::EDIT)) {
-                        throw new Kronolith_Exception(sprintf(_("%s Already Exists"), $match->uid));
-                    }
-            }
-        }
-        $event->save();
-
-        return $event->uid;
-    }
-
-    /**
-     * Imports an event parsed from a string.
-     *
-     * @param string $text      The text to parse into an event
-     * @param string $calendar  The calendar into which the event will be
-     *                          imported.  If 'null', the user's default
-     *                          calendar will be used.
-     *
-     * @return array  The UID of all events that were added.
-     * @throws Kronolith_Exception
-     */
-    public function quickAdd($text, $calendar = null)
-    {
-        if (!isset($calendar)) {
-            $calendar = Kronolith::getDefaultCalendar(Horde_Perms::EDIT);
-        } elseif (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-        $event = Kronolith::quickAdd($text, $calendar);
-        return $event->uid;
-    }
-
-    /**
-     * Exports an event, identified by UID, in the requested content type.
-     *
-     * @param string $uid         Identify the event to export.
-     * @param string $contentType  What format should the data be in?
-     *                            A string with one of:
-     *                            <pre>
-     *                             text/calendar (VCALENDAR 2.0. Recommended as
-     *                                            this is specified in rfc2445)
-     *                             text/x-vcalendar (old VCALENDAR 1.0 format.
-     *                                              Still in wide use)
-     *                             activesync (Horde_ActiveSync_Message_Appointment)
-     *                            </pre>
-     * @param array $options      Any additional options to be passed to the
-     *                            exporter.
-     * @param array $calendars    Require event to be in these calendars.
-     *                            @since 4.2.0
-     *
-     * @return string  The requested data.
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function export($uid, $contentType, array $options = array(), array $calendars = null)
-    {
-        $event = Kronolith::getDriver()->getByUID($uid, $calendars);
-        if (!$event->hasPermission(Horde_Perms::READ)) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-
-        $version = '2.0';
-        switch ($contentType) {
-        case 'text/x-vcalendar':
-            $version = '1.0';
-        case 'text/calendar':
-            $share = $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($event->calendar);
-
-            $iCal = new Horde_Icalendar($version);
-            $iCal->setAttribute('X-WR-CALNAME', $share->get('name'));
-
-            // Create a new vEvent.
-            $iCal->addComponent($event->toiCalendar($iCal));
-
-            return $iCal->exportvCalendar();
-
-        case 'activesync':
-            return $event->toASAppointment($options);
-        }
-
-        throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
-    }
-
-    /**
-     * Exports a calendar in the requested content type.
-     *
-     * @param string $calendar    The calendar to export.
-     * @param string $contentType  What format should the data be in?
-     *                             A string with one of:
-     *                             <pre>
-     *                             text/calendar (VCALENDAR 2.0. Recommended as
-     *                                            this is specified in rfc2445)
-     *                             text/x-vcalendar (old VCALENDAR 1.0 format.
-     *                                              Still in wide use)
-     *                             </pre>
-     *
-     * @return string  The iCalendar representation of the calendar.
-     * @throws Kronolith_Exception
-     */
-    public function exportCalendar($calendar, $contentType)
-    {
-        if (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-
-        $kronolith_driver = Kronolith::getDriver(null, $calendar);
-        $events = $kronolith_driver->listEvents(null, null, array(
-            'cover_dates' => false,
-            'hide_exceptions' => true)
-        );
-        $version = '2.0';
-        switch ($contentType) {
-        case 'text/x-vcalendar':
-            $version = '1.0';
-        case 'text/calendar':
-            $share = $GLOBALS['injector']
-                ->getInstance('Kronolith_Shares')
-                ->getShare($calendar);
-            $iCal = new Horde_Icalendar($version);
-            $iCal->setAttribute('X-WR-CALNAME', $share->get('name'));
-            if (strlen($share->get('desc'))) {
-                $iCal->setAttribute('X-WR-CALDESC', $share->get('desc'));
-            }
-
-            foreach ($events as $dayevents) {
-                foreach ($dayevents as $event) {
-                    $iCal->addComponent($event->toiCalendar($iCal));
-                }
-            }
-
-            return $iCal->exportvCalendar();
-        }
-
-        throw new Kronolith_Exception(sprintf(
-            _("Unsupported Content-Type: %s"),
-            $contentType)
-        );
-    }
-
-    /**
-     * Deletes an event identified by UID.
-     *
-     * @param string|array $uid     A single UID or an array identifying the
-     *                              event(s) to delete.
-     *
-     * @param string $recurrenceId  The reccurenceId for the event instance, if
-     *                              this is a deletion of a recurring event
-     *                              instance ($uid must not be an array).
-     * @param string $range         The range value if deleting a recurring
-     *                              event instance. Only supported values are
-     *                              null or Kronolith::RANGE_THISANDFUTURE.
-     *                              @since 4.1.5
-     *
-     * @throws Kronolith_Exception
-     */
-    public function delete($uid, $recurrenceId = null, $range = null)
-    {
-        // Handle an array of UIDs for convenience of deleting multiple events
-        // at once.
-        if (is_array($uid)) {
-            foreach ($uid as $g) {
-                $this->delete($g);
-            }
-            return;
-        }
-
-        $kronolith_driver = Kronolith::getDriver();
-        $events = $kronolith_driver->getByUID($uid, null, true);
-        $event = null;
-
-        // First try the user's own calendars.
-        if (empty($event)) {
-            $ownerCalendars = Kronolith::listInternalCalendars(true, Horde_Perms::DELETE);
-            foreach ($events as $ev) {
-                if (isset($ownerCalendars[$ev->calendar])) {
-                    $kronolith_driver->open($ev->calendar);
-                    $event = $ev;
-                    break;
-                }
-            }
-        }
-
-        // If not successful, try all calendars the user has access to.
-        if (empty($event)) {
-            $deletableCalendars = Kronolith::listInternalCalendars(false, Horde_Perms::DELETE);
-            foreach ($events as $ev) {
-                if (isset($deletableCalendars[$ev->calendar])) {
-                    $kronolith_driver->open($ev->calendar);
-                    $event = $ev;
-                    break;
-                }
-            }
-        }
-
-        // Are we an admin cleaing up user data?
-        if (empty($event) && $GLOBALS['registry']->isAdmin()) {
-            $event = $events[0];
-        }
-
-        if (empty($event)) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-
-        if ($recurrenceId && $event->recurs() && empty($range)) {
-            $deleteDate = new Horde_Date($recurrenceId);
-            $event->recurrence->addException($deleteDate->format('Y'), $deleteDate->format('m'), $deleteDate->format('d'));
-            $event->save();
-        } elseif ($range == Kronolith::RANGE_THISANDFUTURE) {
-            // Deleting the instance and remaining series.
-            $instance = new Horde_Date($recurrenceId);
-            $recurEnd = clone($instance);
-            $recurEnd->mday--;
-            if ($event->end->compareDate($recurEnd) > 0) {
-                 $kronolith_driver->deleteEvent($event->id);
-            } else {
-                 $event->recurrence->setRecurEnd($recurEnd);
-                 $result = $event->save();
-            }
-        } elseif ($recurrenceId) {
-            throw new Kronolith_Exception(_("Unable to delete event. An exception date was provided but the event does not seem to be recurring."));
-        } else {
-            $kronolith_driver->deleteEvent($event->id);
-        }
-    }
-
-    /**
-     * Replaces the event identified by UID with the content represented in the
-     * specified contentType.
-     *
-     * @param string $uid          Idenfity the event to replace.
-     * @param mixed  $content      The content of the event. String or
-     *                             Horde_Icalendar_Vevent
-     * @param string $contentType  What format is the data in? Currently supports:
-     *                             text/calendar
-     *                             text/x-vcalendar
-     *                             (Ignored if content is Horde_Icalendar_Vevent)
-     *                             activesync (Horde_ActiveSync_Message_Appointment)
-     * @param string $calendar     Ensure the event is replaced in the specified
-     *                             calendar. @since 4.2.0
-     *
-     * @throws Kronolith_Exception
-     */
-    public function replace($uid, $content, $contentType, $calendar = null)
-    {
-        $event = Kronolith::getDriver(null, $calendar)->getByUID($uid);
-
-        if (!$event->hasPermission(Horde_Perms::EDIT) ||
-            ($event->private && $event->creator != $GLOBALS['registry']->getAuth())) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-
-        if ($content instanceof Horde_Icalendar_Vevent) {
-            $component = $content;
-        } elseif ($content instanceof Horde_ActiveSync_Message_Appointment) {
-            $event->fromASAppointment($content);
-            $event->save();
-            $event->uid = $uid;
-            return;
-        } else {
-            switch ($contentType) {
-            case 'text/calendar':
-            case 'text/x-vcalendar':
-                if (!($content instanceof Horde_Icalendar_Vevent)) {
-                    $iCal = new Horde_Icalendar();
-                    if (!$iCal->parsevCalendar($content)) {
-                        throw new Kronolith_Exception(_("There was an error importing the iCalendar data."));
-                    }
-
-                    $components = $iCal->getComponents();
-                    $component = null;
-                    foreach ($components as $content) {
-                        if ($content instanceof Horde_Icalendar_Vevent) {
-                            if ($component !== null) {
-                                throw new Kronolith_Exception(_("Multiple iCalendar components found; only one vEvent is supported."));
-                            }
-                            $component = $content;
-                        }
-
-                    }
-                    if ($component === null) {
-                        throw new Kronolith_Exception(_("No iCalendar data was found."));
-                    }
-                }
-                break;
-
-            default:
-                throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
-            }
-        }
-
-        try {
-            $component->getAttribute('RECURRENCE-ID');
-            $this->_addiCalEvent($component, Kronolith::getDriver(null, $calendar), true);
-        } catch (Horde_Icalendar_Exception $e) {
-            $event->fromiCalendar($component, true);
-            // Ensure we keep the original UID, even when content does not
-            // contain one and fromiCalendar creates a new one.
-            $event->uid = $uid;
-            $event->save();
-        }
-    }
-
-    /**
-     * Generates free/busy information for a given time period.
-     *
-     * @param integer $startstamp  The start of the time period to retrieve.
-     * @param integer $endstamp    The end of the time period to retrieve.
-     * @param string $calendar     The calendar to view free/busy slots for.
-     *                             Defaults to the user's default calendar.
-     *
-     * @return Horde_Icalendar_Vfreebusy  A freebusy object that covers the
-     *                                    specified time period.
-     * @throws Kronolith_Exception
-     */
-    public function getFreeBusy($startstamp = null, $endstamp = null,
-                                $calendar = null)
-    {
-        if (is_null($calendar)) {
-            $calendar = Kronolith::getDefaultCalendar();
-        }
-        // Free/Busy information is globally available; no permission
-        // check is needed.
-        return Kronolith_FreeBusy::generate($calendar, $startstamp, $endstamp, true);
-    }
-
-    /**
-     * Attempt to lookup the free/busy information for the given email address.
-     *
-     * @param string $email  The email to lookup free/busy information for.
-     * @param boolean $json  Return the data in a simple json format. If false,
-     *                       returns the vCalander object.
-     * @since 4.1.0
-     */
-    public function lookupFreeBusy($email, $json = false)
-    {
-        return Kronolith_FreeBusy::get($email, $json);
-    }
-
-    /**
-     * Retrieves a Kronolith_Event object, given an event UID.
-     *
-     * @param string $uid  The event's UID.
-     *
-     * @return Kronolith_Event  A valid Kronolith_Event.
-     * @throws Kronolith_Exception
-     */
-    public function eventFromUID($uid)
-    {
-        $event = Kronolith::getDriver()->getByUID($uid);
-        if (!$event->hasPermission(Horde_Perms::SHOW)) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-
-        return $event;
-    }
-
-    /**
-     * Updates an attendee's response status for a specified event.
-     *
-     * @param Horde_Icalendar_Vevent $response  A Horde_Icalendar_Vevent
-     *                                          object, with a valid UID
-     *                                          attribute that points to an
-     *                                          existing event.  This is
-     *                                          typically the vEvent portion
-     *                                          of an iTip meeting-request
-     *                                          response, with the attendee's
-     *                                          response in an ATTENDEE
-     *                                          parameter.
-     * @param string $sender                    The email address of the
-     *                                          person initiating the
-     *                                          update. Attendees are only
-     *                                          updated if this address
-     *                                          matches.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function updateAttendee($response, $sender = null)
-    {
-        try {
-            $uid = $response->getAttribute('UID');
-        } catch (Horde_Icalendar_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        $events = Kronolith::getDriver()->getByUID($uid, null, true);
-
-        /* First try the user's own calendars. */
-        $ownerCalendars = Kronolith::listInternalCalendars(true, Horde_Perms::EDIT);
-        $event = null;
-        foreach ($events as $ev) {
-            if (isset($ownerCalendars[$ev->calendar])) {
-                $event = $ev;
-                break;
-            }
-        }
-
-        /* If not successful, try all calendars the user has access to. */
-        if (empty($event)) {
-            $editableCalendars = Kronolith::listInternalCalendars(false, Horde_Perms::EDIT);
-            foreach ($events as $ev) {
-                if (isset($editableCalendars[$ev->calendar])) {
-                    $event = $ev;
-                    break;
-                }
-            }
-        }
-
-        if (empty($event) ||
-            ($event->private && $event->creator != $GLOBALS['registry']->getAuth())) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-
-        $atnames = $response->getAttribute('ATTENDEE');
-        if (!is_array($atnames)) {
-            $atnames = array($atnames);
-        }
-        $atparms = $response->getAttribute('ATTENDEE', true);
-
-        $found = false;
-        $error = _("No attendees have been updated because none of the provided email addresses have been found in the event's attendees list.");
-
-        foreach ($atnames as $index => $attendee) {
-            if ($response->getAttribute('VERSION') < 2) {
-                $addr_ob = new Horde_Mail_Rfc822_Address($attendee);
-                if (!$addr_ob->valid) {
-                    continue;
-                }
-
-                $attendee = $addr_ob->bare_address;
-                $name = $addr_ob->personal;
-            } else {
-                $attendee = str_ireplace('mailto:', '', $attendee);
-                $name = isset($atparms[$index]['CN']) ? $atparms[$index]['CN'] : null;
-            }
-            if ($event->hasAttendee($attendee)) {
-                if (is_null($sender) || $sender == $attendee) {
-                    $event->addAttendee($attendee, Kronolith::PART_IGNORE, Kronolith::responseFromICal($atparms[$index]['PARTSTAT']), $name);
-                    $found = true;
-                } else {
-                    $error = _("The attendee hasn't been updated because the update was not sent from the attendee.");
-                }
-            }
-        }
-        $event->save();
-
-        if (!$found) {
-            throw new Kronolith_Exception($error);
-        }
-    }
-
-    /**
-     * Lists events for a given time period.
-     *
-     * @param integer $startstamp      The start of the time period to
-     *                                 retrieve.
-     * @param integer $endstamp        The end of the time period to retrieve.
-     * @param array   $calendars       The calendars to view events from.
-     *                                 Defaults to the user's default calendar.
-     * @param boolean $showRecurrence  Return every instance of a recurring
-     *                                 event?  If false, will only return
-     *                                 recurring events once inside the
-     *                                 $startDate - $endDate range.
-     * @param boolean $alarmsOnly      Filter results for events with alarms.
-     *                                 Defaults to false.
-     * @param boolean $showRemote      Return events from remote calendars and
-     *                                 listTimeObject API as well?
-     *
-     * @param boolean $hideExceptions  Hide events that represent exceptions to
-     *                                 a recurring event (events with baseid
-     *                                 set)?
-     * @param boolean $coverDates      Add multi-day events to all dates?
-     *
-     * @return array  A list of event hashes.
-     * @throws Kronolith_Exception
-     */
-    public function listEvents($startstamp = null, $endstamp = null,
-                               $calendars = null, $showRecurrence = true,
-                               $alarmsOnly = false, $showRemote = true,
-                               $hideExceptions = false, $coverDates = true,
-                               $fetchTags = false)
-    {
-        if (!isset($calendars)) {
-            $calendars = array($GLOBALS['prefs']->getValue('default_share'));
-        } elseif (!is_array($calendars)) {
-            $calendars = array($calendars);
-        }
-        foreach ($calendars as &$calendar) {
-            $calendar = str_replace('internal_', '', $calendar);
-            if (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) {
-                throw new Horde_Exception_PermissionDenied();
-            }
-        }
-
-        return Kronolith::listEvents(
-            new Horde_Date($startstamp),
-            new Horde_Date($endstamp),
-            $calendars, array(
-                'show_recurrence' => $showRecurrence,
-                'has_alarm' => $alarmsOnly,
-                'show_remote' => $showRemote,
-                'hide_exceptions' => $hideExceptions,
-                'cover_dates' => $coverDates,
-                'fetch_tags' => $fetchTags)
-        );
-    }
-
-    /**
-     * Subscribe to a calendar.
-     *
-     * @param array $calendar  Calendar description hash, with required 'type'
-     *                         parameter. Currently supports 'http' and
-     *                         'webcal' for remote calendars.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function subscribe($calendar)
-    {
-        if (!isset($calendar['type'])) {
-            throw new Kronolith_Exception(_("Unknown calendar protocol"));
-        }
-
-        switch ($calendar['type']) {
-        case 'http':
-        case 'webcal':
-            Kronolith::subscribeRemoteCalendar($calendar);
-            break;
-
-        case 'external':
-            $cals = unserialize($GLOBALS['prefs']->getValue('display_external_cals'));
-            if (array_search($calendar['name'], $cals) === false) {
-                $cals[] = $calendar['name'];
-                $GLOBALS['prefs']->setValue('display_external_cals', serialize($cals));
-            }
-
-        default:
-            throw new Kronolith_Exception(_("Unknown calendar protocol"));
-        }
-    }
-
-    /**
-     * Unsubscribe from a calendar.
-     *
-     * @param array $calendar  Calendar description array, with required 'type'
-     *                         parameter. Currently supports 'http' and
-     *                         'webcal' for remote calendars.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function unsubscribe($calendar)
-    {
-        if (!isset($calendar['type'])) {
-            throw new Kronolith_Exception('Unknown calendar specification');
-        }
-
-        switch ($calendar['type']) {
-        case 'http':
-        case 'webcal':
-            Kronolith::subscribeRemoteCalendar($calendar['url']);
-            break;
-
-        case 'external':
-            $cals = unserialize($GLOBALS['prefs']->getValue('display_external_cals'));
-            if (($key = array_search($calendar['name'], $cals)) !== false) {
-                unset($cals[$key]);
-                $GLOBALS['prefs']->setValue('display_external_cals', serialize($cals));
-            }
-
-        default:
-            throw new Kronolith_Exception('Unknown calendar specification');
-        }
-    }
-
-
-    /**
-     * Places an exclusive lock for a calendar or an event.
-     *
-     * @param string $calendar  The id of the calendar to lock
-     * @param string $event     The uid for the event to lock
-     *
-     * @return mixed   A lock ID on success, false if:
-     *                   - The calendar is already locked
-     *                   - The event is already locked
-     *                   - A calendar lock was requested and an event is
-     *                     already locked in the calendar
-     * @throws Kronolith_Exception
-     */
-    public function lock($calendar, $event = null)
-    {
-        if (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-        if (!empty($event)) {
-            $uid = $calendar . ':' . $event;
-        }
-
-        return $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar)->lock($GLOBALS['injector']->getInstance('Horde_Lock'), $uid);
-    }
-
-    /**
-     * Releases a lock.
-     *
-     * @param array $calendar  The event to lock.
-     * @param array $lockid    The lock id to unlock.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function unlock($calendar, $lockid)
-    {
-        if (!Kronolith::hasPermission($calendar, Horde_Perms::EDIT)) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-
-        return $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar)->unlock($GLOBALS['injector']->getInstance('Horde_Lock'), $lockid);
-    }
-
-    /**
-     * Check for existing calendar or event locks.
-     *
-     * @param array $calendar  The calendar to check locks for.
-     * @param array $event     The event to check locks for.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function checkLocks($calendar, $event = null)
-    {
-        if (!Kronolith::hasPermission($calendar, Horde_Perms::READ)) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-        if (!empty($event)) {
-            $uid = $calendar . ':' . $event;
-        }
-        return $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar)->checkLocks($GLOBALS['injector']->getInstance('Horde_Lock'), $uid);
-    }
-
-    /**
-     *
-     * @return array  A list of calendars used to display free/busy information
-     */
-    public function getFbCalendars()
-    {
-        return (unserialize($GLOBALS['prefs']->getValue('fb_cals')));
-    }
-
-    /**
-     * Retrieve the list of used tag_names, tag_ids and the total number
-     * of resources that are linked to that tag.
-     *
-     * @param array $tags   An optional array of tag_ids. If omitted, all tags
-     *                      will be included.
-     * @param string $user  Restrict result to those tagged by $user.
-     *
-     * @return array  An array containing tag_name, and total
-     */
-    public function listTagInfo($tags = null, $user = null)
-    {
-        return $GLOBALS['injector']
-            ->getInstance('Kronolith_Tagger')->getTagInfo($tags, 500, null, $user);
-    }
-
-    /**
-     * SearchTags API:
-     * Returns an application-agnostic array (useful for when doing a tag search
-     * across multiple applications)
-     *
-     * The 'raw' results array can be returned instead by setting $raw = true.
-     *
-     * @param array $names           An array of tag_names to search for.
-     * @param integer $max           The maximum number of resources to return.
-     * @param integer $from          The number of the resource to start with.
-     * @param string $resource_type  The resource type [event, calendar, '']
-     * @param string $user           Restrict results to resources owned by $user.
-     * @param boolean $raw           Return the raw data?
-     *
-     * @return array An array of results:
-     * <pre>
-     *  'title'    - The title for this resource.
-     *  'desc'     - A terse description of this resource.
-     *  'view_url' - The URL to view this resource.
-     *  'app'      - The Horde application this resource belongs to.
-     * </pre>
-     */
-    public function searchTags($names, $max = 10, $from = 0,
-                               $resource_type = '', $user = null, $raw = false)
-    {
-        // TODO: $max, $from, $resource_type not honored
-
-        $results = $GLOBALS['injector']
-            ->getInstance('Kronolith_Tagger')
-            ->search(
-                $names,
-                array('type' => 'event', 'user' => $user));
-
-        // Check for error or if we requested the raw data array.
-        if ($raw) {
-            return $results;
-        }
-
-        $return = array();
-        if (!empty($results['events'])) {
-            foreach ($results['events'] as $event_id) {
-                $driver = Kronolith::getDriver();
-                $event = $driver->getByUid($event_id);
-                $view_url = $event->getViewUrl();
-                $return[] = array(
-                    'title' => $event->title,
-                    'desc'=> $event->start->strftime($GLOBALS['prefs']->getValue('date_format_mini')) . ' ' . $event->start->strftime($GLOBALS['prefs']->getValue('time_format')),
-                    'view_url' => $view_url,
-                    'app' => 'kronolith'
-                );
-            }
-        }
-
-        return $return;
-    }
-
-    /**
-     * Create a new calendar for the existing user.
-     *
-     * @param string $name  The calendar's display name.
-     * @param array $param  Any additional parameters. May include:
-     *   - color: (string) The color to associate with the calendar.
-     *            DEFAULT: none (color will be randomly assigned).
-     *   - description:  (string) The calendar description.
-     *                   DEFAULT: none (empty description).
-     *   - tags:         (array) An array of tags to apply to the new calendar.
-     *
-     *   - synchronize:   (boolean) If true, add calendar to the list of
-     *                             calendars to syncronize.
-     *                    DEFAULT: false (do not add to the list of calendars).
-     * @return string  The new calendar's UID.
-     * @since 4.2.0
-     */
-    public function addCalendar($name, array $params = array())
-    {
-        global $prefs;
-
-        $info = array(
-            'name' => $name,
-            'color' => empty($params['color']) ? null : $params['color'],
-            'description' => empty($params['description']) ? null : $params['description'],
-            'tags' => empty($params['tags']) ? null : $params['tags']
-        );
-
-        $share = Kronolith::addShare($info);
-
-        if (!empty($params['synchronize'])) {
-            $sync = @unserialize($prefs->getValue('sync_calendars'));
-            $sync[] = $share->getName();
-            $prefs->setValue('sync_calendars', serialize($sync));
-        }
-
-        return $share->getName();
-    }
-
-    /**
-     * Delete the specified calendar.
-     *
-     * @param string $id  The calendar id.
-     */
-    public function deleteCalendar($id)
-    {
-        $calendar = $GLOBALS['injector']
-            ->getInstance('Kronolith_Shares')
-            ->getShare($calendar);
-        Kronolith::deleteShare($calendar);
-    }
-
-    /**
-     * Return an internal calendar.
-     *
-     * @todo Note: This returns a Kronolith_Calendar_Object object instead of a hash
-     * to be consistent with other application APIs. For H6 we need to normalize
-     * the APIs to always return non-objects and/or implement some mechanism to
-     * mark API methods as non-RPC safe.
-     *
-     * @param string $id  The calendar uid (share name).
-     *
-     * @return Kronolith_Calendar The calendar object.
-     * @since 4.2.0
-     */
-    public function getCalendar($id = null)
-    {
-       $driver = Kronolith::getDriver(null, $id);
-       return Kronolith::getCalendar($driver);
-    }
-
-    /**
-     * Update an internal calendar's information.
-     *
-     * @param string $id      The calendar id.
-     * @param array $info     An array of calendar information.
-     *                        @see self::addCalendar()
-     * @since 4.2.0
-     */
-    public function updateCalendar($id, array $info)
-    {
-        $calendar = $this->getCalendar(null, $id);
-
-        // Prevent wiping tags if they were not passed.
-        if (!array_key_exists('tags', $info)) {
-            $info['tags'] = Kronolith::getTagger()->getTags($id, Kronolith_Tagger::TYPE_CALENDAR);
-        }
-        Kronolith::updateShare($calendar->share(), $info);
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Application.php 4.2.27-1/kronolith-4.2.23/lib/Application.php
--- 4.2.23-1/kronolith-4.2.23/lib/Application.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Application.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,869 +0,0 @@
-<?php
-/**
- * Kronolith application API.
- *
- * This file defines Horde's core API interface. Other core Horde libraries
- * can interact with Kronolith through this API.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @package Kronolith
- */
-
-/* Determine the base directories. */
-if (!defined('KRONOLITH_BASE')) {
-    define('KRONOLITH_BASE', realpath(__DIR__ . '/..'));
-}
-
-if (!defined('HORDE_BASE')) {
-    /* If Horde does not live directly under the app directory, the HORDE_BASE
-     * constant should be defined in config/horde.local.php. */
-    if (file_exists(KRONOLITH_BASE . '/config/horde.local.php')) {
-        include KRONOLITH_BASE . '/config/horde.local.php';
-    } else {
-        define('HORDE_BASE', realpath(KRONOLITH_BASE . '/..'));
-    }
-}
-
-/* Load the Horde Framework core (needed to autoload
- * Horde_Registry_Application::). */
-require_once HORDE_BASE . '/lib/core.php';
-
-use Sabre\CalDAV;
-
-class Kronolith_Application extends Horde_Registry_Application
-{
-    /**
-     */
-    public $features = array(
-        'alarmHandler' => true,
-        'dynamicView' => true,
-        'smartmobileView' => true,
-        'modseq' => true
-    );
-
-    /**
-     */
-    public $version = 'H5 (4.2.23)';
-
-    /**
-     * Global variables defined:
-     * - $kronolith_shares: TODO
-     */
-    protected function _init()
-    {
-        /* For now, autoloading the Content_* classes depend on there being a
-         * registry entry for the 'content' application that contains at least
-         * the fileroot entry. */
-        $GLOBALS['injector']->getInstance('Horde_Autoloader')
-            ->addClassPathMapper(
-                new Horde_Autoloader_ClassPathMapper_Prefix('/^Content_/', $GLOBALS['registry']->get('fileroot', 'content') . '/lib/'));
-
-        if (!class_exists('Content_Tagger')) {
-            throw new Horde_Exception(_("The Content_Tagger class could not be found. Make sure the Content application is installed."));
-        }
-
-        $GLOBALS['injector']->bindFactory('Kronolith_Geo', 'Kronolith_Factory_Geo', 'create');
-        $GLOBALS['injector']->bindFactory('Kronolith_Shares', 'Kronolith_Factory_Shares', 'create');
-
-        /* Set the timezone variable, if available. */
-        $GLOBALS['registry']->setTimeZone();
-
-        /* Store the request timestamp if it's not already present. */
-        if (!isset($_SERVER['REQUEST_TIME'])) {
-            $_SERVER['REQUEST_TIME'] = time();
-        }
-
-        if (!$GLOBALS['prefs']->getValue('dynamic_view')) {
-            $this->features['dynamicView'] = false;
-        }
-        if ($GLOBALS['registry']->getView() != Horde_Registry::VIEW_DYNAMIC ||
-            !$GLOBALS['prefs']->getValue('dynamic_view') ||
-            empty($this->initParams['nodynamicinit'])) {
-            Kronolith::initialize();
-        }
-    }
-
-    /**
-     */
-    public function perms()
-    {
-        return array(
-            'max_events' => array(
-                'title' => _("Maximum Number of Events"),
-                'type' => 'int'
-            ),
-            'resource_management' => array(
-                'title' => _("Resource Management"),
-                'type' => 'boolean')
-        );
-    }
-
-    /**
-     */
-    public function menu($menu)
-    {
-        global $browser, $conf, $notification, $page_output, $registry, $session;
-
-        /* Check here for guest calendars so that we don't get multiple
-         * messages after redirects, etc. */
-        if (!$registry->getAuth() && !count(Kronolith::listCalendars())) {
-            $notification->push(_("No calendars are available to guests."));
-        }
-
-        if ($browser->hasFeature('dom')) {
-            Horde_Core_Ui_JsCalendar::init(array(
-                'click_month' => true,
-                'click_week' => true,
-                'click_year' => true,
-                'full_weekdays' => true
-            ));
-            $page_output->addScriptFile('goto.js');
-            $page_output->addInlineJsVars(array(
-                'KronolithGoto.dayurl' => strval(Horde::url('day.php')),
-                'KronolithGoto.monthurl' => strval(Horde::url('month.php')),
-                'KronolithGoto.weekurl' => strval(Horde::url('week.php')),
-                'KronolithGoto.yearurl' => strval(Horde::url('year.php'))
-            ));
-            $menu->add(new Horde_Url(''), _("_Goto"), 'kronolith-icon-goto', null, '', null, 'kgotomenu');
-        }
-        $menu->add(Horde::url('search.php'), _("_Search"), 'kronolith-icon-search');
-
-        /* Import/Export. */
-        if ($conf['menu']['import_export'] &&
-            !Kronolith::showAjaxView()) {
-            $menu->add(Horde::url('data.php'), _("_Import/Export"), 'horde-data');
-        }
-
-        if (strlen($session->get('kronolith', 'display_cal'))) {
-            $menu->add(Horde::selfUrl(true)->add('display_cal', ''),
-                       $registry->getAuth()
-                           ? _("Return to my calendars")
-                           : _("Return to calendars"),
-                       'kronolith-icon-back',
-                       null, null, null, '__noselection');
-        }
-    }
-
-    /**
-     * Adds additional items to the sidebar.
-     *
-     * This is for the traditional view. For the dynamic view, see
-     * Kronolith_View_Sidebar.
-     *
-     * @param Horde_View_Sidebar $sidebar  The sidebar object.
-     */
-    public function sidebar($sidebar)
-    {
-        $perms = $GLOBALS['injector']->getInstance('Horde_Core_Perms');
-        if (Kronolith::getDefaultCalendar(Horde_Perms::EDIT) &&
-            ($perms->hasAppPermission('max_events') === true ||
-             $perms->hasAppPermission('max_events') > Kronolith::countEvents())) {
-            $sidebar->addNewButton(_("_New Event"), Horde::url('new.php')->add('url', Horde::signUrl(Horde::selfUrl(true, false, true))));
-        }
-
-        if (strlen($GLOBALS['session']->get('kronolith', 'display_cal'))) {
-            $calendars = Kronolith::displayedCalendars();
-            $sidebar->containers['calendars'] = array(
-                'header' => array(
-                    'id' => 'kronolith-toggle-calendars',
-                    'label' => ngettext("Showing calendar:", "Showing calendars:", count($calendars)),
-                    'collapsed' => false,
-                ),
-            );
-            foreach ($calendars as $calendar) {
-                $row = array(
-                    'label' => $calendar->name(),
-                    'color' => $calendar->background(),
-                    'type' => 'checkbox',
-                );
-                $sidebar->addRow($row, 'calendars');
-            }
-            return;
-        }
-
-        $user = $GLOBALS['registry']->getAuth();
-        $url = Horde::selfUrl();
-        $edit = Horde::url('calendars/edit.php');
-
-        $sidebar->containers['my'] = array(
-            'header' => array(
-                'id' => 'kronolith-toggle-my',
-                'label' => _("My Calendars"),
-                'collapsed' => false,
-            ),
-        );
-        if (!$GLOBALS['prefs']->isLocked('default_share')) {
-            $sidebar->containers['my']['header']['add'] = array(
-                'url' => Horde::url('calendars/create.php'),
-                'label' => _("Create a new Local Calendar"),
-            );
-        }
-        if ($GLOBALS['registry']->isAdmin()) {
-            $sidebar->containers['system'] = array(
-                'header' => array(
-                    'id' => 'kronolith-toggle-system',
-                    'label' => _("System Calendars"),
-                    'collapsed' => true,
-                ),
-            );
-            $sidebar->containers['system']['header']['add'] = array(
-                'url' => Horde::url('calendars/create.php')->add('system', 1),
-                'label' => _("Create a new System Calendar"),
-            );
-        }
-        $sidebar->containers['shared'] = array(
-            'header' => array(
-                'id' => 'kronolith-toggle-shared',
-                'label' => _("Shared Calendars"),
-                'collapsed' => true,
-            ),
-        );
-        foreach (Kronolith::listInternalCalendars() as $id => $calendar) {
-            $row = array(
-                'selected' => in_array($id, $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS)),
-                'url' => $url->copy()->add('toggle_calendar', $id),
-                'label' => Kronolith::getLabel($calendar),
-                'color' => Kronolith::backgroundColor($calendar),
-                'edit' => $edit->add('c', $calendar->getName()),
-                'type' => 'checkbox',
-            );
-            if ($calendar->get('owner') && $calendar->get('owner') == $user) {
-                $sidebar->addRow($row, 'my');
-            } else {
-                $sidebar->addRow($row, 'shared');
-            }
-        }
-
-        if ($GLOBALS['registry']->isAdmin()) {
-            foreach ($GLOBALS['injector']->getInstance('Kronolith_Shares')->listSystemShares() as $id => $calendar) {
-                $row = array(
-                    'selected' => in_array($id, $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS)),
-                    'url' => $url->copy()->add('toggle_calendar', $id),
-                    'label' => $calendar->get('name'),
-                    'color' => Kronolith::backgroundColor($calendar),
-                    'edit' => $edit->add('c', $calendar->getName()),
-                    'type' => 'checkbox',
-                );
-                $sidebar->addRow($row, 'system');
-            }
-        }
-        if (!empty($GLOBALS['conf']['resource']['driver']) &&
-            ($GLOBALS['registry']->isAdmin() || $GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission('resource_management'))) {
-
-            $sidebar->containers['groups'] = array(
-                'header' => array(
-                    'id' => 'kronolith-toggle-groups',
-                    'label' => _("Resource Groups"),
-                    'collapsed' => true,
-                    'add' => array(
-                        'url' => Horde::url('resources/groups/create.php'),
-                        'label' => _("Create a new Resource Group"),
-                    ),
-                ),
-            );
-            $editGroups = Horde::url('resources/groups/edit.php');
-            $sidebar->containers['resources'] = array(
-                'header' => array(
-                    'id' => 'kronolith-toggle-resources',
-                    'label' => _("Resources"),
-                    'collapsed' => true,
-                    'add' => array(
-                        'url' => Horde::url('resources/create.php'),
-                        'label' => _("Create a new Resource"),
-                    ),
-                ),
-            );
-            $edit = Horde::url('resources/edit.php');
-            foreach (Kronolith::getDriver('Resource')->listResources() as $resource) {
-                if ($resource->get('type') == Kronolith_Resource::TYPE_GROUP) {
-                    $row = array(
-                        'label' => $resource->get('name'),
-                        'color' => '#dddddd',
-                        'edit' => $editGroups->add('c', $resource->getId()),
-                        'type' => 'radiobox',
-                    );
-                    $sidebar->addRow($row, 'groups');
-                } else {
-                    $calendar = new Kronolith_Calendar_Resource(array(
-                        'resource' => $resource
-                    ));
-                    $row = array(
-                        'selected' => in_array($resource->get('calendar'), $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_RESOURCE_CALENDARS)),
-                        'url' => $url->copy()->add('toggle_calendar', 'resource_' . $resource->get('calendar')),
-                        'label' => $calendar->name(),
-                        'color' => $calendar->background(),
-                        'edit' => $edit->add('c', $resource->getId()),
-                        'type' => 'checkbox',
-                    );
-                    $sidebar->addRow($row, 'resources');
-                }
-            }
-        }
-
-        foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_EXTERNAL_CALENDARS) as $id => $calendar) {
-            if (!$calendar->display()) {
-                continue;
-            }
-            $app = $GLOBALS['registry']->get(
-                'name',
-                $GLOBALS['registry']->hasInterface($calendar->api()));
-            if (!strlen($app)) {
-                $app = _("Other events");
-            }
-            $container = 'external_' . $app;
-            if (!isset($sidebar->containers[$container])) {
-                $sidebar->containers[$container] = array(
-                    'header' => array(
-                        'id' => 'kronolith-toggle-external-' . $calendar->api(),
-                        'label' => $app,
-                        'collapsed' => true,
-                    ),
-                );
-            }
-            $row = array(
-                'selected' => in_array($id, $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_EXTERNAL_CALENDARS)),
-                'url' => $url->copy()->add('toggle_calendar', 'external_' . $id),
-                'label' => $calendar->name(),
-                'color' => $calendar->background(),
-                'type' => 'checkbox',
-            );
-            $sidebar->addRow($row, $container);
-        }
-
-        $sidebar->containers['remote'] = array(
-            'header' => array(
-                'id' => 'kronolith-toggle-remote',
-                'label' => _("Remote Calendars"),
-                'collapsed' => true,
-                'add' => array(
-                    'url' => Horde::url('calendars/remote_subscribe.php'),
-                    'label' => _("Subscribe to a Remote Calendar"),
-                ),
-            ),
-        );
-        $edit = Horde::url('calendars/remote_edit.php');
-        foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_REMOTE_CALENDARS) as $calendar) {
-            $row = array(
-                'selected' => in_array($calendar->url(), $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_REMOTE_CALENDARS)),
-                'url' => $url->copy()->add('toggle_calendar', 'remote_' . $calendar->url()),
-                'label' => $calendar->name(),
-                'color' => $calendar->background(),
-                'edit' => $edit->add('url', $calendar->url()),
-                'type' => 'checkbox',
-            );
-            $sidebar->addRow($row, 'remote');
-        }
-
-        if (!empty($GLOBALS['conf']['holidays']['enable'])) {
-            $sidebar->containers['holidays'] = array(
-                'header' => array(
-                    'id' => 'kronolith-toggle-holidays',
-                    'label' => _("Holidays"),
-                    'collapsed' => true,
-                ),
-            );
-            foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_HOLIDAYS) as $id => $calendar) {
-                $row = array(
-                    'selected' => in_array($id, $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_HOLIDAYS)),
-                    'url' => $url->copy()->add('toggle_calendar', 'holiday_' . $id),
-                    'label' => $calendar->name(),
-                    'color' => $calendar->background(),
-                    'type' => 'checkbox',
-                );
-                $sidebar->addRow($row, 'holidays');
-            }
-        }
-    }
-
-    /**
-     */
-    public function hasPermission($permission, $allowed, $opts = array())
-    {
-        if (is_array($allowed)) {
-            switch ($permission) {
-            case 'max_events':
-                $allowed = max($allowed);
-                break;
-            }
-        }
-        return $allowed;
-    }
-
-    /**
-     */
-    public function removeUserData($user)
-    {
-        $error = false;
-
-        // Remove all events owned by the user in all calendars.
-        try {
-            Kronolith::removeUserEvents($user);
-        } catch (Exception $e) {
-            Horde::log($e, 'NOTICE');
-            $error = true;
-        }
-
-        // Get the shares owned by the user being deleted.
-        try {
-            $kronolith_shares = $GLOBALS['injector']->getInstance('Kronolith_Shares');
-            $shares = $kronolith_shares->listShares(
-                $user,
-                array('attributes' => $user)
-            );
-            foreach ($shares as $share) {
-                $kronolith_shares->removeShare($share);
-            }
-        } catch (Exception $e) {
-            Horde::log($e, 'NOTICE');
-            $error = true;
-        }
-
-        /* Get a list of all shares this user has perms to and remove the
-         * perms */
-        try {
-            $shares = $kronolith_shares->listShares($user);
-            foreach ($shares as $share) {
-                $share->removeUser($user);
-            }
-        } catch (Horde_Share_Exception $e) {
-            Horde::log($e, 'NOTICE');
-            $error = true;
-        }
-
-        if ($error) {
-            throw new Kronolith_Exception(sprintf(_("There was an error removing calendars for %s. Details have been logged."), $user));
-        }
-    }
-
-    /* Topbar method. */
-
-    /**
-     */
-    public function topbarCreate(Horde_Tree_Renderer_Base $tree, $parent = null,
-                                 array $params = array())
-    {
-        switch ($params['id']) {
-        case 'menu':
-            $menus = array(
-                array('new', _("New Event"), 'new.png', Horde::url('new.php')),
-                array('day', _("Day"), 'dayview.png', Horde::url('day.php')),
-                array('work', _("Work Week"), 'workweekview.png', Horde::url('workweek.php')),
-                array('week', _("Week"), 'weekview.png', Horde::url('week.php')),
-                array('month', _("Month"), 'monthview.png', Horde::url('month.php')),
-                array('year', _("Year"), 'yearview.png', Horde::url('year.php'))
-            );
-            // Dynamic view has no dedicated search page.
-            if (!Kronolith::showAjaxView()) {
-                $menus[] = array('search', _("Search"), 'search.png', Horde::url('search.php'));
-            }
-            foreach ($menus as $menu) {
-                $tree->addNode(array(
-                    'id' => $parent . $menu[0],
-                    'parent' => $parent,
-                    'label' => $menu[1],
-                    'expanded' => false,
-                    'params' => array(
-                        'icon' => Horde_Themes::img($menu[2]),
-                        'url' => $menu[3]
-                    )
-                ));
-            }
-            break;
-        }
-    }
-
-    /* Alarm method. */
-
-    /**
-     */
-    public function listAlarms($time, $user = null)
-    {
-        $current_user = $GLOBALS['registry']->getAuth();
-        if ((empty($user) || $user != $current_user) && !$GLOBALS['registry']->isAdmin()) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-
-        $group = $GLOBALS['injector']->getInstance('Horde_Group');
-        $kronolith_shares = $GLOBALS['injector']->getInstance('Kronolith_Shares');
-
-        $alarm_list = array();
-        $time = new Horde_Date($time);
-        $calendars = is_null($user)
-            ? array_keys($kronolith_shares->listAllShares())
-            : $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS);
-        $alarms = Kronolith::listAlarms($time, $calendars, true);
-        foreach ($alarms as $calendar => $cal_alarms) {
-            if (!$cal_alarms) {
-                continue;
-            }
-            try {
-                $share = $kronolith_shares->getShare($calendar);
-            } catch (Exception $e) {
-                continue;
-            }
-            if (empty($user)) {
-                $users = $share->listUsers(Horde_Perms::READ);
-                $groups = $share->listGroups(Horde_Perms::READ);
-                foreach ($groups as $gid) {
-                    try {
-                        $users = array_merge($users, $group->listUsers($gid));
-                    } catch (Horde_Group_Exception $e) {}
-                }
-                $users = array_unique($users);
-            } else {
-                $users = array($user);
-            }
-            $owner = $share->get('owner');
-            foreach ($cal_alarms as $event) {
-                foreach ($users as $alarm_user) {
-                    if ($alarm_user == $current_user) {
-                        $prefs = $GLOBALS['prefs'];
-                    } else {
-                        $prefs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Prefs')->create('kronolith', array(
-                            'cache' => false,
-                            'user' => $alarm_user
-                        ));
-                    }
-                    // Don't show alarms for private events if not the owner.
-                    if ($event->isPrivate($alarm_user)) {
-                        continue;
-                    }
-                    $shown_calendars = unserialize($prefs->getValue('display_cals'));
-                    $reminder = $prefs->getValue('event_reminder');
-                    if (($reminder == 'owner' && $alarm_user == $owner) ||
-                        ($reminder == 'show' && in_array($calendar, $shown_calendars)) ||
-                        $reminder == 'read') {
-                            $GLOBALS['registry']->setLanguageEnvironment($prefs->getValue('language'));
-                            $alarm = $event->toAlarm($time, $alarm_user, $prefs);
-                            if ($alarm) {
-                                $alarm_list[] = $alarm;
-                            }
-                    }
-                }
-            }
-        }
-
-        return $alarm_list;
-    }
-
-    /* Download data. */
-
-    /**
-     * @throws Kronolith_Exception
-     */
-    public function download(Horde_Variables $vars)
-    {
-        global $display_calendars, $injector;
-
-        switch ($vars->actionID) {
-        case 'export':
-            if ($vars->all_events) {
-                $end = $start = null;
-            } else {
-                $start = new Horde_Date(
-                    $vars->start_year,
-                    $vars->start_month,
-                    $vars->start_day
-                );
-                $end = new Horde_Date(
-                    $vars->end_year,
-                    $vars->end_month,
-                    $vars->end_day
-                );
-            }
-
-            $calendars = $vars->get('exportCal', $display_calendars);
-            if (!is_array($calendars)) {
-                $calendars = array($calendars);
-            }
-            $events = array();
-
-            foreach ($calendars as $calendar) {
-                list($type, $cal) = explode('_', $calendar, 2);
-                $kronolith_driver = Kronolith::getDriver($type, $cal);
-                $calendarObject = Kronolith::getCalendar($kronolith_driver);
-                if (!$calendarObject ||
-                    !$calendarObject->hasPermission(Horde_Perms::READ)) {
-                    throw new Horde_Exception_PermissionDenied();
-                }
-                $events[$calendar] = $kronolith_driver->listEvents(
-                    $start,
-                    $end,
-                    array(
-                        'cover_dates' => false,
-                        'hide_exceptions' => ($vars->exportID == Horde_Data::EXPORT_ICALENDAR)
-                    )
-                );
-            }
-
-            switch ($vars->exportID) {
-            case Horde_Data::EXPORT_CSV:
-                $data = array();
-                foreach ($events as $calevents) {
-                    foreach ($calevents as $dayevents) {
-                        foreach ($dayevents as $event) {
-                            $row = array(
-                                'alarm' => $event->alarm,
-                                'description' => $event->description,
-                                'end_date' => $event->end->format('Y-m-d'),
-                                'end_time' => $event->end->format('H:i:s'),
-                                'location' => $event->location,
-                                'private' => intval($event->private),
-                                'recur_type' => null,
-                                'recur_end_date' => null,
-                                'recur_interval' => null,
-                                'recur_data' => null,
-                                'start_date' => $event->start->format('Y-m-d'),
-                                'start_time' => $event->start->format('H:i:s'),
-                                'tags' => implode(', ', $event->tags),
-                                'title' => $event->getTitle()
-                            );
-
-                            if ($event->recurs()) {
-                                $row['recur_type'] = $event->recurrence->getRecurType();
-                                if ($event->recurrence->hasRecurEnd()) {
-                                    $row['recur_end_date'] = $event->recurrence->recurEnd->format('Y-m-d');
-                                }
-                                $row['recur_interval'] = $event->recurrence->getRecurInterval();
-                                $row['recur_data'] = $event->recurrence->recurData;
-                            }
-
-                            $data[] = $row;
-                        }
-                    }
-                }
-
-                $injector->getInstance('Horde_Core_Factory_Data')
-                    ->create('Csv', array('cleanup' => array($this, 'cleanupData')))
-                    ->exportFile(_("events.csv"), $data, true);
-                exit;
-
-            case Horde_Data::EXPORT_ICALENDAR:
-                $calNames = array();
-                $iCal = new Horde_Icalendar();
-
-                foreach ($events as $calevents) {
-                    foreach ($calevents as $dayevents) {
-                        foreach ($dayevents as $event) {
-                            $calNames[Kronolith::getCalendar($event->getDriver())->name()] = true;
-                            $iCal->addComponent($event->toiCalendar($iCal));
-                        }
-                    }
-                }
-
-                $iCal->setAttribute('X-WR-CALNAME', implode(', ', array_keys($calNames)));
-
-                return array(
-                    'data' => $iCal->exportvCalendar(),
-                    'name' => _("events.ics"),
-                    'type' => 'text/calendar'
-                );
-            }
-        }
-    }
-
-    /**
-     */
-    public function cleanupData()
-    {
-        $GLOBALS['import_step'] = 1;
-        return Horde_Data::IMPORT_FILE;
-    }
-
-    /* DAV methods. */
-
-    /**
-     */
-    public function davGetCollections($user)
-    {
-        global $calendar_manager, $injector, $registry;
-
-        $hordeUser = $registry->convertUsername($user, true);
-        $shares = $injector->getInstance('Kronolith_Shares')
-            ->listShares($hordeUser);
-        $dav = $injector->getInstance('Horde_Dav_Storage');
-        $calendars = array();
-        foreach ($shares as $id => $share) {
-            if ($user == '-system-' && $share->get('owner')) {
-                continue;
-            }
-            $calendar = $calendar_manager
-                ->getEntry(Kronolith::ALL_CALENDARS, $id)
-                ->toHash();
-            try {
-                $id = $dav->getExternalCollectionId($id, 'calendar');
-            } catch (Horde_Dav_Exception $e) {
-            }
-            $calendars[] = array(
-                'id' => $id,
-                'uri' => $id,
-                '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}shared-url' =>
-                    $calendar['caldav'],
-                'principaluri' => 'principals/' . $user,
-                '{http://sabredav.org/ns}owner-principal' =>
-                    'principals/'
-                        . ($share->get('owner')
-                           ? $registry->convertUsername($share->get('owner'), false)
-                           : '-system-'
-                        ),
-                '{DAV:}displayname' => Kronolith::getLabel($share),
-                '{' . CalDAV\Plugin::NS_CALDAV . '}calendar-description' =>
-                    $share->get('desc'),
-                '{http://apple.com/ns/ical/}calendar-color' =>
-                    $share->get('color') . 'ff',
-                '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Property\SupportedCalendarComponentSet(array('VEVENT')),
-                '{http://sabredav.org/ns}read-only' => !$share->hasPermission($hordeUser, Horde_Perms::EDIT),
-            );
-        }
-        return $calendars;
-    }
-
-    /**
-     */
-    public function davGetObjects($collection)
-    {
-        $dav = $GLOBALS['injector']
-            ->getInstance('Horde_Dav_Storage');
-
-        $internal = $dav->getInternalCollectionId($collection, 'calendar') ?: $collection;
-        if (!Kronolith::hasPermission($internal, Horde_Perms::SHOW)) {
-            throw new Kronolith_Exception(_("Calendar does not exist or no permission to edit"));
-        }
-
-        $kronolith_driver = Kronolith::getDriver(null, $internal);
-        $allEvents = $kronolith_driver->listEvents(
-            null,
-            null,
-            array('cover_dates' => false, 'hide_exceptions' => true)
-        );
-        $events = array();
-        foreach ($allEvents as $dayevents) {
-            foreach ($dayevents as $event) {
-                $id = $event->id;
-                $event->loadHistory();
-                $modified = $event->modified ?: $event->created;
-                try {
-                    $id = $dav->getExternalObjectId($id, $internal) ?: $id . '.ics';
-                } catch (Horde_Dav_Exception $e) {
-                }
-                $events[] = array(
-                    'id' => $id,
-                    'uri' => $id,
-                    'lastmodified' => $modified,
-                    'etag' => '"' . md5($event->id . '|' . $modified) . '"',
-                    'calendarid' => $collection,
-                );
-            }
-        }
-
-        return $events;
-    }
-
-    /**
-     */
-    public function davGetObject($collection, $object)
-    {
-        $dav = $GLOBALS['injector']
-            ->getInstance('Horde_Dav_Storage');
-
-        $internal = $dav->getInternalCollectionId($collection, 'calendar') ?: $collection;
-        if (!Kronolith::hasPermission($internal, Horde_Perms::SHOW)) {
-            throw new Kronolith_Exception(_("Calendar does not exist or no permission to edit"));
-        }
-
-        $kronolith_driver = Kronolith::getDriver(null, $internal);
-        try {
-            $object = $dav->getInternalObjectId($object, $internal) ?: preg_replace('/\.ics$/', '', $object);
-        } catch (Horde_Dav_Exception $e) {
-        }
-        $event = $kronolith_driver->getEvent($object);
-        $id = $event->id;
-        try {
-            $id = $dav->getExternalObjectId($id, $internal) ?: $id . '.ics';
-        } catch (Horde_Dav_Exception $e) {
-        }
-
-        $event->loadHistory();
-        $modified = $event->modified ?: $event->created;
-
-        $share = $GLOBALS['injector']
-            ->getInstance('Kronolith_Shares')
-            ->getShare($event->calendar);
-        $ical = new Horde_Icalendar('2.0');
-        $ical->setAttribute('X-WR-CALNAME', $share->get('name'));
-        $ical->addComponent($event->toiCalendar($ical));
-        $data = $ical->exportvCalendar();
-
-        return array(
-            'id' => $id,
-            'calendardata' => $data,
-            'uri' => $id,
-            'lastmodified' => $modified,
-            'etag' => '"' . md5($event->id . '|' . $modified) . '"',
-            'calendarid' => $collection,
-            'size' => strlen($data),
-        );
-    }
-
-    /**
-     */
-    public function davPutObject($collection, $object, $data)
-    {
-        $dav = $GLOBALS['injector']
-            ->getInstance('Horde_Dav_Storage');
-
-        $internal = $dav->getInternalCollectionId($collection, 'calendar') ?: $collection;
-        if (!Kronolith::hasPermission($internal, Horde_Perms::EDIT)) {
-            throw new Kronolith_Exception(_("Calendar does not exist or no permission to edit"));
-        }
-
-        $ical = new Horde_Icalendar();
-        if (!$ical->parsevCalendar($data)) {
-            throw new Kronolith_Exception(_("There was an error importing the iCalendar data."));
-        }
-        $importer = new Kronolith_Icalendar_Handler_Dav(
-            $ical, Kronolith::getDriver(null, $internal), array('object' => $object)
-        );
-        $importer->process();
-    }
-
-    /**
-     */
-    public function davDeleteObject($collection, $object)
-    {
-        $dav = $GLOBALS['injector']->getInstance('Horde_Dav_Storage');
-
-        $internal = $dav->getInternalCollectionId($collection, 'calendar') ?: $collection;
-        if (!Kronolith::hasPermission($internal, Horde_Perms::DELETE)) {
-            throw new Kronolith_Exception(_("Calendar does not exist or no permission to delete"));
-        }
-
-        try {
-            $object = $dav->getInternalObjectId($object, $internal)
-                ?: preg_replace('/\.ics$/', '', $object);
-        } catch (Horde_Dav_Exception $e) {
-        }
-
-        $kronolith_driver = Kronolith::getDriver(null, $internal);
-        $event = $kronolith_driver->getEvent($object);
-        $kronolith_driver->deleteEvent($object);
-
-        try {
-            $dav->deleteExternalObjectId($object, $internal);
-        } catch (Horde_Dav_Exception $e) {
-        }
-
-        // Send iTip messages.
-        // Notifications will get lost, there is no way to return messages to
-        // clients.
-        Kronolith::sendITipNotifications(
-            $event,
-            new Horde_Notification_Handler(new Horde_Notification_Storage_Object()),
-            Kronolith::ITIP_CANCEL
-        );
-    }
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Block/Monthlist.php 4.2.27-1/kronolith-4.2.23/lib/Block/Monthlist.php
--- 4.2.23-1/kronolith-4.2.23/lib/Block/Monthlist.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Block/Monthlist.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,215 +0,0 @@
-<?php
-/**
- * Display a list of calendar items grouped by month.
- */
-class Kronolith_Block_Monthlist extends Horde_Core_Block
-{
-    /**
-     */
-    public function __construct($app, $params = array())
-    {
-        parent::__construct($app, $params);
-
-        $this->_name = _("Upcoming Events");
-    }
-
-    /**
-     */
-    protected function _params()
-    {
-        $params = array(
-            'calendar' => array(
-                'name' => _("Calendar"),
-                'type' => 'enum',
-                'default' => '__all'),
-            'months' => array(
-                'name' => _("Months Ahead"),
-                'type' => 'int',
-                'default' => 2),
-            'maxevents' => array(
-                'name' => _("Maximum number of events to display (0 = no limit)"),
-                'type' => 'int',
-                'default' => 0),
-            'alarms' => array(
-                'name' => _("Show only events that have an alarm set?"),
-                'type' => 'checkbox',
-                'default' => 0
-            )
-        );
-
-        $params['calendar']['values']['__all'] = _("All Visible");
-        foreach (Kronolith::listCalendars(Horde_Perms::SHOW, true) as $id => $cal) {
-            $params['calendar']['values'][$id] = $cal->name();
-        }
-
-        return $params;
-    }
-
-    /**
-     */
-    protected function _title()
-    {
-        $url = Horde::url($GLOBALS['registry']->getInitialPage(), true);
-        if (isset($this->_params['calendar']) &&
-            $this->_params['calendar'] != '__all') {
-            $url->add('display_cal', $this->_params['calendar']);
-        }
-        return $url->link() . _("Upcoming Events") . '</a>';
-    }
-
-    /**
-     */
-    protected function _content()
-    {
-        global $page_output;
-
-        $page_output->addScriptFile('tooltips.js', 'horde');
-
-        $now = new Horde_Date($_SERVER['REQUEST_TIME']);
-        $today = date('j');
-        $current_month = '';
-
-        $startDate = new Horde_Date(array(
-            'year' => date('Y'),
-            'month' => date('n'),
-            'mday' => date('j')));
-        $endDate = new Horde_Date(array(
-            'year' => date('Y'),
-            'month' => date('n') + $this->_params['months'],
-            'mday' => date('j') - 1));
-
-        try {
-            if (isset($this->_params['calendar']) &&
-                $this->_params['calendar'] != '__all') {
-                $calendars = Kronolith::listCalendars();
-                if (!isset($calendars[$this->_params['calendar']])) {
-                    return _("Calendar not found");
-                }
-                if (!$calendars[$this->_params['calendar']]->hasPermission(Horde_Perms::READ)) {
-                    return _("Permission Denied");
-                }
-                list($type, $calendar) = explode('_', $this->_params['calendar'], 2);
-                $driver = Kronolith::getDriver($type, $calendar);
-                $all_events = $driver->listEvents(
-                    $startDate,
-                    $endDate,
-                    array('show_recurrence' => true,
-                          'has_alarm' => !empty($this->_params['alarms']),
-                          'cover_dates' => false)
-                );
-            } else {
-                $all_events = Kronolith::listEvents(
-                    $startDate,
-                    $endDate,
-                    $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS),
-                    array(
-                        'has_alarm' => !empty($this->_params['alarms']),
-                        'cover_dates' => false
-                    )
-                );
-            }
-        } catch (Exception $e) {
-            return '<em>' . $e->getMessage() . '</em>';
-        }
-
-        /* How many days do we need to check. */
-        $days = Date_Calc::dateDiff(
-            $startDate->mday, $startDate->month, $startDate->year,
-            $endDate->mday, $endDate->month, $endDate->year);
-
-        /* Loop through the days. */
-        $totalevents = 0;
-
-        $html = '';
-        for ($i = 0; $i < $days; ++$i) {
-            $day = new Kronolith_Day($startDate->month, $today + $i);
-            $date_stamp = $day->dateString();
-            if (empty($all_events[$date_stamp])) {
-                continue;
-            }
-
-            if (!empty($this->_params['maxevents']) &&
-                $totalevents >= $this->_params['maxevents']) {
-                break;
-            }
-
-            /* Output month header. */
-            if ($current_month != $day->month) {
-                $html .= '<tr><td colspan="4" class="control"><strong>' . $day->strftime('%B') . '</strong></td></tr>';
-            }
-
-            $firstevent = true;
-            $tomorrow = $day->getTomorrow();
-            foreach ($all_events[$date_stamp] as $event) {
-                $isMultiDay = false;
-                if ($event->start->compareDate($day) < 0) {
-                    $event->start = new Horde_Date($day);
-                }
-                if ($event->end->compareDate($tomorrow) >= 1) {
-                    $isMultiDay = true;
-                }
-                if (($event->end->compareDate($now) < 0 && !$event->isAllDay()) ||
-                    (!empty($this->_params['alarms']) && !$event->alarm)) {
-                    continue;
-                }
-
-                if ($firstevent || $isMultiDay) {
-                    $html .= '<tr';
-                    if ($current_month == $day->month) {
-                        $html .= ' class="upcomingday"';
-                    }
-                    $html .= '><td class="text" valign="top" align="right"><strong>';
-                    if ($day->isToday()) {
-                        $html .= _("Today");
-                    } elseif ($day->isTomorrow()) {
-                        $html .= _("Tomorrow");
-                    } else {
-                        $html .= $day->mday;
-                    }
-                    if ($isMultiDay) {
-                        $endDay = new Kronolith_Day($event->end->month, $event->end->mday);
-                        $html .= ' - ';
-                        if ($endDay->isTomorrow()) {
-                            $html .= _("Tomorrow");
-                        } else {
-                            $html .= $event->end->mday;
-                        }
-                    }
-                    $html .= '</strong>&nbsp;</td>';
-                    $firstevent = $isMultiDay;
-                } else {
-                    $html .= '<tr><td class="text">&nbsp;</td>';
-                }
-
-                $html .= '<td class="text" nowrap="nowrap" valign="top">';
-                if ($event->start->compareDate($now) < 0 &&
-                    $event->end->compareDate($now) > 0) {
-                    $html .= '<strong>' . htmlspecialchars($event->getLocation()) . '</strong>';
-                } else {
-                    $html .= htmlspecialchars($event->getLocation());
-                }
-                if ($event->start->compareDate($now) < 0 &&
-                    $event->end->compareDate($now) > 0) {
-                    $html .= '<strong>';
-                }
-                $html .= $event->getLink(null, true, null, true);
-                if ($event->start->compareDate($now) < 0 &&
-                    $event->end->compareDate($now) > 0) {
-                    $html .= '</strong>';
-                }
-                $html .= '</td></tr>';
-
-                $totalevents++;
-            }
-
-            $current_month = $day->strftime('%m');
-        }
-
-        if (empty($html)) {
-            return '<em>' . _("No events to display") . '</em>';
-        }
-
-        return '<table cellspacing="0" width="100%">' . $html . '</table>';
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Block/Month.php 4.2.27-1/kronolith-4.2.23/lib/Block/Month.php
--- 4.2.23-1/kronolith-4.2.23/lib/Block/Month.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Block/Month.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,201 +0,0 @@
-<?php
-/**
- * Display a mini month view of calendar items.
- */
-class Kronolith_Block_Month extends Horde_Core_Block
-{
-    /**
-     */
-    public function __construct($app, $params = array())
-    {
-        parent::__construct($app, $params);
-
-        $this->_name = _("This Month");
-    }
-
-    /**
-     */
-    protected function _params()
-    {
-        $params = array(
-            'calendar' => array(
-                'name' => _("Calendar"),
-                'type' => 'enum',
-                'default' => '__all'
-            )
-        );
-
-        $params['calendar']['values']['__all'] = _("All Visible");
-        foreach (Kronolith::listCalendars(Horde_Perms::SHOW, true) as $id => $cal) {
-            $params['calendar']['values'][$id] = $cal->name();
-        }
-
-        return $params;
-    }
-
-    /**
-     */
-    protected function _title()
-    {
-        $title = _("All Calendars");
-        $url = Horde::url($GLOBALS['registry']->getInitialPage(), true);
-        if (isset($this->_params['calendar']) &&
-            $this->_params['calendar'] != '__all') {
-            $calendars = Kronolith::listCalendars();
-            if (isset($calendars[$this->_params['calendar']])) {
-                $title = htmlspecialchars($calendars[$this->_params['calendar']]->name());
-            } else {
-                $title = _("Calendar not found");
-            }
-            $url->add('display_cal', $this->_params['calendar']);
-        }
-        $date = new Horde_Date(time());
-
-        return $title . ', ' . $url->link() . $date->strftime('%B, %Y') . '</a>';
-    }
-
-    /**
-     */
-    protected function _content()
-    {
-        global $prefs;
-
-        if (isset($this->_params['calendar']) &&
-            $this->_params['calendar'] != '__all') {
-            $calendars = Kronolith::listCalendars();
-            if (!isset($calendars[$this->_params['calendar']])) {
-                return _("Calendar not found");
-            }
-            if (!$calendars[$this->_params['calendar']]->hasPermission(Horde_Perms::READ)) {
-                return _("Permission Denied");
-            }
-        }
-
-        $year = date('Y');
-        $month = date('m');
-        $startday = new Horde_Date(array('mday' => 1,
-                                         'month' => $month,
-                                         'year' => $year));
-        $startday = $startday->dayOfWeek();
-        $daysInView = Date_Calc::weeksInMonth($month, $year) * 7;
-        if (!$prefs->getValue('week_start_monday')) {
-            $startOfView = 1 - $startday;
-
-            // We may need to adjust the number of days in the view if
-            // we're starting weeks on Sunday.
-            if ($startday == Horde_Date::DATE_SUNDAY) {
-                $daysInView -= 7;
-            }
-            $endday = new Horde_Date(array('mday' => Horde_Date_Utils::daysInMonth($month, $year),
-                                           'month' => $month,
-                                           'year' => $year));
-            $endday = $endday->dayOfWeek();
-            if ($endday == Horde_Date::DATE_SUNDAY) {
-                $daysInView += 7;
-            }
-        } else {
-            if ($startday == Horde_Date::DATE_SUNDAY) {
-                $startOfView = -5;
-            } else {
-                $startOfView = 2 - $startday;
-            }
-        }
-
-        $startDate = new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $startOfView));
-        $endDate = new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $startOfView + $daysInView,
-                                        'hour' => 23, 'min' => 59, 'sec' => 59));
-
-        /* Table start. and current month indicator. */
-        $html = '<table cellspacing="1" class="monthgrid" width="100%"><tr>';
-
-        /* Set up the weekdays. */
-        $weekdays = array(_("Mo"), _("Tu"), _("We"), _("Th"), _("Fr"), _("Sa"));
-        if (!$prefs->getValue('week_start_monday')) {
-            array_unshift($weekdays, _("Su"));
-        } else {
-            $weekdays[] = _("Su");
-        }
-        foreach ($weekdays as $weekday) {
-            $html .= '<th class="item">' . $weekday . '</th>';
-        }
-
-        try {
-            if (isset($this->_params['calendar']) &&
-                $this->_params['calendar'] != '__all') {
-                list($type, $calendar) = explode('_', $this->_params['calendar'], 2);
-                $driver = Kronolith::getDriver($type, $calendar);
-                $all_events = $driver->listEvents($startDate, $endDate, array(
-                    'show_recurrence' => true));
-            } else {
-                $all_events = Kronolith::listEvents($startDate, $endDate, $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS));
-            }
-        } catch (Exception $e) {
-            return '<em>' . $e->getMessage() . '</em>';
-        }
-
-        $weekday = 0;
-        $week = -1;
-        for ($day = $startOfView; $day < $startOfView + $daysInView; ++$day) {
-            if ($weekday == 7) {
-                $weekday = 0;
-            }
-            if ($weekday == 0) {
-                ++$week;
-                $html .= '</tr><tr>';
-            }
-
-            $date_ob = new Kronolith_Day($month, $day, $year);
-            if ($date_ob->isToday()) {
-                $td_class = 'kronolith-today';
-            } elseif ($date_ob->month != $month) {
-                $td_class = 'kronolith-othermonth';
-            } elseif ($date_ob->dayOfWeek() == 0 || $date_ob->dayOfWeek() == 6) {
-                $td_class = 'kronolith-weekend';
-            } else {
-                $td_class = '';
-            }
-            $html .= '<td align="center" class="' . $td_class . '">';
-
-            /* Set up the link to the day view. */
-            $url = Horde::url('day.php', true)
-                ->add('date', $date_ob->dateString());
-            if (isset($this->_params['calendar']) &&
-                $this->_params['calendar'] != '__all') {
-                $url->add('display_cal', $this->_params['calendar']);
-            }
-
-            $date_stamp = $date_ob->dateString();
-            if (empty($all_events[$date_stamp])) {
-                /* No events, plain link to the day. */
-                $cell = Horde::linkTooltip($url, _("View Day")) . $date_ob->mday . '</a>';
-            } else {
-                /* There are events; create a cell with tooltip to
-                 * list them. */
-                $day_events = '';
-                foreach ($all_events[$date_stamp] as $event) {
-                    if ($event->isAllDay()) {
-                        $day_events .= _("All day");
-                    } else {
-                        $day_events .= $event->start->strftime($prefs->getValue('twentyFour') ? '%R' : '%I:%M%p') . '-' . $event->end->strftime($prefs->getValue('twentyFour') ? '%R' : '%I:%M%p');
-                    }
-                    $location = $event->getLocation();
-                    $day_events .= ':'
-                        . ($location ? ' (' . htmlspecialchars($location) . ')' : '')
-                        . ' ' . $event->getTitle() . "\n";
-                }
-                $cell = Horde::linkTooltip($url, _("View Day"), '', '', '', $day_events) . $date_ob->mday . '</a>';
-            }
-
-            /* Bold the cell if there are events. */
-            if (!empty($all_events[$date_stamp])) {
-                $cell = '<strong>' . $cell . '</strong>';
-            }
-
-            $html .= $cell . '</td>';
-            ++$weekday;
-        }
-
-        return $html . '</tr></table>';
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Block/Prevmonthlist.php 4.2.27-1/kronolith-4.2.23/lib/Block/Prevmonthlist.php
--- 4.2.23-1/kronolith-4.2.23/lib/Block/Prevmonthlist.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Block/Prevmonthlist.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,191 +0,0 @@
-<?php
-/**
- * Display a list of previous calendar items grouped by month.
- */
-class Kronolith_Block_Prevmonthlist extends Horde_Core_Block
-{
-    /**
-     */
-    public function __construct($app, $params = array())
-    {
-        parent::__construct($app, $params);
-
-        $this->_name = _("Prior Events");
-    }
-
-    /**
-     */
-    protected function _params()
-    {
-        $params = array(
-            'calendar' => array(
-                'name' => _("Calendar"),
-                'type' => 'enum',
-                'default' => '__all'
-            ),
-            'months' => array(
-                'name' => _("Months Before"),
-                'type' => 'int',
-                'default' => 2
-            ),
-            'alarms' => array(
-                'name' => _("Show only events that have an alarm set?"),
-                'type' => 'checkbox',
-                'default' => 0
-            )
-        );
-
-        $params['calendar']['values']['__all'] = _("All Visible");
-        foreach (Kronolith::listCalendars(Horde_Perms::SHOW, true) as $id => $cal) {
-            $params['calendar']['values'][$id] = $cal->name();
-        }
-
-        return $params;
-    }
-
-    /**
-     */
-    protected function _title()
-    {
-        $url = Horde::url($GLOBALS['registry']->getInitialPage(), true);
-        if (isset($this->_params['calendar']) &&
-            $this->_params['calendar'] != '__all') {
-            $url->add('display_cal', $this->_params['calendar']);
-        }
-
-        return $url->link() . $this->getName() . '</a>';
-    }
-
-    /**
-     */
-    protected function _content()
-    {
-        global $calendar_manager, $from_block, $page_output;
-
-        $from_block = true;
-
-        $page_output->addScriptFile('tooltips.js', 'horde');
-
-        $startDate = new Horde_Date(array(
-            'year' => date('Y'),
-            'month' => date('n') - $this->_params['months'],
-            'mday' => date('j')
-        ));
-        $endDate = new Horde_Date(array(
-            'year' => date('Y'),
-            'month' => date('n'),
-            'mday' => date('j') - 1
-        ));
-
-        $current_month = '';
-
-        try {
-            if (isset($this->_params['calendar']) &&
-                $this->_params['calendar'] != '__all') {
-                $calendars = Kronolith::listCalendars();
-                if (!isset($calendars[$this->_params['calendar']])) {
-                    return _("Calendar not found");
-                }
-                if (!$calendars[$this->_params['calendar']]->hasPermission(Horde_Perms::READ)) {
-                    return _("Permission Denied");
-                }
-                list($type, $calendar) = explode('_', $this->_params['calendar']);
-                $driver = Kronolith::getDriver($type, $calendar);
-                $all_events = $driver->listEvents(
-                    $startDate, $endDate, array('show_recurrence' => true));
-            } else {
-                $all_events = Kronolith::listEvents(
-                    $startDate,
-                    $endDate,
-                    $calendar_manager->get(Kronolith::DISPLAY_CALENDARS)
-                );
-            }
-        } catch (Exception $e) {
-            return '<em>' . $e->getMessage() . '</em>';
-        }
-
-        $html = '';
-
-        /* How many days do we need to check. */
-        $days = Date_Calc::dateDiff(
-            $startDate->mday, $startDate->month, $startDate->year,
-            $endDate->mday, $endDate->month, $endDate->year
-        );
-
-        /* Loop through the days. */
-        for ($i = 0; $i <= $days; ++$i) {
-            $day = new Kronolith_Day(
-                $startDate->month, $startDate->mday + $i, $startDate->year
-            );
-            if (empty($all_events[$day->dateString()])) {
-                continue;
-            }
-
-            /* Output month header. */
-            if ($current_month != $day->month) {
-                $current_month = $day->strftime('%m');
-                $html .= '<tr><td colspan="4" class="control"><strong>'
-                    . $day->strftime('%B') . '</strong></td></tr>';
-            }
-
-            $firstevent = true;
-            $tomorrow = $day->getTomorrow();
-            foreach ($all_events[$day->dateString()] as $event) {
-
-                if ($event->start->compareDate($day) < 0) {
-                    $event->start = new Horde_Date($day);
-                }
-                if ($event->end->compareDate($tomorrow) >= 0) {
-                    $event->end = $tomorrow;
-                }
-
-                if ($event->end->compareDate($startDate) < 0) {
-                    continue;
-                }
-
-                if ($this->_params['alarms'] && !$event->alarm) {
-                    continue;
-                }
-                if ($firstevent) {
-                    $html .= '<tr><td class="text" valign="top" align="right"><strong>';
-                    if ($day->isToday()) {
-                        $html .= _("Today");
-                    } elseif ($day->isTomorrow()) {
-                        $html .= _("Tomorrow");
-                    } else {
-                        $html .= $day->mday;
-                    }
-                    $html .= '</strong>&nbsp;</td>';
-                    $firstevent = false;
-                } else {
-                    $html .= '<tr><td class="text">&nbsp;</td>';
-                }
-
-                $html .= '<td class="text" nowrap="nowrap" valign="top">';
-                if ($event->start->compareDate($startDate) < 0 &&
-                    $event->end->compareDate($startDate) > 0) {
-                    $html .= '<strong>'
-                        . htmlspecialchars($event->getLocation()) . '</strong>';
-                } else {
-                    $html .= htmlspecialchars($event->getLocation());
-                }
-                if ($event->start->compareDate($startDate) < 0 &&
-                    $event->end->compareDate($startDate) > 0) {
-                    $html .= '<strong>';
-                }
-                $html .= $event->getLink(null, true, null, true);
-                if ($event->start->compareDate($startDate) < 0 &&
-                    $event->end->compareDate($startDate) > 0) {
-                    $html .= '</strong>';
-                }
-                $html .= '</td></tr>';
-            }
-        }
-
-        if (empty($html)) {
-            return '<em>' . _("No events to display") . '</em>';
-        }
-
-        return '<table cellspacing="0" width="100%">' . $html . '</table>';
-    }
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Block/Summary.php 4.2.27-1/kronolith-4.2.23/lib/Block/Summary.php
--- 4.2.23-1/kronolith-4.2.23/lib/Block/Summary.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Block/Summary.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,211 +0,0 @@
-<?php
-/**
- * Block to display a summary of calendar items.
- */
-class Kronolith_Block_Summary extends Horde_Core_Block
-{
-    /**
-     */
-    public function __construct($app, $params = array())
-    {
-        parent::__construct($app, $params);
-
-        if (!isset($this->_params['days'])) {
-            $this->_params['days'] = 7;
-        }
-
-        $this->_name = _("Calendar Summary");
-    }
-
-    /**
-     */
-    protected function _params()
-    {
-        $params = array(
-            'calendar' => array(
-                'name' => _("Calendar"),
-                'type' => 'enum',
-                'default' => '__all'
-            ),
-            'days' => array(
-                'name' => _("The time span to show"),
-                'type' => 'enum',
-                'default' => 7,
-                'values' => array(
-                    1 => '1 ' . _("day"),
-                    2 => '2 ' . _("days"),
-                    3 => '3 ' . _("days"),
-                    4 => '4 ' . _("days"),
-                    5 => '5 ' . _("days"),
-                    6 => '6 ' . _("days"),
-                    7 => '1 ' . _("week"),
-                    14 => '2 ' . _("weeks"),
-                    21 => '3 ' . _("weeks"),
-                    28 => '4 ' . _("weeks")
-                )
-            ),
-            'maxevents' => array(
-                'name' => _("Maximum number of events to display (0 = no limit)"),
-                'type' => 'int',
-                'default' => 0
-            ),
-            'alarms' => array(
-                'name' => _("Show only events that have an alarm set?"),
-                'type' => 'checkbox',
-                'default' => 0
-            )
-        );
-
-        $params['calendar']['values']['__all'] = _("All Visible");
-        foreach (Kronolith::listCalendars(Horde_Perms::SHOW, true) as $id => $cal) {
-            $params['calendar']['values'][$id] = $cal->name();
-        }
-
-        return $params;
-    }
-
-    /**
-     */
-    protected function _title()
-    {
-        $url = Horde::url($GLOBALS['registry']->getInitialPage(), true);
-        if (isset($this->_params['calendar']) &&
-            $this->_params['calendar'] != '__all') {
-            $url->add('display_cal', $this->_params['calendar']);
-        }
-        return $url->link()
-            . htmlspecialchars($GLOBALS['registry']->get('name')) . '</a>';
-    }
-
-    /**
-     */
-    protected function _content()
-    {
-        $GLOBALS['page_output']->addScriptFile('tooltips.js', 'horde');
-
-        $now = new Horde_Date($_SERVER['REQUEST_TIME']);
-        $today = date('j');
-
-        $startDate = new Horde_Date(array('year' => date('Y'), 'month' => date('n'), 'mday' => date('j')));
-        $endDate = new Horde_Date(array('year' => date('Y'), 'month' => date('n'), 'mday' => date('j') + $this->_params['days']));
-
-        try {
-            if (isset($this->_params['calendar']) &&
-                $this->_params['calendar'] != '__all') {
-                $calendars = Kronolith::listCalendars();
-                if (!isset($calendars[$this->_params['calendar']])) {
-                    return _("Calendar not found");
-                }
-                if (!$calendars[$this->_params['calendar']]->hasPermission(Horde_Perms::READ)) {
-                    return _("Permission Denied");
-                }
-                list($type, $calendar) = explode('_', $this->_params['calendar'], 2);
-                $driver = Kronolith::getDriver($type, $calendar);
-                $all_events = Kronolith::sortEvents(
-                    $driver->listEvents(
-                        $startDate, $endDate, array('show_recurrence' => true))
-                );
-            } else {
-                $all_events = Kronolith::listEvents(
-                    $startDate, $endDate, $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS));
-            }
-        } catch (Exception $e) {
-            return '<em>' . $e->getMessage() . '</em>';
-        }
-
-        $html = '';
-        $iMax = $today + $this->_params['days'];
-        $firstday = true;
-        $totalevents = 0;
-        for ($i = $today; $i < $iMax; ++$i) {
-            $day = new Kronolith_Day(date('n'), $i);
-            $date_stamp = $day->dateString();
-            if (empty($all_events[$date_stamp])) {
-                continue;
-            }
-
-            $firstevent = true;
-            $tomorrow = $day->getTomorrow();
-            foreach ($all_events[$date_stamp] as $event) {
-                if (!empty($this->_params['maxevents']) &&
-                    $totalevents >= $this->_params['maxevents']) {
-                    break 2;
-                }
-
-                if ($event->start->compareDate($day) < 0) {
-                    $event->start = $day;
-                }
-                if ($event->end->compareDate($tomorrow) >= 0) {
-                    $event->end = $tomorrow;
-                }
-                if ($event->end->compareDateTime($now) < 0) {
-                    continue;
-                }
-
-                if (!empty($this->_params['alarms']) && !$event->alarm) {
-                    continue;
-                }
-                $event_active = $event->start->compareDateTime($now) < 0 &&
-                    $event->end->compareDateTime($now) > 0;
-
-                if ($firstevent) {
-                    $html .= '<tr><td colspan="3" class="control"><strong>';
-                    if ($day->isToday()) {
-                        $dayname = _("Today");
-                    } elseif ($day->isTomorrow()) {
-                        $dayname = _("Tomorrow");
-                    } elseif ($day->diff() < 7) {
-                        $dayname = $day->strftime('%A');
-                    } else {
-                        $dayname = $day->strftime($GLOBALS['prefs']->getValue('date_format'));
-                    }
-                    $url = Horde::url('day.php', true)
-                        ->setRaw(false)
-                        ->add('date', $day->dateString());
-                    if (isset($this->_params['calendar']) &&
-                        $this->_params['calendar'] != '__all') {
-                        $url->add('display_cal', $this->_params['calendar']);
-                    }
-                    $html .= $url->link(array('title' => sprintf(_("Goto %s"), $dayname)))
-                        . $dayname . '</a></strong></td></tr>';
-                    $firstevent = false;
-                    $firstday = false;
-                }
-                $html .= '<tr class="linedRow"><td class="text nowrap" valign="top">';
-                if ($event_active) {
-                    $html .= '<strong>';
-                }
-                if ($event->isAllDay()) {
-                    $time = _("All day");
-                } else {
-                    $time = $event->start->format($GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:ia')
-                        . '-' . $event->end->format($GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:ia');
-                }
-                $html .= $time;
-                if ($event_active) {
-                    $html .= '</strong>';
-                }
-                $html .= '&nbsp;</td>';
-
-                $html .= '<td class="text" valign="top"'
-                    . $event->getCSSColors() . '>';
-                if ($event_active) {
-                    $html .= '<strong>';
-                }
-                $html .= ' ' . $event->getLink(null, true, null, true, true);
-                if ($event_active) {
-                    $html .= '</strong>';
-                }
-                $html .= '</td></tr>';
-                $totalevents++;
-            }
-        }
-
-        if (empty($html)) {
-            return '<em>' . _("No events to display") . '</em>';
-        }
-
-        return '<table cellspacing="0" width="100%">' . $html . '</table>';
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Calendar/External/Tasks.php 4.2.27-1/kronolith-4.2.23/lib/Calendar/External/Tasks.php
--- 4.2.23-1/kronolith-4.2.23/lib/Calendar/External/Tasks.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Calendar/External/Tasks.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,99 +0,0 @@
-<?php
-/**
- * Kronolith_Calendar_External_Tasks defines an API for single task lists.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-class Kronolith_Calendar_External_Tasks extends Kronolith_Calendar_External
-{
-    /**
-     * The share of this task list.
-     *
-     * @var Horde_Share_Object
-     */
-    protected $_share;
-
-    /**
-     * Constructor.
-     *
-     * @param array $params  A hash with any parameters that this calendar
-     *                       might need.
-     *                       Required parameters:
-     *                       - share: The share of this calendar.
-     */
-    public function __construct($params = array())
-    {
-        if (!isset($params['share'])) {
-            throw new BadMethodCallException('share parameter is missing');
-        }
-        Kronolith_Calendar::__construct($params);
-    }
-
-    /**
-     * Returns a hash representing this calendar.
-     *
-     * @return array  A simple hash.
-     */
-    public function toHash()
-    {
-        global $calendar_manager, $conf, $injector, $registry;
-
-        $owner = $registry->getAuth() &&
-            $this->_share->get('owner') == $registry->getAuth();
-
-        $hash = parent::toHash();
-        $hash['name']  = Kronolith::getLabel($this->_share);
-        $hash['desc']  = (string)$this->_share->get('desc');
-        $hash['owner'] = $owner;
-        $hash['users'] = Kronolith::listShareUsers($this->_share);
-        $hash['fg']    = Kronolith::foregroundColor($this->_share);
-        $hash['bg']    = Kronolith::backgroundColor($this->_share);
-        $hash['show']  = in_array(
-            'tasks/' . $this->_share->getName(),
-            $calendar_manager->get(Kronolith::DISPLAY_EXTERNAL_CALENDARS)
-        );
-        $hash['edit']  = $this->_share->hasPermission(
-            $registry->getAuth(),
-            Horde_Perms::EDIT
-        );
-        try {
-            $hash['caldav'] = Horde::url(
-                $registry->get('webroot', 'horde')
-                    . ($conf['urls']['pretty'] == 'rewrite'
-                        ? '/rpc/calendars/'
-                        : '/rpc.php/calendars/'),
-                true,
-                -1
-            )
-                . $registry->convertUsername($registry->getAuth(), false) . '/'
-                . $injector->getInstance('Horde_Dav_Storage')
-                    ->getExternalCollectionId($this->_share->getName(), 'tasks')
-                . '/';
-        } catch (Horde_Exception $e) {
-        }
-        $hash['sub'] = Horde::url(
-            $registry->get('webroot', 'horde')
-                . ($conf['urls']['pretty'] == 'rewrite'
-                    ? '/rpc/nag/'
-                    : '/rpc.php/nag/'),
-            true,
-            -1
-        )
-            . ($this->_share->get('owner')
-                ? $registry->convertUsername($this->_share->get('owner'), false)
-                : '-system-')
-            . '/'
-            . $this->_share->getName() . '.ics';
-        if ($owner) {
-            $hash['perms'] = Kronolith::permissionToJson($this->_share->getPermission());
-        }
-
-        return $hash;
-    }
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Calendar/External.php 4.2.27-1/kronolith-4.2.23/lib/Calendar/External.php
--- 4.2.23-1/kronolith-4.2.23/lib/Calendar/External.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Calendar/External.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,112 +0,0 @@
-<?php
-/**
- * Kronolith_Calendar_External defines an API for single timeobject calendars.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-class Kronolith_Calendar_External extends Kronolith_Calendar
-{
-    /**
-     * The application of this timeobject source.
-     *
-     * @var string
-     */
-    protected $_api;
-
-    /**
-     * The ID of this timeobject source.
-     *
-     * @var string
-     */
-    protected $_id;
-
-    /**
-     * The name of this timeobject source.
-     *
-     * @var string
-     */
-    protected $_name;
-
-    /**
-     * The type of timeobject.
-     * I.e., a single or share type.
-     *
-     * @var string
-     */
-    protected $_type = 'share';
-
-    /**
-     * Constructor.
-     *
-     * @param array $params  A hash with any parameters that this calendar
-     *                       might need.
-     */
-    public function __construct($params = array())
-    {
-        if (!isset($params['name'])) {
-            throw new BadMethodCallException('name parameter is missing');
-        }
-        if (!isset($params['id'])) {
-            throw new BadMethodCallException('id parameter is missing');
-        }
-        if (!isset($params['api'])) {
-            throw new BadMethodCallException('api parameter is missing');
-        }
-        if (!empty($params['type'])) {
-            $this->_type = $params['type'];
-        }
-
-        parent::__construct($params);
-    }
-
-    /**
-     * Returns the name of this calendar.
-     *
-     * @return string  This calendar's name.
-     */
-    public function name()
-    {
-        return $this->_name;
-    }
-
-    /**
-     * Whether this calendar is supposed to be displayed in lists.
-     *
-     * @return boolean  True if this calendar should be displayed.
-     */
-    public function display()
-    {
-        return empty($GLOBALS['conf']['share']['hidden']) ||
-            $this->_type != 'share' ||
-            in_array($this->_api . '/' . $this->_id, $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_EXTERNAL_CALENDARS));
-    }
-
-    /**
-     * Returns the application of this calendar.
-     *
-     * @return string  This calendar's timeobject application.
-     */
-    public function api()
-    {
-        return $this->_api;
-    }
-
-    /**
-     * Returns a hash representing this calendar.
-     *
-     * @return array  A simple hash.
-     */
-    public function toHash()
-    {
-        $hash = parent::toHash();
-        $hash['api']  = $GLOBALS['registry']->get('name', $GLOBALS['registry']->hasInterface($this->api()));
-        $hash['show'] = in_array($this->_api . '/' . $this->_id, $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_EXTERNAL_CALENDARS));
-        return $hash;
-    }
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Calendar/Holiday.php 4.2.27-1/kronolith-4.2.23/lib/Calendar/Holiday.php
--- 4.2.23-1/kronolith-4.2.23/lib/Calendar/Holiday.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Calendar/Holiday.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,69 +0,0 @@
-<?php
-/**
- * Kronolith_Calendar_Holiday defines an API for single holiday calendars.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-class Kronolith_Calendar_Holiday extends Kronolith_Calendar
-{
-    /**
-     * The Date_Holidays driver information.
-     *
-     * @var array
-     */
-    protected $_driver;
-
-    /**
-     * Constructor.
-     *
-     * @param array $params  A hash with any parameters that this calendar
-     *                       might need.
-     *                       Required parameters:
-     *                       - share: The share of this calendar.
-     */
-    public function __construct($params = array())
-    {
-        if (!isset($params['driver'])) {
-            throw new BadMethodCallException('driver parameter is missing');
-        }
-        parent::__construct($params);
-    }
-
-    /**
-     * Returns the name of this calendar.
-     *
-     * @return string  This calendar's name.
-     */
-    public function name()
-    {
-        return Horde_Nls_Translation::t($this->_driver['title']);
-    }
-
-    /**
-     * Whether this calendar is supposed to be displayed in lists.
-     *
-     * @return boolean  True if this calendar should be displayed.
-     */
-    public function display()
-    {
-        return in_array($this->_driver['id'], $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_HOLIDAYS));
-    }
-
-    /**
-     * Returns a hash representing this calendar.
-     *
-     * @return array  A simple hash.
-     */
-    public function toHash()
-    {
-        $hash = parent::toHash();
-        $hash['show'] = $this->display();
-        return $hash;
-    }
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Calendar/Internal.php 4.2.27-1/kronolith-4.2.23/lib/Calendar/Internal.php
--- 4.2.23-1/kronolith-4.2.23/lib/Calendar/Internal.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Calendar/Internal.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,181 +0,0 @@
-<?php
-/**
- * Kronolith_Calendar_Internal defines an API for single internal (share)
- * calendars.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-class Kronolith_Calendar_Internal extends Kronolith_Calendar
-{
-    /**
-     * The share of this calendar.
-     *
-     * @var Horde_Share_Object
-     */
-    protected $_share;
-
-    /**
-     * Constructor.
-     *
-     * @param array $params  A hash with any parameters that this calendar
-     *                       might need.
-     *                       Required parameters:
-     *                       - share: The share of this calendar.
-     */
-    public function __construct($params = array())
-    {
-        if (!isset($params['share'])) {
-            throw new BadMethodCallException('share parameter is missing');
-        }
-        if (!($params['share'] instanceof Horde_Share_Object)) {
-            throw new InvalidArgumentException('share parameter is not a Horde_Share_Object');
-        }
-        parent::__construct($params);
-    }
-
-    /**
-     * Returns the owner of this calendar.
-     *
-     * @return string  This calendar's owner.
-     */
-    public function owner()
-    {
-        return $this->_share->get('owner');
-    }
-
-    /**
-     * Returns the name of this calendar.
-     *
-     * @return string  This calendar's name.
-     */
-    public function name()
-    {
-        return Kronolith::getLabel($this->_share);
-    }
-
-    /**
-     * Returns the description of this calendar.
-     *
-     * @return string  This calendar's description.
-     */
-    public function description()
-    {
-        return $this->_share->get('desc');
-    }
-
-    /**
-     * Returns the background color for this calendar.
-     *
-     * @return string  A HTML color code.
-     */
-    public function background()
-    {
-        $color = $this->_share->get('color');
-        return empty($color) ? parent::background() : $color;
-    }
-
-    /**
-     * Encapsulates permissions checking.
-     *
-     * @param integer $permission  The permission to check for.
-     * @param string $user         The user to check permissions for. Defaults
-     *                             to the current user.
-     * @param string $creator      An event creator, to check for creator
-     *                             permissions.
-     *
-     * @return boolean  Whether the user has the permission on this calendar.
-     */
-    public function hasPermission($permission, $user = null, $creator = null)
-    {
-        if ($user === null) {
-            $user = $GLOBALS['registry']->getAuth();
-        }
-        return $this->_share->hasPermission($user, $permission, $creator);
-    }
-
-    /**
-     * Whether this calendar is supposed to be displayed in lists.
-     *
-     * @return boolean  True if this calendar should be displayed.
-     */
-    public function display()
-    {
-        return $this->owner() == $GLOBALS['registry']->getAuth() ||
-            empty($GLOBALS['conf']['share']['hidden']) ||
-            in_array($this->_share->getName(), $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS));
-    }
-
-    /**
-     * Returns the share of this calendar.
-     *
-     * @return Horde_Share_Object  This calendar's share.
-     */
-    public function share()
-    {
-        return $this->_share;
-    }
-
-    /**
-     * Returns a hash representing this calendar.
-     *
-     * @return array  A simple hash.
-     */
-    public function toHash()
-    {
-        global $calendar_manager, $conf, $injector, $registry;
-
-        $id = $this->_share->getName();
-        $owner = $registry->getAuth() &&
-            $this->owner() == $registry->getAuth();
-
-        $hash = parent::toHash();
-        $hash['name']  = $this->name();
-        $hash['owner'] = $owner;
-        $hash['users'] = Kronolith::listShareUsers($this->_share);
-        $hash['show']  = in_array(
-            $id,
-            $calendar_manager->get(Kronolith::DISPLAY_CALENDARS)
-        );
-        $hash['edit']  = $this->hasPermission(Horde_Perms::EDIT);
-        $hash['delete']  = $this->hasPermission(Horde_Perms::DELETE);
-        try {
-            $hash['caldav'] = Horde::url(
-                $registry->get('webroot', 'horde')
-                    . ($conf['urls']['pretty'] == 'rewrite'
-                        ? '/rpc/calendars/'
-                        : '/rpc.php/calendars/'),
-                true,
-                -1
-            )
-                . $registry->convertUsername($registry->getAuth(), false) . '/'
-                . $injector->getInstance('Horde_Dav_Storage')
-                    ->getExternalCollectionId($id, 'calendar')
-                . '/';
-        } catch (Horde_Exception $e) {
-        }
-        $hash['sub'] = Horde::url(
-            $registry->get('webroot', 'horde')
-                . ($conf['urls']['pretty'] == 'rewrite'
-                    ? '/rpc/kronolith/'
-                    : '/rpc.php/kronolith/'),
-            true,
-            -1
-        )
-            . ($this->owner() ? $registry->convertUsername($this->owner(), false) : '-system-') . '/'
-            . $id . '.ics';
-        $hash['feed']  = (string)Kronolith::feedUrl($id);
-        $hash['embed'] = Kronolith::embedCode($id);
-        $hash['tg']    = array_values(Kronolith::getTagger()->getTags($id, Kronolith_Tagger::TYPE_CALENDAR));
-        if ($owner) {
-            $hash['perms'] = Kronolith::permissionToJson($this->_share->getPermission());
-        }
-
-        return $hash;
-    }
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Calendar/Remote.php 4.2.27-1/kronolith-4.2.23/lib/Calendar/Remote.php
--- 4.2.23-1/kronolith-4.2.23/lib/Calendar/Remote.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Calendar/Remote.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,180 +0,0 @@
-<?php
-/**
- * Kronolith_Calendar_Remote defines an API for single external WebDAV or
- * CalDAV calendars.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-class Kronolith_Calendar_Remote extends Kronolith_Calendar
-{
-    /**
-     * The URL of this calendar.
-     *
-     * @var string
-     */
-    protected $_url;
-
-    /**
-     * The name of this calendar.
-     *
-     * @var string
-     */
-    protected $_name;
-
-    /**
-     * The description of this calendar.
-     *
-     * @var string
-     */
-    protected $_desc = '';
-
-    /**
-     * The HTTP user name for this calendar.
-     *
-     * @var string
-     */
-    protected $_user;
-
-    /**
-     * The HTTP password for this calendar.
-     *
-     * @var string
-     */
-    protected $_password;
-
-    /**
-     * The color of this calendar.
-     *
-     * @var string
-     */
-    protected $_color;
-
-    /**
-     * Constructor.
-     *
-     * @param array $params  A hash with any parameters that this calendar
-     *                       might need.
-     *                       Required parameters:
-     *                       - share: The share of this calendar.
-     */
-    public function __construct($params = array())
-    {
-        if (!isset($params['url'])) {
-            throw new BadMethodCallException('url parameter is missing');
-        }
-        if (!isset($params['name'])) {
-            throw new BadMethodCallException('name parameter is missing');
-        }
-        $key = $GLOBALS['registry']->getAuthCredential('password');
-        if ($key) {
-            $secret = $GLOBALS['injector']->getInstance('Horde_Secret');
-            if (!empty($params['user'])) {
-                $params['user'] = $secret->read($key, base64_decode($params['user']));
-            }
-            if (!empty($params['password'])) {
-                $params['password'] = $secret->read($key, base64_decode($params['password']));
-            }
-        }
-        parent::__construct($params);
-    }
-
-    /**
-     * Returns the name of this calendar.
-     *
-     * @return string  This calendar's name.
-     */
-    public function name()
-    {
-        return $this->_name;
-    }
-
-    /**
-     * Returns the description of this calendar.
-     *
-     * @return string  This calendar's description.
-     */
-    public function description()
-    {
-        return $this->_desc;
-    }
-
-    /**
-     * Returns the background color for this calendar.
-     *
-     * @return string  A HTML color code.
-     */
-    public function background()
-    {
-        return empty($this->_color) ? parent::background() : $this->_color;
-    }
-
-    /**
-     * Encapsulates permissions checking.
-     *
-     * @param integer $permission  The permission to check for.
-     * @param string $user         The user to check permissions for. Defaults
-     *                             to the current user.
-     * @param string $creator      An event creator, to check for creator
-     *                             permissions.
-     *
-     * @return boolean  Whether the user has the permission on this calendar.
-     */
-    public function hasPermission($permission, $user = null, $creator = null)
-    {
-        return (boolean)(Kronolith::getDriver('Ical', $this->_url)->getPermission() & $permission);
-    }
-
-    /**
-     * Whether this calendar is supposed to be displayed in lists.
-     *
-     * @return boolean  True if this calendar should be displayed.
-     */
-    public function display()
-    {
-        return in_array($this->_url, $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_REMOTE_CALENDARS));
-    }
-
-    /**
-     * Returns the URL of this calendar.
-     *
-     * @return string  This calendar's URL.
-     */
-    public function url()
-    {
-        return $this->_url;
-    }
-
-    /**
-     * Returns the authentication credentials for this calendar.
-     *
-     * @return array  This calendar's credentials.
-     */
-    public function credentials()
-    {
-        if (!empty($this->_user)) {
-            return array('user' => $this->_user, 'password' => $this->_password);
-        }
-        return array();
-    }
-
-    /**
-     * Returns a hash representing this calendar.
-     *
-     * @return array  A simple hash.
-     */
-    public function toHash()
-    {
-        return array_merge(
-            parent::toHash(),
-            array('show' => in_array($this->_url, $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_REMOTE_CALENDARS)),
-                  'edit' => $this->hasPermission(Horde_Perms::EDIT)),
-            $this->credentials()
-        );
-    }
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Calendar/ResourceGroup.php 4.2.27-1/kronolith-4.2.23/lib/Calendar/ResourceGroup.php
--- 4.2.23-1/kronolith-4.2.23/lib/Calendar/ResourceGroup.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Calendar/ResourceGroup.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,129 +0,0 @@
-<?php
-/**
- * Kronolith_Calendar_ResourceGroup defines an API for single internal
- * resourcegroup calendars.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Jan Schneider <jan@horde.org>
- * @author  Michael J Rubinsky <mrubinsk@horde.org>
- * @package Kronolith
- */
-class Kronolith_Calendar_ResourceGroup extends Kronolith_Calendar
-{
-    /**
-     * The share of this calendar.
-     *
-     * @var Kronolith_Resource_Group
-     */
-    protected $_resource;
-
-    /**
-     * Constructor.
-     *
-     * @param array $params  A hash with any parameters that this calendar
-     *                       might need.
-     *                       Required parameters:
-     *                       - share: The share of this calendar.
-     */
-    public function __construct($params = array())
-    {
-        if (!isset($params['resource'])) {
-            throw new BadMethodCallException('resource parameter is missing.');
-        }
-        if (!($params['resource'] instanceof Kronolith_Resource_Group)) {
-            throw new InvalidArgumentException('resource parameter is not a Kronolith_Resource_Single object.');
-        }
-        parent::__construct($params);
-    }
-
-    /**
-     * Returns the owner of this calendar.
-     *
-     * @return string  This calendar's owner.
-     */
-    public function owner()
-    {
-        // @TODO: what to return here? Resources do not currently have owners.
-        return '';
-    }
-
-    /**
-     * Returns the name of this calendar.
-     *
-     * @return string  This calendar's name.
-     */
-    public function name()
-    {
-        return $this->_resource->get('name');
-    }
-
-    /**
-     * Returns the description of this calendar.
-     *
-     * @return string  This calendar's description.
-     */
-    public function description()
-    {
-        return $this->_resource->get('description');
-    }
-
-    /**
-     * Encapsulates permissions checking.
-     *
-     * @param integer $permission  The permission to check for.
-     * @param string $user         The user to check permissions for. Defaults
-     *                             to the current user.
-     * @param string $creator      An event creator, to check for creator
-     *                             permissions.
-     *
-     * @return boolean  Whether the user has the permission on this calendar.
-     */
-    public function hasPermission($permission, $user = null, $creator = null)
-    {
-        return $this->_resource->hasPermission($user, $permission, $creator);
-    }
-
-    /**
-     * Whether this calendar is supposed to be displayed in lists.
-     *
-     * @return boolean  True if this calendar should be displayed.
-     */
-    public function display()
-    {
-        return in_array($this->_resource->get('calendar'), $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS));
-    }
-
-    /**
-     * Returns a hash representing this calendar.
-     *
-     * @return array  A simple hash.
-     */
-    public function toHash()
-    {
-        $owner = $GLOBALS['registry']->isAdmin();
-        $hash = parent::toHash();
-
-        $hash['id']    = $this->_resource->getId();
-        $hash['name']  = $this->name();
-        $hash['owner'] = $owner;
-        $hash['show']  = $this->display();
-        $hash['edit']  = $this->hasPermission(Horde_Perms::EDIT);
-        $hash['sub']   = null;
-        $hash['feed']  = null;
-        $hash['embed'] = null;
-        $hash['members'] = $this->_resource->get('members');
-        if ($owner) {
-            $hash['perms'] = array(
-                'type' => 'matrix',
-                'default' => 0,
-                'guest' => 0,
-                'creator' => 0);
-        }
-        return $hash;
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Calendar/Resource.php 4.2.27-1/kronolith-4.2.23/lib/Calendar/Resource.php
--- 4.2.23-1/kronolith-4.2.23/lib/Calendar/Resource.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Calendar/Resource.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,133 +0,0 @@
-<?php
-/**
- * Kronolith_Calendar_Resource defines an API for single internal resource
- * calendars.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-class Kronolith_Calendar_Resource extends Kronolith_Calendar
-{
-    /**
-     * The share of this calendar.
-     *
-     * @var Kronolith_Resource_Single
-     */
-    protected $_resource;
-
-    /**
-     * Constructor.
-     *
-     * @param array $params  A hash with any parameters that this calendar
-     *                       might need.
-     *                       Required parameters:
-     *                       - share: The share of this calendar.
-     */
-    public function __construct($params = array())
-    {
-        if (!isset($params['resource'])) {
-            throw new BadMethodCallException('resource parameter is missing.');
-        }
-        if (!($params['resource'] instanceof Kronolith_Resource_Single)) {
-            throw new InvalidArgumentException('resource parameter is not a Kronolith_Resource_Single object.');
-        }
-        parent::__construct($params);
-    }
-
-    /**
-     * Returns the owner of this calendar.
-     *
-     * @return string  This calendar's owner.
-     */
-    public function owner()
-    {
-        // @TODO: what to return here? Resources do not currently have owners.
-        return '';
-    }
-
-    /**
-     * Returns the name of this calendar.
-     *
-     * @return string  This calendar's name.
-     */
-    public function name()
-    {
-        return $this->_resource->get('name');
-    }
-
-    /**
-     * Returns the description of this calendar.
-     *
-     * @return string  This calendar's description.
-     */
-    public function description()
-    {
-        return $this->_resource->get('description');
-    }
-
-    /**
-     * Encapsulates permissions checking.
-     *
-     * @param integer $permission  The permission to check for.
-     * @param string $user         The user to check permissions for. Defaults
-     *                             to the current user.
-     * @param string $creator      An event creator, to check for creator
-     *                             permissions.
-     *
-     * @return boolean  Whether the user has the permission on this calendar.
-     */
-    public function hasPermission($permission, $user = null, $creator = null)
-    {
-        return $this->_resource->hasPermission($user, $permission, $creator);
-    }
-
-    /**
-     * Whether this calendar is supposed to be displayed in lists.
-     *
-     * @return boolean  True if this calendar should be displayed.
-     */
-    public function display()
-    {
-        return in_array($this->_resource->get('calendar'), $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_RESOURCE_CALENDARS));
-    }
-
-    /**
-     * Returns a hash representing this calendar.
-     *
-     * @return array  A simple hash.
-     */
-    public function toHash()
-    {
-        global $registry, $injector;
-
-        $owner = $registry->isAdmin() ||
-                 $injector->getInstance('Horde_Core_Perms')
-                    ->hasAppPermission('resource_management');
-
-        $hash = parent::toHash();
-
-        $hash['id']    = $this->_resource->getId();
-        $hash['name']  = $this->name();
-        $hash['owner'] = $owner;
-        $hash['show']  = $this->display();
-        $hash['edit']  = $this->hasPermission(Horde_Perms::EDIT);
-        $hash['sub']   = null;
-        $hash['feed']  = null;
-        $hash['embed'] = null;
-        $hash['response_type'] = $this->_resource->get('response_type');
-        if ($owner) {
-            $hash['perms'] = array(
-                'type' => 'matrix',
-                'default' => 0,
-                'guest' => 0,
-                'creator' => 0);
-        }
-        return $hash;
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Calendar.php 4.2.27-1/kronolith-4.2.23/lib/Calendar.php
--- 4.2.23-1/kronolith-4.2.23/lib/Calendar.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Calendar.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,138 +0,0 @@
-<?php
-/**
- * Kronolith_Calendar defines an API for single calendars.
- *
- * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-abstract class Kronolith_Calendar
-{
-    /**
-     * Constructor.
-     *
-     * @param array $params  A hash with any parameters that this calendar
-     *                       might need.
-     */
-    public function __construct($params = array())
-    {
-        foreach ($params as $param => $value) {
-            $this->{'_' . $param} = $value;
-        }
-    }
-
-    /**
-     * Returns the owner of this calendar.
-     *
-     * @return string  This calendar's owner.
-     */
-    public function owner()
-    {
-        return $GLOBALS['registry']->getAuth();
-    }
-
-    /**
-     * Returns the name of this calendar.
-     *
-     * @return string  This calendar's name.
-     */
-    abstract public function name();
-
-    /**
-     * Returns the description of this calendar.
-     *
-     * @return string  This calendar's description.
-     */
-    public function description()
-    {
-        return '';
-    }
-
-    /**
-     * Returns the background color for this calendar.
-     *
-     * @return string  A HTML color code.
-     */
-    public function background()
-    {
-        return isset($this->_background) ? $this->_background : '#dddddd';
-    }
-
-    /**
-     * Returns the foreground color for this calendar.
-     *
-     * @return string  A HTML color code.
-     */
-    public function foreground()
-    {
-        return Horde_Image::brightness($this->background()) < 128 ? '#fff' : '#000';
-    }
-
-    /**
-     * Returns the CSS color definition for this calendar.
-     *
-     * @param boolean $with_attribute  Whether to wrap the colors inside a
-     *                                 "style" attribute.
-     *
-     * @return string  A CSS string with color definitions.
-     */
-    public function css($with_attribute = true)
-    {
-        $css = 'background-color:' . $this->background() . ';color:' . $this->foreground();
-        if ($with_attribute) {
-            $css = ' style="' . $css . '"';
-        }
-        return $css;
-    }
-
-    /**
-     * Encapsulates permissions checking.
-     *
-     * @param integer $permission  The permission to check for.
-     * @param string $user         The user to check permissions for. Defaults
-     *                             to the current user.
-     * @param string $creator      An event creator, to check for creator
-     *                             permissions.
-     *
-     * @return boolean  Whether the user has the permission on this calendar.
-     */
-    public function hasPermission($permission, $user = null, $creator = null)
-    {
-        switch ($permission) {
-        case Horde_Perms::SHOW:
-        case Horde_Perms::READ:
-            return true;
-
-        default:
-            return false;
-        }
-    }
-
-    /**
-     * Whether this calendar is supposed to be displayed in lists.
-     *
-     * @return boolean  True if this calendar should be displayed.
-     */
-    abstract public function display();
-
-    /**
-     * Returns a hash representing this calendar.
-     *
-     * @return array  A simple hash.
-     */
-    public function toHash()
-    {
-        return array(
-            'name'  => $this->name(),
-            'desc'  => $this->description(),
-            'owner' => true,
-            'users' => array(),
-            'fg'    => $this->foreground(),
-            'bg'    => $this->background(),
-        );
-    }
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Calendars/Base.php 4.2.27-1/kronolith-4.2.23/lib/Calendars/Base.php
--- 4.2.23-1/kronolith-4.2.23/lib/Calendars/Base.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Calendars/Base.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,109 +0,0 @@
-<?php
-/**
- * The base functionality of the calendars handler.
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Gunnar Wrobel <wrobel@pardus.de>
- * @package Kronolith
- */
-abstract class Kronolith_Calendars_Base
-{
-    /**
-     * The share backend.
-     *
-     * @var Horde_Share_Base
-     */
-    protected $_shares;
-
-    /**
-     * The current user.
-     *
-     * @var string
-     */
-    protected $_user;
-
-    /**
-     * Additional parameters for the tasklist handling.
-     *
-     * @var array
-     */
-    protected $_params;
-
-    /**
-     * Constructor.
-     *
-     * @param Horde_Share_Base $shares The share backend.
-     * @param string           $user   The current user.
-     * @param array            $params Additional parameters.
-     */
-    public function __construct($shares, $user, $params)
-    {
-        $this->_shares = $shares;
-        $this->_user = $user;
-        $this->_params = $params;
-    }
-
-    /**
-     * Create the default calendar share for the current user.
-     *
-     * @return Horde_Share_Object The new default share.
-     */
-    public function createDefaultShare()
-    {
-        $share = $this->_shares->newShare(
-            $this->_user,
-            strval(new Horde_Support_Randomid()),
-            $this->_getDefaultShareName()
-        );
-        $share->set('color', Kronolith::randomColor());
-        $this->_prepareDefaultShare($share);
-        $this->_shares->addShare($share);
-        return $share;
-    }
-
-    /**
-     * Returns the default share's ID, if it can be determined from the share
-     * backend.
-     *
-     * @return string  The default share ID.
-     */
-    public function getDefaultShare()
-    {
-        $shares = $this->_shares->listShares(
-            $this->_user,
-            array('attributes' => $this->_user)
-        );
-        foreach ($shares as $id => $share) {
-            if ($share->get('default')) {
-                return $id;
-            }
-        }
-    }
-
-    /**
-     * Runs any actions after setting a new default tasklist.
-     *
-     * @param string $share  The default share ID.
-     */
-    public function setDefaultShare($share)
-    {
-    }
-
-    /**
-     * Return the name of the default share.
-     *
-     * @return string The name of a default share.
-     */
-    abstract protected function _getDefaultShareName();
-
-    /**
-     * Add any modifiers required to the share in order to mark it as default
-     *
-     * @param Horde_Share_Object $share The new default share.
-     */
-    protected function _prepareDefaultShare($share)
-    {
-    }
-}
\ No newline at end of file
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Calendars/Default.php 4.2.27-1/kronolith-4.2.23/lib/Calendars/Default.php
--- 4.2.23-1/kronolith-4.2.23/lib/Calendars/Default.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Calendars/Default.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,47 +0,0 @@
-<?php
-/**
- * The default calendars handler.
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Gunnar Wrobel <wrobel@pardus.de>
- * @package Kronolith
- */
-class Kronolith_Calendars_Default extends Kronolith_Calendars_Base
-{
-    /**
-     * The current identity.
-     *
-     * @var Horde_Prefs_Identity
-     */
-    private $_identity;
-
-    /**
-     * Constructor.
-     *
-     * @param Horde_Share_Base $shares The share backend.
-     * @param string           $user   The current user.
-     * @param array            $params Additional parameters.
-     */
-    public function __construct($shares, $user, $params)
-    {
-        if (!isset($params['identity'])) {
-            throw new BadMethodCallException('This calendars handler needs an "identity" parameter!');
-        } else {
-            $this->_identity = $params['identity'];
-            unset($params['identity']);
-        }
-        parent::__construct($shares, $user, $params);
-    }
-
-    /**
-     * Return the name of the default share.
-     *
-     * @return string The name of a default share.
-     */
-    protected function _getDefaultShareName()
-    {
-        return sprintf(_("Calendar of %s"), $this->_identity->getName());
-    }
-}
\ No newline at end of file
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Calendars/Kolab.php 4.2.27-1/kronolith-4.2.23/lib/Calendars/Kolab.php
--- 4.2.23-1/kronolith-4.2.23/lib/Calendars/Kolab.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Calendars/Kolab.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,53 +0,0 @@
-<?php
-/**
- * The Kolab specific calendars handler.
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Gunnar Wrobel <wrobel@pardus.de>
- * @package Kronolith
- */
-class Kronolith_Calendars_Kolab extends Kronolith_Calendars_Base
-{
-    /**
-     * Runs any actions after setting a new default calendar.
-     *
-     * @param string $share  The default share ID.
-     */
-    public function setDefaultShare($share)
-    {
-           $calendars = $this->_shares
-               ->listShares(
-                   $this->_user,
-                   array('perm' => Horde_Perms::SHOW,
-                         'attributes' => $this->_user));
-           foreach ($calendars as $id => $calendar) {
-               if ($id == $share) {
-                   $calendar->set('default', true);
-                   $calendar->save();
-                   break;
-               }
-           }
-    }
-
-    /**
-     * Return the name of the default share.
-     *
-     * @return string The name of a default share.
-     */
-    protected function _getDefaultShareName()
-    {
-        return _("Calendar");
-    }
-
-    /**
-     * Add any modifiers required to the share in order to mark it as default
-     *
-     * @param Horde_Share_Object $share The new default share.
-     */
-    protected function _prepareDefaultShare($share)
-    {
-        $share->set('default', true);
-    }
-}
\ No newline at end of file
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/CalendarsManager.php 4.2.27-1/kronolith-4.2.23/lib/CalendarsManager.php
--- 4.2.23-1/kronolith-4.2.23/lib/CalendarsManager.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/CalendarsManager.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,621 +0,0 @@
-<?php
-/**
- * Copyright 2013-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author   Michael J Rubinsky <mrubinsk@horde.org>
- * @license  http://www.horde.org/licenses/gpl GPL
- * @package  Kronolith
- * @category Horde
- */
-
-/**
- * Handles management of the various global calendar lists.
- *
- * @author   Michael J Rubinsky <mrubinsk@horde.org>
- * @license  http://www.horde.org/licenses/gpl GPL
- * @package  Kronolith
- * @category Horde
- */
-class Kronolith_CalendarsManager
-{
-    /**
-     * List of all available internal calendars.
-     *
-     * @var array
-     */
-    protected $_allCalendars = array();
-
-    /**
-     * List of all available remote calendars.
-     *
-     * @var array
-     */
-    protected $_allRemote = false;
-
-    /**
-     * List of all available external calendars.
-     *
-     * @var array
-     */
-    protected $_allExternal = false;
-
-    /**
-     * List of all available Holiday calendars.
-     *
-     * @var array
-     */
-    protected $_allHolidays = false;
-
-    /**
-     * List of all internal calendars that are currently selected to be visible.
-     *
-     * @var array
-     */
-    protected $_displayCalendars;
-
-    /**
-     * List of remote calendars selected for display.
-     *
-     * @var array
-     */
-    protected $_displayRemote;
-
-    /**
-     * List of resource calendars selected for display.
-     *
-     * @var array
-     */
-    protected $_displayResource;
-
-    /**
-     * Lazy loaded list of all resource calendars.
-     *
-     * @var array.
-     */
-    protected $_allResource = false;
-
-    /**
-     * List of holiday calendars selected for display. Used internally to hold
-     * the user prefs for displayed holiday calendars before we need to see
-     * if they are all available.
-     *
-     * @var array
-     */
-    protected $_displayHolidaysInternal = array();
-
-    /**
-     * List of all holidays selected for display.
-     *
-     * @var array
-     */
-    protected $_displayHolidays = false;
-
-    /**
-     * List of external (listTimeObjects) calendars selected for display.
-     *
-     * @var array
-     */
-    protected $_displayExternal = false;
-
-    /**
-     * Const'r
-     * Sets up various display lists and session variables:
-     *
-     * Always set:
-     *  - allCalendars
-     *  - displayCalendars
-     *  - displayResource
-     *
-     * Lazy loaded:
-     *  - allRemote
-     *  - allExternal
-     *  - allHolidays
-     *  - allResource
-     *  - displayRemote
-     *  - displayExternal
-     *  - displayHolidays
-     *
-     * @param string $user  The user to initialize for, if not the current.
-     *                      @since 4.2.4
-     */
-    public function __construct($user = null)
-    {
-        $emptyUser = false;
-        if (empty($user)) {
-            $user = $GLOBALS['registry']->getAuth();
-            $emptyUser = true;
-        }
-        // Always perform the display related checks.
-        $this->_checkDisplayCals();
-        $this->_checkToggleCalendars();
-
-        // Check that all selected shares still exist.
-        foreach (Kronolith::listInternalCalendars(false, Horde_Perms::SHOW, $user) as $id => $calendar) {
-            $this->_allCalendars[$id] = new Kronolith_Calendar_Internal(array('share' => $calendar));
-        }
-        $this->_displayCalendars = array_intersect($this->_displayCalendars, array_keys($this->_allCalendars));
-
-        // Check that the user owns a calendar if we aren't loading a different
-        // user.
-        if ($emptyUser) {
-            $this->_checkForOwnedCalendar();
-        }
-    }
-
-    /**
-     * Return the requested list.
-     *
-     * @param string $list  A Kronolith:: calendar manager constant.
-     *
-     * @return array
-     */
-    public function get($list)
-    {
-        switch ($list) {
-        case Kronolith::ALL_CALENDARS:
-        case Kronolith::DISPLAY_CALENDARS:
-        case Kronolith::DISPLAY_RESOURCE_CALENDARS:
-            $property = '_' . $list;
-            return $this->$property;
-
-        //Lazy loaded
-        case Kronolith::ALL_RESOURCE_CALENDARS:
-            if ($this->_allResource !== false) {
-                return $this->_allResource;
-            }
-            return $this->_getAllResource();
-
-        case Kronolith::ALL_REMOTE_CALENDARS:
-            if ($this->_allRemote !== false) {
-                return $this->_allRemote;
-            }
-            return $this->_getRemoteCalendars();
-
-        case Kronolith::DISPLAY_REMOTE_CALENDARS:
-            // Need to run this at least once to validate remote calendars
-            // still exist in prefs.
-            if ($this->_allRemote === false) {
-                $this->_getRemoteCalendars();
-            }
-            return $this->_displayRemote;
-
-        case Kronolith::ALL_EXTERNAL_CALENDARS:
-            if ($this->_allExternal !== false) {
-                return $this->_allExternal;
-            }
-            return $this->_getAllExternal();
-
-        case Kronolith::DISPLAY_EXTERNAL_CALENDARS:
-            if ($this->_displayExternal !== false) {
-                return $this->_displayExternal;
-            }
-            return $this->_getDisplayExternal();
-
-        case Kronolith::ALL_HOLIDAYS:
-            if ($this->_allHolidays !== false) {
-                return $this->_allHolidays;
-            }
-            return $this->_getAllHolidays();
-
-        case Kronolith::DISPLAY_HOLIDAYS:
-            if ($this->_displayHolidays !== false) {
-                return $this->_displayHolidays;
-            }
-            return $this->_getDisplayHolidays();
-        }
-    }
-
-    /**
-     * Set or replace an existing list with $value.
-     *
-     * @param string $list   The list to set.
-     * @param array  $value  The value to set it to.
-     */
-    public function set($list, array $value)
-    {
-        $property = '_' . $list;
-        $this->$property = $value;
-    }
-
-    /**
-     * Shortcut method for obtaining a single entry in one of the calendar lists
-     *
-     * @param string $list   The calendar list to obtain an entry from.
-     * @param string $entry  The entry to retrieve.
-     *
-     * @return mixed  The requested value | false if not found.
-     */
-    public function getEntry($list, $entry)
-    {
-        $temp = $this->get($list);
-        if (isset($temp[$entry])) {
-            return $temp[$entry];
-        }
-
-        return false;
-    }
-
-    /**
-     * Update display preferences
-     */
-    protected function _checkDisplayCals()
-    {
-        global $session, $prefs, $conf;
-
-        // Update preferences for which calendars to display. If the
-        // user doesn't have any selected calendars to view then fall
-        // back to an available calendar. An empty string passed in this
-        // parameter will clear any existing session value.
-        if (($calId = Horde_Util::getFormData('display_cal')) !== null) {
-            $session->set('kronolith', 'display_cal', $calId);
-        } else {
-            $calId = $session->get('kronolith', 'display_cal');
-        }
-
-        if (strlen($calId)) {
-            // Specifying a value for display_cal is always to make sure
-            // that only the specified calendars are shown. Use the
-            // "toggle_calendar" argument to toggle the state of a single
-            // calendar.
-            $this->_displayCalendars = array();
-            $this->_displayRemote = array();
-            $this->_displayExternal = array();
-            $this->_displayResource = array();
-            $this->_displayHolidaysInternal = array();
-
-            if (strncmp($calId, 'remote_', 7) === 0) {
-                $this->_displayRemote[] = substr($calId, 7);
-            } elseif (strncmp($calId, 'external_', 9) === 0) {
-                $this->_displayExternal[] = substr($calId, 9);
-            } elseif (strncmp($calId, 'resource_', 9) === 0) {
-                $this->_displayResource[] = substr($calId, 9);
-            } elseif (strncmp($calId, 'holidays_', 9) === 0) {
-                $this->_displayHolidaysInternal[] = substr($calId, 9);
-            } else {
-                $this->_displayCalendars[] = (strncmp($calId, 'internal_', 9) === 0)
-                    ? substr($calId, 9)
-                    : $calId;
-            }
-        } else {
-            // Fetch display preferences.
-            $display_prefs = array(
-                'display_cals' => 'displayCalendars',
-                'display_remote_cals' => 'displayRemote',
-                'display_external_cals' => 'displayExternal',
-                'holiday_drivers' => 'displayHolidaysInternal',
-                'display_resource_cals' => 'displayResource'
-            );
-            foreach ($display_prefs as $key => $val) {
-                $pref_val = @unserialize($prefs->getValue($key));
-                $val = '_' . $val;
-                $this->$val = is_array($pref_val)
-                    ? $pref_val
-                    : array();
-            }
-
-            // Run through getDisplayExternal to trim any that no longer exist.
-            $this->_getDisplayExternal();
-
-            if (empty($conf['holidays']['enable'])) {
-                $this->_displayHolidays = array();
-                $this->_displayHolidaysInternal = array();
-            }
-        }
-    }
-
-    /**
-     * Check for single, "toggle" calendars and set display lists and
-     * session values appropriately.
-     */
-    protected function _checkToggleCalendars()
-    {
-        global $prefs, $registry;
-
-        if (($calId = Horde_Util::getFormData('toggle_calendar')) !== null) {
-            if (strncmp($calId, 'remote_', 7) === 0) {
-                $calId = substr($calId, 7);
-                if (($key = array_search($calId, $this->_displayRemote)) === false) {
-                    $this->_displayRemote[] = $calId;
-                } else {
-                    unset($this->_displayRemote[$key]);
-                }
-                $prefs->setValue('display_remote_cals', serialize($this->_displayRemote));
-            } elseif ((strncmp($calId, 'external_', 9) === 0 &&
-                       ($calId = substr($calId, 9))) ||
-                      (strncmp($calId, 'tasklists_', 10) === 0 &&
-                       ($calId = substr($calId, 10)))) {
-                if (($key = array_search($calId, $this->_displayExternal)) === false) {
-                    $this->_displayExternal[] = $calId;
-                } else {
-                    unset($this->_displayExternal[$key]);
-                }
-                $prefs->setValue('display_external_cals', serialize($this->_displayExternal));
-
-                if (strpos($calId, 'tasks/') === 0) {
-                    $tasklists = array();
-                    foreach ($this->_displayExternal as $id) {
-                        if (strpos($id, 'tasks/') === 0) {
-                            $tasklists[] = substr($id, 6);
-                        }
-                    }
-                    try {
-                        $registry->tasks->setDisplayedTasklists($tasklists);
-                    } catch (Horde_Exception $e) {}
-                }
-            } elseif (strncmp($calId, 'holiday_', 8) === 0) {
-                $calId = substr($calId, 8);
-                if (($key = array_search($calId, $this->_displayHolidaysInternal)) === false) {
-                    $this->_displayHolidaysInternal[] = $calId;
-                } else {
-                    unset($this->_displayHolidaysInternal[$key]);
-                }
-                $prefs->setValue('holiday_drivers', serialize($this->_displayHolidaysInternal));
-            } elseif (strncmp($calId, 'resource_', 9) === 0) {
-                $calId = substr($calId, 9);
-                if (($key = array_search($calId, $this->_displayResource)) === false) {
-                    $this->_displayResource[] = $calId;
-                } else {
-                    unset($this->_displayResource[$key]);
-                }
-                $prefs->setValue('display_resource_cals', serialize($this->_displayResource));
-            } elseif (($key = array_search($calId, $this->_displayCalendars)) === false) {
-                $this->_displayCalendars[] = $calId;
-            } else {
-                unset($this->_displayCalendars[$key]);
-            }
-
-            $prefs->setValue('display_cals', serialize($this->_displayCalendars));
-        }
-    }
-
-    /**
-     * Check that the user owns a calendar and if not, creates one.
-     */
-    protected function _checkForOwnedCalendar()
-    {
-        global $prefs, $registry, $conf;
-
-        if (!empty($conf['share']['auto_create']) &&
-            $registry->getAuth() &&
-            !count(Kronolith::listInternalCalendars(true))) {
-            $calendars = $GLOBALS['injector']
-                ->getInstance('Kronolith_Factory_Calendars')
-                ->create();
-
-            $share = $calendars->createDefaultShare();
-            $this->_allCalendars[$share->getName()] = new Kronolith_Calendar_Internal(array('share' => $share));
-            $this->_displayCalendars[] = $share->getName();
-            $prefs->setValue('default_share', $share->getName());
-
-            // Calendar auto-sharing with the user's groups
-            if ($conf['autoshare']['shareperms'] != 'none') {
-                $perm_value = 0;
-                switch ($conf['autoshare']['shareperms']) {
-                case 'read':
-                    $perm_value = Horde_Perms::READ | Horde_Perms::SHOW;
-                    break;
-                case 'edit':
-                    $perm_value = Horde_Perms::READ | Horde_Perms::SHOW | Horde_Perms::EDIT;
-                    break;
-                case 'full':
-                    $perm_value = Horde_Perms::READ | Horde_Perms::SHOW | Horde_Perms::EDIT | Horde_Perms::DELETE;
-                    break;
-                }
-
-                try {
-                    $group_list = $GLOBALS['injector']
-                        ->getInstance('Horde_Group')
-                        ->listGroups($registry->getAuth());
-                    if (count($group_list)) {
-                        $perm = $share->getPermission();
-                        // Add the default perm, not added otherwise
-                        foreach (array_keys($group_list) as $group_id) {
-                            $perm->addGroupPermission($group_id, $perm_value, false);
-                        }
-                        $share->setPermission($perm);
-                        $GLOBALS['notification']->push(sprintf(_("New calendar created and automatically shared with the following group(s): %s."), implode(', ', $group_list)), 'horde.success');
-                    }
-                } catch (Horde_Group_Exception $e) {}
-            }
-
-            $prefs->setValue('display_cals', serialize($this->_displayCalendars));
-        }
-    }
-
-    /**
-     * Return all known external calendars.
-     *
-     * @return array
-     */
-    protected function _getAllExternal()
-    {
-        global $registry, $session;
-
-        $this->_allExternal = array();
-
-       // Make sure all task lists exist.
-        if (Kronolith::hasApiPermission('tasks') &&
-            $registry->hasMethod('tasks/listTimeObjects')) {
-            try {
-                $tasklists = $registry->tasks->listTasklists();
-                $categories = $registry->call('tasks/listTimeObjectCategories');
-                foreach ($categories as $name => $description) {
-                    if (!isset($tasklists[$name])) {
-                        continue;
-                    }
-                    $this->_allExternal['tasks/' . $name] = new Kronolith_Calendar_External_Tasks(array('api' => 'tasks', 'name' => $description['title'], 'share' => $tasklists[$name], 'type' => 'share'));
-                }
-            } catch (Horde_Exception $e) {
-                Horde::log($e, 'DEBUG');
-            }
-        }
-
-        if ($session->exists('kronolith', 'all_external_calendars')) {
-            foreach ($session->get('kronolith', 'all_external_calendars') as $calendar) {
-                if (!Kronolith::hasApiPermission($calendar['a']) ||
-                    $calendar['a'] == 'tasks') {
-                    continue;
-                }
-                $this->_allExternal[$calendar['a'] . '/' . $calendar['n']] = new Kronolith_Calendar_External(array('api' => $calendar['a'], 'name' => $calendar['d'], 'id' => $calendar['n'], 'type' => $calendar['t'], 'background' => $calendar['b']));
-            }
-        } else {
-            $apis = array_unique($registry->listAPIs());
-            $ext_cals = array();
-
-            foreach ($apis as $api) {
-                if ($api == 'tasks' ||
-                    !Kronolith::hasApiPermission($api) ||
-                    !$registry->hasMethod($api . '/listTimeObjects')) {
-                    continue;
-                }
-                try {
-                    $categories = $registry->call($api . '/listTimeObjectCategories');
-                } catch (Horde_Exception $e) {
-                    Horde::log($e, 'DEBUG');
-                    continue;
-                }
-
-                foreach ($categories as $name => $description) {
-                    if (empty($description['background'])) {
-                        $description['background'] = '#dddddd';
-                    }
-                    $this->_allExternal[$api . '/' . $name] = new Kronolith_Calendar_External(array(
-                        'api' => $api,
-                        'name' => $description['title'],
-                        'id' => $name,
-                        'type' => $description['type'],
-                        'background' => $description['background']));
-                    $ext_cals[] = array(
-                        'a' => $api,
-                        'n' => $name,
-                        'd' => $description['title'],
-                        't' => $description['type'],
-                        'b' => $description['background']
-                    );
-                }
-            }
-
-            $session->set('kronolith', 'all_external_calendars', $ext_cals);
-        }
-
-        return $this->_allExternal;
-    }
-
-    /**
-     * Return all external calendars selected for display.
-     *
-     * @return array
-     */
-    protected function _getDisplayExternal()
-    {
-        global $registry, $prefs;
-
-       // Make sure all the external calendars still exist.
-        $_tasklists = $_temp = $this->_displayExternal;
-        if (Kronolith::hasApiPermission('tasks')) {
-            try {
-                $_tasklists = $registry->tasks->getDisplayedTasklists();
-            } catch (Horde_Exception $e) {
-            }
-        }
-        $this->_displayExternal = array();
-        foreach ($this->get(Kronolith::ALL_EXTERNAL_CALENDARS) as $id => $calendar) {
-            if ((substr($id, 0, 6) == 'tasks/' &&
-                 in_array(substr($id, 6), $_tasklists)) ||
-                in_array($id, $_temp)) {
-                $this->_displayExternal[] = $id;
-            }
-        }
-        $prefs->setValue('display_external_cals', serialize($this->_displayExternal));
-
-        return $this->_displayExternal;
-    }
-
-    /**
-     * Return list of all available holidays drivers.
-     *
-     * @return array  The available holidays.
-     */
-    protected function _getAllHolidays()
-    {
-        $this->_allHolidays = array();
-        if (!empty($GLOBALS['conf']['holidays']['enable'])) {
-            if (class_exists('Date_Holidays')) {
-                $dh = new Date_Holidays();
-                foreach ($dh->getInstalledDrivers() as $driver) {
-                    if ($driver['id'] == 'Composite') {
-                        continue;
-                    }
-                    $this->_allHolidays[$driver['id']] = new Kronolith_Calendar_Holiday(array('driver' => $driver));
-                    ksort($this->_allHolidays);
-                }
-            }
-        }
-
-        return $this->_allHolidays;
-    }
-
-    /**
-     * Return list of holiday calendars to be displayed.
-     *
-     * @return array  The holiday calendars to display.
-     */
-    protected function _getDisplayHolidays()
-    {
-        $this->_displayHolidays = array();
-        foreach (array_keys($this->get(Kronolith::ALL_HOLIDAYS)) as $id) {
-            if (in_array($id, $this->_displayHolidaysInternal)) {
-                $this->_displayHolidays[] = $id;
-            }
-        }
-        $GLOBALS['prefs']->setValue('holiday_drivers', serialize($this->_displayHolidays));
-
-        return $this->_displayHolidays;
-    }
-
-    /**
-     * Return list of all resource calendars.
-     *
-     * @return array  Resource calendars, keyed by calendar id.
-     */
-    protected function _getAllResource()
-    {
-        $this->_allResource = array();
-        if (!empty($GLOBALS['conf']['resource']['driver'])) {
-            foreach (Kronolith::getDriver('Resource')->listResources(Horde_Perms::READ, array('type' => Kronolith_Resource::TYPE_SINGLE)) as $resource) {
-                $rcal = new Kronolith_Calendar_Resource(array(
-                    'resource' => $resource
-                ));
-                $this->_allResource[$resource->get('calendar')] = $rcal;
-            }
-        }
-
-        return $this->_allResource;
-    }
-
-    protected function _getRemoteCalendars()
-    {
-        if ($this->_allRemote === false) {
-            // Check that all selected remote calendars are still configured.
-            $tmp = $this->_displayRemote;
-            $this->_allRemote = $this->_displayRemote = array();
-            $calendars = @unserialize($GLOBALS['prefs']->getValue('remote_cals'));
-            if (!is_array($calendars)) {
-                $calendars = array();
-            }
-            foreach ($calendars as $calendar) {
-                $this->_allRemote[$calendar['url']] = new Kronolith_Calendar_Remote($calendar);
-                if (in_array($calendar['url'], $tmp)) {
-                    $this->_displayRemote[] = $calendar['url'];
-                }
-            }
-            $GLOBALS['prefs']->setValue('display_remote_cals', serialize($this->_displayRemote));
-        }
-
-        return $this->_allRemote;
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Day.php 4.2.27-1/kronolith-4.2.23/lib/Day.php
--- 4.2.23-1/kronolith-4.2.23/lib/Day.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Day.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,119 +0,0 @@
-<?php
-/**
- * The Kronolith_Day:: class provides an API for dealing with days.
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Kronolith
- */
-class Kronolith_Day extends Horde_Date
-{
-    /**
-     * How many time slots are we dividing each hour into? Set from user
-     * preferences.
-     *
-     * @var integer
-     */
-    public $slotsPerHour;
-
-    /**
-     * How many slots do we have per day? Calculated from $_slotsPerHour.
-     *
-     * @see $_slotsPerHour
-     * @var integer
-     */
-    public $slotsPerDay;
-
-    /**
-     * How many minutes are in each slot? Calculated from $_slotsPerHour.
-     *
-     * @see $_slotsPerHour
-     * @var integer
-     */
-    public $slotLength;
-
-    /**
-     * Array of slots holding hours and minutes for each piece of this day.
-     *
-     * @var array
-     */
-    public $slots = array();
-
-    /**
-     * Constructor.
-     *
-     * @param integer $month
-     * @param integer $day
-     * @param integer $year
-     */
-    public function __construct($month = null, $day = null, $year = null)
-    {
-        if (is_null($month)) {
-            $month = date('n');
-        }
-        if (is_null($year)) {
-            $year = date('Y');
-        }
-        if (is_null($day)) {
-            $day = date('j');
-        }
-        parent::__construct(array('year' => $year, 'month' => $month, 'mday' => $day));
-
-        $this->slotsPerHour = $GLOBALS['prefs']->getValue('slots_per_hour');
-        if (!$this->slotsPerHour) {
-            $this->slotsPerHour = 1;
-        }
-        $this->slotsPerDay = $this->slotsPerHour * 24;
-        $this->slotLength = 60 / $this->slotsPerHour;
-
-        for ($i = 0; $i < $this->slotsPerDay; $i++) {
-            $minutes = $i * $this->slotLength;
-            $this->slots[$i]['hour'] = (int)($minutes / 60);
-            $this->slots[$i]['min'] = $minutes % 60;
-        }
-    }
-
-    public function getTime($format, $offset = 0)
-    {
-        $date = new Horde_Date(array('month' => $this->month,
-                                     'mday' => $this->mday + $offset,
-                                     'year' => $this->year));
-        return $date->strftime($format);
-    }
-
-    public function getTomorrow()
-    {
-        $date = new Horde_Date(array('month' => $this->month,
-                                     'mday' => $this->mday + 1,
-                                     'year' => $this->year));
-        return $date;
-    }
-
-    public function getYesterday()
-    {
-        $date = new Horde_Date(array('month' => $this->month,
-                                     'mday' => $this->mday - 1,
-                                     'year' => $this->year));
-        return $date;
-    }
-
-    public function isToday()
-    {
-        return $this->compareDate(new Horde_Date(mktime(0, 0, 0))) == 0;
-    }
-
-    public function isTomorrow()
-    {
-        $date = new Horde_Date(array('month' => $this->month,
-                                     'mday' => $this->mday - 1,
-                                     'year' => $this->year));
-        return $date->compareDate(new Horde_Date(mktime(0, 0, 0))) == 0;
-    }
-
-    public function diff($other = null)
-    {
-        $day2 = new Kronolith_Day();
-        return Date_Calc::dateDiff($this->mday, $this->month, $this->year,
-                                   $day2->mday, $day2->month, $day2->year);
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Driver/Holidays.php 4.2.27-1/kronolith-4.2.23/lib/Driver/Holidays.php
--- 4.2.23-1/kronolith-4.2.23/lib/Driver/Holidays.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Driver/Holidays.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,154 +0,0 @@
-<?php
-/**
- * The Kronolith_Driver_Holidays implements support for the PEAR package
- * Date_Holidays.
- *
- * Copyright 2006-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @see     http://pear.php.net/packages/Date_Holidays
- * @author  Stephan Hohmann <webmaster@dasourcerer.net>
- * @package Kronolith
- */
-class Kronolith_Driver_Holidays extends Kronolith_Driver
-{
-    public function listAlarms($date, $fullevent = false)
-    {
-        return array();
-    }
-
-    /**
-     * Lists all events in the time range, optionally restricting results to
-     * only events with alarms.
-     *
-     * @param Horde_Date $startDate  The start of range date.
-     * @param Horde_Date $endDate    The end of date range.
-     * @param array $options         Additional options:
-     *   - show_recurrence: (boolean) Return every instance of a recurring
-     *                       event?
-     *                      DEFAULT: false (Only return recurring events once
-     *                      inside $startDate - $endDate range)
-     *   - has_alarm:       (boolean) Only return events with alarms.
-     *                      DEFAULT: false (Return all events)
-     *   - json:            (boolean) Store the results of the event's toJson()
-     *                      method?
-     *                      DEFAULT: false
-     *   - cover_dates:     (boolean) Add the events to all days that they
-     *                      cover?
-     *                      DEFAULT: true
-     *   - hide_exceptions: (boolean) Hide events that represent exceptions to
-     *                      a recurring event.
-     *                      DEFAULT: false (Do not hide exception events)
-     *   - fetch_tags:      (boolean) Fetch tags for all events.
-     *                      DEFAULT: false (Do not fetch event tags)
-     *
-     * @throws Kronolith_Exception
-     */
-    protected function _listEvents(Horde_Date $startDate = null,
-                                   Horde_Date $endDate = null,
-                                   array $options = array())
-    {
-        if (!class_exists('Date_Holidays')) {
-            Horde::log('Support for Date_Holidays has been enabled but the package seems to be missing.', 'ERR');
-            return array();
-        }
-
-        if (is_null($startDate) && !is_null($endDate)) {
-            $startDate = clone $endDate;
-            $startDate->year--;
-        }
-        if (is_null($endDate) && !is_null($startDate)) {
-            $endDate = clone $startDate;
-            $endDate->year++;
-        }
-        if ($options['has_alarm'] || is_null($startDate) || is_null($endDate)) {
-            return array();
-        }
-
-        $startDate = clone $startDate;
-        $startDate->hour = $startDate->min = $startDate->sec = 0;
-        $endDate = clone $endDate;
-        $endDate->hour = 23;
-        $endDate->min = $endDate->sec = 59;
-
-        Date_Holidays::staticSetProperty('DIE_ON_MISSING_LOCALE', false);
-
-        $results = array();
-        for ($year = $startDate->year; $year <= $endDate->year; $year++) {
-            $dh = Date_Holidays::factory($this->calendar, $year, $this->_params['language']);
-            if (Date_Holidays::isError($dh)) {
-                Horde::log(sprintf('Factory was unable to produce driver object for driver %s in year %s with locale %s',
-                                          $this->calendar, $year, $this->_params['language']), 'ERR');
-                continue;
-            }
-            $dh->addTranslation($this->_params['language']);
-            $events = $this->_getEvents($dh, $startDate, $endDate);
-            foreach ($events as $event) {
-                Kronolith::addEvents($results, $event, $startDate, $endDate,
-                                     $options['show_recurrence'],
-                                     $options['json'],
-                                     $options['cover_dates']);
-            }
-        }
-
-        return $results;
-    }
-
-    /**
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function getEvent($eventId = null)
-    {
-        if (!$eventId) {
-            $date = new Date();
-            return new Kronolith_Event_Holidays($this, new Date_Holidays_Holiday(null, null, $date, null));
-        }
-
-        list($id, $date) = explode('-', $eventId, 2);
-        $year = substr($date, 0, 4);
-
-        $dh = Date_Holidays::factory($this->calendar, $year, $this->_params['language']);
-        if (Date_Holidays::isError($dh)) {
-            Horde::log(sprintf('Factory was unable to produce driver object for driver %s in year %s with locale %s',
-                                      $this->calendar, $year, $this->_params['language']), 'ERR');
-            return false;
-        }
-        $dh->addTranslation($this->_params['language']);
-
-        $event = $dh->getHoliday($id);
-        if ($event instanceof PEAR_Error) {
-            throw new Horde_Exception_NotFound($event);
-        }
-
-        return new Kronolith_Event_Holidays($this, $event);
-    }
-
-    private function _getEvents($dh, $startDate, $endDate)
-    {
-        $events = array();
-        for ($date = new Horde_Date($startDate);
-             $date->compareDate($endDate) <= 0;
-             $date->mday++) {
-            $holidays = $dh->getHolidayForDate($date->format('Y-m-d'), null, true);
-            if (Date_Holidays::isError($holidays)) {
-                Horde::log(sprintf('Unable to retrieve list of holidays from %s to %s',
-                           (string)$startDate, (string)$endDate), __FILE__, __LINE__);
-                continue;
-            }
-
-            if (is_null($holidays)) {
-                continue;
-            }
-
-            foreach ($holidays as $holiday) {
-                $event = new Kronolith_Event_Holidays($this, $holiday);
-                $events[] = $event;
-            }
-        }
-        return $events;
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Driver/Horde.php 4.2.27-1/kronolith-4.2.23/lib/Driver/Horde.php
--- 4.2.23-1/kronolith-4.2.23/lib/Driver/Horde.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Driver/Horde.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,185 +0,0 @@
-<?php
-/**
- * The Kronolith_Driver_Horde class implements the Kronolith_Driver API for
- * time objects retrieved from other Horde applications.
- *
- * Possible driver parameters:
- *
- * Copyright 2009-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-class Kronolith_Driver_Horde extends Kronolith_Driver
-{
-    /**
-     * The API (application) of the current calendar.
-     *
-     * @var string
-     */
-    public $api;
-
-    public function open($calendar)
-    {
-        parent::open($calendar);
-        list($this->api,) = explode('/', $this->calendar, 2);
-    }
-
-    public function listAlarms($date, $fullevent = false)
-    {
-        return array();
-    }
-
-    /**
-     * Lists all events in the time range, optionally restricting results to
-     * only events with alarms.
-     *
-     * @param Horde_Date $startDate  The start of range date.
-     * @param Horde_Date $endDate    The end of date range.
-     * @param array $options         Additional options:
-     *   - show_recurrence: (boolean) Return every instance of a recurring
-     *                       event?
-     *                      DEFAULT: false (Only return recurring events once
-     *                      inside $startDate - $endDate range)
-     *   - has_alarm:       (boolean) Only return events with alarms.
-     *                      DEFAULT: false (Return all events)
-     *   - json:            (boolean) Store the results of the event's toJson()
-     *                      method?
-     *                      DEFAULT: false
-     *   - cover_dates:     (boolean) Add the events to all days that they
-     *                      cover?
-     *                      DEFAULT: true
-     *   - hide_exceptions: (boolean) Hide events that represent exceptions to
-     *                      a recurring event.
-     *                      DEFAULT: false (Do not hide exception events)
-     *   - fetch_tags:      (boolean) Fetch tags for all events.
-     *                      DEFAULT: false (Do not fetch event tags)
-     *
-     * @throws Kronolith_Exception
-     */
-    protected function _listEvents(Horde_Date $startDate = null,
-                                   Horde_Date $endDate = null,
-                                   array $options = array())
-    {
-        list($this->api, $category) = explode('/', $this->calendar, 2);
-        if (!$this->_params['registry']->hasMethod($this->api . '/listTimeObjects')) {
-            return array();
-        }
-
-        if (is_null($startDate)) {
-            $startDate = new Horde_Date(
-                array('mday' => 1, 'month' => 1, 'year' => 0000));
-        }
-        if (is_null($endDate)) {
-            $endDate = new Horde_Date(
-                array('mday' => 31, 'month' => 12, 'year' => 9999));
-        }
-
-        $startDate = clone $startDate;
-        $startDate->hour = $startDate->min = $startDate->sec = 0;
-        $endDate = clone $endDate;
-        $endDate->hour = 23;
-        $endDate->min = $endDate->sec = 59;
-
-        try {
-            $eventsList = $this->_params['registry']->call(
-                $this->api . '/listTimeObjects',
-                array(array($category), $startDate, $endDate));
-        } catch (Horde_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        $results = array();
-        foreach ($eventsList as $eventsListItem) {
-            try {
-                $event = new Kronolith_Event_Horde($this, $eventsListItem);
-            } catch (Kronolith_Exception $e) {
-                Horde::log($e, 'NOTICE');
-                continue;
-            }
-
-            // Ignore events out of the period.
-            $recurs = $event->recurs();
-            if (
-                // Starts after the period.
-                $event->start->compareDateTime($endDate) > 0 ||
-                // End before the period and doesn't recur.
-                (!$recurs &&
-                 $event->end->compareDateTime($startDate) < 0)) {
-                continue;
-            }
-
-            if ($recurs) {
-                // Fixed end date? Check if end is before start period.
-                if ($event->recurrence->hasRecurEnd() &&
-                    $event->recurrence->recurEnd->compareDateTime($startDate) < 0) {
-                    continue;
-                } else {
-                    $next = $event->recurrence->nextRecurrence($startDate);
-                    if ($next == false || $next->compareDateTime($endDate) > 0) {
-                        continue;
-                    }
-                }
-            }
-
-            Kronolith::addEvents(
-                $results, $event, $startDate, $endDate,
-                $options['show_recurrence'], $options['json'], $options['cover_dates']);
-        }
-
-        return $results;
-    }
-
-    /**
-     * Updates an existing event in the backend.
-     *
-     * @param Kronolith_Event_Horde $event  The event to save.
-     *
-     * @return string  The event id.
-     * @throws Kronolith_Exception
-     */
-    protected function _updateEvent(Kronolith_Event $event)
-    {
-        if (!isset($this->api)) {
-            list($this->api,) = explode('/', $this->calendar, 2);
-        }
-        try {
-            $this->_params['registry']->call($this->api . '/saveTimeObject', array($event->toTimeobject()));
-        } catch (Horde_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        return $event->timeobject['id'];
-    }
-
-    /**
-     * @todo: implement getTimeObject in timeobjects API.
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function getEvent($eventId = null, $start = null)
-    {
-        $end = null;
-        if ($start) {
-            $start = new Horde_Date($start);
-            $end = clone $start;
-            $end->mday++;
-        }
-
-        $events = $this->listEvents(
-            $start,
-            $end,
-            array('show_recurrence' => (bool)$start));
-        foreach ($events as $day) {
-            if (isset($day[$eventId])) {
-                return $day[$eventId];
-            }
-        }
-
-        throw new Horde_Exception_NotFound(_("Event not found"));
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Driver/Ical.php 4.2.27-1/kronolith-4.2.23/lib/Driver/Ical.php
--- 4.2.23-1/kronolith-4.2.23/lib/Driver/Ical.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Driver/Ical.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,821 +0,0 @@
-<?php
-/**
- * The Kronolith_Driver_Ical class implements the Kronolith_Driver API for
- * iCalendar data.
- *
- * Possible driver parameters:
- * - url:      The location of the remote calendar.
- * - proxy:    A hash with HTTP proxy information.
- * - user:     The user name for HTTP Basic Authentication.
- * - password: The password for HTTP Basic Authentication.
- *
- * Copyright 2004-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-class Kronolith_Driver_Ical extends Kronolith_Driver
-{
-    /**
-     * Cache events as we fetch them to avoid fetching or parsing the same
-     * event twice.
-     *
-     * @var array
-     */
-    protected $_cache = array();
-
-    /**
-     * HTTP client object.
-     *
-     * @var Horde_Http_Client
-     */
-    protected $_client;
-
-    /**
-     * A list of DAV support levels.
-     *
-     * @var array
-     */
-    protected $_davSupport;
-
-    /**
-     * The Horde_Perms permissions mask matching the CalDAV ACL.
-     *
-     * @var integer
-     */
-    protected $_permission;
-
-    /**
-     * Selects a calendar as the currently opened calendar.
-     *
-     * @param string $calendar  A calendar identifier.
-     */
-    public function open($calendar)
-    {
-        parent::open($calendar);
-        $this->_client = null;
-        $this->_permission = 0;
-        unset($this->_davSupport);
-    }
-
-    /**
-     * Returns the background color of the current calendar.
-     *
-     * @return string  The calendar color.
-     */
-    public function backgroundColor()
-    {
-        return $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_REMOTE_CALENDARS, $this->calendar)
-            ? $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_REMOTE_CALENDARS, $this->calendar)->background()
-            : '#dddddd';
-    }
-
-    public function listAlarms($date, $fullevent = false)
-    {
-        return array();
-    }
-
-    /**
-     * Lists all events in the time range, optionally restricting results to
-     * only events with alarms.
-     *
-     * @param Horde_Date $startDate  The start of range date.
-     * @param Horde_Date $endDate    The end of date range.
-     * @param array $options         Additional options:
-     *   - show_recurrence: (boolean) Return every instance of a recurring
-     *                       event?
-     *                      DEFAULT: false (Only return recurring events once
-     *                      inside $startDate - $endDate range)
-     *   - has_alarm:       (boolean) Only return events with alarms.
-     *                      DEFAULT: false (Return all events)
-     *   - json:            (boolean) Store the results of the event's toJson()
-     *                      method?
-     *                      DEFAULT: false
-     *   - cover_dates:     (boolean) Add the events to all days that they
-     *                      cover?
-     *                      DEFAULT: true
-     *   - hide_exceptions: (boolean) Hide events that represent exceptions to
-     *                      a recurring event.
-     *                      DEFAULT: false (Do not hide exception events)
-     *   - fetch_tags:      (boolean) Fetch tags for all events.
-     *                      DEFAULT: false (Do not fetch event tags)
-     *
-     * @throws Kronolith_Exception
-     */
-    protected function _listEvents(Horde_Date $startDate = null,
-                                   Horde_Date $endDate = null,
-                                   array $options = array())
-    {
-        if ($this->isCalDAV()) {
-            try {
-                return $this->_listCalDAVEvents(
-                    $startDate, $endDate, $options['show_recurrence'],
-                    $options['has_alarm'], $options['json'],
-                    $options['cover_dates'], $options['hide_exceptions']);
-            } catch (Kronolith_Exception $e) {
-                // Fall back to regular ICS downloads. At least Nextcloud
-                // advertises calendars as CalDAV capable, but then denying
-                // CalDAV requests.
-                $this->_davSupport = false;
-            }
-        }
-        return $this->_listWebDAVEvents(
-            $startDate, $endDate, $options['show_recurrence'],
-            $options['has_alarm'], $options['json'],
-            $options['cover_dates'], $options['hide_exceptions']);
-    }
-
-    /**
-     * Lists all events in the time range, optionally restricting results to
-     * only events with alarms.
-     *
-     * @param Horde_Date $startInterval  Start of range date object.
-     * @param Horde_Date $endInterval    End of range data object.
-     * @param boolean $showRecurrence    Return every instance of a recurring
-     *                                   event? If false, will only return
-     *                                   recurring events once inside the
-     *                                   $startDate - $endDate range.
-     * @param boolean $hasAlarm          Only return events with alarms?
-     * @param boolean $json              Store the results of the events'
-     *                                   toJson() method?
-     * @param boolean $coverDates        Whether to add the events to all days
-     *                                   that they cover.
-     * $param boolean $hideExceptions    Hide events that represent exceptions
-     *                                   to a recurring event.
-     *
-     * @return array  Events in the given time range.
-     * @throws Kronolith_Exception
-     */
-    protected function _listWebDAVEvents(
-        $startDate = null, $endDate = null, $showRecurrence = false,
-        $hasAlarm = false, $json = false, $coverDates = true,
-        $hideExceptions = false
-    )
-    {
-        $ical = $this->getRemoteCalendar();
-
-        if (is_null($startDate)) {
-            $startDate = new Horde_Date(array('mday' => 1,
-                                              'month' => 1,
-                                              'year' => 0000));
-        }
-        if (is_null($endDate)) {
-            $endDate = new Horde_Date(array('mday' => 31,
-                                            'month' => 12,
-                                            'year' => 9999));
-        }
-
-        $startDate = clone $startDate;
-        $startDate->hour = $startDate->min = $startDate->sec = 0;
-        $endDate = clone $endDate;
-        $endDate->hour = 23;
-        $endDate->min = $endDate->sec = 59;
-
-        $results = array();
-        $this->_processComponents(
-            $results, $ical, $startDate, $endDate, $showRecurrence, $json,
-            $coverDates, $hideExceptions
-        );
-
-        return $results;
-    }
-
-    /**
-     * Lists all events in the time range, optionally restricting results to
-     * only events with alarms.
-     *
-     * @param Horde_Date $startInterval  Start of range date object.
-     * @param Horde_Date $endInterval    End of range data object.
-     * @param boolean $showRecurrence    Return every instance of a recurring
-     *                                   event? If false, will only return
-     *                                   recurring events once inside the
-     *                                   $startDate - $endDate range.
-     * @param boolean $hasAlarm          Only return events with alarms?
-     * @param boolean $json              Store the results of the events'
-     *                                   toJson() method?
-     * @param boolean $coverDates        Whether to add the events to all days
-     *                                   that they cover.
-     * $param boolean $hideExceptions    Hide events that represent exceptions
-     *                                   to a recurring event.
-     *
-     * @return array  Events in the given time range.
-     * @throws Kronolith_Exception
-     */
-    protected function _listCalDAVEvents(
-        $startDate = null, $endDate = null, $showRecurrence = false,
-        $hasAlarm = false, $json = false, $coverDates = true,
-        $hideExceptions = false
-    )
-    {
-        if (!is_null($startDate)) {
-            $startDate = clone $startDate;
-            $startDate->hour = $startDate->min = $startDate->sec = 0;
-        }
-        if (!is_null($endDate)) {
-            $endDate = clone $endDate;
-            $endDate->hour = 23;
-            $endDate->min = $endDate->sec = 59;
-        }
-
-        /* Build report query. */
-        $xml = new XMLWriter();
-        $xml->openMemory();
-        $xml->setIndent(true);
-        $xml->startDocument();
-        $xml->startElementNS('C', 'calendar-query', 'urn:ietf:params:xml:ns:caldav');
-        $xml->writeAttribute('xmlns:D', 'DAV:');
-        $xml->startElement('D:prop');
-        $xml->writeElement('D:getetag');
-        $xml->startElement('C:calendar-data');
-        $xml->startElement('C:comp');
-        $xml->writeAttribute('name', 'VCALENDAR');
-        $xml->startElement('C:comp');
-        $xml->writeAttribute('name', 'VEVENT');
-        $xml->endElement();
-        $xml->endElement();
-        $xml->endElement();
-        $xml->endElement();
-        $xml->startElement('C:filter');
-        $xml->startElement('C:comp-filter');
-        $xml->writeAttribute('name', 'VCALENDAR');
-        $xml->startElement('C:comp-filter');
-        $xml->writeAttribute('name', 'VEVENT');
-        if (!is_null($startDate) ||
-            !is_null($endDate)) {
-            $xml->startElement('C:time-range');
-            if (!is_null($startDate)) {
-                $xml->writeAttribute('start', $startDate->toiCalendar());
-            }
-            if (!is_null($endDate)) {
-                $xml->writeAttribute('end', $endDate->toiCalendar());
-            }
-        }
-        $xml->endDocument();
-
-        $url = $this->_getUrl();
-        list($response, $events) = $this->_request('REPORT', $url, $xml,
-                                                   array('Depth' => 1));
-        if (!$events->children('DAV:')->response) {
-            return array();
-        }
-        if (isset($response['headers']['content-location'])) {
-            $path = $response['headers']['content-location'];
-        } else {
-            $parsedUrl = parse_url($url);
-            $path = $parsedUrl['path'];
-        }
-
-        $results = array();
-        foreach ($events->children('DAV:')->response as $response) {
-            if (!$response->children('DAV:')->propstat) {
-                continue;
-            }
-            $ical = new Horde_Icalendar();
-            try {
-                $ical->parsevCalendar($response->children('DAV:')->propstat->prop->children('urn:ietf:params:xml:ns:caldav')->{'calendar-data'});
-            } catch (Horde_Icalendar_Exception $e) {
-                throw new Kronolith_Exception($e);
-            }
-            $this->_processComponents(
-                $results, $ical, $startDate, $endDate, $showRecurrence, $json,
-                $coverDates, $hideExceptions,
-                trim(str_replace($path, '', $response->href), '/')
-            );
-        }
-
-        return $results;
-    }
-
-    /**
-     * Processes the components of a Horde_Icalendar container into an event
-     * list.
-     *
-     * @param array $results             Gets filled with the events in the
-     *                                   given time range.
-     * @param Horde_Icalendar $ical      An Horde_Icalendar container.
-     * @param Horde_Date $startInterval  Start of range date.
-     * @param Horde_Date $endInterval    End of range date.
-     * @param boolean $showRecurrence    Return every instance of a recurring
-     *                                   event? If false, will only return
-     *                                   recurring events once inside the
-     *                                   $startDate - $endDate range.
-     * @param boolean $json              Store the results of the events'
-     *                                   toJson() method?
-     * @param boolean $coverDates        Whether to add the events to all days
-     *                                   that they cover.
-     * $param boolean $hideExceptions    Hide events that represent exceptions
-     *                                   to a recurring event.
-     * @param string $id                 Enforce a certain event id (not UID).
-     *
-     * @throws Kronolith_Exception
-     */
-    protected function _processComponents(
-        &$results, $ical, $startDate, $endDate, $showRecurrence, $json,
-        $coverDates, $hideExceptions, $id = null
-    )
-    {
-        $components = $ical->getComponents();
-        $events = array();
-        $count = count($components);
-        $exceptions = array();
-        for ($i = 0; $i < $count; $i++) {
-            $component = $components[$i];
-            if ($component->getType() == 'vEvent') {
-                try {
-                    $event = new Kronolith_Event_Ical($this, $component);
-                } catch (Kronolith_Exception $e) {
-                    Horde::log(
-                        sprintf(
-                            'Failed parse event from remote calendar: url = "%s"',
-                            $this->calendar
-                        ),
-                        'INFO'
-                    );
-                    continue;
-                }
-                $event->permission = $this->getPermission();
-                // Force string so JSON encoding is consistent across drivers.
-                $event->id = $id ? $id : 'ical' . $i;
-
-                /* Catch RECURRENCE-ID attributes which mark single recurrence
-                 * instances. */
-                try {
-                    $recurrence_id = $component->getAttribute('RECURRENCE-ID');
-                    if (is_int($recurrence_id) &&
-                        is_string($uid = $component->getAttribute('UID')) &&
-                        is_int($seq = $component->getAttribute('SEQUENCE'))) {
-                        $exceptions[$uid][$seq] = $recurrence_id;
-                        if ($hideExceptions) {
-                            continue;
-                        }
-                        $event->id .= '/' . $recurrence_id;
-                    }
-                } catch (Horde_Icalendar_Exception $e) {}
-
-                /* Ignore events out of the period. */
-                $recurs = $event->recurs();
-                if (
-                    /* Starts after the period. */
-                    ($endDate && $event->start->compareDateTime($endDate) > 0) ||
-                    /* End before the period and doesn't recur. */
-                    ($startDate && !$recurs &&
-                     $event->end->compareDateTime($startDate) < 0)) {
-                    continue;
-                }
-
-                if ($recurs && $startDate) {
-                    // Fixed end date? Check if end is before start period.
-                    if ($event->recurrence->hasRecurEnd() &&
-                        $event->recurrence->recurEnd->compareDateTime($startDate) < 0) {
-                        continue;
-                    } elseif ($endDate) {
-                        $next = $event->recurrence->nextRecurrence($startDate);
-                        if ($next == false || $next->compareDateTime($endDate) > 0) {
-                            continue;
-                        }
-                    }
-                }
-
-                $events[] = $event;
-            }
-        }
-
-        /* Loop through all explicitly defined recurrence intances and create
-         * exceptions for those in the event with the matching recurrence. */
-        foreach ($events as $key => $event) {
-            if ($event->recurs() &&
-                isset($exceptions[$event->uid][$event->sequence])) {
-                $timestamp = $exceptions[$event->uid][$event->sequence];
-                $events[$key]->recurrence->addException(date('Y', $timestamp), date('m', $timestamp), date('d', $timestamp));
-            }
-            Kronolith::addEvents($results, $event, $startDate, $endDate,
-                                 $showRecurrence, $json, $coverDates);
-        }
-    }
-
-    /**
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function getEvent($eventId = null)
-    {
-        if (!$eventId) {
-            $event = new Kronolith_Event_Ical($this);
-            $event->permission = $this->getPermission();
-            return $event;
-        }
-
-        if ($this->isCalDAV()) {
-            if (preg_match('/(.*)-(\d+)$/', $eventId, $matches)) {
-                $eventId = $matches[1];
-                //$recurrenceId = $matches[2];
-            }
-            $url = trim($this->_getUrl(), '/') . '/' . $eventId;
-            try {
-                $response = $this->_getClient($url)->request('GET');
-            } catch (Horde_Dav_Exception $e) {
-                throw new Kronolith_Exception($e);
-            }
-            if ($response['statusCode'] == 200) {
-                $ical = new Horde_Icalendar();
-                try {
-                    $ical->parsevCalendar($response['body']);
-                } catch (Horde_Icalendar_Exception $e) {
-                    throw new Kronolith_Exception($e);
-                }
-                $results = array();
-                $this->_processComponents($results, $ical, null, null, false,
-                                          false, false, false, $eventId);
-                $event = reset(reset($results));
-                if (!$event) {
-                    throw new Horde_Exception_NotFound(_("Event not found"));
-                }
-                return $event;
-            }
-        }
-
-        $eventId = str_replace('ical', '', $eventId);
-        $ical = $this->getRemoteCalendar();
-        $components = $ical->getComponents();
-        if (isset($components[$eventId]) &&
-            $components[$eventId]->getType() == 'vEvent') {
-            $event = new Kronolith_Event_Ical($this, $components[$eventId]);
-            $event->permission = $this->getPermission();
-            $event->id = 'ical' . $eventId;
-            return $event;
-        }
-
-        throw new Horde_Exception_NotFound(_("Event not found"));
-    }
-
-    /**
-     * Updates an existing event in the backend.
-     *
-     * @param Kronolith_Event $event  The event to save.
-     *
-     * @return string  The event id.
-     * @throws Horde_Mime_Exception
-     * @throws Kronolith_Exception
-     */
-    protected function _updateEvent(Kronolith_Event $event)
-    {
-        $response = $this->_saveEvent($event);
-        if (!in_array($response['statusCode'], array(200, 204))) {
-            Horde::log(sprintf('Failed to update event on remote calendar: url = "%s", status = %s',
-                                      $response['url'], $response['statusCode']), 'INFO');
-            throw new Kronolith_Exception(_("The event could not be updated on the remote server."));
-        }
-        return $event->id;
-    }
-
-    /**
-     * Adds an event to the backend.
-     *
-     * @param Kronolith_Event $event  The event to save.
-     *
-     * @return string  The event id.
-     * @throws Horde_Mime_Exception
-     * @throws Kronolith_Exception
-     */
-    protected function _addEvent(Kronolith_Event $event)
-    {
-        if (!$event->uid) {
-            $event->uid = (string)new Horde_Support_Uuid;
-        }
-        if (!$event->id) {
-            $event->id = $event->uid . '.ics';
-        }
-
-        $response = $this->_saveEvent($event);
-        if (!in_array($response['statusCode'], array(200, 201, 204))) {
-            Horde::log(sprintf('Failed to create event on remote calendar: status = %s',
-                                      $response['statusCode']), 'INFO');
-            throw new Kronolith_Exception(_("The event could not be added to the remote server."));
-        }
-        return $event->id;
-    }
-
-    /**
-     * Updates an existing event in the backend.
-     *
-     * @param Kronolith_Event $event  The event to save.
-     *
-     * @return string  The event id.
-     * @throws Horde_Mime_Exception
-     * @throws Kronolith_Exception
-     */
-    protected function _saveEvent($event)
-    {
-        $ical = new Horde_Icalendar();
-        $ical->addComponent($event->toiCalendar($ical));
-
-        $url = trim($this->_getUrl(), '/') . '/' . $event->id;
-        try {
-            return $this->_getClient($url)
-                ->request(
-                    'PUT',
-                    '',
-                    $ical->exportvCalendar(),
-                    array('Content-Type' => 'text/calendar')
-                );
-        } catch (Horde_Dav_Exception $e) {
-            Horde::log($e, 'INFO');
-            throw new Kronolith_Exception($e);
-        }
-    }
-
-    /**
-     * Deletes an event.
-     *
-     * @param string $eventId  The ID of the event to delete.
-     * @param boolean $silent  Don't send notifications, used when deleting
-     *                         events in bulk from maintenance tasks.
-     *
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     * @throws Horde_Mime_Exception
-     */
-    protected function _deleteEvent($eventId, $silent = false)
-    {
-        /* Fetch the event for later use. */
-        if ($eventId instanceof Kronolith_Event) {
-            $event = $eventId;
-            $eventId = $event->id;
-        } else {
-            $event = $this->getEvent($eventId);
-        }
-
-        if (!$this->isCalDAV()) {
-            throw new Kronolith_Exception(_("Deleting events is not supported with this remote calendar."));
-        }
-
-        if (preg_match('/(.*)-(\d+)$/', $eventId)) {
-            throw new Kronolith_Exception(_("Cannot delete exceptions (yet)."));
-        }
-
-        $url = trim($this->_getUrl(), '/') . '/' . $eventId;
-        try {
-            $response = $this->_getClient($url)->request('DELETE');
-        } catch (Horde_Dav_Exception $e) {
-            Horde::log($e, 'INFO');
-            throw new Kronolith_Exception($e);
-        }
-        if (!in_array($response['statusCode'], array(200, 202, 204))) {
-            Horde::log(sprintf('Failed to delete event from remote calendar: url = "%s", status = %s',
-                                      $url, $response['statusCode']), 'INFO');
-            throw new Kronolith_Exception(_("The event could not be deleted from the remote server."));
-        }
-
-        return $event;
-    }
-
-    /**
-     * Fetches a remote calendar into the cache and return the data.
-     *
-     * @param boolean $cache  Whether to return data from the cache.
-     *
-     * @return Horde_Icalendar  The calendar data.
-     * @throws Kronolith_Exception
-     */
-    public function getRemoteCalendar($cache = true)
-    {
-        $url = $this->_getUrl();
-        $cacheOb = $GLOBALS['injector']->getInstance('Horde_Cache');
-        $cacheVersion = 2;
-        $signature = 'kronolith_remote_'  . $cacheVersion . '_' . $url . '_' . serialize($this->_params);
-        if ($cache) {
-            $calendar = $cacheOb->get($signature, 3600);
-            if ($calendar) {
-                $calendar = unserialize($calendar);
-                if (!is_object($calendar)) {
-                    throw new Kronolith_Exception($calendar);
-                }
-                return $calendar;
-            }
-        }
-
-        try {
-            $response = $this->_getClient($url)->request('GET');
-            if ($response['statusCode'] != 200) {
-                throw new Horde_Dav_Exception('Request Failed', $response['statusCode']);
-            }
-        } catch (Horde_Dav_Exception $e) {
-            Horde::log(
-                sprintf('Failed to retrieve remote calendar: url = "%s", status = %s',
-                        $url, $e->getCode()),
-                'INFO'
-            );
-            $error = sprintf(
-                _("Could not open %s: %s"),
-                $url, $e->getMessage()
-            );
-            if ($cache) {
-                $cacheOb->set($signature, serialize($error));
-            }
-            throw new Kronolith_Exception($error, $e->getCode());
-        }
-
-        /* Log fetch at DEBUG level. */
-        Horde::log(
-            sprintf('Retrieved remote calendar for %s: url = "%s"',
-                    $GLOBALS['registry']->getAuth(), $url),
-            'DEBUG'
-        );
-
-        $ical = new Horde_Icalendar();
-        try {
-            $ical->parsevCalendar($response['body']);
-        } catch (Horde_Icalendar_Exception $e) {
-            if ($cache) {
-                $cacheOb->set($signature, serialize($e->getMessage()));
-            }
-            throw new Kronolith_Exception($e);
-        }
-
-        if ($cache) {
-            $cacheOb->set($signature, serialize($ical));
-        }
-
-        return $ical;
-    }
-
-    /**
-     * Returns whether the remote calendar is a CalDAV server, and propagates
-     * the $_davSupport propery with the server's DAV capabilities.
-     *
-     * @return boolean  True if the remote calendar is a CalDAV server.
-     * @throws Kronolith_Exception
-     */
-    public function isCalDAV()
-    {
-        if (isset($this->_davSupport)) {
-            return $this->_davSupport
-                ? in_array('calendar-access', $this->_davSupport)
-                : false;
-        }
-
-        $client = $this->_getClient($this->_getUrl());
-        try {
-            $this->_davSupport = $client->options();
-        } catch (Horde_Dav_Exception $e) {
-            if ($e->getCode() != 405) {
-                Horde::log($e, 'INFO');
-            }
-            $this->_davSupport = false;
-            return false;
-        }
-
-        if (!$this->_davSupport) {
-            $this->_davSupport = false;
-            return false;
-        }
-
-        if (!in_array('calendar-access', $this->_davSupport)) {
-            return false;
-        }
-
-        /* Check if this URL is a collection. */
-        try {
-            $properties = $client->propfind(
-                '',
-                array('{DAV:}resourcetype', '{DAV:}current-user-privilege-set')
-             );
-        } catch (\Sabre\DAV\Exception $e) {
-            Horde::log($e, 'INFO');
-            return false;
-        } catch (Horde_Dav_Exception $e) {
-            Horde::log($e, 'INFO');
-            return false;
-        }
-
-        if (!$properties['{DAV:}resourcetype']->is('{DAV:}collection')) {
-            throw new Kronolith_Exception(_("The remote server URL does not point to a CalDAV directory."));
-        }
-
-        /* Read ACLs. */
-        if (!empty($properties['{DAV:}current-user-privilege-set'])) {
-            $privileges = $properties['{DAV:}current-user-privilege-set'];
-            if ($privileges->has('{DAV:}read')) {
-                /* GET access. */
-                $this->_permission |= Horde_Perms::SHOW;
-                $this->_permission |= Horde_Perms::READ;
-            }
-            if ($privileges->has('{DAV:}write') ||
-                $privileges->has('{DAV:}write-content')) {
-                /* PUT access. */
-                $this->_permission |= Horde_Perms::EDIT;
-            }
-            if ($privileges->has('{DAV:}unbind')) {
-                /* DELETE access. */
-                $this->_permission |= Horde_Perms::DELETE;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns the permissions for the current calendar.
-     *
-     * @return integer  A Horde_Perms permission bit mask.
-     */
-    public function getPermission()
-    {
-        if ($this->isCalDAV()) {
-            return $this->_permission;
-        }
-        return Horde_Perms::SHOW | Horde_Perms::READ;
-    }
-
-    /**
-     * Sends a CalDAV request.
-     *
-     * @param string $method  A request method.
-     * @param string $url     A request URL.
-     * @param XMLWriter $xml  An XMLWriter object with the body content.
-     * @param array $headers  A hash with additional request headers.
-     *
-     * @return array  The Horde_Http_Response object and the parsed
-     *                SimpleXMLElement results.
-     * @throws Kronolith_Exception
-     */
-    protected function _request($method, $url, XMLWriter $xml = null,
-                                array $headers = array())
-    {
-        try {
-            $response = $this->_getClient($url)
-                ->request($method,
-                          '',
-                          $xml ? $xml->outputMemory() : null,
-                          array_merge(array('Cache-Control' => 'no-cache',
-                                            'Pragma' => 'no-cache',
-                                            'Content-Type' => 'application/xml'),
-                                      $headers));
-        } catch (Horde_Dav_Exception $e) {
-            Horde::log($e, 'INFO');
-            throw new Kronolith_Exception($e);
-        }
-        if ($response['statusCode'] != 207) {
-            throw new Kronolith_Exception(_("Unexpected response from remote server."));
-        }
-        libxml_use_internal_errors(true);
-        try {
-            $xml = new SimpleXMLElement($response['body']);
-        } catch (Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-        return array($response, $xml);
-    }
-
-    /**
-     * Returns the URL of this calendar.
-     *
-     * Does any necessary trimming and URL scheme fixes on the user-provided
-     * calendar URL.
-     *
-     * @return string  The URL of this calendar.
-     */
-    protected function _getUrl()
-    {
-        $url = trim($this->calendar);
-        if (strpos($url, 'http') !== 0) {
-            $url = str_replace(array('webcal://', 'webdav://', 'webdavs://'),
-                               array('http://', 'http://', 'https://'),
-                               $url);
-        }
-        return $url;
-    }
-
-    /**
-     * Returns a configured, cached DAV client.
-     *
-     * @param string $uri  The base URI for any requests.
-     *
-     * @return Horde_Dav_Client  A DAV client.
-     */
-    protected function _getClient($uri)
-    {
-        $hordeOptions = array(
-            'request.timeout' => isset($this->_params['timeout'])
-                ? $this->_params['timeout']
-                : 5
-        );
-        $sabreOptions = array('baseUri' => $uri);
-        if (!empty($this->_params['user'])) {
-            $hordeOptions['request.username'] = $sabreOptions['userName'] = $this->_params['user'];
-            $hordeOptions['request.password'] = $sabreOptions['password'] = $this->_params['password'];
-        }
-        $sabreOptions['client'] = $GLOBALS['injector']
-            ->getInstance('Horde_Core_Factory_HttpClient')
-            ->create($hordeOptions);
-
-        $this->_client = new Horde_Dav_Client($sabreOptions);
-
-        return $this->_client;
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Driver/Kolab.php 4.2.27-1/kronolith-4.2.23/lib/Driver/Kolab.php
--- 4.2.23-1/kronolith-4.2.23/lib/Driver/Kolab.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Driver/Kolab.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,524 +0,0 @@
-<?php
-/**
- * Horde Kronolith driver for the Kolab IMAP Server.
- *
- * Copyright 2004-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Thomas Jarosch <thomas.jarosch@intra2net.com>
- * @author  Gunnar Wrobel <wrobel@pardus.de>
- * @author  Stuart Binge <omicron@mighty.co.za>
- * @package Kronolith
- */
-class Kronolith_Driver_Kolab extends Kronolith_Driver
-{
-    /**
-     * Our Kolab server connection.
-     *
-     * @var Kolab
-     */
-    private $_kolab = null;
-
-    /**
-     * Internal cache of Kronolith_Event_Kolab. eventID is key
-     *
-     * @var array
-     */
-    private $_events_cache;
-
-    /**
-     * Indicates if we have synchronized this folder
-     *
-     * @var boolean
-     */
-    private $_synchronized;
-
-    /**
-     * The current calendar.
-     *
-     * @var Horde_Kolab_Storage_Data
-     */
-    private $_data;
-
-    /**
-     * Attempts to open a Kolab Groupware folder.
-     */
-    public function initialize()
-    {
-        if (empty($this->_params['storage'])) {
-            throw new InvalidArgumentException('Missing required Horde_Kolab_Storage instance');
-        }
-        $this->_kolab = $this->_params['storage'];
-        $this->reset();
-    }
-
-    /**
-     * Selects a calendar as the currently opened calendar.
-     *
-     * @param string $calendar  A calendar identifier.
-     */
-    public function open($calendar)
-    {
-        if ($this->calendar == $calendar) {
-            return;
-        }
-        $this->calendar = $calendar;
-        $this->reset();
-    }
-
-    /**
-     * Returns the background color of the current calendar.
-     *
-     * @return string  The calendar color.
-     */
-    public function backgroundColor()
-    {
-        if ($GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $this->calendar) !== false) {
-            return $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $this->calendar)->background();
-        }
-        return '#dddddd';
-    }
-
-    /**
-     * Reset internal variable on share change
-     */
-    public function reset()
-    {
-        $this->_events_cache = array();
-        $this->_synchronized = false;
-    }
-
-    // We delay initial synchronization to the first use
-    // so multiple calendars don't add to the total latency.
-    // This function must be called before all internal driver functions
-    public function synchronize($force = false)
-    {
-        if ($this->_synchronized && !$force) {
-            return;
-        }
-
-        // Connect to the Kolab backend
-        try {
-            $this->_data = $this->_kolab->getData(
-                $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $this->calendar)->share()->get('folder'),
-                'event'
-            );
-        } catch (Kolab_Storage_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        // build internal event cache
-        $this->_events_cache = $uids = array();
-        $events = $this->_data->getObjects();
-        foreach ($events as $event) {
-            $this->_events_cache[Horde_Url::uriB64Encode($event['uid'])] = new Kronolith_Event_Kolab($this, $event);
-            $uids[] = $event['uid'];
-        }
-        $tags = Kronolith::getTagger()->getTags(array_unique($uids));
-        foreach ($this->_events_cache as &$event) {
-            if (isset($tags[$event->uid])) {
-                $event->synchronizeTags($tags[$event->uid]);
-            }
-        }
-
-        $this->_synchronized = true;
-    }
-
-    /**
-     * @throws Kronolith_Exception
-     */
-    public function listAlarms($date, $fullevent = false)
-    {
-        $allevents = $this->listEvents($date, null, array('has_alarm' => true));
-        $events = array();
-
-        foreach (array_keys($allevents) as $eventId) {
-            $event = $this->getEvent($eventId);
-            if (!$event->recurs()) {
-                $start = new Horde_Date($event->start);
-                $start->min -= $event->alarm;
-                if ($start->compareDateTime($date) <= 0 &&
-                    $date->compareDateTime($event->end) <= -1) {
-                    $events[] = $fullevent ? $event : $eventId;
-                }
-            } else {
-                if ($next = $event->recurrence->nextRecurrence($date)) {
-                    if ($event->recurrence->hasException($next->year, $next->month, $next->mday)) {
-                        continue;
-                    }
-                    $start = new Horde_Date($next);
-                    $start->min -= $event->alarm;
-                    $end = new Horde_Date(array('year' => $next->year,
-                                                'month' => $next->month,
-                                                'mday' => $next->mday,
-                                                'hour' => $event->end->hour,
-                                                'min' => $event->end->min,
-                                                'sec' => $event->end->sec));
-                    if ($start->compareDateTime($date) <= 0 &&
-                        $date->compareDateTime($end) <= -1) {
-                        if ($fullevent) {
-                            $event->start = $start;
-                            $event->end = $end;
-                            $events[] = $event;
-                        } else {
-                            $events[] = $eventId;
-                        }
-                    }
-                }
-            }
-        }
-
-        return is_array($events) ? $events : array();
-    }
-
-    /**
-     * Checks if the event's UID already exists and returns all event
-     * ids with that UID.
-     *
-     * @param string $uid          The event's uid.
-     * @param string $calendar_id  Calendar to search in.
-     *
-     * @return string|boolean  Returns a string with event_id or false if
-     *                         not found.
-     * @throws Kronolith_Exception
-     */
-    public function exists($uid, $calendar_id = null)
-    {
-        // Log error if someone uses this function in an unsupported way
-        if ($calendar_id != $this->calendar) {
-            throw new BadMethodCallException(sprintf('Kolab::exists called for calendar %s. Currently active is %s.', $calendar_id, $this->calendar));
-        }
-
-        $this->synchronize();
-
-        if ($this->_data->objectIdExists($uid)) {
-            return $uid;
-        }
-
-        return false;
-    }
-
-    /**
-     * Lists all events in the time range, optionally restricting results to
-     * only events with alarms.
-     *
-     * @param Horde_Date $startDate  The start of range date.
-     * @param Horde_Date $endDate    The end of date range.
-     * @param array $options         Additional options:
-     *   - show_recurrence: (boolean) Return every instance of a recurring
-     *                       event?
-     *                      DEFAULT: false (Only return recurring events once
-     *                      inside $startDate - $endDate range)
-     *   - has_alarm:       (boolean) Only return events with alarms.
-     *                      DEFAULT: false (Return all events)
-     *   - json:            (boolean) Store the results of the event's toJson()
-     *                      method?
-     *                      DEFAULT: false
-     *   - cover_dates:     (boolean) Add the events to all days that they
-     *                      cover?
-     *                      DEFAULT: true
-     *   - hide_exceptions: (boolean) Hide events that represent exceptions to
-     *                      a recurring event.
-     *                      DEFAULT: false (Do not hide exception events)
-     *   - fetch_tags:      (boolean) Fetch tags for all events.
-     *                      DEFAULT: false (Do not fetch event tags)
-     *
-     * @throws Kronolith_Exception
-     */
-    protected function _listEvents(Horde_Date $startDate = null,
-                                   Horde_Date $endDate = null,
-                                   array $options = array())
-    {
-        $this->synchronize();
-
-        if (empty($startDate)) {
-            $startDate = new Horde_Date(
-                array('mday' => 1, 'month' => 1, 'year' => 0000));
-        }
-        if (empty($endDate)) {
-            $endDate = new Horde_Date(
-                array('mday' => 31, 'month' => 12, 'year' => 9999));
-        }
-        if (!($startDate instanceOf Horde_Date)) {
-            $startDate = new Horde_Date($startDate);
-        }
-        if (!($endDate instanceOf Horde_Date)) {
-            $endDate = new Horde_Date($endDate);
-        }
-
-        $startDate = clone $startDate;
-        $startDate->hour = $startDate->min = $startDate->sec = 0;
-        $endDate = clone $endDate;
-        $endDate->hour = 23;
-        $endDate->min = $endDate->sec = 59;
-
-        $events = array();
-        foreach ($this->_events_cache as $event) {
-            if ($options['has_alarm'] && !$event->alarm) {
-                continue;
-            }
-
-            if ($options['hide_exceptions'] && !empty($event->baseid)) {
-                continue;
-            }
-
-            /* Ignore events out of the period. */
-            $recurs = $event->recurs();
-            if (
-                /* Starts after the period. */
-                $event->start->compareDateTime($endDate) > 0 ||
-                /* End before the period and doesn't recur. */
-                (!$recurs &&
-                 $event->end->compareDateTime($startDate) < 0)) {
-                continue;
-            }
-
-            if ($recurs) {
-                // Fixed end date? Check if end is before start period.
-                if ($event->recurrence->hasRecurEnd() &&
-                    $event->recurrence->recurEnd->compareDateTime($startDate) < 0) {
-                    continue;
-                } else {
-                    $next = $event->recurrence->nextRecurrence($startDate);
-                    if ($next == false || $next->compareDateTime($endDate) > 0) {
-                        continue;
-                    }
-                }
-            }
-
-            Kronolith::addEvents(
-                $events, $event, $startDate, $endDate,
-                $options['show_recurrence'],
-                $options['json'],
-                $options['cover_dates']);
-        }
-
-        return $events;
-    }
-
-    /**
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function getEvent($eventId = null)
-    {
-        if (!strlen($eventId)) {
-            return new Kronolith_Event_Kolab($this);
-        }
-
-        $this->synchronize();
-
-        if (isset($this->_events_cache[$eventId])) {
-            return $this->_events_cache[$eventId];
-        }
-
-        throw new Horde_Exception_NotFound(sprintf(_("Event not found: %s"), $eventId));
-    }
-
-    /**
-     * Get an event or events with the given UID value.
-     *
-     * @param string $uid The UID to match
-     * @param array $calendars A restricted array of calendar ids to search
-     * @param boolean $getAll Return all matching events? If this is false,
-     * an error will be returned if more than one event is found.
-     *
-     * @return Kronolith_Event
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function getByUID($uid, $calendars = null, $getAll = false)
-    {
-        if (!is_array($calendars)) {
-            $calendars = array_keys(Kronolith::listInternalCalendars(false, Horde_Perms::READ));
-        }
-        $id = Horde_Url::uriB64Encode($uid);
-
-        foreach ($calendars as $calendar) {
-            $this->open($calendar);
-            $this->synchronize();
-
-            if (!isset($this->_events_cache[$id])) {
-                continue;
-            }
-
-            // Ok, found event
-            $event = $this->_events_cache[$id];
-
-            if ($getAll) {
-                return array($event);
-            }
-
-            return $event;
-        }
-
-        throw new Horde_Exception_NotFound(sprintf(_("Event not found: %s"), $uid));
-    }
-
-    /**
-     * Updates an existing event in the backend.
-     *
-     * @param Kronolith_Event $event  The event to save.
-     *
-     * @return string  The event id.
-     * @throws Horde_Mime_Exception
-     */
-    protected function _updateEvent(Kronolith_Event $event)
-    {
-        return $this->_saveEvent($event, true);
-    }
-
-    /**
-     * Adds an event to the backend.
-     *
-     * @param Kronolith_Event $event  The event to save.
-     *
-     * @return string  The event id.
-     * @throws Horde_Mime_Exception
-     */
-    protected function _addEvent(Kronolith_Event $event)
-    {
-        return $this->_saveEvent($event, false);
-    }
-
-    /**
-     * Saves an event in the backend.
-     *
-     * @param Kronolith_Event $event  The event to save.
-     *
-     * @return string  The event id.
-     * @throws Horde_Mime_Exception
-     */
-    protected function _saveEvent($event, $edit)
-    {
-        $this->synchronize();
-
-        $action = $edit
-            ? array('action' => 'modify')
-            : array('action' => 'add');
-
-        if (!$event->uid) {
-            $event->uid = $this->_data->generateUID();
-            $event->id = Horde_Url::uriB64Encode($event->uid);
-        }
-
-        $object = $event->toKolab();
-        if ($edit) {
-            $this->_data->modify($object);
-        } else {
-            $this->_data->create($object);
-        }
-
-        /* Deal with tags */
-        if ($edit) {
-            $this->_updateTags($event);
-        } else {
-            $this->_addTags($event);
-        }
-
-        /* Notify about the changed event. */
-        Kronolith::sendNotification($event, $edit ? 'edit' : 'add');
-
-        /* Log the creation/modification of this item in the history log. */
-        try {
-            $GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $event->calendar . ':' . $event->uid, $action, true);
-        } catch (Exception $e) {
-            Horde::log($e, 'ERR');
-        }
-
-        // refresh IMAP cache
-        $this->synchronize(true);
-
-        if (is_callable('Kolab', 'triggerFreeBusyUpdate')) {
-            //Kolab::triggerFreeBusyUpdate($this->_data->parseFolder($event->calendar));
-        }
-
-        return $event->id;
-    }
-
-    /**
-     * Moves an event to a new calendar.
-     *
-     * @param string $eventId      The event to move.
-     * @param string $newCalendar  The new calendar.
-     *
-     * @return Kronolith_Event  The old event.
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    protected function _move($eventId, $newCalendar)
-    {
-        $event = $this->getEvent($eventId);
-        $this->synchronize();
-
-        $target = $GLOBALS['injector']
-            ->getInstance('Kronolith_Shares')
-            ->getShare($newCalendar)
-            ->get('folder');
-
-        $this->_data->move($event->uid, $target);
-        unset($this->_events_cache[$eventId]);
-        try {
-            $this->_kolab->getData($target, 'event')->synchronize();
-        } catch (Kolab_Storage_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        if (is_callable('Kolab', 'triggerFreeBusyUpdate')) {
-            //Kolab::triggerFreeBusyUpdate($this->_data->parseFolder($this->calendar));
-            //Kolab::triggerFreeBusyUpdate($this->_data->parseFolder($newCalendar));
-        }
-
-        return $event;
-    }
-
-    /**
-     * Delete all of a calendar's events.
-     *
-     * @param string $calendar  The name of the calendar to delete.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function delete($calendar)
-    {
-        $this->open($calendar);
-        $result = $this->synchronize();
-
-        $result = $this->_data->deleteAll($calendar);
-        if (is_callable('Kolab', 'triggerFreeBusyUpdate')) {
-            //Kolab::triggerFreeBusyUpdate($this->_data->parseFolder($calendar));
-        }
-    }
-
-    /**
-     * Deletes an event.
-     *
-     * @param string $eventId  The ID of the event to delete.
-     *
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     * @throws Horde_Mime_Exception
-     */
-    protected function _deleteEvent($eventId, $silent = false)
-    {
-        if ($eventId instanceof Kronolith_Event) {
-            $event = $eventId;
-            $this->synchronize();
-        } else {
-            $event = $this->getEvent($eventId);
-        }
-
-        $this->_data->delete($event->uid);
-        unset($this->_events_cache[$event->id]);
-
-        /* Notify about the deleted event. */
-        if (!$silent) {
-            $this->_handleNotifications($event, 'delete');
-        }
-
-        return $event;
-    }
-
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Driver/Mock.php 4.2.27-1/kronolith-4.2.23/lib/Driver/Mock.php
--- 4.2.23-1/kronolith-4.2.23/lib/Driver/Mock.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Driver/Mock.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,63 +0,0 @@
-<?php
-/**
- * The Kronolith_Driver_Mock class provides a Kronolith dummy driver.
- *
- * Copyright 2011-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Gunnar Wrobel <wrobel@pardus.de>
- * @package Kronolith
- */
-class Kronolith_Driver_Mock extends Kronolith_Driver
-{
-
-    /**
-     * List all alarms.
-     *
-     * @param Horde_Date $date    The date to list alarms for
-     * @param boolean $fullevent  Return the full event objects?
-     *
-     * @return array  An array of event ids, or Kronolith_Event objects
-     * @throws Kronolith_Exception
-     */
-    public function listAlarms($date, $fullevent = false)
-    {
-        return array();
-    }
-
-    /**
-     * Lists all events in the time range, optionally restricting results to
-     * only events with alarms.
-     *
-     * @param Horde_Date $startDate  The start of range date.
-     * @param Horde_Date $endDate    The end of date range.
-     * @param array $options         Additional options:
-     *   - show_recurrence: (boolean) Return every instance of a recurring
-     *                       event?
-     *                      DEFAULT: false (Only return recurring events once
-     *                      inside $startDate - $endDate range)
-     *   - has_alarm:       (boolean) Only return events with alarms.
-     *                      DEFAULT: false (Return all events)
-     *   - json:            (boolean) Store the results of the event's toJson()
-     *                      method?
-     *                      DEFAULT: false
-     *   - cover_dates:     (boolean) Add the events to all days that they
-     *                      cover?
-     *                      DEFAULT: true
-     *   - hide_exceptions: (boolean) Hide events that represent exceptions to
-     *                      a recurring event.
-     *                      DEFAULT: false (Do not hide exception events)
-     *   - fetch_tags:      (boolean) Fetch tags for all events.
-     *                      DEFAULT: false (Do not fetch event tags)
-     *
-     * @throws Kronolith_Exception
-     */
-    protected function _listEvents(Horde_Date $startDate = null,
-                                   Horde_Date $endDate = null,
-                                   array $options = array())
-    {
-        return array();
-    }
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Driver/Resource/Sql.php 4.2.27-1/kronolith-4.2.23/lib/Driver/Resource/Sql.php
--- 4.2.23-1/kronolith-4.2.23/lib/Driver/Resource/Sql.php	2017-09-19 15:02:33.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Driver/Resource/Sql.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,573 +0,0 @@
-<?php
-/**
- * The Kronolith_Driver_Resource class implements the Kronolith_Driver API for
- * storing resource calendars in a SQL backend.
- *
- * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Luc Saillard <luc.saillard@fr.alcove.com>
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @author  Jan Schneider <jan@horde.org>
- * @author  Michael J Rubinsky <mrubinsk@horde.org>
- * @package Kronolith
- */
-class Kronolith_Driver_Resource_Sql extends Kronolith_Driver
-{
-    /**
-     * The main event storage driver.
-     *
-     * @var Kronolith_Driver
-     */
-    protected $_driver;
-
-    /**
-     * Column information as Horde_Db_Adapter_Base_Column objects.
-     *
-     * @var array
-     */
-    protected $_columns = array();
-
-    /**
-     * The class name of the event object to instantiate.
-     *
-     * @var string
-     */
-    protected $_eventClass = 'Kronolith_Event_Resource_Sql';
-
-    /**
-     * Attempts to open a connection to the SQL server.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function initialize()
-    {
-        if (empty($this->_params['db'])) {
-            throw new InvalidArgumentException('Missing required Horde_Db_Adapter instance');
-        }
-        try {
-            $this->_db = $this->_params['db'];
-        } catch (Horde_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        $this->_params = array_merge(array(
-            'table' => 'kronolith_resources'
-        ), $this->_params);
-
-        $this->_driver = Kronolith::getDriver();
-        $this->_columns = $this->_db->columns($this->_params['table']);
-    }
-
-    /**
-     * Selects a calendar as the currently opened calendar.
-     *
-     * @param string $calendar  A calendar identifier.
-     */
-    public function open($calendar)
-    {
-        $this->calendar = $calendar;
-        $this->_driver->open($calendar);
-    }
-
-    /**
-     * Lists all events in the time range, optionally restricting results to
-     * only events with alarms.
-     *
-     * @param Horde_Date $startDate  The start of range date.
-     * @param Horde_Date $endDate    The end of date range.
-     * @param array $options         Additional options:
-     *   - show_recurrence: (boolean) Return every instance of a recurring
-     *                       event?
-     *                      DEFAULT: false (Only return recurring events once
-     *                      inside $startDate - $endDate range)
-     *   - has_alarm:       (boolean) Only return events with alarms.
-     *                      DEFAULT: false (Return all events)
-     *   - json:            (boolean) Store the results of the event's toJson()
-     *                      method?
-     *                      DEFAULT: false
-     *   - cover_dates:     (boolean) Add the events to all days that they
-     *                      cover?
-     *                      DEFAULT: true
-     *   - hide_exceptions: (boolean) Hide events that represent exceptions to
-     *                      a recurring event.
-     *                      DEFAULT: false (Do not hide exception events)
-     *   - fetch_tags:      (boolean) Fetch tags for all events.
-     *                      DEFAULT: false (Do not fetch event tags)
-     *
-     * @throws Kronolith_Exception
-     */
-    public function listEvents(Horde_Date $startDate = null,
-                               Horde_Date $endDate = null,
-                               array $options = array())
-    {
-        $json = !empty($options['json']);
-        $options['json'] = false;
-        $events = $this->_driver->listEvents($startDate, $endDate, $options);
-        $results = array();
-
-        foreach ($events as $period_key => $period) {
-            foreach ($period as $event_id => $event) {
-                $resource_event = $this->_buildResourceEvent($event);
-                $results[$period_key][$event_id] = $json
-                    ? $resource_event->toJson()
-                    : $resource_event;
-            }
-        }
-
-        return $results;
-    }
-
-    protected function _buildResourceEvent($driver_event)
-    {
-        $resource_event = new $this->_eventClass($this);
-        $resource_event->fromDriver($driver_event->toProperties(true));
-        $resource_event->calendar = $this->calendar;
-
-        return $resource_event;
-    }
-
-    /**
-     * Get an event or events with the given UID value.
-     *
-     * @param string $uid       The UID to match
-     * @param array $calendars  A restricted array of calendar ids to search
-     * @param boolean $getAll   Return all matching events?
-     *
-     * @return Kronolith_Event
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function getByUID($uid, $calendars = null, $getAll = false)
-    {
-       $event = new $this->_eventClass($this);
-       $driver_event = $this->_driver->getByUID($uid, $calendars, $getAll);
-       $event->fromDriver($driver_event->toProperties(true));
-       $event->calendar = $this->calendar;
-       return $event;
-    }
-
-    /**
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function getEvent($eventId = null)
-    {
-        if (!strlen($eventId)) {
-            $event = new $this->_eventClass($this);
-            $event->calendar = $this->calendar;
-
-            return $event;
-        }
-
-        $driver_event = $this->_driver->getEvent($eventId);
-        $event = $this->_buildResourceEvent($driver_event);
-
-        return $event;
-    }
-
-    /**
-     * Saves an event in the backend.
-     *
-     * If it is a new event, it is added, otherwise the event is updated.
-     *
-     * @param Kronolith_Event $event  The event to save.
-     *
-     * @return string  The event id.
-     * @throws Horde_Mime_Exception
-     * @throws Kronolith_Exception
-     */
-    public function saveEvent(Kronolith_Event $event)
-    {
-        return $this->_driver->saveEvent($event);
-    }
-
-    /**
-     * Delete an event.
-     *
-     * Since this is the Kronolith_Resource's version of the event, if we
-     * delete it, we must also make sure to remove it from the event that
-     * it is attached to. Not sure if there is a better way to do this...
-     *
-     * @param string|Kronolith_Event_Resource_Sql $eventId  The ID of the event
-     *                                                      to delete.
-     * @param boolean $silent  Don't send notifications, used when deleting
-     *                         events in bulk from maintenance tasks.
-     * @param boolean $keep_bound  If true, does not remove the resource from
-     *                             the bound event. @since 4.2.2
-     *
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function deleteEvent($eventId, $silent = false, $keep_bound = false)
-    {
-        if ($eventId instanceof Kronolith_Event_Resource_Sql) {
-            $delete_event = $eventId;
-            $eventId = $delete_event->id;
-        } else {
-            $delete_event = $this->getEvent($eventId);
-        }
-
-        if ($keep_bound) {
-            return;
-        }
-
-        $uid = $delete_event->uid;
-        $events = $this->_driver->getByUID($uid, null, true);
-        foreach ($events as $e) {
-            $resources = $e->getResources();
-            if (count($resources)) {
-                $r = $this->getResource($this->getResourceIdByCalendar($delete_event->calendar));
-                $e->removeResource($r);
-                $e->save();
-            }
-        }
-        $this->_driver->open($this->calendar);
-        $this->_driver->deleteEvent($delete_event, $silent);
-    }
-
-    /**
-     * Save or update a Kronolith_Resource
-     *
-     * @param Kronolith_Resource_Base $resource
-     *
-     * @return Kronolith_Resource object
-     * @throws Kronolith_Exception, Horde_Exception_PermissionDenied
-     */
-    public function save(Kronolith_Resource_Base $resource)
-    {
-        if (!$GLOBALS['registry']->isAdmin() &&
-            !$GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission('resource_management')) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-        if ($resource->getId()) {
-            $query = 'UPDATE ' . $this->_params['table'] . ' SET resource_name = ?, '
-                . 'resource_calendar = ? , resource_description = ?, '
-                . 'resource_response_type = ?, resource_type = ?, '
-                . 'resource_members = ?, resource_email = ? WHERE resource_id = ?';
-
-            $values = array($this->convertToDriver($resource->get('name')),
-                            $resource->get('calendar'),
-                            $this->convertToDriver($resource->get('description')),
-                            $resource->get('response_type'),
-                            $resource->get('type'),
-                            serialize($resource->get('members')),
-                            $resource->get('email'),
-                            $resource->getId());
-
-            try {
-                $this->_db->update($query, $values);
-            } catch (Horde_Db_Exception $e) {
-                throw new Kronolith_Exception($e);
-            }
-        } else {
-            $query = 'INSERT INTO ' . $this->_params['table']
-                . ' (resource_name, resource_calendar, '
-                .  'resource_description, resource_response_type, '
-                . ' resource_type, resource_members, resource_email)'
-                . ' VALUES (?, ?, ?, ?, ?, ?, ?)';
-            $values = array($this->convertToDriver($resource->get('name')),
-                            $resource->get('calendar'),
-                            $this->convertToDriver($resource->get('description')),
-                            $resource->get('response_type'),
-                            $resource->get('type'),
-                            serialize($resource->get('members')),
-                            $resource->get('email'));
-            try {
-                $id = $this->_db->insert($query, $values);
-            } catch (Horde_Db_Exception $e) {
-                throw new Kronolith_Exception($e);
-            }
-            $resource->setId($id);
-        }
-
-        return $resource;
-    }
-
-    /**
-     * Removes a resource from storage, along with any events in the resource's
-     * calendar.
-     *
-     * @param Kronolith_Resource_Base $resource  The kronolith resource to remove
-     *
-     * @throws Kronolith_Exception, Horde_Exception_PermissionDenied
-     */
-    public function delete($resource)
-    {
-        if (!$GLOBALS['registry']->isAdmin() &&
-            !$GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission('resource_management')) {
-            throw new Horde_Exception_PermissionDenied();
-        }
-
-        if (!$resource->getId()) {
-            throw new Kronolith_Exception(_("Resource not valid."));
-        }
-
-        // Get group memberships and remove from group.
-        $groups = $this->getGroupMemberships($resource->getId());
-        foreach ($groups as $id) {
-            $rg = $this->getResource($id);
-            $members = $rg->get('members');
-            unset($members[array_search($resource->getId(), $members)]);
-            $rg->set('members', $members);
-            $rg->save();
-        }
-
-        $this->_deleteResourceCalendar($resource->get('calendar'));
-        try {
-            $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE resource_id = ?';
-            $this->_db->delete($query, array($resource->getId()));
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-    }
-
-    /**
-     * Obtain a Kronolith_Resource by the resource's id
-     *
-     * @param integer $id  The key for the Kronolith_Resource
-     *
-     * @return Kronolith_Resource_Base
-     * @throws Kronolith_Exception
-     */
-    public function getResource($id)
-    {
-        $query = 'SELECT resource_id, resource_name, resource_calendar, '
-            . 'resource_description, resource_response_type, resource_type, '
-            . 'resource_members, resource_email FROM ' . $this->_params['table']
-            . ' WHERE resource_id = ?';
-
-        try {
-            $results = $this->_db->selectOne($query, array($id));
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-        if (!count($results)) {
-            throw new Horde_Exception_NotFound('Resource not found');
-        }
-
-        $class = 'Kronolith_Resource_' . $results['resource_type'];
-        if (!class_exists($class)) {
-            throw new Kronolith_Exception('Could not load the class definition for ' . $class);
-        }
-
-        return new $class($this->_fromDriver($results));
-    }
-
-    /**
-     * Obtain the resource id associated with the given calendar uid.
-     *
-     * @param string $calendar  The calendar's uid.
-     *
-     * @return integer  The Kronolith_Resource id.
-     * @throws Kronolith_Exception
-     */
-    public function getResourceIdByCalendar($calendar)
-    {
-        $query = 'SELECT resource_id FROM ' . $this->_params['table']
-            . ' WHERE resource_calendar = ?';
-        try {
-            $result = $this->_db->selectValue($query, array($calendar));
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-        if (empty($result)) {
-            throw new Horde_Exception_NotFound('Resource not found');
-        }
-
-        return $result;
-    }
-
-    /**
-     * Determine if the provided calendar id represents a resource's calendar.
-     *
-     * @param string $calendar  The calendar identifier to check.
-     *
-     * @return boolean
-     */
-    public function isResourceCalendar($calendar)
-    {
-        $query = 'SELECT count(*) FROM ' . $this->_params['table']
-            . ' WHERE resource_calendar = ?';
-        try {
-            return $this->_db->selectValue($query, array($calendar)) > 0;
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-    }
-
-    /**
-     * Return a list of Kronolith_Resources
-     *
-     * Right now, all users have Horde_Perms::READ, but only system admins have
-     * Horde_Perms::EDIT | Horde_Perms::DELETE
-     *
-     * @param integer $perms   A Horde_Perms::* constant.
-     * @param array $filter    A hash of field/values to filter on.
-     * @param string $orderby  Field to order results by. Null for no ordering.
-     *
-     * @return an array of Kronolith_Resource objects.
-     * @throws Kronolith_Exception
-     */
-    public function listResources($perms = Horde_Perms::READ, array $filter = array(), $orderby = null)
-    {
-        if (($perms & (Horde_Perms::EDIT | Horde_Perms::DELETE)) &&
-            !$GLOBALS['registry']->isAdmin()) {
-            return array();
-        }
-
-        $query = 'SELECT resource_id, resource_name, resource_calendar, resource_description,'
-            . ' resource_response_type, resource_type, resource_members, resource_email FROM '
-            . $this->_params['table'];
-        if (count($filter)) {
-            $clause = ' WHERE ';
-            $i = 0;
-            $c = count($filter);
-            foreach (array_keys($filter) as $field) {
-                $clause .= 'resource_' . $field . ' = ?' . (($i++ < ($c - 1)) ? ' AND ' : '');
-            }
-            $query .= $clause;
-        }
-
-        if (!empty($orderby)) {
-            $query .= ' ORDER BY resource_' . $orderby;
-        }
-
-        try {
-            $results = $this->_db->selectAll($query, $filter);
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-        $return = array();
-        foreach ($results as $row) {
-            $class = 'Kronolith_Resource_' . $row['resource_type'];
-            $return[$row['resource_id']] = new $class($this->_fromDriver(array_merge(array('resource_id' => $row['resource_id']), $row)));
-        }
-
-        return $return;
-    }
-
-    /**
-     * Obtain the group id for each group the specified resource is a member of.
-     *
-     * @param integer $resource_id  The resource id to check for.
-     *
-     * @return array  An array of group ids.
-     * @throws Kronolith_Exception
-     */
-    public function getGroupMemberships($resource_id)
-    {
-        $groups = $this->listResources(Horde_Perms::READ, array('type' => Kronolith_Resource::TYPE_GROUP));
-        $in = array();
-        foreach ($groups as $group) {
-            $members = $group->get('members');
-            if (array_search($resource_id, $members) !== false) {
-                $in[] = $group->getId();
-            }
-        }
-
-        return $in;
-    }
-
-    /**
-     * Converts a value from the driver's charset to the default
-     * charset.
-     *
-     * @param mixed $value  A value to convert.
-     *
-     * @return mixed  The converted value.
-     */
-    public function convertFromDriver($value)
-    {
-        return Horde_String::convertCharset($value, $this->_params['charset'], 'UTF-8');
-    }
-
-    /**
-     * Converts a value from the default charset to the driver's
-     * charset.
-     *
-     * @param mixed $value  A value to convert.
-     *
-     * @return mixed  The converted value.
-     */
-    public function convertToDriver($value)
-    {
-        return Horde_String::convertCharset($value, 'UTF-8', $this->_params['charset']);
-    }
-
-    /**
-     * Delete the resource calendar
-     *
-     * @param string $calendar  The calendar id.
-     */
-    public function _deleteResourceCalendar($calendar)
-    {
-        $this->open($calendar);
-        $events = $this->listEvents(null, null, array('cover_dates' => false));
-        foreach ($events as $dayevents) {
-            foreach ($dayevents as $event) {
-                $this->deleteEvent($event, true);
-            }
-        }
-    }
-
-    /**
-     * Convert from driver keys and charset to Kronolith keys and charset.
-     *
-     * @param array $params  The key/values to convert.
-     *
-     * @return array  An array of converted values.
-     */
-    protected function _fromDriver(array $params)
-    {
-        $return = array();
-
-        foreach ($params as $field => $value) {
-            switch ($field) {
-            case 'resource_description':
-                $value = $this->_columns['resource_description']
-                    ->binaryToString($value);
-                // Fall through.
-            case 'resource_name':
-                $value = $this->convertFromDriver($value);
-                break;
-            case 'resource_members':
-                $value = $this->_columns['resource_members']
-                    ->binaryToString($value);
-                $value = @unserialize($value);
-                break;
-            }
-
-            $return[str_replace('resource_', '', $field)] = $value;
-        }
-
-        return $return;
-    }
-
-    /**
-     * Helper function to update an existing event's tags to tagger storage.
-     *
-     * @param Kronolith_Event $event  The event to update
-     */
-    protected function _updateTags(Kronolith_Event $event)
-    {
-        // noop
-    }
-
-    /**
-     * Helper function to add tags from a newly creted event to the tagger.
-     *
-     * @param Kronolith_Event $event  The event to save tags to storage for.
-     */
-    protected function _addTags(Kronolith_Event $event)
-    {
-        // noop
-    }
-
-    protected function _handleNotifications(Kronolith_Event $event, $action)
-    {
-        // noop
-    }
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Driver/Sql.php 4.2.27-1/kronolith-4.2.23/lib/Driver/Sql.php
--- 4.2.23-1/kronolith-4.2.23/lib/Driver/Sql.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Driver/Sql.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,926 +0,0 @@
-<?php
-/**
- * The Kronolith_Driver_Sql class implements the Kronolith_Driver API for a
- * SQL backend.
- *
- * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Luc Saillard <luc.saillard@fr.alcove.com>
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-class Kronolith_Driver_Sql extends Kronolith_Driver
-{
-    /**
-     * The object handle for the current database connection.
-     *
-     * @var Horde_Db_Adapter
-     */
-    protected $_db;
-
-    /**
-     * Column information as Horde_Db_Adapter_Base_Column objects.
-     *
-     * @var array
-     */
-    protected $_columns = array();
-
-    /**
-     * Cache events as we fetch them to avoid fetching the same event from the
-     * DB twice.
-     *
-     * @var array
-     */
-    protected $_cache = array();
-
-    /**
-     * The class name of the event object to instantiate.
-     *
-     * Can be overwritten by sub-classes.
-     *
-     * @var string
-     */
-    protected $_eventClass = 'Kronolith_Event_Sql';
-
-    /**
-     * Returns the background color of the current calendar.
-     *
-     * @return string  The calendar color.
-     */
-    public function backgroundColor()
-    {
-        if ($GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $this->calendar) !== false) {
-            return $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $this->calendar)->background();
-        }
-        return '#dddddd';
-    }
-
-    /**
-     * Returns whether this driver supports per-event timezones.
-     *
-     * @return boolean  Whether this drivers suppports per-event timezones.
-     */
-    public function supportsTimezones()
-    {
-        return $this->getParam('utc');
-    }
-
-    /**
-     *
-     * @param Horde_Date $date    The date to list alarms for
-     * @param boolean $fullevent  Return the full event objects?
-     *
-     * @return array  An array of event ids, or Kronolith_Event objects
-     * @throws Kronolith_Exception
-     */
-    public function listAlarms($date, $fullevent = false)
-    {
-        $allevents = $this->listEvents($date, null, array('has_alarm' => true));
-        $events = array();
-        foreach ($allevents as $dayevents) {
-            foreach ($dayevents as $event) {
-                if (!$event->recurs()) {
-                    $start = new Horde_Date($event->start);
-                    $start->min -= $event->alarm;
-                    if ($start->compareDateTime($date) <= 0 &&
-                        $date->compareDateTime($event->end) <= -1) {
-                        $events[] = $fullevent ? $event : $event->id;
-                    }
-                } else {
-                    // Need to start at the beginning of the day to catch the
-                    // case where we might be within the event's timespan
-                    // when we call this, hence nextRecurrence() would miss the
-                    // current event.
-                    $start = clone $date;
-                    $start->min = 0;
-                    $start->hour = 0;
-                    $start->sec = 0;
-                    if ($next = $event->recurrence->nextRecurrence($start)) {
-                        if ($event->recurrence->hasException($next->year, $next->month, $next->mday)) {
-                            continue;
-                        }
-                        $start = new Horde_Date($next);
-                        $start->min -= $event->alarm;
-                        $diff = Date_Calc::dateDiff(
-                            $event->start->mday,
-                            $event->start->month,
-                            $event->start->year,
-                            $event->end->mday,
-                            $event->end->month,
-                            $event->end->year
-                        );
-                        if ($diff == -1) {
-                            $diff = 0;
-                        }
-                        $end = new Horde_Date(array(
-                            'year' => $next->year,
-                            'month' => $next->month,
-                            'mday' => $next->mday + $diff,
-                            'hour' => $event->end->hour,
-                            'min' => $event->end->min,
-                            'sec' => $event->end->sec)
-                        );
-                        if ($start->compareDateTime($date) <= 0 &&
-                            $date->compareDateTime($end) <= -1) {
-                            if ($fullevent) {
-                                $event->start = $next;
-                                $event->end = $end;
-                                $events[] = $event;
-                            } else {
-                                $events[] = $event->id;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        return $events;
-    }
-
-    /**
-     * Searches a calendar.
-     *
-     * @param object $query  An object with the criteria to search for.
-     * @param boolean $json  Store the results of the events' toJson() method?
-     *
-     * @return mixed  An array of Kronolith_Events.
-     * @throws Kronolith_Exception
-     */
-    public function search($query, $json = false)
-    {
-        /* Build SQL conditions based on the query string. */
-        $cond = '((';
-        $values = array();
-
-        foreach (array('title', 'location', 'url', 'description') as $field) {
-            if (!empty($query->$field)) {
-                $binds = $this->_db->buildClause('event_' . $field, 'LIKE', $this->convertToDriver($query->$field), true);
-                if (is_array($binds)) {
-                    $cond .= $binds[0] . ' AND ';
-                    $values = array_merge($values, $binds[1]);
-                } else {
-                    $cond .= $binds;
-                }
-            }
-        }
-
-        if (!empty($query->baseid)) {
-            $binds = $this->_db->buildClause('event_baseid', '=', $query->baseid, true);
-            if (is_array($binds)) {
-                $cond .= $binds[0] . ' AND ';
-                $values = array_merge($values, $binds[1]);
-            } else {
-                $cond .= $binds;
-            }
-        }
-
-        if (isset($query->status)) {
-            $binds = $this->_db->buildClause('event_status', '=', $query->status, true);
-            if (is_array($binds)) {
-                $cond .= $binds[0] . ' AND ';
-                $values = array_merge($values, $binds[1]);
-            } else {
-                $cond .= $binds;
-            }
-        }
-        if (!empty($query->creator)) {
-            $binds = $this->_db->buildClause('event_creator_id', '=', $query->creator, true);
-            if (is_array($binds)) {
-                $cond .= $binds[0] . ' AND ';
-                $values = array_merge($values, $binds[1]);
-            } else {
-                $cond .= $binds;
-            }
-        }
-
-        if ($cond == '((') {
-            $cond = '';
-        } else {
-            $cond = substr($cond, 0, strlen($cond) - 5) . '))';
-        }
-
-        $eventIds = $this->_listEventsConditional(empty($query->start) ? null : $query->start,
-                                                  empty($query->end) ? null : $query->end,
-                                                  $cond,
-                                                  $values);
-        $events = array();
-        foreach ($eventIds as $eventId) {
-            Kronolith::addSearchEvents($events, $this->getEvent($eventId), $query, $json);
-        }
-
-        return $events;
-    }
-
-    /**
-     * Checks if the event's UID already exists and returns all event
-     * ids with that UID.
-     *
-     * @param string $uid          The event's uid.
-     * @param string $calendar_id  Calendar to search in.
-     *
-     * @return string|boolean  Returns a string with event_id or false if
-     *                         not found.
-     * @throws Kronolith_Exception
-     */
-    public function exists($uid, $calendar_id = null)
-    {
-        $query = 'SELECT event_id  FROM ' . $this->_params['table'] . ' WHERE event_uid = ?';
-        $values = array($uid);
-
-        if (!is_null($calendar_id)) {
-            $query .= ' AND calendar_id = ?';
-            $values[] = $calendar_id;
-        }
-
-        try {
-            $event = $this->_db->selectValue($query, $values);
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        return !empty($event) ? $event : false;
-    }
-
-    /**
-     * Lists all events in the time range, optionally restricting results to
-     * only events with alarms.
-     *
-     * @param Horde_Date $startDate  The start of range date.
-     * @param Horde_Date $endDate    The end of date range.
-     * @param array $options         Additional options:
-     *   - show_recurrence: (boolean) Return every instance of a recurring
-     *                       event?
-     *                      DEFAULT: false (Only return recurring events once
-     *                      inside $startDate - $endDate range)
-     *   - has_alarm:       (boolean) Only return events with alarms.
-     *                      DEFAULT: false (Return all events)
-     *   - json:            (boolean) Store the results of the event's toJson()
-     *                      method?
-     *                      DEFAULT: false
-     *   - cover_dates:     (boolean) Add the events to all days that they
-     *                      cover?
-     *                      DEFAULT: true
-     *   - hide_exceptions: (boolean) Hide events that represent exceptions to
-     *                      a recurring event.
-     *                      DEFAULT: false (Do not hide exception events)
-     *   - fetch_tags:      (boolean) Fetch tags for all events.
-     *                      DEFAULT: false (Do not fetch event tags)
-     *
-     * @throws Kronolith_Exception
-     */
-    protected function _listEvents(Horde_Date $startDate = null,
-                                   Horde_Date $endDate = null,
-                                   array $options = array())
-    {
-        if (!is_null($startDate)) {
-            $startDate = clone $startDate;
-            $startDate->hour = $startDate->min = $startDate->sec = 0;
-        }
-        if (!is_null($endDate)) {
-            $endDate = clone $endDate;
-            $endDate->hour = 23;
-            $endDate->min = $endDate->sec = 59;
-        }
-
-        $conditions =  $options['has_alarm'] ? 'event_alarm > ?' : '';
-        $values = $options['has_alarm'] ? array(0) : array();
-        if ($options['hide_exceptions']) {
-            if (!empty($conditions)) {
-                $conditions .= ' AND ';
-            }
-            $conditions .= "event_baseid = ''";
-        }
-
-        $events = $this->_listEventsConditional($startDate, $endDate, $conditions, $values);
-        $results = array();
-        $tags = null;
-        if ($options['fetch_tags'] && count($events)) {
-            $tags = Kronolith::getTagger()->getTags(array_keys($events));
-        }
-        foreach ($events as $id) {
-            $event = $this->getEvent($id);
-            if (isset($tags) && !empty($tags[$event->uid])) {
-                $event->tags = $tags[$event->uid];
-            }
-            Kronolith::addEvents(
-                $results, $event, $startDate, $endDate, $options['show_recurrence'],
-                $options['json'], $options['cover_dates']);
-        }
-
-        return $results;
-    }
-
-    /**
-     * Lists all events that satisfy the given conditions.
-     *
-     * @param Horde_Date $startInterval  Start of range date object.
-     * @param Horde_Date $endInterval    End of range data object.
-     * @param string $conditions         Conditions, given as SQL clauses.
-     * @param array $vals                SQL bind variables for use with
-     *                                   $conditions clauses.
-     *
-     * @return array  Events in the given time range satisfying the given
-     *                conditions.
-     * @throws Kronolith_Exception
-     */
-    private function _listEventsConditional(Horde_Date $startInterval = null,
-                                            Horde_Date $endInterval = null,
-                                            $conditions = '', array $vals = array())
-    {
-        if ($this->getParam('utc')) {
-            if (!is_null($startInterval)) {
-                $startInterval = clone $startInterval;
-                $startInterval->setTimezone('UTC');
-            }
-            if (!is_null($endInterval)) {
-                $endInterval = clone $endInterval;
-                $endInterval->setTimezone('UTC');
-            }
-        }
-        $q = 'SELECT event_id, event_uid, event_description, event_location,' .
-            ' event_private, event_status, event_attendees,' .
-            ' event_title, event_recurcount, event_url, event_timezone,' .
-            ' event_recurtype, event_recurenddate, event_recurinterval,' .
-            ' event_recurdays, event_start, event_end, event_allday,' .
-            ' event_alarm, event_alarm_methods, event_modified,' .
-            ' event_exceptions, event_creator_id, event_resources, event_baseid,' .
-            ' event_exceptionoriginaldate FROM ' . $this->_params['table'] .
-            ' WHERE calendar_id = ?';
-        $values = array($this->calendar);
-
-        if ($conditions) {
-            $q .= ' AND ' . $conditions;
-            $values = array_merge($values, $vals);
-        }
-
-        if (!is_null($startInterval) && !is_null($endInterval)) {
-            $etime = $endInterval->format('Y-m-d H:i:s');
-            $stime = $startInterval->format('Y-m-d H:i:s');
-            $q .= ' AND ((event_end >= ? AND event_start <= ?) OR (event_recurenddate >= ? AND event_start <= ? AND event_recurtype <> ?))';
-            array_push($values, $stime, $etime, $stime, $etime, Horde_Date_Recurrence::RECUR_NONE);
-        } elseif (!is_null($startInterval)) {
-            $stime = $startInterval->format('Y-m-d H:i:s');
-            $q .= ' AND ((event_end >= ?) OR (event_recurenddate >= ? AND event_recurtype <> ?))';
-            array_push($values, $stime, $stime, Horde_Date_Recurrence::RECUR_NONE);
-        } elseif (!is_null($endInterval)) {
-            $q .= ' AND (event_start <= ?)';
-            $values[] = $endInterval->format('Y-m-d H:i:s');
-        }
-
-        /* Run the query. */
-        try {
-            $qr = $this->_db->selectAll($q, $values);
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        $events = array();
-        foreach ($qr as $row) {
-            /* If the event did not have a UID before, we need to give it
-             * one. */
-            if (empty($row['event_uid'])) {
-                $row['event_uid'] = (string)new Horde_Support_Guid;
-
-                /* Save the new UID for data integrity. */
-                $query = 'UPDATE ' . $this->_params['table'] . ' SET event_uid = ? WHERE event_id = ?';
-                $values = array($row['event_uid'], $row['event_id']);
-                try {
-                    $this->_db->update($query, $values);
-                } catch (Horde_Db_Exception $e) {
-                    throw new Kronolith_Exception($e);
-                }
-            }
-
-            /* Convert TEXT/CLOB fields. */
-            $row = $this->convertBlobs($row);
-
-            /* We have all the information we need to create an event object
-             * for this event, so go ahead and cache it. */
-            $this->_cache[$this->calendar][$row['event_id']] = new $this->_eventClass($this, $row);
-            if ($row['event_recurtype'] == Horde_Date_Recurrence::RECUR_NONE) {
-                $events[$row['event_uid']] = $row['event_id'];
-            } else {
-                $next = $this->nextRecurrence($row['event_id'], $startInterval);
-                if ($next &&
-                    (is_null($endInterval) ||
-                     $next->compareDateTime($endInterval) < 0)) {
-                    $events[$row['event_uid']] = $row['event_id'];
-                }
-            }
-        }
-
-        return $events;
-    }
-
-    /**
-     * Returns the number of events in the current calendar.
-     *
-     * @return integer  The number of events.
-     * @throws Kronolith_Exception
-     */
-    public function countEvents()
-    {
-        $query = sprintf('SELECT count(*) FROM %s WHERE calendar_id = ?',
-                         $this->_params['table']);
-
-        /* Run the query. */
-        try {
-            $result = $this->_db->selectValue($query, array($this->calendar));
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        return $result;
-    }
-
-    /**
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function getEvent($eventId = null)
-    {
-        if (!strlen($eventId)) {
-            return new $this->_eventClass($this);
-        }
-
-        if (isset($this->_cache[$this->calendar][$eventId])) {
-            return $this->_cache[$this->calendar][$eventId];
-        }
-
-        $query = 'SELECT event_id, event_uid, event_description,' .
-            ' event_location, event_private, event_status, event_attendees,' .
-            ' event_title, event_recurcount, event_url, event_timezone,' .
-            ' event_recurtype, event_recurenddate, event_recurinterval,' .
-            ' event_recurdays, event_start, event_end, event_allday,' .
-            ' event_alarm, event_alarm_methods, event_modified,' .
-            ' event_exceptions, event_creator_id, event_resources,' .
-            ' event_baseid, event_exceptionoriginaldate FROM ' .
-            $this->_params['table'] . ' WHERE event_id = ? AND calendar_id = ?';
-
-        $values = array($eventId, $this->calendar);
-
-        try {
-            $event = $this->_db->selectOne($query, $values);
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-        if ($event) {
-            /* Convert TEXT/CLOB fields. */
-            $event = $this->convertBlobs($event);
-
-            $this->_cache[$this->calendar][$eventId] = new $this->_eventClass($this, $event);
-            return $this->_cache[$this->calendar][$eventId];
-        }
-
-        throw new Horde_Exception_NotFound(_("Event not found"));
-    }
-
-    /**
-     * Get an event or events with the given UID value.
-     *
-     * @param string $uid       The UID to match
-     * @param array $calendars  A restricted array of calendar ids to search
-     * @param boolean $getAll   Return all matching events?
-     *
-     * @return Kronolith_Event
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function getByUID($uid, $calendars = null, $getAll = false)
-    {
-        $query = 'SELECT event_id, event_uid, calendar_id, event_description,' .
-            ' event_location, event_private, event_status, event_attendees,' .
-            ' event_title, event_recurcount, event_url, event_timezone,' .
-            ' event_recurtype, event_recurenddate, event_recurinterval,' .
-            ' event_recurdays, event_start, event_end, event_allday,' .
-            ' event_alarm, event_alarm_methods, event_modified,' .
-            ' event_exceptions, event_creator_id, event_resources, event_baseid,' .
-            ' event_exceptionoriginaldate FROM ' . $this->_params['table'] .
-            ' WHERE event_uid = ?';
-        $values = array((string)$uid);
-
-        /* Optionally filter by calendar */
-        if (!empty($calendars)) {
-            if (!count($calendars)) {
-                throw new Kronolith_Exception(_("No calendars to search"));
-            }
-            $query .= ' AND calendar_id IN (?' . str_repeat(', ?', count($calendars) - 1) . ')';
-            $values = array_merge($values, $calendars);
-        }
-
-        try {
-            $events = $this->_db->selectAll($query, $values);
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-        if (!count($events)) {
-            throw new Horde_Exception_NotFound(sprintf(_("%s not found"), $uid));
-        }
-
-        $eventArray = array();
-        foreach ($events as $event) {
-            /* Convert TEXT/CLOB fields. */
-            $event = $this->convertBlobs($event);
-
-            $this->open($event['calendar_id']);
-            $this->_cache[$this->calendar][$event['event_id']] = new $this->_eventClass($this, $event);
-            $eventArray[] = $this->_cache[$this->calendar][$event['event_id']];
-        }
-
-        if ($getAll) {
-            return $eventArray;
-        }
-
-        /* First try the user's own calendars. */
-        $ownerCalendars = Kronolith::listInternalCalendars(true, Horde_Perms::READ);
-        $event = null;
-        foreach ($eventArray as $ev) {
-            if (isset($ownerCalendars[$ev->calendar])) {
-                $event = $ev;
-                break;
-            }
-        }
-
-        /* If not successful, try all calendars the user has access too. */
-        if (empty($event)) {
-            $readableCalendars = Kronolith::listInternalCalendars(false, Horde_Perms::READ);
-            foreach ($eventArray as $ev) {
-                if (isset($readableCalendars[$ev->calendar])) {
-                    $event = $ev;
-                    break;
-                }
-            }
-        }
-
-        if (empty($event)) {
-            $event = $eventArray[0];
-        }
-
-        return $event;
-    }
-
-    /**
-     * Builds a history hash for a modified event.
-     *
-     * We don't write it in here because we don't want to commit history before
-     * the actual changes are made.
-     *
-     * @param Kronolith_Event $event  The event to log.
-     *
-     * @return array  The change log.
-     * @throws Horde_Mime_Exception
-     * @throws Kronolith_Exception
-     */
-    protected function _buildEventHistory(Kronolith_Event $event)
-    {
-        $changes = array('action' => 'modify');
-
-        /* We cannot use getEvent() because of caching. */
-        $oldProperties = $this->getbyUID(
-            $event->uid,
-            array($event->calendar))->toProperties();
-        $newProperties = $event->toProperties();
-        if (empty($oldProperties)) {
-            return $changes;
-        }
-
-        foreach (array_keys($newProperties) as $property) {
-            if (!isset($oldProperties[$property]) ||
-                ($oldProperties[$property] != $newProperties[$property])) {
-                $changes['new'][$property] = $newProperties[$property];
-                $changes['old'][$property] = !isset($oldProperties[$property]) ? $oldProperties[$property] : null;
-            }
-        }
-
-        return $changes;
-    }
-
-    /**
-     * Updates an existing event in the backend.
-     *
-     * @param Kronolith_Event $event  The event to save.
-     *
-     * @return string  The event id.
-     * @throws Horde_Mime_Exception
-     * @throws Kronolith_Exception
-     */
-    protected function _updateEvent(Kronolith_Event $event)
-    {
-        $values = array();
-        $query = 'UPDATE ' . $this->_params['table'] . ' SET ';
-        foreach ($event->toProperties() as $key => $val) {
-            $query .= " $key = ?,";
-            $values[] = $val;
-        }
-        $query = substr($query, 0, -1);
-        $query .= ' WHERE event_id = ?';
-        $values[] = $event->id;
-
-        $history = $this->_buildEventHistory($event);
-
-        try {
-            $this->_db->update($query, $values);
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        /* Log the modification of this item in the history log. */
-        if ($event->uid) {
-            try {
-                $GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $this->calendar . ':' . $event->uid, $history, true);
-            } catch (Exception $e) {
-                Horde::log($e, 'ERR');
-            }
-        }
-
-        /* If this event is an exception, we need to modify the base event's
-         * history log also, or some sync clients will never pick up the
-         * change. */
-        if ($event->baseid) {
-            try {
-                $GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $this->calendar . ':' . $event->baseid, $history, true);
-            } catch (Exception $e) {
-                Horde::log($e, 'ERR');
-            }
-        }
-        $this->_updateTags($event);
-
-        /* Update Geolocation */
-        try {
-            $GLOBALS['injector']->getInstance('Kronolith_Geo')->setLocation($event->id, $event->geoLocation);
-        } catch (Kronolith_Exception $e) {
-
-        }
-
-        /* Notify users about the changed event. */
-        $this->_handleNotifications($event, 'edit');
-
-        return $event->id;
-    }
-
-    /**
-     * Adds an event to the backend.
-     *
-     * @param Kronolith_Event $event  The event to save.
-     *
-     * @return string  The event id.
-     * @throws Horde_Mime_Exception
-     * @throws Kronolith_Exception
-     */
-    protected function _addEvent(Kronolith_Event $event)
-    {
-        if (!$event->id) {
-            $event->id = (string)new Horde_Support_Randomid;
-        }
-        if (!$event->uid) {
-            $event->uid = (string)new Horde_Support_Guid;
-        }
-
-        $query = 'INSERT INTO ' . $this->_params['table'];
-        $cols_name = ' (event_id, event_uid,';
-        $cols_values = ' VALUES (?, ?,';
-        $values = array($event->id, $event->uid);
-        foreach ($event->toProperties() as $key => $val) {
-            $cols_name .= " $key,";
-            $cols_values .= ' ?,';
-            $values[] = $val;
-        }
-        $cols_name .= ' calendar_id)';
-        $cols_values .= ' ?)';
-        $values[] = $this->calendar;
-        $query .= $cols_name . $cols_values;
-
-        try {
-            $this->_db->insert($query, $values);
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-        /* Log the creation of this item in the history log. */
-        try {
-            $GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $this->calendar . ':' . $event->uid, array('action' => 'add'), true);
-        } catch (Exception $e) {
-            Horde::log($e, 'ERR');
-        }
-
-        $this->_addTags($event);
-
-        /* Update Geolocation */
-        if ($event->geoLocation) {
-            try {
-                $GLOBALS['injector']->getInstance('Kronolith_Geo')->setLocation($event->id, $event->geoLocation);
-            } catch (Kronolith_Exception $e) {
-
-            }
-        }
-
-        /* Notify users about the new event. */
-        $this->_handleNotifications($event, 'add');
-
-        return $event->id;
-    }
-
-    /**
-     * Moves an event to a new calendar.
-     *
-     * @param string $eventId      The event to move.
-     * @param string $newCalendar  The new calendar.
-     *
-     * @return Kronolith_Event  The old event.
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    protected function _move($eventId, $newCalendar)
-    {
-        /* Fetch the event for later use. */
-        $event = $this->getEvent($eventId);
-
-        $query = 'UPDATE ' . $this->_params['table'] . ' SET calendar_id = ? WHERE calendar_id = ? AND event_id = ?';
-        $values = array($newCalendar, $this->calendar, $eventId);
-
-        /* Attempt the move query. */
-        try {
-            $this->_db->update($query, $values);
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        return $event;
-    }
-
-    /**
-     * Delete all of a calendar's events.
-     *
-     * @param string $calendar  The name of the calendar to delete.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function delete($calendar)
-    {
-        $oldCalendar = $this->calendar;
-        $this->open($calendar);
-        $events = $this->listEvents(null, null, array('cover_dates' => false, 'hide_exceptions' => true));
-        $uids = array();
-        foreach ($events as $dayevents) {
-            foreach ($dayevents as $event) {
-                $uids[] = $event->uid;
-            }
-        }
-        foreach ($uids as $uid) {
-            $event = $this->getByUID($uid, array($calendar));
-            $this->deleteEvent($event->id);
-        }
-
-        $this->open($oldCalendar);
-    }
-
-    /**
-     * Deletes an event.
-     *
-     * @param string $eventId  The ID of the event to delete.
-     * @param boolean $silent  Don't send notifications, used when deleting
-     *                         events in bulk from maintenance tasks.
-     *
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     * @throws Horde_Mime_Exception
-     */
-    protected function _deleteEvent($eventId, $silent = false)
-    {
-        /* Fetch the event for later use. */
-        if ($eventId instanceof Kronolith_Event) {
-            $event = $eventId;
-            $eventId = $event->id;
-        } else {
-            $event = $this->getEvent($eventId);
-        }
-        $original_uid = $event->uid;
-        $isRecurring = $event->recurs();
-
-        $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE event_id = ? AND calendar_id = ?';
-        try {
-            $this->_db->delete($query, array($eventId, $this->calendar));
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        /* Notify about the deleted event. */
-        if (!$silent) {
-            $this->_handleNotifications($event, 'delete');
-        }
-
-        /* Now check for any exceptions that THIS event may have */
-        if ($isRecurring) {
-            $query = 'SELECT event_id FROM ' . $this->_params['table'] . ' WHERE event_baseid = ? AND calendar_id = ?';
-            $values = array($original_uid, $this->calendar);
-
-            try {
-                $result = $this->_db->selectValues($query, $values);
-            } catch (Horde_Db_Exception $e) {
-                throw new Kronolith_Exception($e);
-            }
-            foreach ($result as $id) {
-                $this->deleteEvent($id, true);
-            }
-        }
-
-        return $event;
-    }
-
-    /**
-     * Filters a list of events to return only those that belong to certain
-     * calendars.
-     *
-     * @param array $uids      A list of event UIDs.
-     * @param array $calendar  A list of calendar IDs.
-     *
-     * @return array  Event UIDs filtered by calendar IDs.
-     * @throws Kronolith_Exception
-     */
-    public function filterEventsByCalendar($uids, $calendar)
-    {
-        $sql = 'SELECT event_uid FROM kronolith_events WHERE calendar_id IN (' . str_repeat('?, ', count($calendar) - 1) . '?) '
-            . 'AND event_uid IN (' . str_repeat('?,', count($uids) - 1) . '?)';
-
-        try {
-            $result = $this->_db->selectValues($sql, array_merge($calendar, $uids));
-        } catch (Horde_Db_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        return $result;
-    }
-
-    /**
-     * Attempts to open a connection to the SQL server.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function initialize()
-    {
-        if (empty($this->_params['db'])) {
-            throw new InvalidArgumentException('Missing required Horde_Db_Adapter instance');
-        }
-        try {
-            $this->_db = $this->_params['db'];
-        } catch (Horde_Exception $e) {
-            throw new Kronolith_Exception($e);
-        }
-
-        $this->_params = array_merge(array(
-            'table' => 'kronolith_events'
-        ), $this->_params);
-
-        $this->_columns = $this->_db->columns($this->_params['table']);
-    }
-
-    /**
-     * Converts a value from the driver's charset to the default
-     * charset.
-     *
-     * @param mixed $value  A value to convert.
-     *
-     * @return mixed  The converted value.
-     */
-    public function convertFromDriver($value)
-    {
-        return Horde_String::convertCharset($value, $this->_params['charset'], 'UTF-8');
-    }
-
-    /**
-     * Converts a value from the default charset to the driver's
-     * charset.
-     *
-     * @param mixed $value  A value to convert.
-     *
-     * @return mixed  The converted value.
-     */
-    public function convertToDriver($value)
-    {
-        return Horde_String::convertCharset($value, 'UTF-8', $this->_params['charset']);
-    }
-
-    /**
-     * Converts TEXT/CLOB fields in an event.
-     *
-     * @param array $event  An event hash with TEXT/CLOB columns.
-     *
-     * @return array  The event with TEXT/CLOB columns converted to strings.
-     */
-    public function convertBlobs($event)
-    {
-        $clobs = array(
-            'alarm_methods', 'attendees', 'description', 'exceptions',
-            'location', 'resources'
-        );
-        foreach ($clobs as $clob) {
-            $event['event_' . $clob] = $this->_columns['event_' . $clob]
-                ->binaryToString($event['event_' . $clob]);
-        }
-        return $event;
-    }
-}
diff -pruN 4.2.23-1/kronolith-4.2.23/lib/Driver.php 4.2.27-1/kronolith-4.2.23/lib/Driver.php
--- 4.2.23-1/kronolith-4.2.23/lib/Driver.php	2017-09-19 15:02:34.000000000 +0000
+++ 4.2.27-1/kronolith-4.2.23/lib/Driver.php	1970-01-01 00:00:00.000000000 +0000
@@ -1,650 +0,0 @@
-<?php
-/**
- * Kronolith_Driver defines an API for implementing storage backends for
- * Kronolith.
- *
- * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-class Kronolith_Driver
-{
-    /**
-     * The current calendar.
-     *
-     * @var string
-     */
-    public $calendar;
-
-    /**
-     * The HTML background color to be used for this event.
-     *
-     * @var string
-     */
-    public $backgroundColor = '#ddd';
-
-    /**
-     * The HTML foreground color to be used for this event.
-     *
-     * @var string
-     */
-    public $foregroundColor = '#000';
-
-    /**
-     * A hash containing any parameters for the current driver.
-     *
-     * @var array
-     */
-    protected $_params = array();
-
-    /**
-     * An error message to throw when something is wrong.
-     *
-     * @var string
-     */
-    private $_errormsg;
-
-    /**
-     * Constructor.
-     *
-     * Just stores the $params in our newly-created object. All other work is
-     * done by {@link initialize()}.
-     *
-     * @param array $params     Any parameters needed for this driver.
-     * @param string $errormsg  A custom error message to use.
-     */
-    public function __construct(array $params = array(), $errormsg = null)
-    {
-        $this->_params = $params;
-        if ($errormsg === null) {
-            $this->_errormsg = _("The Calendar backend is not currently available.");
-        } else {
-            $this->_errormsg = $errormsg;
-        }
-    }
-
-    /**
-     * Returns a configuration for this driver.
-     *
-     * @param string $param  A parameter name.
-     *
-     * @return mixed  The parameter value or null if not set.
-     */
-    public function getParam($param)
-    {
-        return isset($this->_params[$param]) ? $this->_params[$param] : null;
-    }
-
-    /**
-     * Sets a configuration for this driver.
-     *
-     * @param string $param  A parameter name.
-     * @param mixed $value   The parameter value.
-     */
-    public function setParam($param, $value)
-    {
-        $this->_params[$param] = $value;
-    }
-
-    /**
-     * Sets all configuration parameters for this driver.
-     *
-     * @param string $params  A parameters hash.
-     */
-    public function setParams($params)
-    {
-        $this->_params = $params;
-    }
-
-    /**
-     * Selects a calendar as the currently opened calendar.
-     *
-     * @param string $calendar  A calendar identifier.
-     */
-    public function open($calendar)
-    {
-        $this->calendar = $calendar;
-    }
-
-    /**
-     * Returns the background color of the current calendar.
-     *
-     * @return string  The calendar color.
-     */
-    public function backgroundColor()
-    {
-        return '#dddddd';
-    }
-
-    /**
-     * Returns the colors of the current calendar.
-     *
-     * @return array  The calendar background and foreground color.
-     */
-    public function colors()
-    {
-        $color = $this->backgroundColor();
-        return array($color, Kronolith::foregroundColor($color));
-    }
-
-    /**
-     * Returns whether this driver supports per-event timezones.
-     *
-     * @return boolean  Whether this drivers suppports per-event timezones.
-     */
-    public function supportsTimezones()
-    {
-        return false;
-    }
-
-    /**
-     * Searches a calendar.
-     *
-     * @param object $query  An object with the criteria to search for.
-     * @param boolean $json  Store the results of the events' toJson() method?
-     *
-     * @return array An array of search results keyed by date, with each date
-     *               containing an array of Kronolith_Events occuring on that
-     *               date.
-     * @throws Kronolith_Exception
-     */
-    public function search($query, $json = false)
-    {
-        /* Our default implementation first gets <em>all</em> events in a
-         * specific period, and then filters based on the actual values that
-         * are filled in. Drivers can optimize this behavior if they have the
-         * ability. */
-        $results = array();
-
-        $events = $this->listEvents(
-            (!empty($query->start) ? $query->start : null),
-            (!empty($query->end) ? $query->end : null));
-        foreach ($events as $day_events) {
-            foreach ($day_events as $event) {
-                if ((((!isset($query->start) ||
-                       $event->end->compareDateTime($query->start) > 0) &&
-                      (!isset($query->end) ||
-                       $event->end->compareDateTime($query->end) < 0)) ||
-                     ($event->recurs() &&
-                      $event->end->compareDateTime($query->start) >= 0 &&
-                      $event->start->compareDateTime($query->end) <= 0)) &&
-                    (empty($query->title) ||
-                     stristr($event->getTitle(), $query->title)) &&
-                    (empty($query->location) ||
-                     stristr($event->location, $query->location)) &&
-                    (empty($query->description) ||
-                     stristr($event->description, $query->description)) &&
-                    (empty($query->creator) ||
-                     stristr($event->creator, $query->creator)) &&
-                    (!isset($query->status) ||
-                     $event->status == $query->status) &&
-                    (empty($query->baseid) ||
-                     $event->baseid == $query->baseid)) {
-                    Kronolith::addEvents($results, $event, $event->start, $event->end, false, $json, false);
-                }
-            }
-        }
-
-        return $results;
-    }
-
-    /**
-     * Finds the next recurrence of $eventId that's after $afterDate.
-     *
-     * @param string $eventId        The ID of the event to fetch.
-     * @param Horde_Date $afterDate  Return events after this date.
-     *
-     * @return Horde_Date|boolean  The date of the next recurrence or false if
-     *                             the event does not recur after $afterDate.
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function nextRecurrence($eventId, $afterDate)
-    {
-        $event = $this->getEvent($eventId);
-        return $event->recurs() ? $event->recurrence->nextRecurrence($afterDate) : false;
-    }
-
-    /**
-     * Returns the number of events in the current calendar.
-     *
-     * @return integer  The number of events.
-     * @throws Kronolith_Exception
-     */
-    public function countEvents()
-    {
-        $count = 0;
-        foreach ($this->listEvents() as $dayevents) {
-            $count += count($dayevents);
-        }
-        return $count;
-    }
-
-    /**
-     * Stub to initiate a driver.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function initialize()
-    {
-        return true;
-    }
-
-    /**
-     * Stub to be overridden in the child class.
-     *
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function getEvent($eventId = null)
-    {
-        throw new Kronolith_Exception($this->_errormsg);
-    }
-
-    /**
-     * Stub to be overridden in the child class.
-     *
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function getByUID($uid, $calendars = null, $getAll = false)
-    {
-        throw new Kronolith_Exception($this->_errormsg);
-    }
-
-    /**
-     * Stub to be overridden in the child class.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function listAlarms($date, $fullevent = false)
-    {
-        throw new Kronolith_Exception($this->_errormsg);
-    }
-
-    /**
-     * Lists all events in the time range, optionally restricting results to
-     * only events with alarms.
-     *
-     * @param Horde_Date $startDate  The start of range date.
-     * @param Horde_Date $endDate    The end of date range.
-     * @param array $options         Additional options:
-     *   - show_recurrence: (boolean) Return every instance of a recurring
-     *                       event?
-     *                      DEFAULT: false (Only return recurring events once
-     *                      inside $startDate - $endDate range)
-     *   - has_alarm:       (boolean) Only return events with alarms.
-     *                      DEFAULT: false (Return all events)
-     *   - json:            (boolean) Store the results of the event's toJson()
-     *                      method?
-     *                      DEFAULT: false
-     *   - cover_dates:     (boolean) Add the events to all days that they
-     *                      cover?
-     *                      DEFAULT: true
-     *   - hide_exceptions: (boolean) Hide events that represent exceptions to
-     *                      a recurring event.
-     *                      DEFAULT: false (Do not hide exception events)
-     *   - fetch_tags:      (boolean) Fetch tags for all events.
-     *                      DEFAULT: false (Do not fetch event tags)
-     *
-     * @throws Kronolith_Exception
-     */
-    public function listEvents(Horde_Date $startDate = null,
-                               Horde_Date $endDate = null,
-                               array $options = array())
-    {
-        // Defaults
-        $options = array_merge(array(
-            'show_recurrence' => false,
-            'has_alarm' => false,
-            'json' => false,
-            'cover_dates' => true,
-            'hide_exceptions' => false,
-            'fetch_tags' => false), $options);
-
-        return $this->_listEvents($startDate, $endDate, $options);
-    }
-
-    /**
-     * Stub to be overridden in concrete class.
-     *
-     * @param Horde_Date $startDate  The start of range date.
-     * @param Horde_Date $endDate    The end of date range.
-     * @param array $options         Additional options:
-     *   - show_recurrence: (boolean) Return every instance of a recurring event?
-     *                      DEFAULT: false (Only return recurring events once
-     *                      inside $startDate - $endDate range).
-     *   - has_alarm: (boolean) Only return events with alarms.
-     *                DEFAULT: false (Return all events)
-     *   - json: (boolean) Store the results of the event's toJson() method?
-     *           DEFAULT: false
-     *   - cover_dates: (boolean) Add the events to all days that they cover?
-     *                  DEFAULT: true
-     *   - hide_exceptions: (boolean) Hide events that represent exceptions to
-     *                      a recurring event.
-     *                      DEFAULT: false (Do not hide exception events)
-     *   - fetch_tags: (boolean) Fetch tags for all events.
-     *                 DEFAULT: false (Do not fetch event tags)
-     *
-     * @throws Kronolith_Exception
-     */
-    protected function _listEvents(
-        Horde_Date $startDate = null, Horde_Date $endDate = null, array $options = array())
-    {
-        throw new Kronolith_Exception($this->_errormsg);
-    }
-
-    /**
-     * Saves an event in the backend.
-     *
-     * If it is a new event, it is added, otherwise the event is updated.
-     *
-     * @param Kronolith_Event $event  The event to save.
-     *
-     * @return string  The event id.
-     * @throws Horde_Mime_Exception
-     * @throws Kronolith_Exception
-     */
-    public function saveEvent(Kronolith_Event $event)
-    {
-        if (empty($event->start) || $event->start->year <= 0 ||
-            empty($event->end) || $event->end->year <= 0) {
-            throw new Kronolith_Exception(_("Invalid date"));
-        }
-        if ($event->stored || $event->exists()) {
-            // If this event recurs and has bound exceptions, we must make sure
-            // that the exceptionoriginaldate is updated in those exceptions as
-            // well. See Bug: 13512
-            if ($event->recurs()) {
-                foreach ($event->boundExceptions() as $bound) {
-                    $t = $event->start->strftime('%T');
-                    $bound->exceptionoriginaldate = new Horde_Date($bound->start->strftime('%Y-%m-%d') . 'T' . $t);
-                    $bound->save();
-                }
-            }
-            return $this->_updateEvent($event);
-        }
-
-        return $this->_addEvent($event);
-    }
-
-    /**
-     * Stub to be overridden in the child class.
-     *
-     * @throws Kronolith_Exception
-     */
-    protected function _addEvent(Kronolith_Event $event)
-    {
-        throw new Kronolith_Exception($this->_errormsg);
-    }
-
-    /**
-     * Stub to be overridden in the child class.
-     *
-     * @throws Kronolith_Exception
-     */
-    protected function _updateEvent(Kronolith_Event $event)
-    {
-        throw new Kronolith_Exception($this->_errormsg);
-    }
-
-    /**
-     * Stub for child class to override if it can implement.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function exists($uid, $calendar_id = null)
-    {
-        throw new BadMethodCallException('Not supported');
-    }
-
-    /**
-     * Moves an event to a new calendar.
-     *
-     * @param string $eventId      The event to move.
-     * @param string $newCalendar  The new calendar.
-     *
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     */
-    public function move($eventId, $newCalendar)
-    {
-        $event = $this->_move($eventId, $newCalendar);
-
-        /* Attempt to move any bound exceptions. */
-        foreach ($event->boundExceptions() as $exception) {
-            $this->move($exception->id, $newCalendar);
-        }
-
-        /* Log the moving of this item in the history log. */
-        $uid = $event->uid;
-        if ($uid) {
-            $history = $GLOBALS['injector']->getInstance('Horde_History');
-            try {
-                $history->log('kronolith:' . $event->calendar . ':' . $uid, array('action' => 'delete'), true);
-                $history->log('kronolith:' . $newCalendar . ':' . $uid, array('action' => 'add'), true);
-            } catch (Exception $e) {
-                Horde::log($e, 'ERR');
-            }
-        }
-    }
-
-    /**
-     * Stub to be overridden in the child class.
-     *
-     * @throws Kronolith_Exception
-     */
-    protected function _move($eventId, $newCalendar)
-    {
-        throw new BadMethodCallException('Not supported');
-    }
-
-    /**
-     * Stub to be overridden in the child class.
-     *
-     * Note: This method only "Purges" the calendar - removing the
-     * events, it doesn't remove the calendar itself.
-     *
-     * @throws Kronolith_Exception
-     */
-    public function delete($calendar)
-    {
-        throw new BadMethodCallException('Not supported');
-    }
-
-    /**
-     * Deletes an event.
-     *
-     * @param mixed $eventId   Either the event id to delete, or the event
-     *                         object.
-     * @param boolean $silent  Don't send notifications, used when deleting
-     *                         events in bulk from maintenance tasks.
-     *
-     * @throws Kronolith_Exception
-     * @throws Horde_Exception_NotFound
-     * @throws Horde_Mime_Exception
-     */
-    public function deleteEvent($eventId, $silent = false)
-    {
-        $event = $this->_deleteEvent($eventId, $silent);
-        if (!$event) {
-            return;
-        }
-
-        /* Log the deletion of this item in the history log. */
-        if ($event->uid) {
-            try {
-                $GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $this->calendar . ':' . $event->uid, array('action' => 'delete'), true);
-            } catch (Exception $e) {
-                Horde::log($e, 'ERR');
-            }
-        }
-
-        /* Remove the event from any resources that are attached to it. */
-        $resources = $event->getResources();
-        if (count($resources)) {
-            $rd = Kronolith::getDriver('Resource');
-            foreach ($resources as $uid => $resource) {
-                if ($resource['response'] !== Kronolith::RESPONSE_DECLINED &&
-                    $resource['response'] !== Kronolith::RESPONSE_NONE) {
-                    try {
-                        $r = $rd->getResource($uid);
-                        $r->removeEvent($event);
-                    } catch (Horde_Exception_NotFound $e) {
-                    } catch (Kronolith_Exception $e) {
-                    }
-                }
-            }
-        }
-
-        /* Remove any pending alarms. */
-        $GLOBALS['injector']->getInstance('Horde_Alarm')->delete($event->uid);
-
-        /* Remove any tags */
-        $tagger = Kronolith::getTagger();
-        $tagger->replaceTags($event->uid, array(), $event->creator, Kronolith_Tagger::TYPE_EVENT);
-
-        