Rename drivers, Split-up event classes.
authorJan Schneider <jan@horde.org>
Thu, 26 Feb 2009 11:41:56 +0000 (12:41 +0100)
committerJan Schneider <jan@horde.org>
Thu, 26 Feb 2009 13:26:59 +0000 (14:26 +0100)
16 files changed:
kronolith/lib/Driver.php
kronolith/lib/Driver/Holidays.php [new file with mode: 0644]
kronolith/lib/Driver/Ical.php [new file with mode: 0644]
kronolith/lib/Driver/Kolab.php [new file with mode: 0644]
kronolith/lib/Driver/Sql.php [new file with mode: 0644]
kronolith/lib/Driver/holidays.php [deleted file]
kronolith/lib/Driver/ical.php [deleted file]
kronolith/lib/Driver/kolab.php [deleted file]
kronolith/lib/Driver/sql.php [deleted file]
kronolith/lib/Event.php [new file with mode: 0644]
kronolith/lib/Event/Holidays.php [new file with mode: 0644]
kronolith/lib/Event/Ical.php [new file with mode: 0644]
kronolith/lib/Event/Kolab.php [new file with mode: 0644]
kronolith/lib/Event/Sql.php [new file with mode: 0644]
kronolith/lib/Kronolith.php
kronolith/lib/tests/bug6031.phpt

index 6e824cd..c0340d2 100644 (file)
@@ -3,6 +3,11 @@
  * Kronolith_Driver defines an API for implementing storage backends for
  * Kronolith.
  *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
  * @author  Chuck Hagenbuch <chuck@horde.org>
  * @author  Jan Schneider <jan@horde.org>
  * @package Kronolith
@@ -165,7 +170,7 @@ class Kronolith_Driver
      */
     public function nextRecurrence($eventId, $afterDate)
     {
-        $event = &$this->getEvent($eventId);
+        $event = $this->getEvent($eventId);
         if (is_a($event, 'PEAR_Error')) {
             return $event;
         }
@@ -197,7 +202,8 @@ class Kronolith_Driver
             $params = Horde::getDriverConfig('calendar', $driver);
         }
 
-        include_once dirname(__FILE__) . '/Driver/' . $driver . '.php';
+        $driver = String::ucfirst($driver);
+
         $class = 'Kronolith_Driver_' . $driver;
         if (class_exists($class)) {
             $driver = new $class($params);
@@ -301,2206 +307,3 @@ class Kronolith_Driver
     }
 
 }
-
-/**
- * Kronolith_Event defines a generic API for events.
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @author  Jan Schneider <jan@horde.org>
- * @package Kronolith
- */
-class Kronolith_Event
-{
-    /**
-     * Flag that is set to true if this event has data from either a storage
-     * backend or a form or other import method.
-     *
-     * @var boolean
-     */
-    public $initialized = false;
-
-    /**
-     * Flag that is set to true if this event exists in a storage driver.
-     *
-     * @var boolean
-     */
-    public $stored = false;
-
-    /**
-     * The driver unique identifier for this event.
-     *
-     * @var string
-     */
-    public $eventID = null;
-
-    /**
-     * The UID for this event.
-     *
-     * @var string
-     */
-    protected $_uid = null;
-
-    /**
-     * The iCalendar SEQUENCE for this event.
-     *
-     * @var integer
-     */
-    protected $_sequence = null;
-
-    /**
-     * The user id of the creator of the event.
-     *
-     * @var string
-     */
-    public $creatorID = null;
-
-    /**
-     * The title of this event.
-     *
-     * @var string
-     */
-    public $title = '';
-
-    /**
-     * The location this event occurs at.
-     *
-     * @var string
-     */
-    public $location = '';
-
-    /**
-     * The status of this event.
-     *
-     * @var integer
-     */
-    public $status = Kronolith::STATUS_CONFIRMED;
-
-    /**
-     * The description for this event
-     *
-     * @var string
-     */
-    public $description = '';
-
-    /**
-     * Remote description of this event (URL).
-     *
-     * @var string
-     */
-    public $remoteUrl = '';
-
-    /**
-     * Remote calendar name.
-     *
-     * @var string
-     */
-    public $remoteCal = '';
-
-    /**
-     * Whether the event is private.
-     *
-     * @var boolean
-     */
-    public $private = false;
-
-    /**
-     * This tag's events.
-     *
-     * @var mixed  Array of tags or comma delimited string.
-     */
-    public $tags = array();
-
-    /**
-     * All the attendees of this event.
-     *
-     * This is an associative array where the keys are the email addresses
-     * of the attendees, and the values are also associative arrays with
-     * keys 'attendance' and 'response' pointing to the attendees' attendance
-     * and response values, respectively.
-     *
-     * @var array
-     */
-    public $attendees = array();
-
-    /**
-     * The start time of the event.
-     *
-     * @var Horde_Date
-     */
-    public $start;
-
-    /**
-     * The end time of the event.
-     *
-     * @var Horde_Date
-     */
-    public $end;
-
-    /**
-     * The duration of this event in minutes
-     *
-     * @var integer
-     */
-    public $durMin = 0;
-
-    /**
-     * Whether this is an all-day event.
-     *
-     * @var boolean
-     */
-    public $allday = false;
-
-    /**
-     * Number of minutes before the event starts to trigger an alarm.
-     *
-     * @var integer
-     */
-    public $alarm = 0;
-
-    /**
-     * The particular alarm methods overridden for this event.
-     *
-     * @var array
-     */
-    public $methods;
-
-    /**
-     * The identifier of the calender this event exists on.
-     *
-     * @var string
-     */
-    protected $_calendar;
-
-    /**
-     * The HTML background color to be used for this event.
-     *
-     * @var string
-     */
-    protected $_backgroundColor;
-
-    /**
-     * The HTML foreground color to be used for this event.
-     *
-     * @var string
-     */
-    protected $_foregroundColor;
-
-    /**
-     * The VarRenderer class to use for printing select elements.
-     *
-     * @var Horde_UI_VarRenderer
-     */
-    private $_varRenderer;
-
-    /**
-     * The Horde_Date_Recurrence class for this event.
-     *
-     * @var Horde_Date_Recurrence
-     */
-    public $recurrence;
-
-    /**
-     * Constructor.
-     *
-     * @param Kronolith_Driver $driver  The backend driver that this event is
-     *                                  stored in.
-     * @param mixed $eventObject        Backend specific event object
-     *                                  that this will represent.
-     */
-    public function __construct(&$driver, $eventObject = null)
-    {
-        static $alarm;
-
-        /* Set default alarm value. */
-        if (!isset($alarm) && isset($GLOBALS['prefs'])) {
-            $alarm = $GLOBALS['prefs']->getValue('default_alarm');
-        }
-        $this->alarm = $alarm;
-
-        $this->_calendar = $driver->getCalendar();
-        if (!empty($this->_calendar)) {
-            $share = $GLOBALS['all_calendars'][$this->_calendar];
-            $this->_backgroundColor = $share->get('color');
-            if (empty($this->_backgroundColor)) {
-                $this->_backgroundColor = '#dddddd';
-            }
-            $this->_foregroundColor = Horde_Image::brightness($this->_backgroundColor) < 128 ? '#f6f6f6' : '#000';
-        }
-
-        if ($eventObject !== null) {
-            $this->fromDriver($eventObject);
-            $tagger = Kronolith::getTagger();
-            $this->tags = $tagger->getTags($this->getUID(), 'event');
-        }
-    }
-
-    /**
-     * Returns a reference to a driver that's valid for this event.
-     *
-     * @return Kronolith_Driver  A driver that this event can use to save
-     *                           itself, etc.
-     */
-    public function getDriver()
-    {
-        global $kronolith_driver;
-        if ($kronolith_driver->getCalendar() != $this->_calendar) {
-            $kronolith_driver->open($this->_calendar);
-        }
-
-        return $kronolith_driver;
-    }
-
-    /**
-     * Returns the share this event belongs to.
-     *
-     * @return Horde_Share  This event's share.
-     */
-    public function getShare()
-    {
-        if (isset($GLOBALS['all_calendars'][$this->getCalendar()])) {
-            $share = $GLOBALS['all_calendars'][$this->getCalendar()];
-        } else {
-            $share = PEAR::raiseError('Share not found');
-        }
-        return $share;
-    }
-
-    /**
-     * Encapsulates permissions checking.
-     *
-     * @param integer $permission  The permission to check for.
-     * @param string $user         The user to check permissions for.
-     *
-     * @return boolean
-     */
-    public function hasPermission($permission, $user = null)
-    {
-        if ($user === null) {
-            $user = Auth::getAuth();
-        }
-
-        if ($this->remoteCal) {
-            switch ($permission) {
-            case PERMS_SHOW:
-            case PERMS_READ:
-            case PERMS_EDIT:
-                return true;
-
-            default:
-                return false;
-            }
-        }
-
-        return (!is_a($share = &$this->getShare(), 'PEAR_Error') &&
-                $share->hasPermission($user, $permission, $this->getCreatorId()));
-    }
-
-    /**
-     * Saves changes to this event.
-     *
-     * @return mixed  True or a PEAR_Error on failure.
-     */
-    public function save()
-    {
-        if (!$this->isInitialized()) {
-            return PEAR::raiseError('Event not yet initialized');
-        }
-
-        $this->toDriver();
-        $driver = &$this->getDriver();
-        $result = $driver->saveEvent($this);
-        if (!is_a($result, 'PEAR_Error') &&
-            !empty($GLOBALS['conf']['alarms']['driver'])) {
-            $alarm = $this->toAlarm(new Horde_Date($_SERVER['REQUEST_TIME']));
-            if ($alarm) {
-                $alarm['start'] = new Horde_Date($alarm['start']);
-                $alarm['end'] = new Horde_Date($alarm['end']);
-                $horde_alarm = Horde_Alarm::factory();
-                $horde_alarm->set($alarm);
-            }
-        }
-
-        return $result;
-    }
-
-    /**
-     * Exports this event in iCalendar format.
-     *
-     * @param Horde_iCalendar &$calendar  A Horde_iCalendar object that acts as
-     *                                    a container.
-     *
-     * @return Horde_iCalendar_vevent  The vEvent object for this event.
-     */
-    public function toiCalendar(&$calendar)
-    {
-        $vEvent = &Horde_iCalendar::newComponent('vevent', $calendar);
-        $v1 = $calendar->getAttribute('VERSION') == '1.0';
-
-        if ($this->isAllDay()) {
-            $vEvent->setAttribute('DTSTART', $this->start, array('VALUE' => 'DATE'));
-            $vEvent->setAttribute('DTEND', $this->end, array('VALUE' => 'DATE'));
-        } else {
-            $vEvent->setAttribute('DTSTART', $this->start);
-            $vEvent->setAttribute('DTEND', $this->end);
-        }
-
-        $vEvent->setAttribute('DTSTAMP', $_SERVER['REQUEST_TIME']);
-        $vEvent->setAttribute('UID', $this->_uid);
-
-        /* Get the event's history. */
-        $history = &Horde_History::singleton();
-        $created = $modified = null;
-        $log = $history->getHistory('kronolith:' . $this->_calendar . ':' . $this->_uid);
-        if ($log && !is_a($log, 'PEAR_Error')) {
-            foreach ($log->getData() as $entry) {
-                switch ($entry['action']) {
-                case 'add':
-                    $created = $entry['ts'];
-                    break;
-
-                case 'modify':
-                    $modified = $entry['ts'];
-                    break;
-                }
-            }
-        }
-        if (!empty($created)) {
-            $vEvent->setAttribute($v1 ? 'DCREATED' : 'CREATED', $created);
-            if (empty($modified)) {
-                $modified = $created;
-            }
-        }
-        if (!empty($modified)) {
-            $vEvent->setAttribute('LAST-MODIFIED', $modified);
-        }
-
-        $vEvent->setAttribute('SUMMARY', $v1 ? $this->getTitle() : String::convertCharset($this->getTitle(), NLS::getCharset(), 'utf-8'));
-        $name = Kronolith::getUserName($this->getCreatorId());
-        if (!$v1) {
-            $name = String::convertCharset($name, NLS::getCharset(), 'utf-8');
-        }
-        $vEvent->setAttribute('ORGANIZER',
-                              'mailto:' . Kronolith::getUserEmail($this->getCreatorId()),
-                              array('CN' => $name));
-        if (!$this->isPrivate() || $this->getCreatorId() == Auth::getAuth()) {
-            if (!empty($this->description)) {
-                $vEvent->setAttribute('DESCRIPTION', $v1 ? $this->description : String::convertCharset($this->description, NLS::getCharset(), 'utf-8'));
-            }
-
-            // Tags
-            $tags = $this->tags;
-            if (is_array($tags)) {
-                $tags = implode(', ', $tags);
-            }
-            if (!empty($tags)) {
-                $vEvent->setAttribute('CATEGORIES', $v1 ? $tags : String::convertCharset($tags, NLS::getCharset(), 'utf-8'));
-            }
-
-            // Location
-            if (!empty($this->location)) {
-                $vEvent->setAttribute('LOCATION', $v1 ? $this->location : String::convertCharset($this->location, NLS::getCharset(), 'utf-8'));
-            }
-        }
-        $vEvent->setAttribute('CLASS', $this->isPrivate() ? 'PRIVATE' : 'PUBLIC');
-
-        // Status.
-        switch ($this->getStatus()) {
-        case Kronolith::STATUS_FREE:
-            // This is not an official iCalendar value, but we need it for
-            // synchronization.
-            $vEvent->setAttribute('STATUS', 'FREE');
-            $vEvent->setAttribute('TRANSP', $v1 ? 1 : 'TRANSPARENT');
-            break;
-        case Kronolith::STATUS_TENTATIVE:
-            $vEvent->setAttribute('STATUS', 'TENTATIVE');
-            $vEvent->setAttribute('TRANSP', $v1 ? 0 : 'OPAQUE');
-            break;
-        case Kronolith::STATUS_CONFIRMED:
-            $vEvent->setAttribute('STATUS', 'CONFIRMED');
-            $vEvent->setAttribute('TRANSP', $v1 ? 0 : 'OPAQUE');
-            break;
-        case Kronolith::STATUS_CANCELLED:
-            if ($v1) {
-                $vEvent->setAttribute('STATUS', 'DECLINED');
-                $vEvent->setAttribute('TRANSP', 1);
-            } else {
-                $vEvent->setAttribute('STATUS', 'CANCELLED');
-                $vEvent->setAttribute('TRANSP', 'TRANSPARENT');
-            }
-            break;
-        }
-
-        // Attendees.
-        foreach ($this->getAttendees() as $email => $status) {
-            $params = array();
-            switch ($status['attendance']) {
-            case Kronolith::PART_REQUIRED:
-                if ($v1) {
-                    $params['EXPECT'] = 'REQUIRE';
-                } else {
-                    $params['ROLE'] = 'REQ-PARTICIPANT';
-                }
-                break;
-
-            case Kronolith::PART_OPTIONAL:
-                if ($v1) {
-                    $params['EXPECT'] = 'REQUEST';
-                } else {
-                    $params['ROLE'] = 'OPT-PARTICIPANT';
-                }
-                break;
-
-            case Kronolith::PART_NONE:
-                if ($v1) {
-                    $params['EXPECT'] = 'FYI';
-                } else {
-                    $params['ROLE'] = 'NON-PARTICIPANT';
-                }
-                break;
-            }
-
-            switch ($status['response']) {
-            case Kronolith::RESPONSE_NONE:
-                if ($v1) {
-                    $params['STATUS'] = 'NEEDS ACTION';
-                    $params['RSVP'] = 'YES';
-                } else {
-                    $params['PARTSTAT'] = 'NEEDS-ACTION';
-                    $params['RSVP'] = 'TRUE';
-                }
-                break;
-
-            case Kronolith::RESPONSE_ACCEPTED:
-                if ($v1) {
-                    $params['STATUS'] = 'ACCEPTED';
-                } else {
-                    $params['PARTSTAT'] = 'ACCEPTED';
-                }
-                break;
-
-            case Kronolith::RESPONSE_DECLINED:
-                if ($v1) {
-                    $params['STATUS'] = 'DECLINED';
-                } else {
-                    $params['PARTSTAT'] = 'DECLINED';
-                }
-                break;
-
-            case Kronolith::RESPONSE_TENTATIVE:
-                if ($v1) {
-                    $params['STATUS'] = 'TENTATIVE';
-                } else {
-                    $params['PARTSTAT'] = 'TENTATIVE';
-                }
-                break;
-            }
-
-            if (strpos($email, '@') === false) {
-                $email = '';
-            }
-            if ($v1) {
-                if (!empty($status['name'])) {
-                    if (!empty($email)) {
-                        $email = ' <' . $email . '>';
-                    }
-                    $email = $status['name'] . $email;
-                    $email = Horde_Mime_Address::trimAddress($email);
-                }
-            } else {
-                if (!empty($status['name'])) {
-                    $params['CN'] = String::convertCharset($status['name'], NLS::getCharset(), 'utf-8');
-                }
-                if (!empty($email)) {
-                    $email = 'mailto:' . $email;
-                }
-            }
-
-            $vEvent->setAttribute('ATTENDEE', $email, $params);
-        }
-
-        // Alarms.
-        if (!empty($this->alarm)) {
-            if ($v1) {
-                $alarm = new Horde_Date($this->start);
-                $alarm->min -= $this->alarm;
-                $vEvent->setAttribute('AALARM', $alarm);
-            } else {
-                $vAlarm = &Horde_iCalendar::newComponent('valarm', $vEvent);
-                $vAlarm->setAttribute('ACTION', 'DISPLAY');
-                $vAlarm->setAttribute('TRIGGER;VALUE=DURATION', '-PT' . $this->alarm . 'M');
-                $vEvent->addComponent($vAlarm);
-            }
-        }
-
-        // Recurrence.
-        if ($this->recurs()) {
-            if ($v1) {
-                $rrule = $this->recurrence->toRRule10($calendar);
-            } else {
-                $rrule = $this->recurrence->toRRule20($calendar);
-            }
-            if (!empty($rrule)) {
-                $vEvent->setAttribute('RRULE', $rrule);
-            }
-
-            // Exceptions.
-            $exceptions = $this->recurrence->getExceptions();
-            foreach ($exceptions as $exception) {
-                if (!empty($exception)) {
-                    list($year, $month, $mday) = sscanf($exception, '%04d%02d%02d');
-                    $exdate = new Horde_Date(array(
-                        'year' => $year,
-                        'month' => $month,
-                        'mday' => $mday,
-                        'hour' => $this->start->hour,
-                        'min' => $this->start->min,
-                        'sec' => $this->start->sec,
-                    ));
-                    $vEvent->setAttribute('EXDATE', array($exdate));
-                }
-            }
-        }
-
-        return $vEvent;
-    }
-
-    /**
-     * Updates the properties of this event from a Horde_iCalendar_vevent
-     * object.
-     *
-     * @param Horde_iCalendar_vevent $vEvent  The iCalendar data to update
-     *                                        from.
-     */
-    public function fromiCalendar($vEvent)
-    {
-        // Unique ID.
-        $uid = $vEvent->getAttribute('UID');
-        if (!empty($uid) && !is_a($uid, 'PEAR_Error')) {
-            $this->setUID($uid);
-        }
-
-        // Sequence.
-        $seq = $vEvent->getAttribute('SEQUENCE');
-        if (is_int($seq)) {
-            $this->_sequence = $seq;
-        }
-
-        // Title, tags and description.
-        $title = $vEvent->getAttribute('SUMMARY');
-        if (!is_array($title) && !is_a($title, 'PEAR_Error')) {
-            $this->setTitle($title);
-        }
-
-        // Tags
-        $categories = $vEvent->getAttributeValues('CATEGORIES');
-        if (!is_a($categories, 'PEAR_Error')) {
-            $this->tags = $categories;
-        }
-
-        // Description
-        $desc = $vEvent->getAttribute('DESCRIPTION');
-        if (!is_array($desc) && !is_a($desc, 'PEAR_Error')) {
-            $this->setDescription($desc);
-        }
-
-        // Remote Url
-        $url = $vEvent->getAttribute('URL');
-        if (!is_array($url) && !is_a($url, 'PEAR_Error')) {
-            $this->remoteUrl = $url;
-        }
-
-        // Location
-        $location = $vEvent->getAttribute('LOCATION');
-        if (!is_array($location) && !is_a($location, 'PEAR_Error')) {
-            $this->setLocation($location);
-        }
-
-        // Class
-        $class = $vEvent->getAttribute('CLASS');
-        if (!is_array($class) && !is_a($class, 'PEAR_Error')) {
-            $class = String::upper($class);
-            if ($class == 'PRIVATE' || $class == 'CONFIDENTIAL') {
-                $this->setPrivate(true);
-            } else {
-                $this->setPrivate(false);
-            }
-        }
-
-        // Status.
-        $status = $vEvent->getAttribute('STATUS');
-        if (!is_array($status) && !is_a($status, 'PEAR_Error')) {
-            $status = String::upper($status);
-            if ($status == 'DECLINED') {
-                $status = 'CANCELLED';
-            }
-            if (defined('Kronolith::STATUS_' . $status)) {
-                $this->setStatus(constant('Kronolith::STATUS_' . $status));
-            }
-        }
-
-        // Start and end date.
-        $start = $vEvent->getAttribute('DTSTART');
-        if (!is_a($start, 'PEAR_Error')) {
-            if (!is_array($start)) {
-                // Date-Time field
-                $this->start = new Horde_Date($start);
-            } else {
-                // Date field
-                $this->start = new Horde_Date(
-                    array('year'  => (int)$start['year'],
-                          'month' => (int)$start['month'],
-                          'mday'  => (int)$start['mday']));
-            }
-        }
-        $end = $vEvent->getAttribute('DTEND');
-        if (!is_a($end, 'PEAR_Error')) {
-            if (!is_array($end)) {
-                // Date-Time field
-                $this->end = new Horde_Date($end);
-                // All day events are transferred by many device as
-                // DSTART: YYYYMMDDT000000 DTEND: YYYYMMDDT2359(59|00)
-                // Convert accordingly
-                if (is_object($this->start) && $this->start->hour == 0 &&
-                    $this->start->min == 0 && $this->start->sec == 0 &&
-                    $this->end->hour == 23 && $this->end->min == 59) {
-                    $this->end = new Horde_Date(
-                        array('year'  => (int)$this->end->year,
-                              'month' => (int)$this->end->month,
-                              'mday'  => (int)$this->end->mday + 1));
-                }
-            } elseif (is_array($end) && !is_a($end, 'PEAR_Error')) {
-                // Date field
-                $this->end = new Horde_Date(
-                    array('year'  => (int)$end['year'],
-                          'month' => (int)$end['month'],
-                          'mday'  => (int)$end['mday']));
-            }
-        } else {
-            $duration = $vEvent->getAttribute('DURATION');
-            if (!is_array($duration) && !is_a($duration, 'PEAR_Error')) {
-                $this->end = new Horde_Date($this->start);
-                $this->end->sec += $duration;
-            } else {
-                // End date equal to start date as per RFC 2445.
-                $this->end = new Horde_Date($this->start);
-                if (is_array($start)) {
-                    // Date field
-                    $this->end->mday++;
-                }
-            }
-        }
-
-        // vCalendar 1.0 alarms
-        $alarm = $vEvent->getAttribute('AALARM');
-        if (!is_array($alarm) &&
-            !is_a($alarm, 'PEAR_Error') &&
-            intval($alarm)) {
-            $this->alarm = intval(($this->start->timestamp() - $alarm) / 60);
-        }
-
-        // @TODO: vCalendar 2.0 alarms
-
-        // Attendance.
-        // Importing attendance may result in confusion: editing an imported
-        // copy of an event can cause invitation updates to be sent from
-        // people other than the original organizer. So we don't import by
-        // default. However to allow updates by SyncML replication, the custom
-        // X-ATTENDEE attribute is used which has the same syntax as
-        // ATTENDEE.
-        $attendee = $vEvent->getAttribute('X-ATTENDEE');
-        if (!is_a($attendee, 'PEAR_Error')) {
-
-            if (!is_array($attendee)) {
-                $attendee = array($attendee);
-            }
-            $params = $vEvent->getAttribute('X-ATTENDEE', true);
-            if (!is_array($params)) {
-                $params = array($params);
-            }
-            for ($i = 0; $i < count($attendee); ++$i) {
-                $attendee[$i] = str_replace(array('MAILTO:', 'mailto:'), '',
-                                            $attendee[$i]);
-                $email = Horde_Mime_Address::bareAddress($attendee[$i]);
-                // Default according to rfc2445:
-                $attendance = Kronolith::PART_REQUIRED;
-                // vCalendar 2.0 style:
-                if (!empty($params[$i]['ROLE'])) {
-                    switch($params[$i]['ROLE']) {
-                    case 'OPT-PARTICIPANT':
-                        $attendance = Kronolith::PART_OPTIONAL;
-                        break;
-
-                    case 'NON-PARTICIPANT':
-                        $attendance = Kronolith::PART_NONE;
-                        break;
-                    }
-                }
-                // vCalendar 1.0 style;
-                if (!empty($params[$i]['EXPECT'])) {
-                    switch($params[$i]['EXPECT']) {
-                    case 'REQUEST':
-                        $attendance = Kronolith::PART_OPTIONAL;
-                        break;
-
-                    case 'FYI':
-                        $attendance = Kronolith::PART_NONE;
-                        break;
-                    }
-                }
-                $response = Kronolith::RESPONSE_NONE;
-                if (empty($params[$i]['PARTSTAT']) &&
-                    !empty($params[$i]['STATUS'])) {
-                    $params[$i]['PARTSTAT']  = $params[$i]['STATUS'];
-                }
-
-                if (!empty($params[$i]['PARTSTAT'])) {
-                    switch($params[$i]['PARTSTAT']) {
-                    case 'ACCEPTED':
-                        $response = Kronolith::RESPONSE_ACCEPTED;
-                        break;
-
-                    case 'DECLINED':
-                        $response = Kronolith::RESPONSE_DECLINED;
-                        break;
-
-                    case 'TENTATIVE':
-                        $response = Kronolith::RESPONSE_TENTATIVE;
-                        break;
-                    }
-                }
-                $name = isset($params[$i]['CN']) ? $params[$i]['CN'] : null;
-
-                $this->addAttendee($email, $attendance, $response, $name);
-            }
-        }
-
-        // Recurrence.
-        $rrule = $vEvent->getAttribute('RRULE');
-        if (!is_array($rrule) && !is_a($rrule, 'PEAR_Error')) {
-            $this->recurrence = new Horde_Date_Recurrence($this->start);
-            if (strpos($rrule, '=') !== false) {
-                $this->recurrence->fromRRule20($rrule);
-            } else {
-                $this->recurrence->fromRRule10($rrule);
-            }
-
-            // Exceptions.
-            $exdates = $vEvent->getAttributeValues('EXDATE');
-            if (is_array($exdates)) {
-                foreach ($exdates as $exdate) {
-                    if (is_array($exdate)) {
-                        $this->recurrence->addException((int)$exdate['year'],
-                                                        (int)$exdate['month'],
-                                                        (int)$exdate['mday']);
-                    }
-                }
-            }
-        }
-
-        $this->initialized = true;
-    }
-
-    /**
-     * Imports the values for this event from an array of values.
-     *
-     * @param array $hash  Array containing all the values.
-     */
-    public function fromHash($hash)
-    {
-        // See if it's a new event.
-        if ($this->getId() === null) {
-            $this->setCreatorId(Auth::getAuth());
-        }
-        if (!empty($hash['title'])) {
-            $this->setTitle($hash['title']);
-        } else {
-            return PEAR::raiseError(_("Events must have a title."));
-        }
-        if (!empty($hash['description'])) {
-            $this->setDescription($hash['description']);
-        }
-        if (!empty($hash['location'])) {
-            $this->setLocation($hash['location']);
-        }
-        if (!empty($hash['start_date'])) {
-            $date = explode('-', $hash['start_date']);
-            if (empty($hash['start_time'])) {
-                $time = array(0, 0, 0);
-            } else {
-                $time = explode(':', $hash['start_time']);
-                if (count($time) == 2) {
-                    $time[2] = 0;
-                }
-            }
-            if (count($time) == 3 && count($date) == 3) {
-                $this->start = new Horde_Date(array('year' => $date[0],
-                                                    'month' => $date[1],
-                                                    'mday' => $date[2],
-                                                    'hour' => $time[0],
-                                                    'min' => $time[1],
-                                                    'sec' => $time[2]));
-            }
-        } else {
-            return PEAR::raiseError(_("Events must have a start date."));
-        }
-        if (empty($hash['duration'])) {
-            if (empty($hash['end_date'])) {
-                $hash['end_date'] = $hash['start_date'];
-            }
-            if (empty($hash['end_time'])) {
-                $hash['end_time'] = $hash['start_time'];
-            }
-        } else {
-            $weeks = str_replace('W', '', $hash['duration'][1]);
-            $days = str_replace('D', '', $hash['duration'][2]);
-            $hours = str_replace('H', '', $hash['duration'][4]);
-            $minutes = isset($hash['duration'][5]) ? str_replace('M', '', $hash['duration'][5]) : 0;
-            $seconds = isset($hash['duration'][6]) ? str_replace('S', '', $hash['duration'][6]) : 0;
-            $hash['duration'] = ($weeks * 60 * 60 * 24 * 7) + ($days * 60 * 60 * 24) + ($hours * 60 * 60) + ($minutes * 60) + $seconds;
-            $this->end = new Horde_Date($this->start);
-            $this->end->sec += $hash['duration'];
-        }
-        if (!empty($hash['end_date'])) {
-            $date = explode('-', $hash['end_date']);
-            if (empty($hash['end_time'])) {
-                $time = array(0, 0, 0);
-            } else {
-                $time = explode(':', $hash['end_time']);
-                if (count($time) == 2) {
-                    $time[2] = 0;
-                }
-            }
-            if (count($time) == 3 && count($date) == 3) {
-                $this->end = new Horde_Date(array('year' => $date[0],
-                                                  'month' => $date[1],
-                                                  'mday' => $date[2],
-                                                  'hour' => $time[0],
-                                                  'min' => $time[1],
-                                                  'sec' => $time[2]));
-            }
-        }
-        if (!empty($hash['alarm'])) {
-            $this->setAlarm($hash['alarm']);
-        } elseif (!empty($hash['alarm_date']) &&
-                  !empty($hash['alarm_time'])) {
-            $date = explode('-', $hash['alarm_date']);
-            $time = explode(':', $hash['alarm_time']);
-            if (count($time) == 2) {
-                $time[2] = 0;
-            }
-            if (count($time) == 3 && count($date) == 3) {
-                $alarm = new Horde_Date(array('hour'  => $time[0],
-                                              'min'   => $time[1],
-                                              'sec'   => $time[2],
-                                              'month' => $date[1],
-                                              'mday'  => $date[2],
-                                              'year'  => $date[0]));
-                $this->setAlarm(($this->start->timestamp() - $alarm->timestamp()) / 60);
-            }
-        }
-        if (!empty($hash['recur_type'])) {
-            $this->recurrence = new Horde_Date_Recurrence($this->start);
-            $this->recurrence->setRecurType($hash['recur_type']);
-            if (!empty($hash['recur_end_date'])) {
-                $date = explode('-', $hash['recur_end_date']);
-                $this->recurrence->setRecurEnd(new Horde_Date(array('year' => $date[0], 'month' => $date[1], 'mday' => $date[2])));
-            }
-            if (!empty($hash['recur_interval'])) {
-                $this->recurrence->setRecurInterval($hash['recur_interval']);
-            }
-            if (!empty($hash['recur_data'])) {
-                $this->recurrence->setRecurOnDay($hash['recur_data']);
-            }
-        }
-
-        $this->initialized = true;
-    }
-
-    /**
-     * Returns an alarm hash of this event suitable for Horde_Alarm.
-     *
-     * @param Horde_Date $time  Time of alarm.
-     * @param string $user      The user to return alarms for.
-     * @param Prefs $prefs      A Prefs instance.
-     *
-     * @return array  Alarm hash or null.
-     */
-    public function toAlarm($time, $user = null, $prefs = null)
-    {
-        if (!$this->getAlarm()) {
-            return;
-        }
-
-        if ($this->recurs()) {
-            $eventDate = $this->recurrence->nextRecurrence($time);
-            if ($eventDate && $this->recurrence->hasException($eventDate->year, $eventDate->month, $eventDate->mday)) {
-                return;
-            }
-        }
-
-        if (empty($user)) {
-            $user = Auth::getAuth();
-        }
-        if (empty($prefs)) {
-            $prefs = $GLOBALS['prefs'];
-        }
-
-        $methods = !empty($this->methods) ? $this->methods : @unserialize($prefs->getValue('event_alarms'));
-        $start = clone $this->start;
-        $start->min -= $this->getAlarm();
-        if (isset($methods['notify'])) {
-            $methods['notify']['show'] = array(
-                '__app' => $GLOBALS['registry']->getApp(),
-                'event' => $this->getId(),
-                'calendar' => $this->getCalendar());
-            if (!empty($methods['notify']['sound'])) {
-                if ($methods['notify']['sound'] == 'on') {
-                    // Handle boolean sound preferences.
-                    $methods['notify']['sound'] = $GLOBALS['registry']->get('themesuri') . '/sounds/theetone.wav';
-                } else {
-                    // Else we know we have a sound name that can be
-                    // served from Horde.
-                    $methods['notify']['sound'] = $GLOBALS['registry']->get('themesuri', 'horde') . '/sounds/' . $methods['notify']['sound'];
-                }
-            }
-        }
-        if (isset($methods['popup'])) {
-            $methods['popup']['message'] = $this->getTitle($user);
-            $description = $this->getDescription();
-            if (!empty($description)) {
-                $methods['popup']['message'] .= "\n\n" . $description;
-            }
-        }
-        if (isset($methods['mail'])) {
-            $methods['mail']['body'] = sprintf(
-                _("We would like to remind you of this upcoming event.\n\n%s\n\nLocation: %s\n\nDate: %s\nTime: %s\n\n%s"),
-                $this->getTitle($user),
-                $this->location,
-                $this->start->strftime($prefs->getValue('date_format')),
-                $this->start->format($prefs->getValue('twentyFour') ? 'H:i' : 'h:ia'),
-                $this->getDescription());
-        }
-
-        return array(
-            'id' => $this->getUID(),
-            'user' => $user,
-            'start' => $start->timestamp(),
-            'end' => $this->end->timestamp(),
-            'methods' => array_keys($methods),
-            'params' => $methods,
-            'title' => $this->getTitle($user),
-            'text' => $this->getDescription());
-    }
-
-    /**
-     * Returns a simple object suitable for json transport representing this
-     * event.
-     *
-     * @return object  A simple object.
-     */
-    public function toJSON()
-    {
-        $json = new stdClass;
-        $json->t = $this->getTitle();
-        $json->c = $this->getCalendar();
-        $json->bg = $this->_backgroundColor;
-        $json->fg = $this->_foregroundColor;
-        return $json;
-    }
-
-    /**
-     * TODO
-     */
-    public function isInitialized()
-    {
-        return $this->initialized;
-    }
-
-    /**
-     * TODO
-     */
-    public function isStored()
-    {
-        return $this->stored;
-    }
-
-    /**
-     * Checks if the current event is already present in the calendar.
-     *
-     * Does the check based on the uid.
-     *
-     * @return boolean  True if event exists, false otherwise.
-     */
-    public function exists()
-    {
-        if (!isset($this->_uid) || !isset($this->_calendar)) {
-            return false;
-        }
-
-        $eventID = $GLOBALS['kronolith_driver']->exists($this->_uid, $this->_calendar);
-        if (is_a($eventID, 'PEAR_Error') || !$eventID) {
-            return false;
-        } else {
-            $this->eventID = $eventID;
-            return true;
-        }
-    }
-
-    public function getDuration()
-    {
-        static $duration = null;
-        if (isset($duration)) {
-            return $duration;
-        }
-
-        if ($this->start && $this->end) {
-            $dur_day_match = Date_Calc::dateDiff($this->start->mday,
-                                                 $this->start->month,
-                                                 $this->start->year,
-                                                 $this->end->mday,
-                                                 $this->end->month,
-                                                 $this->end->year);
-            $dur_hour_match = $this->end->hour - $this->start->hour;
-            $dur_min_match = $this->end->min - $this->start->min;
-            while ($dur_min_match < 0) {
-                $dur_min_match += 60;
-                --$dur_hour_match;
-            }
-            while ($dur_hour_match < 0) {
-                $dur_hour_match += 24;
-                --$dur_day_match;
-            }
-            if ($dur_hour_match == 0 && $dur_min_match == 0 &&
-                $this->end->mday - $this->start->mday == 1) {
-                $dur_day_match = 1;
-                $dur_hour_match = 0;
-                $dur_min_match = 0;
-                $whole_day_match = true;
-            } else {
-                $whole_day_match = false;
-            }
-        } else {
-            $dur_day_match = 0;
-            $dur_hour_match = 1;
-            $dur_min_match = 0;
-            $whole_day_match = false;
-        }
-
-        $duration = new stdClass;
-        $duration->day = $dur_day_match;
-        $duration->hour = $dur_hour_match;
-        $duration->min = $dur_min_match;
-        $duration->wholeDay = $whole_day_match;
-
-        return $duration;
-    }
-
-    /**
-     * Returns whether this event is a recurring event.
-     *
-     * @return boolean  True if this is a recurring event.
-     */
-    public function recurs()
-    {
-        return isset($this->recurrence) &&
-            !$this->recurrence->hasRecurType(Horde_Date_Recurrence::RECUR_NONE);
-    }
-
-    /**
-     * Returns a description of this event's recurring type.
-     *
-     * @return string  Human readable recurring type.
-     */
-    public function getRecurName()
-    {
-        return $this->recurs()
-            ? $this->recurrence->getRecurName()
-            : _("No recurrence");
-    }
-
-    /**
-     * Returns a correcty formatted exception date for recurring events and a
-     * link to delete this exception.
-     *
-     * @param string $date  Exception in the format Ymd.
-     *
-     * @return string  The formatted date and delete link.
-     */
-    public function exceptionLink($date)
-    {
-        if (!preg_match('/(\d{4})(\d{2})(\d{2})/', $date, $match)) {
-            return '';
-        }
-        $horde_date = new Horde_Date(array('year' => $match[1],
-                                           'month' => $match[2],
-                                           'mday' => $match[3]));
-        $formatted = $horde_date->strftime($GLOBALS['prefs']->getValue('date_format'));
-        return $formatted
-            . Horde::link(Util::addParameter(Horde::applicationUrl('edit.php'), array('calendar' => $this->getCalendar(), 'eventID' => $this->eventID, 'del_exception' => $date, 'url' => Util::getFormData('url'))), sprintf(_("Delete exception on %s"), $formatted))
-            . Horde::img('delete-small.png', _("Delete"), '', $GLOBALS['registry']->getImageDir('horde'))
-            . '</a>';
-    }
-
-    /**
-     * Returns a list of exception dates for recurring events including links
-     * to delete them.
-     *
-     * @return string  List of exception dates and delete links.
-     */
-    public function exceptionsList()
-    {
-        return implode(', ', array_map(array($this, 'exceptionLink'), $this->recurrence->getExceptions()));
-    }
-
-    public function getCalendar()
-    {
-        return $this->_calendar;
-    }
-
-    public function setCalendar($calendar)
-    {
-        $this->_calendar = $calendar;
-    }
-
-    public function isRemote()
-    {
-        return (bool)$this->remoteCal;
-    }
-
-    /**
-     * Returns the locally unique identifier for this event.
-     *
-     * @return string  The local identifier for this event.
-     */
-    public function getId()
-    {
-        return $this->eventID;
-    }
-
-    /**
-     * Sets the locally unique identifier for this event.
-     *
-     * @param string $eventId  The local identifier for this event.
-     */
-    public function setId($eventId)
-    {
-        if (substr($eventId, 0, 10) == 'kronolith:') {
-            $eventId = substr($eventId, 10);
-        }
-        $this->eventID = $eventId;
-    }
-
-    /**
-     * Returns the global UID for this event.
-     *
-     * @return string  The global UID for this event.
-     */
-    public function getUID()
-    {
-        return $this->_uid;
-    }
-
-    /**
-     * Sets the global UID for this event.
-     *
-     * @param string $uid  The global UID for this event.
-     */
-    public function setUID($uid)
-    {
-        $this->_uid = $uid;
-    }
-
-    /**
-     * Returns the iCalendar SEQUENCE for this event.
-     *
-     * @return integer  The sequence for this event.
-     */
-    public function getSequence()
-    {
-        return $this->_sequence;
-    }
-
-    /**
-     * Returns the id of the user who created the event.
-     *
-     * @return string  The creator id
-     */
-    public function getCreatorId()
-    {
-        return !empty($this->creatorID) ? $this->creatorID : Auth::getAuth();
-    }
-
-    /**
-     * Sets the id of the creator of the event.
-     *
-     * @param string $creatorID  The user id for the user who created the event
-     */
-    public function setCreatorId($creatorID)
-    {
-        $this->creatorID = $creatorID;
-    }
-
-    /**
-     * Returns the title of this event.
-     *
-     * @param string $user  The current user.
-     *
-     * @return string  The title of this event.
-     */
-    public function getTitle($user = null)
-    {
-        if (isset($this->external) ||
-            isset($this->contactID) ||
-            $this->remoteCal) {
-            return !empty($this->title) ? $this->title : _("[Unnamed event]");
-        }
-
-        if (!$this->isInitialized()) {
-            return '';
-        }
-
-        if ($user === null) {
-            $user = Auth::getAuth();
-        }
-
-        $twentyFour = $GLOBALS['prefs']->getValue('twentyFour');
-        $start = $this->start->format($twentyFour ? 'G:i' : 'g:ia');
-        $end = $this->end->format($twentyFour ? 'G:i' : 'g:ia');
-
-        // We explicitly allow admin access here for the alarms notifications.
-        if (!Auth::isAdmin() && $this->isPrivate() &&
-            $this->getCreatorId() != $user) {
-            return sprintf(_("Private Event from %s to %s"), $start, $end);
-        } elseif (Auth::isAdmin() || $this->hasPermission(PERMS_READ, $user)) {
-            return strlen($this->title) ? $this->title : _("[Unnamed event]");
-        } else {
-            return sprintf(_("Event from %s to %s"), $start, $end);
-        }
-    }
-
-    /**
-     * Sets the title of this event.
-     *
-     * @param string  The new title for this event.
-     */
-    public function setTitle($title)
-    {
-        $this->title = $title;
-    }
-
-    /**
-     * Returns the description of this event.
-     *
-     * @return string  The description of this event.
-     */
-    public function getDescription()
-    {
-        return $this->description;
-    }
-
-    /**
-     * Sets the description of this event.
-     *
-     * @param string $description  The new description for this event.
-     */
-    public function setDescription($description)
-    {
-        $this->description = $description;
-    }
-
-    /**
-     * Returns the location this event occurs at.
-     *
-     * @return string  The location of this event.
-     */
-    public function getLocation()
-    {
-        return $this->location;
-    }
-
-    /**
-     * Sets the location this event occurs at.
-     *
-     * @param string $location  The new location for this event.
-     */
-    public function setLocation($location)
-    {
-        $this->location = $location;
-    }
-
-    /**
-     * Returns whether this event is private.
-     *
-     * @return boolean  Whether this even is private.
-     */
-    public function isPrivate()
-    {
-        return $this->private;
-    }
-
-    /**
-     * Sets the private flag of this event.
-     *
-     * @param boolean $private  Whether this event should be marked private.
-     */
-    public function setPrivate($private)
-    {
-        $this->private = !empty($private);
-    }
-
-    /**
-     * Returns the event status.
-     *
-     * @return integer  The status of this event.
-     */
-    public function getStatus()
-    {
-        return $this->status;
-    }
-
-    /**
-     * Checks whether the events status is the same as the specified value.
-     *
-     * @param integer $status  The status value to check against.
-     *
-     * @return boolean  True if the events status is the same as $status.
-     */
-    public function hasStatus($status)
-    {
-        return ($status == $this->status);
-    }
-
-    /**
-     * Sets the status of this event.
-     *
-     * @param integer $status  The new event status.
-     */
-    public function setStatus($status)
-    {
-        $this->status = $status;
-    }
-
-    /**
-     * Returns the entire attendees array.
-     *
-     * @return array  A copy of the attendees array.
-     */
-    public function getAttendees()
-    {
-        return $this->attendees;
-    }
-
-    /**
-     * Checks to see whether the specified attendee is associated with the
-     * current event.
-     *
-     * @param string $email  The email address of the attendee.
-     *
-     * @return boolean  True if the specified attendee is present for this
-     *                  event.
-     */
-    public function hasAttendee($email)
-    {
-        $email = String::lower($email);
-        return isset($this->attendees[$email]);
-    }
-
-    /**
-     * Sets the entire attendee array.
-     *
-     * @param array $attendees  The new attendees array. This should be of the
-     *                          correct format to avoid driver problems.
-     */
-    public function setAttendees($attendees)
-    {
-        $this->attendees = array_change_key_case($attendees);
-    }
-
-    /**
-     * Adds a new attendee to the current event.
-     *
-     * This will overwrite an existing attendee if one exists with the same
-     * email address.
-     *
-     * @param string $email        The email address of the attendee.
-     * @param integer $attendance  The attendance code of the attendee.
-     * @param integer $response    The response code of the attendee.
-     * @param string $name         The name of the attendee.
-     */
-    public function addAttendee($email, $attendance, $response, $name = null)
-    {
-        $email = String::lower($email);
-        if ($attendance == Kronolith::PART_IGNORE) {
-            if (isset($this->attendees[$email])) {
-                $attendance = $this->attendees[$email]['attendance'];
-            } else {
-                $attendance = Kronolith::PART_REQUIRED;
-            }
-        }
-        if (empty($name) && isset($this->attendees[$email]) &&
-            !empty($this->attendees[$email]['name'])) {
-            $name = $this->attendees[$email]['name'];
-        }
-
-        $this->attendees[$email] = array(
-            'attendance' => $attendance,
-            'response' => $response,
-            'name' => $name
-        );
-    }
-
-    /**
-     * Removes the specified attendee from the current event.
-     *
-     * @param string $email  The email address of the attendee.
-     */
-    public function removeAttendee($email)
-    {
-        $email = String::lower($email);
-        if (isset($this->attendees[$email])) {
-            unset($this->attendees[$email]);
-        }
-    }
-
-    public function isAllDay()
-    {
-        return $this->allday ||
-            ($this->start->hour == 0 && $this->start->min == 0 && $this->start->sec == 0 &&
-             (($this->end->hour == 0 && $this->end->min == 0 && $this->end->sec == 0) ||
-              ($this->end->hour == 23 && $this->end->min == 59)) &&
-             ($this->end->mday > $this->start->mday ||
-              $this->end->month > $this->start->month ||
-              $this->end->year > $this->start->year));
-    }
-
-    public function getAlarm()
-    {
-        return $this->alarm;
-    }
-
-    public function setAlarm($alarm)
-    {
-        $this->alarm = $alarm;
-    }
-
-    public function readForm()
-    {
-        global $prefs, $cManager;
-
-        // Event owner.
-        $targetcalendar = Util::getFormData('targetcalendar');
-        if (strpos($targetcalendar, ':')) {
-            list(, $creator) = explode(':', $targetcalendar, 2);
-        } else {
-            $creator = isset($this->eventID) ? $this->getCreatorId() : Auth::getAuth();
-        }
-        $this->setCreatorId($creator);
-
-        // Basic fields.
-        $this->setTitle(Util::getFormData('title', $this->title));
-        $this->setDescription(Util::getFormData('description', $this->description));
-        $this->setLocation(Util::getFormData('location', $this->location));
-        $this->setPrivate(Util::getFormData('private'));
-
-        // Status.
-        $this->setStatus(Util::getFormData('status', $this->status));
-
-        // Attendees.
-        if (isset($_SESSION['kronolith']['attendees']) && is_array($_SESSION['kronolith']['attendees'])) {
-            $this->setAttendees($_SESSION['kronolith']['attendees']);
-        }
-
-        // Event start.
-        $start = Util::getFormData('start');
-        $start_year = $start['year'];
-        $start_month = $start['month'];
-        $start_day = $start['day'];
-        $start_hour = Util::getFormData('start_hour');
-        $start_min = Util::getFormData('start_min');
-        $am_pm = Util::getFormData('am_pm');
-
-        if (!$prefs->getValue('twentyFour')) {
-            if ($am_pm == 'PM') {
-                if ($start_hour != 12) {
-                    $start_hour += 12;
-                }
-            } elseif ($start_hour == 12) {
-                $start_hour = 0;
-            }
-        }
-
-        if (Util::getFormData('end_or_dur') == 1) {
-            if (Util::getFormData('whole_day') == 1) {
-                $start_hour = 0;
-                $start_min = 0;
-                $dur_day = 0;
-                $dur_hour = 24;
-                $dur_min = 0;
-            } else {
-                $dur_day = (int)Util::getFormData('dur_day');
-                $dur_hour = (int)Util::getFormData('dur_hour');
-                $dur_min = (int)Util::getFormData('dur_min');
-            }
-        }
-
-        $this->start = new Horde_Date(array('hour' => $start_hour,
-                                            'min' => $start_min,
-                                            'month' => $start_month,
-                                            'mday' => $start_day,
-                                            'year' => $start_year));
-
-        if (Util::getFormData('end_or_dur') == 1) {
-            // Event duration.
-            $this->end = new Horde_Date(array('hour' => $start_hour + $dur_hour,
-                                              'min' => $start_min + $dur_min,
-                                              'month' => $start_month,
-                                              'mday' => $start_day + $dur_day,
-                                              'year' => $start_year));
-        } else {
-            // Event end.
-            $end = Util::getFormData('end');
-            $end_year = $end['year'];
-            $end_month = $end['month'];
-            $end_day = $end['day'];
-            $end_hour = Util::getFormData('end_hour');
-            $end_min = Util::getFormData('end_min');
-            $end_am_pm = Util::getFormData('end_am_pm');
-
-            if (!$prefs->getValue('twentyFour')) {
-                if ($end_am_pm == 'PM') {
-                    if ($end_hour != 12) {
-                        $end_hour += 12;
-                    }
-                } elseif ($end_hour == 12) {
-                    $end_hour = 0;
-                }
-            }
-
-            $this->end = new Horde_Date(array('hour' => $end_hour,
-                                              'min' => $end_min,
-                                              'month' => $end_month,
-                                              'mday' => $end_day,
-                                              'year' => $end_year));
-            if ($this->end->compareDateTime($this->start) < 0) {
-                $this->end = new Horde_Date($this->start);
-            }
-        }
-
-        // Alarm.
-        if (Util::getFormData('alarm') == 1) {
-            $this->setAlarm(Util::getFormData('alarm_value') * Util::getFormData('alarm_unit'));
-            // Notification.
-            if (Util::getFormData('alarm_change_method')) {
-                $types = Util::getFormData('event_alarms');
-                if (!empty($types)) {
-                    $methods = array();
-                    foreach ($types as $type) {
-                        $methods[$type] = array();
-                        switch ($type){
-                        case 'notify':
-                            $methods[$type]['sound'] = Util::getFormData('event_alarms_sound');
-                            break;
-                        case 'mail':
-                            $methods[$type]['email'] = Util::getFormData('event_alarms_email');
-                            break;
-                        case 'popup':
-                            break;
-                        }
-                    }
-                    $this->methods = $methods;
-                }
-            } else {
-                $this->methods = array();
-            }
-        } else {
-            $this->setAlarm(0);
-            $this->methods = array();
-        }
-
-        // Recurrence.
-        $recur = Util::getFormData('recur');
-        if ($recur !== null && $recur !== '') {
-            if (!isset($this->recurrence)) {
-                $this->recurrence = new Horde_Date_Recurrence($this->start);
-            }
-            if (Util::getFormData('recur_enddate_type') == 'date') {
-                $recur_enddate = Util::getFormData('recur_enddate');
-                if ($this->recurrence->hasRecurEnd()) {
-                    $recurEnd = $this->recurrence->recurEnd;
-                    $recurEnd->month = $recur_enddate['month'];
-                    $recurEnd->mday = $recur_enddate['day'];
-                    $recurEnd->year = $recur_enddate['year'];
-                } else {
-                    $recurEnd = new Horde_Date(
-                        array('hour' => 23,
-                              'min' => 59,
-                              'sec' => 59,
-                              'month' => $recur_enddate['month'],
-                              'mday' => $recur_enddate['day'],
-                              'year' => $recur_enddate['year']));
-                }
-                $this->recurrence->setRecurEnd($recurEnd);
-            } elseif (Util::getFormData('recur_enddate_type') == 'count') {
-                $this->recurrence->setRecurCount(Util::getFormData('recur_count'));
-            } elseif (Util::getFormData('recur_enddate_type') == 'none') {
-                $this->recurrence->setRecurCount(0);
-                $this->recurrence->setRecurEnd(null);
-            }
-
-            $this->recurrence->setRecurType($recur);
-            switch ($recur) {
-            case Horde_Date_Recurrence::RECUR_DAILY:
-                $this->recurrence->setRecurInterval(Util::getFormData('recur_daily_interval', 1));
-                break;
-
-            case Horde_Date_Recurrence::RECUR_WEEKLY:
-                $weekly = Util::getFormData('weekly');
-                $weekdays = 0;
-                if (is_array($weekly)) {
-                    foreach ($weekly as $day) {
-                        $weekdays |= $day;
-                    }
-                }
-
-                if ($weekdays == 0) {
-                    // Sunday starts at 0.
-                    switch ($this->start->dayOfWeek()) {
-                    case 0: $weekdays |= Horde_Date::MASK_SUNDAY; break;
-                    case 1: $weekdays |= Horde_Date::MASK_MONDAY; break;
-                    case 2: $weekdays |= Horde_Date::MASK_TUESDAY; break;
-                    case 3: $weekdays |= Horde_Date::MASK_WEDNESDAY; break;
-                    case 4: $weekdays |= Horde_Date::MASK_THURSDAY; break;
-                    case 5: $weekdays |= Horde_Date::MASK_FRIDAY; break;
-                    case 6: $weekdays |= Horde_Date::MASK_SATURDAY; break;
-                    }
-                }
-
-                $this->recurrence->setRecurInterval(Util::getFormData('recur_weekly_interval', 1));
-                $this->recurrence->setRecurOnDay($weekdays);
-                break;
-
-            case Horde_Date_Recurrence::RECUR_MONTHLY_DATE:
-                $this->recurrence->setRecurInterval(Util::getFormData('recur_day_of_month_interval', 1));
-                break;
-
-            case Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY:
-                $this->recurrence->setRecurInterval(Util::getFormData('recur_week_of_month_interval', 1));
-                break;
-
-            case Horde_Date_Recurrence::RECUR_YEARLY_DATE:
-                $this->recurrence->setRecurInterval(Util::getFormData('recur_yearly_interval', 1));
-                break;
-
-            case Horde_Date_Recurrence::RECUR_YEARLY_DAY:
-                $this->recurrence->setRecurInterval(Util::getFormData('recur_yearly_day_interval', 1));
-                break;
-
-            case Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY:
-                $this->recurrence->setRecurInterval(Util::getFormData('recur_yearly_weekday_interval', 1));
-                break;
-            }
-        }
-
-        // Tags.
-        $this->tags = Util::getFormData('tags');
-
-        $this->initialized = true;
-    }
-
-    public function html($property)
-    {
-        global $prefs;
-
-        $options = array();
-        $attributes = '';
-        $sel = false;
-        $label = '';
-
-        switch ($property) {
-        case 'start[year]':
-            return  '<label for="' . $this->_formIDEncode($property) . '" class="hidden">' . _("Start Year") . '</label>' .
-                '<input name="' . $property . '" value="' . $this->start->year .
-                '" type="text" onchange="' . $this->js($property) .
-                '" id="' . $this->_formIDEncode($property) . '" size="4" maxlength="4" />';
-
-        case 'start[month]':
-            $sel = $this->start->month;
-            for ($i = 1; $i < 13; ++$i) {
-                $options[$i] = strftime('%b', mktime(1, 1, 1, $i, 1));
-            }
-            $attributes = ' onchange="' . $this->js($property) . '"';
-            $label = _("Start Month");
-            break;
-
-        case 'start[day]':
-            $sel = $this->start->mday;
-            for ($i = 1; $i < 32; ++$i) {
-                $options[$i] = $i;
-            }
-            $attributes = ' onchange="' . $this->js($property) . '"';
-            $label = _("Start Day");
-            break;
-
-        case 'start_hour':
-            $sel = $this->start->format($prefs->getValue('twentyFour') ? 'G' : 'g');
-            $hour_min = $prefs->getValue('twentyFour') ? 0 : 1;
-            $hour_max = $prefs->getValue('twentyFour') ? 24 : 13;
-            for ($i = $hour_min; $i < $hour_max; ++$i) {
-                $options[$i] = $i;
-            }
-            $attributes = ' onchange="document.eventform.whole_day.checked = false; KronolithEventForm.updateEndDate();"';
-            $label = _("Start Hour");
-            break;
-
-        case 'start_min':
-            $sel = sprintf('%02d', $this->start->min);
-            for ($i = 0; $i < 12; ++$i) {
-                $min = sprintf('%02d', $i * 5);
-                $options[$min] = $min;
-            }
-            $attributes = ' onchange="document.eventform.whole_day.checked = false; KronolithEventForm.updateEndDate();"';
-            $label = _("Start Minute");
-            break;
-
-        case 'end[year]':
-            return  '<label for="' . $this->_formIDEncode($property) . '" class="hidden">' . _("End Year") . '</label>' .
-                '<input name="' . $property . '" value="' . $this->end->year .
-                '" type="text" onchange="' . $this->js($property) .
-                '" id="' . $this->_formIDEncode($property) . '" size="4" maxlength="4" />';
-
-        case 'end[month]':
-            $sel = $this->isInitialized() ? $this->end->month : $this->start->month;
-            for ($i = 1; $i < 13; ++$i) {
-                $options[$i] = strftime('%b', mktime(1, 1, 1, $i, 1));
-            }
-            $attributes = ' onchange="' . $this->js($property) . '"';
-            $label = _("End Month");
-            break;
-
-        case 'end[day]':
-            $sel = $this->isInitialized() ? $this->end->mday : $this->start->mday;
-            for ($i = 1; $i < 32; ++$i) {
-                $options[$i] = $i;
-            }
-            $attributes = ' onchange="' . $this->js($property) . '"';
-            $label = _("End Day");
-            break;
-
-        case 'end_hour':
-            $sel = $this->isInitialized() ?
-                $this->end->format($prefs->getValue('twentyFour') ? 'G' : 'g') :
-                $this->start->format($prefs->getValue('twentyFour') ? 'G' : 'g') + 1;
-            $hour_min = $prefs->getValue('twentyFour') ? 0 : 1;
-            $hour_max = $prefs->getValue('twentyFour') ? 24 : 13;
-            for ($i = $hour_min; $i < $hour_max; ++$i) {
-                $options[$i] = $i;
-            }
-            $attributes = ' onchange="KronolithEventForm.updateDuration(); document.eventform.end_or_dur[0].checked = true"';
-            $label = _("End Hour");
-            break;
-
-        case 'end_min':
-            $sel = $this->isInitialized() ? $this->end->min : $this->start->min;
-            $sel = sprintf('%02d', $sel);
-            for ($i = 0; $i < 12; ++$i) {
-                $min = sprintf('%02d', $i * 5);
-                $options[$min] = $min;
-            }
-            $attributes = ' onchange="KronolithEventForm.updateDuration(); document.eventform.end_or_dur[0].checked = true"';
-            $label = _("End Minute");
-            break;
-
-        case 'dur_day':
-            $dur = $this->getDuration();
-            return  '<label for="' . $property . '" class="hidden">' . _("Duration Day") . '</label>' .
-                '<input name="' . $property . '" value="' . $dur->day .
-                '" type="text" onchange="' . $this->js($property) .
-                '" id="' . $property . '" size="4" maxlength="4" />';
-
-        case 'dur_hour':
-            $dur = $this->getDuration();
-            $sel = $dur->hour;
-            for ($i = 0; $i < 24; ++$i) {
-                $options[$i] = $i;
-            }
-            $attributes = ' onchange="' . $this->js($property) . '"';
-            $label = _("Duration Hour");
-            break;
-
-        case 'dur_min':
-            $dur = $this->getDuration();
-            $sel = $dur->min;
-            for ($i = 0; $i < 13; ++$i) {
-                $min = sprintf('%02d', $i * 5);
-                $options[$min] = $min;
-            }
-            $attributes = ' onchange="' . $this->js($property) . '"';
-            $label = _("Duration Minute");
-            break;
-
-        case 'recur_enddate[year]':
-            if ($this->isInitialized()) {
-                $end = ($this->recurs() && $this->recurrence->hasRecurEnd())
-                        ? $this->recurrence->recurEnd->year
-                        : $this->end->year;
-            } else {
-                $end = $this->start->year;
-            }
-            return  '<label for="' . $this->_formIDEncode($property) . '" class="hidden">' . _("Recurrence End Year") . '</label>' .
-                '<input name="' . $property . '" value="' . $end .
-                '" type="text" onchange="' . $this->js($property) .
-                '" id="' . $this->_formIDEncode($property) . '" size="4" maxlength="4" />';
-
-        case 'recur_enddate[month]':
-            if ($this->isInitialized()) {
-                $sel = ($this->recurs() && $this->recurrence->hasRecurEnd())
-                    ? $this->recurrence->recurEnd->month
-                    : $this->end->month;
-            } else {
-                $sel = $this->start->month;
-            }
-            for ($i = 1; $i < 13; ++$i) {
-                $options[$i] = strftime('%b', mktime(1, 1, 1, $i, 1));
-            }
-            $attributes = ' onchange="' . $this->js($property) . '"';
-            $label = _("Recurrence End Month");
-            break;
-
-        case 'recur_enddate[day]':
-            if ($this->isInitialized()) {
-                $sel = ($this->recurs() && $this->recurrence->hasRecurEnd())
-                    ? $this->recurrence->recurEnd->mday
-                    : $this->end->mday;
-            } else {
-                $sel = $this->start->mday;
-            }
-            for ($i = 1; $i < 32; ++$i) {
-                $options[$i] = $i;
-            }
-            $attributes = ' onchange="' . $this->js($property) . '"';
-            $label = _("Recurrence End Day");
-            break;
-        }
-
-        if (!$this->_varRenderer) {
-            $this->_varRenderer = Horde_UI_VarRenderer::factory('html');
-        }
-
-        return '<label for="' . $this->_formIDEncode($property) . '" class="hidden">' . $label . '</label>' .
-            '<select name="' . $property . '"' . $attributes . ' id="' . $this->_formIDEncode($property) . '">' .
-            $this->_varRenderer->_selectOptions($options, $sel) .
-            '</select>';
-    }
-
-    public function js($property)
-    {
-        switch ($property) {
-        case 'start[month]':
-        case 'start[year]':
-        case 'start[day]':
-        case 'start':
-            return 'KronolithEventForm.updateWday(\'start_wday\'); document.eventform.whole_day.checked = false; KronolithEventForm.updateEndDate();';
-
-        case 'end[month]':
-        case 'end[year]':
-        case 'end[day]':
-        case 'end':
-            return 'KronolithEventForm.updateWday(\'end_wday\'); updateDuration(); document.eventform.end_or_dur[0].checked = true;';
-
-        case 'recur_enddate[month]':
-        case 'recur_enddate[year]':
-        case 'recur_enddate[day]':
-        case 'recur_enddate':
-            return 'KronolithEventForm.updateWday(\'recur_end_wday\'); document.eventform.recur_enddate_type[1].checked = true;';
-
-        case 'dur_day':
-        case 'dur_hour':
-        case 'dur_min':
-            return 'document.eventform.whole_day.checked = false; KronolithEventForm.updateEndDate(); document.eventform.end_or_dur[1].checked = true;';
-        }
-    }
-
-    /**
-     * @param array $params
-     *
-     * @return string
-     */
-    public function getViewUrl($params = array(), $full = false)
-    {
-        $params['eventID'] = $this->eventID;
-        if ($this->remoteUrl) {
-            return $this->remoteUrl;
-        } elseif ($this->remoteCal) {
-            $params['calendar'] = '**remote';
-            $params['remoteCal'] = $this->remoteCal;
-        } else {
-            $params['calendar'] = $this->getCalendar();
-        }
-
-        return Horde::applicationUrl(Util::addParameter('event.php', $params), $full);
-    }
-
-    /**
-     * @param array $params
-     *
-     * @return string
-     */
-    public function getEditUrl($params = array())
-    {
-        $params['view'] = 'EditEvent';
-        $params['eventID'] = $this->eventID;
-        if ($this->remoteCal) {
-            $params['calendar'] = '**remote';
-            $params['remoteCal'] = $this->remoteCal;
-        } else {
-            $params['calendar'] = $this->getCalendar();
-        }
-
-        return Horde::applicationUrl(Util::addParameter('event.php', $params));
-    }
-
-    /**
-     * @param array $params
-     *
-     * @return string
-     */
-    public function getDeleteUrl($params = array())
-    {
-        $params['view'] = 'DeleteEvent';
-        $params['eventID'] = $this->eventID;
-        $params['calendar'] = $this->getCalendar();
-        return Horde::applicationUrl(Util::addParameter('event.php', $params));
-    }
-
-    /**
-     * @param array $params
-     *
-     * @return string
-     */
-    public function getExportUrl($params = array())
-    {
-        $params['view'] = 'ExportEvent';
-        $params['eventID'] = $this->eventID;
-        if ($this->remoteCal) {
-            $params['calendar'] = '**remote';
-            $params['remoteCal'] = $this->remoteCal;
-        } else {
-            $params['calendar'] = $this->getCalendar();
-        }
-
-        return Horde::applicationUrl(Util::addParameter('event.php', $params));
-    }
-
-    public function getLink($datetime = null, $icons = true, $from_url = null, $full = false)
-    {
-        global $prefs, $registry;
-
-        if (is_null($datetime)) {
-            $datetime = $this->start;
-        }
-        if (is_null($from_url)) {
-            $from_url = Horde::selfUrl(true, false, true);
-        }
-
-        $link = '';
-        $event_title = $this->getTitle();
-        if (isset($this->external)) {
-            $link = $registry->link($this->external . '/show', $this->external_params);
-            $link = Horde::linkTooltip(Horde::url($link), '', 'event-tentative', '', '', String::wrap($this->description));
-        } elseif (isset($this->eventID) && $this->hasPermission(PERMS_READ)) {
-            $link = Horde::linkTooltip($this->getViewUrl(array('datetime' => $datetime->strftime('%Y%m%d%H%M%S'), 'url' => $from_url), $full),
-                                       $event_title,
-                                       $this->getStatusClass(), '', '',
-                                       $this->getTooltip(),
-                                       '',
-                                       array('style' => $this->getCSSColors(false)));
-        }
-
-        $link .= @htmlspecialchars($event_title, ENT_QUOTES, NLS::getCharset());
-
-        if ($this->hasPermission(PERMS_READ) &&
-            (isset($this->eventID) ||
-             isset($this->external))) {
-            $link .= '</a>';
-        }
-
-        if ($icons && $prefs->getValue('show_icons')) {
-            $icon_color = $this->_foregroundColor == '#000' ? '000' : 'fff';
-            $status = '';
-            if ($this->alarm) {
-                if ($this->alarm % 10080 == 0) {
-                    $alarm_value = $this->alarm / 10080;
-                    $title = $alarm_value == 1 ?
-                        _("Alarm 1 week before") :
-                        sprintf(_("Alarm %d weeks before"), $alarm_value);
-                } elseif ($this->alarm % 1440 == 0) {
-                    $alarm_value = $this->alarm / 1440;
-                    $title = $alarm_value == 1 ?
-                        _("Alarm 1 day before") :
-                        sprintf(_("Alarm %d days before"), $alarm_value);
-                } elseif ($this->alarm % 60 == 0) {
-                    $alarm_value = $this->alarm / 60;
-                    $title = $alarm_value == 1 ?
-                        _("Alarm 1 hour before") :
-                        sprintf(_("Alarm %d hours before"), $alarm_value);
-                } else {
-                    $alarm_value = $this->alarm;
-                    $title = $alarm_value == 1 ?
-                        _("Alarm 1 minute before") :
-                        sprintf(_("Alarm %d minutes before"), $alarm_value);
-                }
-                $status .= Horde::img('alarm-' . $icon_color . '.png', $title,
-                                      array('title' => $title,
-                                            'class' => 'iconAlarm'),
-                                      Horde::url($registry->getImageDir(), true, -1));
-            }
-
-            if ($this->recurs()) {
-                $title = Kronolith::recurToString($this->recurrence->getRecurType());
-                $status .= Horde::img('recur-' . $icon_color . '.png', $title,
-                                      array('title' => $title,
-                                            'class' => 'iconRecur'),
-                                      Horde::url($registry->getImageDir(), true, -1));
-            }
-
-            if ($this->isPrivate()) {
-                $title = _("Private event");
-                $status .= Horde::img('private-' . $icon_color . '.png', $title,
-                                      array('title' => $title,
-                                            'class' => 'iconPrivate'),
-                                      Horde::url($registry->getImageDir(), true, -1));
-            }
-
-            if (!empty($this->attendees)) {
-                $title = count($this->attendees) == 1
-                    ? _("1 attendee")
-                    : sprintf(_("%s attendees"), count($this->attendees));
-                $status .= Horde::img('attendees.png', $title,
-                                      array('title' => $title,
-                                            'class' => 'iconPeople'),
-                                      Horde::url($registry->getImageDir(), true, -1));
-            }
-
-            if (!empty($status)) {
-                $link .= ' ' . $status;
-            }
-
-            if (!$this->eventID || !empty($this->external)) {
-                return $link;
-            }
-
-            $edit = '';
-            $delete = '';
-            if ((!$this->isPrivate() || $this->getCreatorId() == Auth::getAuth())
-                && $this->hasPermission(PERMS_EDIT)) {
-                $editurl = $this->getEditUrl(array('datetime' => $datetime->strftime('%Y%m%d%H%M%S'),
-                                                   'url' => $from_url));
-                $edit = Horde::link($editurl, sprintf(_("Edit %s"), $event_title), 'iconEdit')
-                    . Horde::img('edit-' . $icon_color . '.png', _("Edit"), '', Horde::url($registry->getImageDir(), true, -1))
-                    . '</a>';
-            }
-            if ($this->hasPermission(PERMS_DELETE)) {
-                $delurl = $this->getDeleteUrl(array('datetime' => $datetime->strftime('%Y%m%d%H%M%S'),
-                                                    'url' => $from_url));
-                $delete = Horde::link($delurl, sprintf(_("Delete %s"), $event_title), 'iconDelete')
-                    . Horde::img('delete-' . $icon_color . '.png', _("Delete"), '', Horde::url($registry->getImageDir(), true, -1))
-                    . '</a>';
-            }
-
-            if ($edit || $delete) {
-                $link .= $edit . $delete;
-            }
-        }
-
-        return $link;
-    }
-
-    /**
-     * Returns the CSS color definition for this event.
-     *
-     * @param boolean $with_attribute  Whether to wrap the colors inside a
-     *                                 "style" attribute.
-     *
-     * @return string  A CSS string with color definitions.
-     */
-    public function getCSSColors($with_attribute = true)
-    {
-        $css = 'background-color:' . $this->_backgroundColor . ';color:' . $this->_foregroundColor;
-        if ($with_attribute) {
-            $css = ' style="' . $css . '"';
-        }
-        return $css;
-    }
-
-    /**
-     * @return string  A tooltip for quick descriptions of this event.
-     */
-    public function getTooltip()
-    {
-        $tooltip = $this->getTimeRange()
-            . "\n" . sprintf(_("Owner: %s"), ($this->getCreatorId() == Auth::getAuth() ?
-                                              _("Me") : Kronolith::getUserName($this->getCreatorId())));
-
-        if (!$this->isPrivate() || $this->getCreatorId() == Auth::getAuth()) {
-            if ($this->location) {
-                $tooltip .= "\n" . _("Location") . ': ' . $this->location;
-            }
-
-            if ($this->description) {
-                $tooltip .= "\n\n" . String::wrap($this->description);
-            }
-        }
-
-        return $tooltip;
-    }
-
-    /**
-     * @return string  The time range of the event ("All Day", "1:00pm-3:00pm",
-     *                 "08:00-22:00").
-     */
-    public function getTimeRange()
-    {
-        if ($this->isAllDay()) {
-            return _("All day");
-        } elseif (($cmp = $this->start->compareDate($this->end)) > 0) {
-            $df = $GLOBALS['prefs']->getValue('date_format');
-            if ($cmp > 0) {
-                return $this->end->strftime($df) . '-'
-                    . $this->start->strftime($df);
-            } else {
-                return $this->start->strftime($df) . '-'
-                    . $this->end->strftime($df);
-            }
-        } else {
-            $twentyFour = $GLOBALS['prefs']->getValue('twentyFour');
-            return $this->start->format($twentyFour ? 'G:i' : 'g:ia')
-                . '-'
-                . $this->end->format($twentyFour ? 'G:i' : 'g:ia');
-        }
-    }
-
-    /**
-     * @return string  The CSS class for the event based on its status.
-     */
-    public function getStatusClass()
-    {
-        switch ($this->status) {
-        case Kronolith::STATUS_CANCELLED:
-            return 'event-cancelled';
-
-        case Kronolith::STATUS_TENTATIVE:
-        case Kronolith::STATUS_FREE:
-            return 'event-tentative';
-        }
-
-        return 'event';
-    }
-
-    private function _formIDEncode($id)
-    {
-        return str_replace(array('[', ']'),
-                           array('_', ''),
-                           $id);
-    }
-
-}
diff --git a/kronolith/lib/Driver/Holidays.php b/kronolith/lib/Driver/Holidays.php
new file mode 100644 (file)
index 0000000..4f7c81b
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+/**
+ * The Kronolith_Driver_Holidays implements support for the PEAR package
+ * Date_Holidays.
+ *
+ * Copyright 2006-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @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();
+    }
+
+    /**
+     * Returns a list of all holidays occuring between <code>$startDate</code>
+     * and <code>$endDate</code>.
+     *
+     * @param int|Horde_Date $startDate  The start of the datespan to be
+     *                                   checked. Defaults to the current date.
+     * @param int|Horde_Date $endDate    The end of the datespan. Defaults to
+     *                                   the current date.
+     * @param bool $hasAlarm             Left in for compatibility reasons and
+     *                                   has no effect on this function.
+     *                                   Defaults to <code>false</code>
+     *
+     * @return array  An array of all holidays within the given datespan.
+     */
+    public function listEvents($startDate = null, $endDate = null, $hasAlarm = false)
+    {
+        global $language;
+
+        $events = array();
+
+        if (is_null($startDate)) {
+            $startDate = new Horde_Date($_SERVER['REQUEST_TIME']);
+        }
+
+        if (is_null($endDate)) {
+            $endDate = new Horde_Date($_SERVER['REQUEST_TIME']);
+        }
+
+        Date_Holidays::staticSetProperty('DIE_ON_MISSING_LOCALE', false);
+        foreach (unserialize($GLOBALS['prefs']->getValue('holiday_drivers')) as $driver) {
+            for ($year = $startDate->year; $year <= $endDate->year; $year++) {
+                $dh = Date_Holidays::factory($driver, $year, $language);
+                if (Date_Holidays::isError($dh)) {
+                    Horde::logMessage(sprintf('Factory was unable to produce driver object for driver %s in year %s with locale %s',
+                                              $driver, $year, $language),
+                                      __FILE__, __LINE__, PEAR_LOG_ERR);
+                    continue;
+                }
+                $dh->addTranslation($language);
+                $events = array_merge($events, $this->_getEvents($dh, $startDate, $endDate));
+            }
+        }
+
+        return $events;
+    }
+
+    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::logMessage(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);
+                $event->fromDriver($holiday);
+                $events[] = $event;
+            }
+        }
+        return $events;
+    }
+
+    private function _getTranslationFile($driver)
+    {
+        static $data_dir;
+        if (!isset($data_dir)) {
+            include_once 'PEAR/Config.php';
+            $pear_config = new PEAR_Config();
+            $data_dir = $pear_config->get('data_dir');
+        }
+        if (empty($data_dir)) {
+            return;
+        }
+
+        foreach (array('', '_' . $driver) as $pkg_ext) {
+            foreach (array('ser', 'xml') as $format) {
+                $location = $data_dir . '/Date_Holidays' . $pkg_ext . '/lang/'
+                    . $driver . '/' . $GLOBALS['language'] . '.' . $format;
+                if (file_exists($location)) {
+                    return array($format, $location);
+                }
+            }
+        }
+
+        return array(null, null);
+    }
+
+}
diff --git a/kronolith/lib/Driver/Ical.php b/kronolith/lib/Driver/Ical.php
new file mode 100644 (file)
index 0000000..7b5c5fd
--- /dev/null
@@ -0,0 +1,187 @@
+<?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-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @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
+     */
+    private $_cache = array();
+
+    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 $startInterval  Start of range date object.
+     * @param Horde_Date $endInterval    End of range data object.
+     * @param boolean $hasAlarm          Only return events with alarms?
+     *                                   Defaults to all events.
+     *
+     * @return array  Events in the given time range.
+     */
+    public function listEvents($startDate = null, $endDate = null,
+                               $hasAlarm = false)
+    {
+        $data = $this->_getRemoteCalendar();
+        if (is_a($data, 'PEAR_Error')) {
+            return $data;
+        }
+
+        $iCal = new Horde_iCalendar();
+        if (!$iCal->parsevCalendar($data)) {
+            return array();
+        }
+
+        $components = $iCal->getComponents();
+        $events = array();
+        $count = count($components);
+        $exceptions = array();
+        for ($i = 0; $i < $count; $i++) {
+            $component = $components[$i];
+            if ($component->getType() == 'vEvent') {
+                $event = new Kronolith_Event_Ical($this);
+                $event->status = Kronolith::STATUS_FREE;
+                $event->fromiCalendar($component);
+                $event->remoteCal = $this->_params['url'];
+                $event->eventID = $i;
+
+                /* Catch RECURRENCE-ID attributes which mark single recurrence
+                 * instances. */
+                $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;
+                }
+
+                /* Ignore events out of the period. */
+                if (
+                    /* Starts after the period. */
+                    $event->start->compareDateTime($endDate) > 0 ||
+                    /* End before the period and doesn't recur. */
+                    (!$event->recurs() &&
+                     $event->end->compareDateTime($startDate) < 0) ||
+                    /* Recurs and ... */
+                    ($event->recurs() &&
+                      /* ... has a recurrence end before the period. */
+                      ($event->recurrence->hasRecurEnd() &&
+                       $event->recurrence->recurEnd->compareDateTime($startDate) < 0))) {
+                    continue;
+                }
+
+                $events[] = $event;
+            }
+        }
+
+        /* Loop through all explicitly defined recurrence intances and create
+         * exceptions for those in the event with the matchin recurrence. */
+        foreach ($events as $key => $event) {
+            if ($event->recurs() &&
+                isset($exceptions[$event->getUID()][$event->getSequence()])) {
+                $timestamp = $exceptions[$event->getUID()][$event->getSequence()];
+                $events[$key]->recurrence->addException(date('Y', $timestamp), date('m', $timestamp), date('d', $timestamp));
+            }
+        }
+
+        return $events;
+    }
+
+    public function getEvent($eventId = null)
+    {
+        $data = $this->_getRemoteCalendar();
+        if (is_a($data, 'PEAR_Error')) {
+            return $data;
+        }
+
+        $iCal = new Horde_iCalendar();
+        if (!$iCal->parsevCalendar($data)) {
+            return array();
+        }
+
+        $components = $iCal->getComponents();
+        if (isset($components[$eventId]) &&
+            $components[$eventId]->getType() == 'vEvent') {
+            $event = new Kronolith_Event_Ical($this);
+            $event->status = Kronolith::STATUS_FREE;
+            $event->fromiCalendar($components[$eventId]);
+            $event->remoteCal = $this->_params['url'];
+            $event->eventID = $eventId;
+
+            return $event;
+        }
+
+        return false;
+    }
+
+    /**
+     * Fetches a remote calendar into the session and return the data.
+     *
+     * @return mixed  Either the calendar data, or an error on failure.
+     */
+    private function _getRemoteCalendar()
+    {
+        $url = trim($this->_params['url']);
+
+        /* Treat webcal:// URLs as http://. */
+        if (substr($url, 0, 9) == 'webcal://') {
+            $url = str_replace('webcal://', 'http://', $url);
+        }
+
+        if (empty($_SESSION['kronolith']['remote'][$url])) {
+            $options['method'] = 'GET';
+            $options['timeout'] = 5;
+            $options['allowRedirects'] = true;
+
+            if (isset($this->_params['proxy'])) {
+                $options = array_merge($options, $this->_params['proxy']);
+            }
+
+            $http = new HTTP_Request($url, $options);
+            if (!empty($this->_params['user'])) {
+                $http->setBasicAuth($this->_params['user'],
+                                    $this->_params['password']);
+            }
+            @$http->sendRequest();
+            if ($http->getResponseCode() != 200) {
+                Horde::logMessage(sprintf('Failed to retrieve remote calendar: url = "%s", status = %s',
+                                          $url, $http->getResponseCode()),
+                                  __FILE__, __LINE__, PEAR_LOG_ERR);
+                return PEAR::raiseError(sprintf(_("Could not open %s."), $url));
+            }
+            $_SESSION['kronolith']['remote'][$url] = $http->getResponseBody();
+
+            /* Log fetch at DEBUG level. */
+            Horde::logMessage(sprintf('Retrieved remote calendar for %s: url = "%s"',
+                                      Auth::getAuth(), $url),
+                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
+        }
+
+        return $_SESSION['kronolith']['remote'][$url];
+    }
+
+}
diff --git a/kronolith/lib/Driver/Kolab.php b/kronolith/lib/Driver/Kolab.php
new file mode 100644 (file)
index 0000000..2b9fae1
--- /dev/null
@@ -0,0 +1,498 @@
+<?php
+
+require_once 'Horde/Kolab.php';
+require_once 'Horde/Identity.php';
+
+/**
+ * Horde Kronolith driver for the Kolab IMAP Server.
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @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/UID is key
+     *
+     * @var array
+     */
+    private $_events_cache;
+
+    /**
+     * Indicates if we have synchronized this folder
+     *
+     * @var boolean
+     */
+    private $_synchronized;
+
+    /**
+     * Shortcut to the imap connection
+     *
+     * @var Kolab_IMAP
+     */
+    private $_store;
+
+    /**
+     * Attempts to open a Kolab Groupware folder.
+     *
+     * @return boolean  True on success, PEAR_Error on failure.
+     */
+    public function initialize()
+    {
+        $this->_kolab = new Kolab();
+        $this->reset();
+        return true;
+    }
+
+    /**
+     * Change current calendar
+     */
+    public function open($calendar)
+    {
+        if ($this->_calendar != $calendar) {
+            $this->_calendar = $calendar;
+            $this->reset();
+        }
+
+        return true;
+    }
+
+    /**
+     * 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
+        $result = $this->_kolab->open($this->_calendar, 1);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        $this->_store = $this->_kolab->_storage;
+
+        // build internal event cache
+        $this->_events_cache = array();
+        $events = $this->_store->getObjects();
+        foreach ($events as $event) {
+            $this->_events_cache[$event['uid']] = new Kronolith_Event_Kolab($this, $event);
+        }
+
+        $this->_synchronized = true;
+    }
+
+    public function listAlarms($date, $fullevent = false)
+    {
+        $allevents = $this->listEvents($date, null, true);
+        $events = array();
+
+        foreach ($allevents as $eventId) {
+            $event = $this->getEvent($eventId);
+            if (is_a($event, 'PEAR_Error')) {
+                return $event;
+            }
+
+            if (!$event->recurs()) {
+                $start = new Horde_Date($event->start);
+                $start->min -= $event->getAlarm();
+                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->getAlarm();
+                    $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.
+     */
+    public function exists($uid, $calendar_id = null)
+    {
+        // Log error if someone uses this function in an unsupported way
+        if ($calendar_id != $this->_calendar) {
+            Horde::logMessage(sprintf("Kolab::exists called for calendar %s. Currently active is %s.", $calendar_id, $this->_calendar), __FILE__, __LINE__, PEAR_LOG_ERR);
+            return PEAR::raiseError(sprintf("Kolab::exists called for calendar %s. Currently active is %s.", $calendar_id, $this->_calendar));
+        }
+
+        $result = $this->synchronize();
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        if ($this->_store->objectUidExists($uid)) {
+            return $uid;
+        }
+
+        return false;
+    }
+
+    /**
+     * 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 $hasAlarm          Only return events with alarms?
+     *                                   Defaults to all events.
+     *
+     * @return array  Events in the given time range.
+     */
+    public function listEvents($startDate = null, $endDate = null, $hasAlarm = false)
+    {
+        $result = $this->synchronize();
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        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));
+        }
+
+        $ids = array();
+
+        foreach($this->_events_cache as $event) {
+            if ($hasAlarm && !$event->getAlarm()) {
+                continue;
+            }
+
+            $keep_event = false;
+            /* check if event period intersects with given period */
+            if (!(($endDate->compareDateTime($event->start) < 0) ||
+                  ($startDate->compareDateTime($event->end) > 0))) {
+                $keep_event = true;
+            }
+
+            /* do recurrence expansion if not keeping anyway */
+            if (!$keep_event && $event->recurs()) {
+                $next = $event->recurrence->nextRecurrence($startDate);
+                while ($next !== false &&
+                       $event->recurrence->hasException($next->year, $next->month, $next->mday)) {
+                    $next->mday++;
+                    $next = $event->recurrence->nextRecurrence($next);
+                }
+
+                if ($next !== false) {
+                    $duration = $next->timestamp() - $event->start->timestamp();
+                    $next_end = new Horde_Date($event->end->timestamp() + $duration);
+
+                    if ((!(($endDate->compareDateTime($next) < 0) ||
+                           ($startDate->compareDateTime($next_end) > 0)))) {
+                        $keep_event = true;
+                    }
+                }
+            }
+
+            if ($keep_event) {
+                $ids[$event->getUID()] = $event->getUID();
+            }
+        }
+
+        return $ids;
+    }
+
+    public function getEvent($eventId = null)
+    {
+        if (is_null($eventId)) {
+            return new Kronolith_Event_Kolab($this);
+        }
+
+        $result = $this->synchronize();
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        if (array_key_exists($eventId, $this->_events_cache)) {
+            return $this->_events_cache[$eventId];
+        }
+
+        return PEAR::raiseError(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
+     */
+    public function getByUID($uid, $calendars = null, $getAll = false)
+    {
+        if (!is_array($calendars)) {
+            $calendars = array_keys(Kronolith::listCalendars(true, PERMS_READ));
+        }
+
+        foreach ($calendars as $calendar) {
+            $this->open($calendar);
+            $this->synchronize();
+
+            if (!array_key_exists($uid, $this->_events_cache)) {
+                continue;
+            }
+
+            // Ok, found event
+            $event = $this->_events_cache[$uid];
+
+            if ($getAll) {
+                $events = array();
+                $events[] = $event;
+                return $events;
+            } else {
+                return $event;
+            }
+        }
+
+        return PEAR::raiseError(sprintf(_("Event not found: %s"), $uid));
+    }
+
+    /**
+     * 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 mixed  UID on success, a PEAR error otherwise
+     */
+    public function saveEvent(&$event)
+    {
+        $result = $this->synchronize();
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        $uid = $event->getUID();
+        if ($uid == null) {
+            $event->setUID($this->_store->generateUID());
+        }
+
+        $attributes = $event->toDriver();
+
+        $edit = false;
+        $stored_uid = null;
+        if ($event->isStored() || $event->exists()) {
+            $stored_uid = $attributes['uid'];
+            $action = array('action' => 'modify');
+            $edit = true;
+        } else {
+            $action = array('action' => 'add');
+        }
+
+        $result = $this->_store->save($attributes, $stored_uid);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        /* Deal with tags */
+        $tagger = Kronolith::getTagger();
+        if (!empty($edit)) {
+            $tagger->replaceTags($event->getUID(), $event->tags, 'event');
+        } else {
+            $tagger->tag($event->getUID(), $event->tags, 'event');
+        }
+
+        /* Notify about the changed event. */
+        $result = Kronolith::sendNotification($event, $edit ? 'edit' : 'add');
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+        }
+
+        /* Log the creation/modification of this item in the history log. */
+        $history = Horde_History::singleton();
+        $history->log('kronolith:' . $event->getCalendar() . ':' . $event->getUID(), $action, true);
+
+        // refresh IMAP cache
+        $this->synchronize(true);
+
+        if (is_callable('Kolab', 'triggerFreeBusyUpdate')) {
+            Kolab::triggerFreeBusyUpdate($this->_store->parseFolder($event->getCalendar()));
+        }
+
+        return $event->getUID();
+    }
+
+    /**
+     * Move an event to a new calendar.
+     *
+     * @param string $eventId      The event to move.
+     * @param string $newCalendar  The new calendar.
+     */
+    public function move($eventId, $newCalendar)
+    {
+        $event = $this->getEvent($eventId);
+
+        $result = $this->synchronize();
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        global $kronolith_shares;
+        $target = $kronolith_shares->getShare($newCalendar);
+        $folder = $target->get('folder');
+
+        $result = $this->_store->move($eventId, $folder);
+        if ($result) {
+            unset($this->_events_cache[$eventId]);
+        }
+
+        if (is_callable('Kolab', 'triggerFreeBusyUpdate')) {
+            Kolab::triggerFreeBusyUpdate($this->_store->parseFolder($this->_calendar));
+            Kolab::triggerFreeBusyUpdate($this->_store->parseFolder($newCalendar));
+        }
+
+        /* Log the moving of this item in the history log. */
+        $uid = $event->getUID();
+        $history = Horde_History::singleton();
+        $history->log('kronolith:' . $event->getCalendar() . ':' . $uid, array('action' => 'delete'), true);
+        $history->log('kronolith:' . $newCalendar . ':' . $uid, array('action' => 'add'), true);
+
+        return $result;
+    }
+
+    /**
+     * Delete a calendar and all its events.
+     *
+     * @param string $calendar  The name of the calendar to delete.
+     *
+     * @return mixed  True or a PEAR_Error on failure.
+     */
+    public function delete($calendar)
+    {
+        $this->open($calendar);
+        $result = $this->synchronize();
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        $result = $this->_store->deleteAll($calendar);
+        if (is_callable('Kolab', 'triggerFreeBusyUpdate')) {
+            Kolab::triggerFreeBusyUpdate($this->_store->parseFolder($calendar));
+        }
+        return true;
+    }
+
+    /**
+     * Rename a calendar.
+     *
+     * @param string $from  The current name of the calendar.
+     * @param string $to    The new name of the calendar.
+     *
+     * @return mixed  True or a PEAR_Error on failure.
+     */
+    public function rename($from, $to)
+    {
+        // FIXME: We can't do much here. We'd need to be called after
+        // renaming the share here. This needs to be checked again.
+        // kolab/issue2249 ([horde/kronolith] Kronolith is unable to
+        // trigger a free/busy update on a folder rename)
+        return true;
+    }
+
+    /**
+     * Delete an event.
+     *
+     * @param string $eventId  The ID of the event to delete.
+     *
+     * @return mixed  True or a PEAR_Error on failure.
+     */
+    public function deleteEvent($eventId, $silent = false)
+    {
+        $result = $this->synchronize();
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        if (!$this->_store->objectUidExists($eventId)) {
+            return PEAR::raiseError(sprintf(_("Event not found: %s"), $eventId));
+        }
+
+        $event = $this->getEvent($eventId);
+
+        if ($this->_store->delete($eventId)) {
+            // Notify about the deleted event.
+            if (!$silent) {
+                $result = Kronolith::sendNotification($event, 'delete');
+                if (is_a($result, 'PEAR_Error')) {
+                    Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+                }
+            }
+
+            /* Log the deletion of this item in the history log. */
+            $history = Horde_History::singleton();
+            $history->log('kronolith:' . $event->getCalendar() . ':' . $event->getUID(), array('action' => 'delete'), true);
+
+            if (is_callable('Kolab', 'triggerFreeBusyUpdate')) {
+                Kolab::triggerFreeBusyUpdate($this->_store->parseFolder($event->getCalendar()));
+            }
+
+            unset($this->_events_cache[$eventId]);
+        } else {
+            return PEAR::raiseError(sprintf(_("Cannot delete event: %s"), $eventId));
+        }
+
+        return true;
+    }
+
+}
diff --git a/kronolith/lib/Driver/Sql.php b/kronolith/lib/Driver/Sql.php
new file mode 100644 (file)
index 0000000..ba3a568
--- /dev/null
@@ -0,0 +1,866 @@
+<?php
+/**
+ * The Kronolith_Driver_Sql class implements the Kronolith_Driver API for a
+ * SQL backend.
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @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 DB
+     */
+    private $_db;
+
+    /**
+     * Handle for the current database connection, used for writing. Defaults
+     * to the same handle as $_db if a separate write database is not required.
+     *
+     * @var DB
+     */
+    private $_write_db;
+
+    /**
+     * Cache events as we fetch them to avoid fetching the same event from the
+     * DB twice.
+     *
+     * @var array
+     */
+    private $_cache = array();
+
+    public function listAlarms($date, $fullevent = false)
+    {
+        require_once 'Date/Calc.php';
+
+        $allevents = $this->listEvents($date, null, true);
+        if (is_a($allevents, 'PEAR_Error')) {
+            return $allevents;
+        }
+
+        $events = array();
+        foreach ($allevents as $eventId) {
+            $event = $this->getEvent($eventId);
+            if (is_a($event, 'PEAR_Error')) {
+                continue;
+            }
+
+            if (!$event->recurs()) {
+                $start = new Horde_Date($event->start);
+                $start->min -= $event->getAlarm();
+                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->getAlarm();
+                    $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 = $start;
+                            $event->end = $end;
+                            $events[] = $event;
+                        } else {
+                            $events[] = $eventId;
+                        }
+                    }
+                }
+            }
+        }
+
+        return is_array($events) ? $events : array();
+    }
+
+    public function search($query)
+    {
+        require_once 'Horde/SQL.php';
+
+        /* Build SQL conditions based on the query string. */
+        $cond = '((';
+        $values = array();
+
+        if (!empty($query->title)) {
+            $binds = Horde_SQL::buildClause($this->_db, 'event_title', 'LIKE', $this->convertToDriver($query->title), true);
+            if (is_array($binds)) {
+                $cond .= $binds[0] . ' AND ';
+                $values = array_merge($values, $binds[1]);
+            } else {
+                $cond .= $binds;
+            }
+        }
+        if (!empty($query->location)) {
+            $binds = Horde_SQL::buildClause($this->_db, 'event_location', 'LIKE', $this->convertToDriver($query->location), true);
+            if (is_array($binds)) {
+                $cond .= $binds[0] . ' AND ';
+                $values = array_merge($values, $binds[1]);
+            } else {
+                $cond .= $binds;
+            }
+        }
+        if (!empty($query->description)) {
+            $binds = Horde_SQL::buildClause($this->_db, 'event_description', 'LIKE', $this->convertToDriver($query->description), true);
+            if (is_array($binds)) {
+                $cond .= $binds[0] . ' AND ';
+                $values = array_merge($values, $binds[1]);
+            } else {
+                $cond .= $binds;
+            }
+        }
+        if (isset($query->status)) {
+            $binds = Horde_SQL::buildClause($this->_db, '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->creatorID)) {
+            $binds = Horde_SQL::buildClause($this->_db, 'event_creator_id', '=', $query->creatorID, 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($query->start,
+                                                 empty($query->end)
+                                                 ? new Horde_Date(array('mday' => 31, 'month' => 12, 'year' => 9999))
+                                                 : $query->end,
+                                                 $cond,
+                                                 $values);
+        if (is_a($eventIds, 'PEAR_Error')) {
+            return $eventIds;
+        }
+
+        $events = array();
+        foreach ($eventIds as $eventId) {
+            $event = $this->getEvent($eventId);
+            if (is_a($event, 'PEAR_Error')) {
+                return $event;
+            }
+            $events[] = $event;
+        }
+
+        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.
+     */
+    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;
+        }
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Kronolith_Driver_Sql::exists(): user = "%s"; query = "%s"',
+                                  Auth::getAuth(), $query),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $event = $this->_db->getRow($query, $values, DB_FETCHMODE_ASSOC);
+        if (is_a($event, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $event;
+        }
+
+        if ($event) {
+            return $event['event_id'];
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 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 $hasAlarm          Only return events with alarms?
+     *                                   Defaults to all events.
+     *
+     * @return array  Events in the given time range.
+     */
+    public function listEvents($startDate = null, $endDate = null,
+                               $hasAlarm = false)
+    {
+        if (empty($endDate)) {
+            $endInterval = new Horde_Date(array('mday' => 31, 'month' => 12,
+                                                'year' => 9999));
+        } else {
+            $endInterval = clone $endDate;
+            $endInterval->mday++;
+        }
+
+        $startInterval = null;
+        if (empty($startDate)) {
+            $startInterval = new Horde_Date(array('mday' => 1, 'month' => 1,
+                                                  'year' => 0000));
+        } else {
+            $startInterval = clone $startDate;
+        }
+
+        return $this->listEventsConditional($startInterval, $endInterval,
+                                            $hasAlarm ? 'event_alarm > ?' : '',
+                                            $hasAlarm ? array(0) : array());
+    }
+
+    /**
+     * 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.
+     */
+    public function listEventsConditional($startInterval, $endInterval,
+                                          $conditions = '', $vals = array())
+    {
+        $q = 'SELECT event_id, event_uid, event_description, event_location,' .
+            ' event_private, event_status, event_attendees,' .
+            ' event_title, event_recurcount,' .
+            ' 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' .
+            ' FROM ' . $this->_params['table'] .
+            ' WHERE calendar_id = ? AND ((';
+        $values = array($this->_calendar);
+
+        if ($conditions) {
+            $q .= $conditions . ')) AND ((';
+            $values = array_merge($values, $vals);
+        }
+
+        $etime = $endInterval->format('Y-m-d H:i:s');
+        $stime = null;
+        if (isset($startInterval)) {
+            $stime = $startInterval->format('Y-m-d H:i:s');
+            $q .= 'event_end > ? AND ';
+            $values[] = $stime;
+        }
+        $q .= 'event_start < ?) OR (';
+        $values[] = $etime;
+        if (isset($stime)) {
+            $q .= 'event_recurenddate >= ? AND ';
+            $values[] = $stime;
+        }
+        $q .= 'event_start <= ?' .
+            ' AND event_recurtype <> ?))';
+        array_push($values, $etime, Horde_Date_Recurrence::RECUR_NONE);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Kronolith_Driver_Sql::listEventsConditional(): user = "%s"; query = "%s"; values = "%s"',
+                                  Auth::getAuth(), $q, implode(',', $values)),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Run the query. */
+        $qr = $this->_db->query($q, $values);
+        if (is_a($qr, 'PEAR_Error')) {
+            Horde::logMessage($qr, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $qr;
+        }
+
+        $events = array();
+        $row = $qr->fetchRow(DB_FETCHMODE_ASSOC);
+        while ($row && !is_a($row, 'PEAR_Error')) {
+            /* If the event did not have a UID before, we need to give
+             * it one. */
+            if (empty($row['event_uid'])) {
+                $row['event_uid'] = $this->generateUID();
+
+                /* 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']);
+
+                /* Log the query at a DEBUG log level. */
+                Horde::logMessage(sprintf('Kronolith_Driver_Sql::listEventsConditional(): user = %s; query = "%s"; values = "%s"',
+                                          Auth::getAuth(), $query, implode(',', $values)),
+                                  __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+                $result = $this->_write_db->query($query, $values);
+                if (is_a($result, 'PEAR_Error')) {
+                    Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+                }
+            }
+
+            /* 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 Kronolith_Event_Sql($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 && $next->compareDate($endInterval) < 0) {
+                    $events[$row['event_uid']] = $row['event_id'];
+                }
+            }
+
+            $row = $qr->fetchRow(DB_FETCHMODE_ASSOC);
+        }
+
+        return $events;
+    }
+
+    public function getEvent($eventId = null)
+    {
+        if (is_null($eventId)) {
+            return new Kronolith_Event_Sql($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_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' .
+            ' FROM ' . $this->_params['table'] . ' WHERE event_id = ? AND calendar_id = ?';
+        $values = array($eventId, $this->_calendar);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Kronolith_Driver_Sql::getEvent(): user = "%s"; query = "%s"; values = "%s"',
+                                  Auth::getAuth(), $query, implode(',', $values)),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $event = $this->_db->getRow($query, $values, DB_FETCHMODE_ASSOC);
+        if (is_a($event, 'PEAR_Error')) {
+            Horde::logMessage($event, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $event;
+        }
+
+        if ($event) {
+            $this->_cache[$this->_calendar][$eventId] = new Kronolith_Event_Sql($this, $event);
+            return $this->_cache[$this->_calendar][$eventId];
+        } else {
+            return PEAR::raiseError(_("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? If this is false,
+     * an error will be returned if more than one event is found.
+     *
+     * @return Kronolith_Event
+     */
+    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_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' .
+            ' FROM ' . $this->_params['table'] . ' WHERE event_uid = ?';
+        $values = array($uid);
+
+        /* Optionally filter by calendar */
+        if (!is_null($calendars)) {
+            if (!count($calendars)) {
+                return PEAR::raiseError(_("No calendars to search"));
+            }
+            $query .= ' AND calendar_id IN (?' . str_repeat(', ?', count($calendars) - 1) . ')';
+            $values = array_merge($values, $calendars);
+        }
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Kronolith_Driver_Sql::getByUID(): user = "%s"; query = "%s"; values = "%s"',
+                                  Auth::getAuth(), $query, implode(',', $values)),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $events = $this->_db->getAll($query, $values, DB_FETCHMODE_ASSOC);
+        if (is_a($events, 'PEAR_Error')) {
+            Horde::logMessage($events, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $events;
+        }
+        if (!count($events)) {
+            return PEAR::raiseError($uid . ' not found');
+        }
+
+        $eventArray = array();
+        foreach ($events as $event) {
+            $this->open($event['calendar_id']);
+            $this->_cache[$this->_calendar][$event['event_id']] = new Kronolith_Event_Sql($this, $event);
+            $eventArray[] = $this->_cache[$this->_calendar][$event['event_id']];
+        }
+
+        if ($getAll) {
+            return $eventArray;
+        }
+
+        /* First try the user's own calendars. */
+        $ownerCalendars = Kronolith::listCalendars(true, PERMS_READ);
+        $event = null;
+        foreach ($eventArray as $ev) {
+            if (isset($ownerCalendars[$ev->getCalendar()])) {
+                $event = $ev;
+                break;
+            }
+        }
+
+        /* If not successful, try all calendars the user has access too. */
+        if (empty($event)) {
+            $readableCalendars = Kronolith::listCalendars(false, PERMS_READ);
+            foreach ($eventArray as $ev) {
+                if (isset($readableCalendars[$ev->getCalendar()])) {
+                    $event = $ev;
+                    break;
+                }
+            }
+        }
+
+        if (empty($event)) {
+            $event = $eventArray[0];
+        }
+
+        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.
+     */
+    public function saveEvent($event)
+    {
+        if ($event->isStored() || $event->exists()) {
+            $values = array();
+
+            $query = 'UPDATE ' . $this->_params['table'] . ' SET ';
+
+            foreach ($event->getProperties() as $key => $val) {
+                $query .= " $key = ?,";
+                $values[] = $val;
+            }
+            $query = substr($query, 0, -1);
+            $query .= ' WHERE event_id = ?';
+            $values[] = $event->getId();
+
+            /* Log the query at a DEBUG log level. */
+            Horde::logMessage(sprintf('Kronolith_Driver_Sql::saveEvent(): user = "%s"; query = "%s"; values = "%s"',
+                                      Auth::getAuth(), $query, implode(',', $values)),
+                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+            $result = $this->_write_db->query($query, $values);
+            if (is_a($result, 'PEAR_Error')) {
+                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+                return $result;
+            }
+
+            /* Log the modification of this item in the history log. */
+            if ($event->getUID()) {
+                $history = Horde_History::singleton();
+                $history->log('kronolith:' . $this->_calendar . ':' . $event->getUID(), array('action' => 'modify'), true);
+            }
+
+            /* Update tags */
+            $tagger = Kronolith::getTagger();
+            $tagger->replaceTags($event->getUID(), $event->tags, 'event');
+
+            /* Notify users about the changed event. */
+            $result = Kronolith::sendNotification($event, 'edit');
+            if (is_a($result, 'PEAR_Error')) {
+                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            }
+
+            return $event->getId();
+        } else {
+            if ($event->getId()) {
+                $id = $event->getId();
+            } else {
+                $id = hash('md5', uniqid(mt_rand(), true));
+                $event->setId($id);
+            }
+
+            if ($event->getUID()) {
+                $uid = $event->getUID();
+            } else {
+                $uid = $this->generateUID();
+                $event->setUID($uid);
+            }
+
+            $query = 'INSERT INTO ' . $this->_params['table'];
+            $cols_name = ' (event_id, event_uid,';
+            $cols_values = ' VALUES (?, ?,';
+            $values = array($id, $uid);
+
+            foreach ($event->getProperties() as $key => $val) {
+                $cols_name .= " $key,";
+                $cols_values .= ' ?,';
+                $values[] = $val;
+            }
+
+            $cols_name .= ' calendar_id)';
+            $cols_values .= ' ?)';
+            $values[] = $this->_calendar;
+
+            $query .= $cols_name . $cols_values;
+
+            /* Log the query at a DEBUG log level. */
+            Horde::logMessage(sprintf('Kronolith_Driver_Sql::saveEvent(): user = "%s"; query = "%s"; values = "%s"',
+                                Auth::getAuth(), $query, implode(',', $values)),
+                                __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+            $result = $this->_write_db->query($query, $values);
+            if (is_a($result, 'PEAR_Error')) {
+                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+                return $result;
+            }
+
+            /* Log the creation of this item in the history log. */
+            $history = Horde_History::singleton();
+            $history->log('kronolith:' . $this->_calendar . ':' . $uid, array('action' => 'add'), true);
+
+            /* Deal with any tags */
+            $tagger = Kronolith::getTagger();
+            $tagger->tag($event->getUID(), $event->tags, 'event');
+
+            /* Notify users about the new event. */
+            $result = Kronolith::sendNotification($event, 'add');
+            if (is_a($result, 'PEAR_Error')) {
+                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            }
+
+            return $id;
+        }
+    }
+
+    /**
+     * Move an event to a new calendar.
+     *
+     * @param string $eventId      The event to move.
+     * @param string $newCalendar  The new calendar.
+     */
+    public function move($eventId, $newCalendar)
+    {
+        /* Fetch the event for later use. */
+        $event = $this->getEvent($eventId);
+        if (is_a($event, 'PEAR_Error')) {
+            return $event;
+        }
+
+        $query = 'UPDATE ' . $this->_params['table'] . ' SET calendar_id = ? WHERE calendar_id = ? AND event_id = ?';
+        $values = array($newCalendar, $this->_calendar, $eventId);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Kronolith_Driver_Sql::move(): %s; values = "%s"',
+                                  $query, implode(',', $values)),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        /* Attempt the move query. */
+        $result = $this->_write_db->query($query, $values);
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+
+        /* Log the moving of this item in the history log. */
+        $uid = $event->getUID();
+        if ($uid) {
+            $history = Horde_History::singleton();
+            $history->log('kronolith:' . $this->_calendar . ':' . $uid, array('action' => 'delete'), true);
+            $history->log('kronolith:' . $newCalendar . ':' . $uid, array('action' => 'add'), true);
+        }
+
+        return true;
+    }
+
+    /**
+     * Delete a calendar and all its events.
+     *
+     * @param string $calendar  The name of the calendar to delete.
+     *
+     * @return mixed  True or a PEAR_Error on failure.
+     */
+    public function delete($calendar)
+    {
+        $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE calendar_id = ?';
+        $values = array($calendar);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Kronolith_Driver_Sql::delete(): user = "%s"; query = "%s"; values = "%s"',
+                                  Auth::getAuth(), $query, implode(',', $values)),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        return $this->_write_db->query($query, $values);
+    }
+
+    /**
+     * Delete 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.
+     *
+     * @return mixed  True or a PEAR_Error on failure.
+     */
+    public function deleteEvent($eventId, $silent = false)
+    {
+        /* Fetch the event for later use. */
+        $event = $this->getEvent($eventId);
+        if (is_a($event, 'PEAR_Error')) {
+            return $event;
+        }
+
+        $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE event_id = ? AND calendar_id = ?';
+        $values = array($eventId, $this->_calendar);
+
+        /* Log the query at a DEBUG log level. */
+        Horde::logMessage(sprintf('Kronolith_Driver_Sql::deleteEvent(): user = "%s"; query = "%s"; values = "%s"',
+                                  Auth::getAuth(), $query, implode(',', $values)),
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $result = $this->_write_db->query($query, $values);
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+
+        /* Log the deletion of this item in the history log. */
+        if ($event->getUID()) {
+            $history = Horde_History::singleton();
+            $history->log('kronolith:' . $this->_calendar . ':' . $event->getUID(), array('action' => 'delete'), true);
+        }
+
+        /* Remove any pending alarms. */
+        if (@include_once 'Horde/Alarm.php') {
+            $alarm = Horde_Alarm::factory();
+            $alarm->delete($event->getUID());
+        }
+
+        /* Remove any tags */
+        $tagger = Kronolith::getTagger();
+        $tagger->replaceTags($event->getUID(), array(), 'event');
+
+        /* Notify about the deleted event. */
+        if (!$silent) {
+            $result = Kronolith::sendNotification($event, 'delete');
+            if (is_a($result, 'PEAR_Error')) {
+                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Attempts to open a connection to the SQL server.
+     *
+     * @return boolean True.
+     */
+    public function initialize()
+    {
+        Horde::assertDriverConfig($this->_params, 'calendar',
+            array('phptype'));
+
+        if (!isset($this->_params['database'])) {
+            $this->_params['database'] = '';
+        }
+        if (!isset($this->_params['username'])) {
+            $this->_params['username'] = '';
+        }
+        if (!isset($this->_params['hostspec'])) {
+            $this->_params['hostspec'] = '';
+        }
+        if (!isset($this->_params['table'])) {
+            $this->_params['table'] = 'kronolith_events';
+        }
+
+        /* Connect to the SQL server using the supplied parameters. */
+        $this->_write_db = DB::connect($this->_params,
+                                       array('persistent' => !empty($this->_params['persistent']),
+                                             'ssl' => !empty($this->_params['ssl'])));
+        if (is_a($this->_write_db, 'PEAR_Error')) {
+            return $this->_write_db;
+        }
+        $this->_initConn($this->_write_db);
+
+        /* Check if we need to set up the read DB connection
+         * seperately. */
+        if (!empty($this->_params['splitread'])) {
+            $params = array_merge($this->_params, $this->_params['read']);
+            $this->_db = DB::connect($params,
+                                     array('persistent' => !empty($params['persistent']),
+                                           'ssl' => !empty($params['ssl'])));
+            if (is_a($this->_db, 'PEAR_Error')) {
+                return $this->_db;
+            }
+            $this->_initConn($this->_db);
+        } else {
+            /* Default to the same DB handle for the writer too. */
+            $this->_db = $this->_write_db;
+        }
+
+        return true;
+    }
+
+    /**
+     */
+    private function _initConn(&$db)
+    {
+        // Set DB portability options.
+        switch ($db->phptype) {
+        case 'mssql':
+            $db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+            break;
+        default:
+            $db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+        }
+
+        /* Handle any database specific initialization code to run. */
+        switch ($db->dbsyntax) {
+        case 'oci8':
+            $query = "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'";
+
+            /* Log the query at a DEBUG log level. */
+            Horde::logMessage(sprintf('Kronolith_Driver_Sql::_initConn(): user = "%s"; query = "%s"',
+                                      Auth::getAuth(), $query),
+                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+            $db->query($query);
+            break;
+
+        case 'pgsql':
+            $query = "SET datestyle TO 'iso'";
+
+            /* Log the query at a DEBUG log level. */
+            Horde::logMessage(sprintf('Kronolith_Driver_Sql::_initConn(): user = "%s"; query = "%s"',
+                                      Auth::getAuth(), $query),
+                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+            $db->query($query);
+            break;
+        }
+    }
+
+    /**
+     * 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 String::convertCharset($value, $this->_params['charset']);
+    }
+
+    /**
+     * 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 String::convertCharset($value, NLS::getCharset(), $this->_params['charset']);
+    }
+
+    /**
+     * Remove all events owned by the specified user in all calendars.
+     *
+     *
+     * @param string $user  The user name to delete events for.
+     *
+     * @param mixed  True | PEAR_Error
+     */
+    public function removeUserData($user)
+    {
+        if (!Auth::isAdmin()) {
+            return PEAR::raiseError(_("Permission Denied"));
+        }
+
+        $shares = $GLOBALS['kronolith_shares']->listShares($user, PERMS_EDIT);
+        if (is_a($shares, 'PEAR_Error')) {
+            return $shares;
+        }
+
+        foreach (array_keys($shares) as $calendar) {
+            $ids = Kronolith::listEventIds(null, null, $calendar);
+            if (is_a($ids, 'PEAR_Error')) {
+                return $ids;
+            }
+            $uids = array();
+            foreach ($ids as $cal) {
+                $uids = array_merge($uids, array_keys($cal));
+            }
+
+            foreach ($uids as $uid) {
+                $event = $this->getByUID($uid);
+                if (is_a($event, 'PEAR_Error')) {
+                    return $event;
+                }
+
+                $this->deleteEvent($event->getId());
+            }
+        }
+        return true;
+    }
+}
diff --git a/kronolith/lib/Driver/holidays.php b/kronolith/lib/Driver/holidays.php
deleted file mode 100644 (file)
index 4823feb..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-<?php
-/**
- * The Kronolith_Driver_holidays implements support for the PEAR package
- * Date_Holidays.
- *
- * @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();
-    }
-
-    /**
-     * Returns a list of all holidays occuring between <code>$startDate</code>
-     * and <code>$endDate</code>.
-     *
-     * @param int|Horde_Date $startDate  The start of the datespan to be
-     *                                   checked. Defaults to the current date.
-     * @param int|Horde_Date $endDate    The end of the datespan. Defaults to
-     *                                   the current date.
-     * @param bool $hasAlarm             Left in for compatibility reasons and
-     *                                   has no effect on this function.
-     *                                   Defaults to <code>false</code>
-     *
-     * @return array  An array of all holidays within the given datespan.
-     */
-    public function listEvents($startDate = null, $endDate = null, $hasAlarm = false)
-    {
-        global $language;
-
-        $events = array();
-
-        if (is_null($startDate)) {
-            $startDate = new Horde_Date($_SERVER['REQUEST_TIME']);
-        }
-
-        if (is_null($endDate)) {
-            $endDate = new Horde_Date($_SERVER['REQUEST_TIME']);
-        }
-
-        Date_Holidays::staticSetProperty('DIE_ON_MISSING_LOCALE', false);
-        foreach (unserialize($GLOBALS['prefs']->getValue('holiday_drivers')) as $driver) {
-            for ($year = $startDate->year; $year <= $endDate->year; $year++) {
-                $dh = Date_Holidays::factory($driver, $year, $language);
-                if (Date_Holidays::isError($dh)) {
-                    Horde::logMessage(sprintf('Factory was unable to produce driver object for driver %s in year %s with locale %s',
-                                              $driver, $year, $language),
-                                      __FILE__, __LINE__, PEAR_LOG_ERR);
-                    continue;
-                }
-                $dh->addTranslation($language);
-                $events = array_merge($events, $this->_getEvents($dh, $startDate, $endDate));
-            }
-        }
-
-        return $events;
-    }
-
-    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::logMessage(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);
-                $event->fromDriver($holiday);
-                $events[] = $event;
-            }
-        }
-        return $events;
-    }
-
-    private function _getTranslationFile($driver)
-    {
-        static $data_dir;
-        if (!isset($data_dir)) {
-            include_once 'PEAR/Config.php';
-            $pear_config = new PEAR_Config();
-            $data_dir = $pear_config->get('data_dir');
-        }
-        if (empty($data_dir)) {
-            return;
-        }
-
-        foreach (array('', '_' . $driver) as $pkg_ext) {
-            foreach (array('ser', 'xml') as $format) {
-                $location = $data_dir . '/Date_Holidays' . $pkg_ext . '/lang/'
-                    . $driver . '/' . $GLOBALS['language'] . '.' . $format;
-                if (file_exists($location)) {
-                    return array($format, $location);
-                }
-            }
-        }
-
-        return array(null, null);
-    }
-
-}
-
-class Kronolith_Event_holidays extends Kronolith_Event
-{
-    /**
-     * The status of this event.
-     *
-     * @var integer
-     */
-    public $status = Kronolith::STATUS_FREE;
-
-    /**
-     * Whether this is an all-day event.
-     *
-     * @var boolean
-     */
-    public $allday = true;
-
-    /**
-     * Parse in an event from the driver.
-     *
-     * @param Date_Holidays_Holiday $dhEvent  A holiday returned
-     *                                        from the driver
-     */
-    public function fromDriver($dhEvent)
-    {
-        $this->stored = true;
-        $this->initialized = true;
-        $this->setTitle(String::convertCharset($dhEvent->getTitle(), 'UTF-8'));
-        $this->setId($dhEvent->getInternalName());
-
-        $this->start = new Horde_Date($dhEvent->_date->getTime());
-        $this->end = new Horde_Date($this->start);
-        $this->end->mday++;
-    }
-
-    /**
-     * Return this events title.
-     *
-     * @return string The title of this event
-     */
-    public function getTitle()
-    {
-        return $this->title;
-    }
-
-    /**
-     * Is this event an all-day event?
-     *
-     * Since there are no holidays lasting only a few hours, this is always
-     * true.
-     *
-     * @return boolean <code>true</code>
-     */
-    public function isAllDay()
-    {
-        return true;
-    }
-
-}
diff --git a/kronolith/lib/Driver/ical.php b/kronolith/lib/Driver/ical.php
deleted file mode 100644 (file)
index d633f28..0000000
+++ /dev/null
@@ -1,199 +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.
- *
- * @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
-     */
-    private $_cache = array();
-
-    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 $startInterval  Start of range date object.
-     * @param Horde_Date $endInterval    End of range data object.
-     * @param boolean $hasAlarm          Only return events with alarms?
-     *                                   Defaults to all events.
-     *
-     * @return array  Events in the given time range.
-     */
-    public function listEvents($startDate = null, $endDate = null,
-                               $hasAlarm = false)
-    {
-        $data = $this->_getRemoteCalendar();
-        if (is_a($data, 'PEAR_Error')) {
-            return $data;
-        }
-
-        $iCal = new Horde_iCalendar();
-        if (!$iCal->parsevCalendar($data)) {
-            return array();
-        }
-
-        $components = $iCal->getComponents();
-        $events = array();
-        $count = count($components);
-        $exceptions = array();
-        for ($i = 0; $i < $count; $i++) {
-            $component = $components[$i];
-            if ($component->getType() == 'vEvent') {
-                $event = new Kronolith_Event_ical($this);
-                $event->status = Kronolith::STATUS_FREE;
-                $event->fromiCalendar($component);
-                $event->remoteCal = $this->_params['url'];
-                $event->eventID = $i;
-
-                /* Catch RECURRENCE-ID attributes which mark single recurrence
-                 * instances. */
-                $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;
-                }
-
-                /* Ignore events out of the period. */
-                if (
-                    /* Starts after the period. */
-                    $event->start->compareDateTime($endDate) > 0 ||
-                    /* End before the period and doesn't recur. */
-                    (!$event->recurs() &&
-                     $event->end->compareDateTime($startDate) < 0) ||
-                    /* Recurs and ... */
-                    ($event->recurs() &&
-                      /* ... has a recurrence end before the period. */
-                      ($event->recurrence->hasRecurEnd() &&
-                       $event->recurrence->recurEnd->compareDateTime($startDate) < 0))) {
-                    continue;
-                }
-
-                $events[] = $event;
-            }
-        }
-
-        /* Loop through all explicitly defined recurrence intances and create
-         * exceptions for those in the event with the matchin recurrence. */
-        foreach ($events as $key => $event) {
-            if ($event->recurs() &&
-                isset($exceptions[$event->getUID()][$event->getSequence()])) {
-                $timestamp = $exceptions[$event->getUID()][$event->getSequence()];
-                $events[$key]->recurrence->addException(date('Y', $timestamp), date('m', $timestamp), date('d', $timestamp));
-            }
-        }
-
-        return $events;
-    }
-
-    public function getEvent($eventId = null)
-    {
-        $data = $this->_getRemoteCalendar();
-        if (is_a($data, 'PEAR_Error')) {
-            return $data;
-        }
-
-        $iCal = new Horde_iCalendar();
-        if (!$iCal->parsevCalendar($data)) {
-            return array();
-        }
-
-        $components = $iCal->getComponents();
-        if (isset($components[$eventId]) &&
-            $components[$eventId]->getType() == 'vEvent') {
-            $event = new Kronolith_Event_ical($this);
-            $event->status = Kronolith::STATUS_FREE;
-            $event->fromiCalendar($components[$eventId]);
-            $event->remoteCal = $this->_params['url'];
-            $event->eventID = $eventId;
-
-            return $event;
-        }
-
-        return false;
-    }
-
-    /**
-     * Fetches a remote calendar into the session and return the data.
-     *
-     * @return mixed  Either the calendar data, or an error on failure.
-     */
-    private function _getRemoteCalendar()
-    {
-        $url = trim($this->_params['url']);
-
-        /* Treat webcal:// URLs as http://. */
-        if (substr($url, 0, 9) == 'webcal://') {
-            $url = str_replace('webcal://', 'http://', $url);
-        }
-
-        if (empty($_SESSION['kronolith']['remote'][$url])) {
-            $options['method'] = 'GET';
-            $options['timeout'] = 5;
-            $options['allowRedirects'] = true;
-
-            if (isset($this->_params['proxy'])) {
-                $options = array_merge($options, $this->_params['proxy']);
-            }
-
-            $http = new HTTP_Request($url, $options);
-            if (!empty($this->_params['user'])) {
-                $http->setBasicAuth($this->_params['user'],
-                                    $this->_params['password']);
-            }
-            @$http->sendRequest();
-            if ($http->getResponseCode() != 200) {
-                Horde::logMessage(sprintf('Failed to retrieve remote calendar: url = "%s", status = %s',
-                                          $url, $http->getResponseCode()),
-                                  __FILE__, __LINE__, PEAR_LOG_ERR);
-                return PEAR::raiseError(sprintf(_("Could not open %s."), $url));
-            }
-            $_SESSION['kronolith']['remote'][$url] = $http->getResponseBody();
-
-            /* Log fetch at DEBUG level. */
-            Horde::logMessage(sprintf('Retrieved remote calendar for %s: url = "%s"',
-                                      Auth::getAuth(), $url),
-                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
-        }
-
-        return $_SESSION['kronolith']['remote'][$url];
-    }
-
-}
-
-class Kronolith_Event_ical extends Kronolith_Event
-{
-
-    public function fromDriver($vEvent)
-    {
-        $this->fromiCalendar($vEvent);
-        $this->initialized = true;
-        $this->stored = true;
-    }
-
-    public function toDriver()
-    {
-        return $this->toiCalendar();
-    }
-
-}
diff --git a/kronolith/lib/Driver/kolab.php b/kronolith/lib/Driver/kolab.php
deleted file mode 100644 (file)
index a5a7e7f..0000000
+++ /dev/null
@@ -1,734 +0,0 @@
-<?php
-
-require_once 'Horde/Kolab.php';
-require_once 'Horde/Identity.php';
-
-/**
- * Horde Kronolith driver for the Kolab IMAP Server.
- *
- * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @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/UID is key
-     *
-     * @var array
-     */
-    private $_events_cache;
-
-    /**
-     * Indicates if we have synchronized this folder
-     *
-     * @var boolean
-     */
-    private $_synchronized;
-
-    /**
-     * Shortcut to the imap connection
-     *
-     * @var Kolab_IMAP
-     */
-    private $_store;
-
-    /**
-     * Attempts to open a Kolab Groupware folder.
-     *
-     * @return boolean  True on success, PEAR_Error on failure.
-     */
-    public function initialize()
-    {
-        $this->_kolab = new Kolab();
-        $this->reset();
-        return true;
-    }
-
-    /**
-     * Change current calendar
-     */
-    public function open($calendar)
-    {
-        if ($this->_calendar != $calendar) {
-            $this->_calendar = $calendar;
-            $this->reset();
-        }
-
-        return true;
-    }
-
-    /**
-     * 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
-        $result = $this->_kolab->open($this->_calendar, 1);
-        if (is_a($result, 'PEAR_Error')) {
-            return $result;
-        }
-        $this->_store = $this->_kolab->_storage;
-
-        // build internal event cache
-        $this->_events_cache = array();
-        $events = $this->_store->getObjects();
-        foreach ($events as $event) {
-            $this->_events_cache[$event['uid']] = new Kronolith_Event_kolab($this, $event);
-        }
-
-        $this->_synchronized = true;
-    }
-
-    public function listAlarms($date, $fullevent = false)
-    {
-        $allevents = $this->listEvents($date, null, true);
-        $events = array();
-
-        foreach ($allevents as $eventId) {
-            $event = $this->getEvent($eventId);
-            if (is_a($event, 'PEAR_Error')) {
-                return $event;
-            }
-
-            if (!$event->recurs()) {
-                $start = new Horde_Date($event->start);
-                $start->min -= $event->getAlarm();
-                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->getAlarm();
-                    $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.
-     */
-    public function exists($uid, $calendar_id = null)
-    {
-        // Log error if someone uses this function in an unsupported way
-        if ($calendar_id != $this->_calendar) {
-            Horde::logMessage(sprintf("Kolab::exists called for calendar %s. Currently active is %s.", $calendar_id, $this->_calendar), __FILE__, __LINE__, PEAR_LOG_ERR);
-            return PEAR::raiseError(sprintf("Kolab::exists called for calendar %s. Currently active is %s.", $calendar_id, $this->_calendar));
-        }
-
-        $result = $this->synchronize();
-        if (is_a($result, 'PEAR_Error')) {
-            return $result;
-        }
-
-        if ($this->_store->objectUidExists($uid)) {
-            return $uid;
-        }
-
-        return false;
-    }
-
-    /**
-     * 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 $hasAlarm          Only return events with alarms?
-     *                                   Defaults to all events.
-     *
-     * @return array  Events in the given time range.
-     */
-    public function listEvents($startDate = null, $endDate = null, $hasAlarm = false)
-    {
-        $result = $this->synchronize();
-        if (is_a($result, 'PEAR_Error')) {
-            return $result;
-        }
-
-        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));
-        }
-
-        $ids = array();
-
-        foreach($this->_events_cache as $event) {
-            if ($hasAlarm && !$event->getAlarm()) {
-                continue;
-            }
-
-            $keep_event = false;
-            /* check if event period intersects with given period */
-            if (!(($endDate->compareDateTime($event->start) < 0) ||
-                  ($startDate->compareDateTime($event->end) > 0))) {
-                $keep_event = true;
-            }
-
-            /* do recurrence expansion if not keeping anyway */
-            if (!$keep_event && $event->recurs()) {
-                $next = $event->recurrence->nextRecurrence($startDate);
-                while ($next !== false &&
-                       $event->recurrence->hasException($next->year, $next->month, $next->mday)) {
-                    $next->mday++;
-                    $next = $event->recurrence->nextRecurrence($next);
-                }
-
-                if ($next !== false) {
-                    $duration = $next->timestamp() - $event->start->timestamp();
-                    $next_end = new Horde_Date($event->end->timestamp() + $duration);
-
-                    if ((!(($endDate->compareDateTime($next) < 0) ||
-                           ($startDate->compareDateTime($next_end) > 0)))) {
-                        $keep_event = true;
-                    }
-                }
-            }
-
-            if ($keep_event) {
-                $ids[$event->getUID()] = $event->getUID();
-            }
-        }
-
-        return $ids;
-    }
-
-    public function getEvent($eventId = null)
-    {
-        if (is_null($eventId)) {
-            return new Kronolith_Event_kolab($this);
-        }
-
-        $result = $this->synchronize();
-        if (is_a($result, 'PEAR_Error')) {
-            return $result;
-        }
-
-        if (array_key_exists($eventId, $this->_events_cache)) {
-            return $this->_events_cache[$eventId];
-        }
-
-        return PEAR::raiseError(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
-     */
-    public function getByUID($uid, $calendars = null, $getAll = false)
-    {
-        if (!is_array($calendars)) {
-            $calendars = array_keys(Kronolith::listCalendars(true, PERMS_READ));
-        }
-
-        foreach ($calendars as $calendar) {
-            $this->open($calendar);
-            $this->synchronize();
-
-            if (!array_key_exists($uid, $this->_events_cache)) {
-                continue;
-            }
-
-            // Ok, found event
-            $event = $this->_events_cache[$uid];
-
-            if ($getAll) {
-                $events = array();
-                $events[] = $event;
-                return $events;
-            } else {
-                return $event;
-            }
-        }
-
-        return PEAR::raiseError(sprintf(_("Event not found: %s"), $uid));
-    }
-
-    /**
-     * 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 mixed  UID on success, a PEAR error otherwise
-     */
-    public function saveEvent(&$event)
-    {
-        $result = $this->synchronize();
-        if (is_a($result, 'PEAR_Error')) {
-            return $result;
-        }
-
-        $uid = $event->getUID();
-        if ($uid == null) {
-            $event->setUID($this->_store->generateUID());
-        }
-
-        $attributes = $event->toDriver();
-
-        $edit = false;
-        $stored_uid = null;
-        if ($event->isStored() || $event->exists()) {
-            $stored_uid = $attributes['uid'];
-            $action = array('action' => 'modify');
-            $edit = true;
-        } else {
-            $action = array('action' => 'add');
-        }
-
-        $result = $this->_store->save($attributes, $stored_uid);
-        if (is_a($result, 'PEAR_Error')) {
-            return $result;
-        }
-
-        /* Deal with tags */
-        $tagger = Kronolith::getTagger();
-        if (!empty($edit)) {
-            $tagger->replaceTags($event->getUID(), $event->tags, 'event');
-        } else {
-            $tagger->tag($event->getUID(), $event->tags, 'event');
-        }
-
-        /* Notify about the changed event. */
-        $result = Kronolith::sendNotification($event, $edit ? 'edit' : 'add');
-        if (is_a($result, 'PEAR_Error')) {
-            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
-        }
-
-        /* Log the creation/modification of this item in the history log. */
-        $history = Horde_History::singleton();
-        $history->log('kronolith:' . $event->getCalendar() . ':' . $event->getUID(), $action, true);
-
-        // refresh IMAP cache
-        $this->synchronize(true);
-
-        if (is_callable('Kolab', 'triggerFreeBusyUpdate')) {
-            Kolab::triggerFreeBusyUpdate($this->_store->parseFolder($event->getCalendar()));
-        }
-
-        return $event->getUID();
-    }
-
-    /**
-     * Move an event to a new calendar.
-     *
-     * @param string $eventId      The event to move.
-     * @param string $newCalendar  The new calendar.
-     */
-    public function move($eventId, $newCalendar)
-    {
-        $event = $this->getEvent($eventId);
-
-        $result = $this->synchronize();
-        if (is_a($result, 'PEAR_Error')) {
-            return $result;
-        }
-
-        global $kronolith_shares;
-        $target = $kronolith_shares->getShare($newCalendar);
-        $folder = $target->get('folder');
-
-        $result = $this->_store->move($eventId, $folder);
-        if ($result) {
-            unset($this->_events_cache[$eventId]);
-        }
-
-        if (is_callable('Kolab', 'triggerFreeBusyUpdate')) {
-            Kolab::triggerFreeBusyUpdate($this->_store->parseFolder($this->_calendar));
-            Kolab::triggerFreeBusyUpdate($this->_store->parseFolder($newCalendar));
-        }
-
-        /* Log the moving of this item in the history log. */
-        $uid = $event->getUID();
-        $history = Horde_History::singleton();
-        $history->log('kronolith:' . $event->getCalendar() . ':' . $uid, array('action' => 'delete'), true);
-        $history->log('kronolith:' . $newCalendar . ':' . $uid, array('action' => 'add'), true);
-
-        return $result;
-    }
-
-    /**
-     * Delete a calendar and all its events.
-     *
-     * @param string $calendar  The name of the calendar to delete.
-     *
-     * @return mixed  True or a PEAR_Error on failure.
-     */
-    public function delete($calendar)
-    {
-        $this->open($calendar);
-        $result = $this->synchronize();
-        if (is_a($result, 'PEAR_Error')) {
-            return $result;
-        }
-
-        $result = $this->_store->deleteAll($calendar);
-        if (is_callable('Kolab', 'triggerFreeBusyUpdate')) {
-            Kolab::triggerFreeBusyUpdate($this->_store->parseFolder($calendar));
-        }
-        return true;
-    }
-
-    /**
-     * Rename a calendar.
-     *
-     * @param string $from  The current name of the calendar.
-     * @param string $to    The new name of the calendar.
-     *
-     * @return mixed  True or a PEAR_Error on failure.
-     */
-    public function rename($from, $to)
-    {
-        // FIXME: We can't do much here. We'd need to be called after
-        // renaming the share here. This needs to be checked again.
-        // kolab/issue2249 ([horde/kronolith] Kronolith is unable to
-        // trigger a free/busy update on a folder rename)
-        return true;
-    }
-
-    /**
-     * Delete an event.
-     *
-     * @param string $eventId  The ID of the event to delete.
-     *
-     * @return mixed  True or a PEAR_Error on failure.
-     */
-    public function deleteEvent($eventId, $silent = false)
-    {
-        $result = $this->synchronize();
-        if (is_a($result, 'PEAR_Error')) {
-            return $result;
-        }
-
-        if (!$this->_store->objectUidExists($eventId)) {
-            return PEAR::raiseError(sprintf(_("Event not found: %s"), $eventId));
-        }
-
-        $event = $this->getEvent($eventId);
-
-        if ($this->_store->delete($eventId)) {
-            // Notify about the deleted event.
-            if (!$silent) {
-                $result = Kronolith::sendNotification($event, 'delete');
-                if (is_a($result, 'PEAR_Error')) {
-                    Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
-                }
-            }
-
-            /* Log the deletion of this item in the history log. */
-            $history = Horde_History::singleton();
-            $history->log('kronolith:' . $event->getCalendar() . ':' . $event->getUID(), array('action' => 'delete'), true);
-
-            if (is_callable('Kolab', 'triggerFreeBusyUpdate')) {
-                Kolab::triggerFreeBusyUpdate($this->_store->parseFolder($event->getCalendar()));
-            }
-
-            unset($this->_events_cache[$eventId]);
-        } else {
-            return PEAR::raiseError(sprintf(_("Cannot delete event: %s"), $eventId));
-        }
-
-        return true;
-    }
-
-}
-
-/**
- * @package Kronolith
- */
-class Kronolith_Event_kolab extends Kronolith_Event
-{
-
-    public function fromDriver($event)
-    {
-        $this->eventID = $event['uid'];
-        $this->setUID($this->eventID);
-
-        if (isset($event['summary'])) {
-            $this->title = $event['summary'];
-        }
-        if (isset($event['body'])) {
-            $this->description = $event['body'];
-        }
-        if (isset($event['location'])) {
-            $this->location = $event['location'];
-        }
-
-        if (isset($event['sensitivity']) &&
-            ($event['sensitivity'] == 'private' || $event['sensitivity'] == 'confidential')) {
-            $this->setPrivate(true);
-        }
-
-        if (isset($event['organizer']['smtp-address'])) {
-            if (Kronolith::isUserEmail(Auth::getAuth(), $event['organizer']['smtp-address'])) {
-                $this->creatorID = Auth::getAuth();
-            } else {
-                $this->creatorID = $event['organizer']['smtp-address'];
-            }
-        }
-
-        if (isset($event['alarm'])) {
-            $this->alarm = $event['alarm'];
-        }
-
-        $this->start = new Horde_Date($event['start-date']);
-        $this->end = new Horde_Date($event['end-date']);
-        $this->durMin = ($this->end->timestamp() - $this->start->timestamp()) / 60;
-
-        if (isset($event['show-time-as'])) {
-            switch ($event['show-time-as']) {
-                case 'free':
-                    $this->status = Kronolith::STATUS_FREE;
-                    break;
-
-                case 'tentative':
-                    $this->status = Kronolith::STATUS_TENTATIVE;
-                    break;
-
-                case 'busy':
-                case 'outofoffice':
-                default:
-                    $this->status = Kronolith::STATUS_CONFIRMED;
-            }
-        } else {
-            $this->status = Kronolith::STATUS_CONFIRMED;
-        }
-
-        // Recurrence
-        if (isset($event['recurrence'])) {
-            $this->recurrence = new Horde_Date_Recurrence($this->start);
-            $this->recurrence->fromHash($event['recurrence']);
-        }
-
-        // Attendees
-        $attendee_count = 0;
-        foreach($event['attendee'] as $attendee) {
-            $name = $attendee['display-name'];
-            $email = $attendee['smtp-address'];
-
-            $role = $attendee['role'];
-            switch ($role) {
-            case 'optional':
-                $role = Kronolith::PART_OPTIONAL;
-                break;
-
-            case 'resource':
-                $role = Kronolith::PART_NONE;
-                break;
-
-            case 'required':
-            default:
-                $role = Kronolith::PART_REQUIRED;
-                break;
-            }
-
-            $status = $attendee['status'];
-            switch ($status) {
-            case 'accepted':
-                $status = Kronolith::RESPONSE_ACCEPTED;
-                break;
-
-            case 'declined':
-                $status = Kronolith::RESPONSE_DECLINED;
-                break;
-
-            case 'tentative':
-                $status = Kronolith::RESPONSE_TENTATIVE;
-                break;
-
-            case 'none':
-            default:
-                $status = Kronolith::RESPONSE_NONE;
-                break;
-            }
-
-            // Attendees without an email address get added as incremented number
-            if (empty($email)) {
-                $email = $attendee_count;
-                $attendee_count++;
-            }
-
-            $this->addAttendee($email, $role, $status, $name);
-        }
-
-        $this->initialized = true;
-        $this->stored = true;
-    }
-
-    public function toDriver()
-    {
-        $event = array();
-        $event['uid'] = $this->getUID();
-        $event['summary'] = $this->title;
-        $event['body']  = $this->description;
-        $event['location'] = $this->location;
-
-        if ($this->isPrivate()) {
-            $event['sensitivity'] = 'private';
-        } else {
-            $event['sensitivity'] = 'public';
-        }
-
-        // Only set organizer if this is a new event
-        if ($this->getID() == null) {
-            $organizer = array(
-                            'display-name' => Kronolith::getUserName($this->getCreatorId()),
-                            'smtp-address' => Kronolith::getUserEmail($this->getCreatorId())
-                         );
-            $event['organizer'] = $organizer;
-        }
-
-        if ($this->alarm != 0) {
-            $event['alarm'] = $this->alarm;
-        }
-
-        $event['start-date'] = $this->start->timestamp();
-        $event['end-date'] = $this->end->timestamp();
-        $event['_is_all_day'] = $this->isAllDay();
-
-        switch ($this->status) {
-        case Kronolith::STATUS_FREE:
-        case Kronolith::STATUS_CANCELLED:
-            $event['show-time-as'] = 'free';
-            break;
-
-        case Kronolith::STATUS_TENTATIVE:
-            $event['show-time-as'] = 'tentative';
-            break;
-
-        // No mapping for outofoffice
-        case Kronolith::STATUS_CONFIRMED:
-        default:
-            $event['show-time-as'] = 'busy';
-        }
-
-        // Recurrence
-        if ($this->recurs()) {
-            $event['recurrence'] = $this->recurrence->toHash();
-        } else {
-            $event['recurrence'] = array();
-        }
-
-
-        // Attendees
-        $event['attendee'] = array();
-        $attendees = $this->getAttendees();
-
-        foreach($attendees as $email => $attendee) {
-            $new_attendee = array();
-            $new_attendee['display-name'] = $attendee['name'];
-
-            // Attendee without an email address
-            if (is_int($email)) {
-                $new_attendee['smtp-address'] = '';
-            } else {
-                $new_attendee['smtp-address'] = $email;
-            }
-
-            switch ($attendee['attendance']) {
-            case Kronolith::PART_OPTIONAL:
-                $new_attendee['role'] = 'optional';
-                break;
-
-            case Kronolith::PART_NONE:
-                $new_attendee['role'] = 'resource';
-                break;
-
-            case Kronolith::PART_REQUIRED:
-            default:
-                $new_attendee['role'] = 'required';
-                break;
-            }
-
-            $new_attendee['request-response'] = '0';
-
-            switch ($attendee['response']) {
-            case Kronolith::RESPONSE_ACCEPTED:
-                $new_attendee['status'] = 'accepted';
-                break;
-
-            case Kronolith::RESPONSE_DECLINED:
-                $new_attendee['status'] = 'declined';
-                break;
-
-            case Kronolith::RESPONSE_TENTATIVE:
-                $new_attendee['status'] = 'tentative';
-                break;
-
-            case Kronolith::RESPONSE_NONE:
-            default:
-                $new_attendee['status'] = 'none';
-                break;
-            }
-
-            $event['attendee'][] = $new_attendee;
-        }
-
-        return $event;
-    }
-
-}
diff --git a/kronolith/lib/Driver/sql.php b/kronolith/lib/Driver/sql.php
deleted file mode 100644 (file)
index ab2ab36..0000000
+++ /dev/null
@@ -1,1030 +0,0 @@
-<?php
-/**
- * The Kronolith_Driver_sql:: class implements the Kronolith_Driver API for a
- * SQL backend.
- *
- * @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 DB
-     */
-    private $_db;
-
-    /**
-     * Handle for the current database connection, used for writing. Defaults
-     * to the same handle as $_db if a separate write database is not required.
-     *
-     * @var DB
-     */
-    private $_write_db;
-
-    /**
-     * Cache events as we fetch them to avoid fetching the same event from the
-     * DB twice.
-     *
-     * @var array
-     */
-    private $_cache = array();
-
-    public function listAlarms($date, $fullevent = false)
-    {
-        require_once 'Date/Calc.php';
-
-        $allevents = $this->listEvents($date, null, true);
-        if (is_a($allevents, 'PEAR_Error')) {
-            return $allevents;
-        }
-
-        $events = array();
-        foreach ($allevents as $eventId) {
-            $event = $this->getEvent($eventId);
-            if (is_a($event, 'PEAR_Error')) {
-                continue;
-            }
-
-            if (!$event->recurs()) {
-                $start = new Horde_Date($event->start);
-                $start->min -= $event->getAlarm();
-                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->getAlarm();
-                    $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 = $start;
-                            $event->end = $end;
-                            $events[] = $event;
-                        } else {
-                            $events[] = $eventId;
-                        }
-                    }
-                }
-            }
-        }
-
-        return is_array($events) ? $events : array();
-    }
-
-    public function search($query)
-    {
-        require_once 'Horde/SQL.php';
-
-        /* Build SQL conditions based on the query string. */
-        $cond = '((';
-        $values = array();
-
-        if (!empty($query->title)) {
-            $binds = Horde_SQL::buildClause($this->_db, 'event_title', 'LIKE', $this->convertToDriver($query->title), true);
-            if (is_array($binds)) {
-                $cond .= $binds[0] . ' AND ';
-                $values = array_merge($values, $binds[1]);
-            } else {
-                $cond .= $binds;
-            }
-        }
-        if (!empty($query->location)) {
-            $binds = Horde_SQL::buildClause($this->_db, 'event_location', 'LIKE', $this->convertToDriver($query->location), true);
-            if (is_array($binds)) {
-                $cond .= $binds[0] . ' AND ';
-                $values = array_merge($values, $binds[1]);
-            } else {
-                $cond .= $binds;
-            }
-        }
-        if (!empty($query->description)) {
-            $binds = Horde_SQL::buildClause($this->_db, 'event_description', 'LIKE', $this->convertToDriver($query->description), true);
-            if (is_array($binds)) {
-                $cond .= $binds[0] . ' AND ';
-                $values = array_merge($values, $binds[1]);
-            } else {
-                $cond .= $binds;
-            }
-        }
-        if (isset($query->status)) {
-            $binds = Horde_SQL::buildClause($this->_db, '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->creatorID)) {
-            $binds = Horde_SQL::buildClause($this->_db, 'event_creator_id', '=', $query->creatorID, 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($query->start,
-                                                 empty($query->end)
-                                                 ? new Horde_Date(array('mday' => 31, 'month' => 12, 'year' => 9999))
-                                                 : $query->end,
-                                                 $cond,
-                                                 $values);
-        if (is_a($eventIds, 'PEAR_Error')) {
-            return $eventIds;
-        }
-
-        $events = array();
-        foreach ($eventIds as $eventId) {
-            $event = $this->getEvent($eventId);
-            if (is_a($event, 'PEAR_Error')) {
-                return $event;
-            }
-            $events[] = $event;
-        }
-
-        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.
-     */
-    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;
-        }
-
-        /* Log the query at a DEBUG log level. */
-        Horde::logMessage(sprintf('Kronolith_Driver_sql::exists(): user = "%s"; query = "%s"',
-                                  Auth::getAuth(), $query),
-                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        $event = $this->_db->getRow($query, $values, DB_FETCHMODE_ASSOC);
-        if (is_a($event, 'PEAR_Error')) {
-            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
-            return $event;
-        }
-
-        if ($event) {
-            return $event['event_id'];
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * 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 $hasAlarm          Only return events with alarms?
-     *                                   Defaults to all events.
-     *
-     * @return array  Events in the given time range.
-     */
-    public function listEvents($startDate = null, $endDate = null,
-                               $hasAlarm = false)
-    {
-        if (empty($endDate)) {
-            $endInterval = new Horde_Date(array('mday' => 31, 'month' => 12,
-                                                'year' => 9999));
-        } else {
-            $endInterval = clone $endDate;
-            $endInterval->mday++;
-        }
-
-        $startInterval = null;
-        if (empty($startDate)) {
-            $startInterval = new Horde_Date(array('mday' => 1, 'month' => 1,
-                                                  'year' => 0000));
-        } else {
-            $startInterval = clone $startDate;
-        }
-
-        return $this->listEventsConditional($startInterval, $endInterval,
-                                            $hasAlarm ? 'event_alarm > ?' : '',
-                                            $hasAlarm ? array(0) : array());
-    }
-
-    /**
-     * 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.
-     */
-    public function listEventsConditional($startInterval, $endInterval,
-                                          $conditions = '', $vals = array())
-    {
-        $q = 'SELECT event_id, event_uid, event_description, event_location,' .
-            ' event_private, event_status, event_attendees,' .
-            ' event_title, event_recurcount,' .
-            ' 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' .
-            ' FROM ' . $this->_params['table'] .
-            ' WHERE calendar_id = ? AND ((';
-        $values = array($this->_calendar);
-
-        if ($conditions) {
-            $q .= $conditions . ')) AND ((';
-            $values = array_merge($values, $vals);
-        }
-
-        $etime = $endInterval->format('Y-m-d H:i:s');
-        $stime = null;
-        if (isset($startInterval)) {
-            $stime = $startInterval->format('Y-m-d H:i:s');
-            $q .= 'event_end > ? AND ';
-            $values[] = $stime;
-        }
-        $q .= 'event_start < ?) OR (';
-        $values[] = $etime;
-        if (isset($stime)) {
-            $q .= 'event_recurenddate >= ? AND ';
-            $values[] = $stime;
-        }
-        $q .= 'event_start <= ?' .
-            ' AND event_recurtype <> ?))';
-        array_push($values, $etime, Horde_Date_Recurrence::RECUR_NONE);
-
-        /* Log the query at a DEBUG log level. */
-        Horde::logMessage(sprintf('Kronolith_Driver_sql::listEventsConditional(): user = "%s"; query = "%s"; values = "%s"',
-                                  Auth::getAuth(), $q, implode(',', $values)),
-                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        /* Run the query. */
-        $qr = $this->_db->query($q, $values);
-        if (is_a($qr, 'PEAR_Error')) {
-            Horde::logMessage($qr, __FILE__, __LINE__, PEAR_LOG_ERR);
-            return $qr;
-        }
-
-        $events = array();
-        $row = $qr->fetchRow(DB_FETCHMODE_ASSOC);
-        while ($row && !is_a($row, 'PEAR_Error')) {
-            /* If the event did not have a UID before, we need to give
-             * it one. */
-            if (empty($row['event_uid'])) {
-                $row['event_uid'] = $this->generateUID();
-
-                /* 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']);
-
-                /* Log the query at a DEBUG log level. */
-                Horde::logMessage(sprintf('Kronolith_Driver_sql::listEventsConditional(): user = %s; query = "%s"; values = "%s"',
-                                          Auth::getAuth(), $query, implode(',', $values)),
-                                  __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-                $result = $this->_write_db->query($query, $values);
-                if (is_a($result, 'PEAR_Error')) {
-                    Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
-                }
-            }
-
-            /* 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 Kronolith_Event_sql($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 && $next->compareDate($endInterval) < 0) {
-                    $events[$row['event_uid']] = $row['event_id'];
-                }
-            }
-
-            $row = $qr->fetchRow(DB_FETCHMODE_ASSOC);
-        }
-
-        return $events;
-    }
-
-    public function getEvent($eventId = null)
-    {
-        if (is_null($eventId)) {
-            return new Kronolith_Event_sql($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_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' .
-            ' FROM ' . $this->_params['table'] . ' WHERE event_id = ? AND calendar_id = ?';
-        $values = array($eventId, $this->_calendar);
-
-        /* Log the query at a DEBUG log level. */
-        Horde::logMessage(sprintf('Kronolith_Driver_sql::getEvent(): user = "%s"; query = "%s"; values = "%s"',
-                                  Auth::getAuth(), $query, implode(',', $values)),
-                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        $event = $this->_db->getRow($query, $values, DB_FETCHMODE_ASSOC);
-        if (is_a($event, 'PEAR_Error')) {
-            Horde::logMessage($event, __FILE__, __LINE__, PEAR_LOG_ERR);
-            return $event;
-        }
-
-        if ($event) {
-            $this->_cache[$this->_calendar][$eventId] = new Kronolith_Event_sql($this, $event);
-            return $this->_cache[$this->_calendar][$eventId];
-        } else {
-            return PEAR::raiseError(_("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? If this is false,
-     * an error will be returned if more than one event is found.
-     *
-     * @return Kronolith_Event
-     */
-    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_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' .
-            ' FROM ' . $this->_params['table'] . ' WHERE event_uid = ?';
-        $values = array($uid);
-
-        /* Optionally filter by calendar */
-        if (!is_null($calendars)) {
-            if (!count($calendars)) {
-                return PEAR::raiseError(_("No calendars to search"));
-            }
-            $query .= ' AND calendar_id IN (?' . str_repeat(', ?', count($calendars) - 1) . ')';
-            $values = array_merge($values, $calendars);
-        }
-
-        /* Log the query at a DEBUG log level. */
-        Horde::logMessage(sprintf('Kronolith_Driver_sql::getByUID(): user = "%s"; query = "%s"; values = "%s"',
-                                  Auth::getAuth(), $query, implode(',', $values)),
-                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        $events = $this->_db->getAll($query, $values, DB_FETCHMODE_ASSOC);
-        if (is_a($events, 'PEAR_Error')) {
-            Horde::logMessage($events, __FILE__, __LINE__, PEAR_LOG_ERR);
-            return $events;
-        }
-        if (!count($events)) {
-            return PEAR::raiseError($uid . ' not found');
-        }
-
-        $eventArray = array();
-        foreach ($events as $event) {
-            $this->open($event['calendar_id']);
-            $this->_cache[$this->_calendar][$event['event_id']] = new Kronolith_Event_sql($this, $event);
-            $eventArray[] = $this->_cache[$this->_calendar][$event['event_id']];
-        }
-
-        if ($getAll) {
-            return $eventArray;
-        }
-
-        /* First try the user's own calendars. */
-        $ownerCalendars = Kronolith::listCalendars(true, PERMS_READ);
-        $event = null;
-        foreach ($eventArray as $ev) {
-            if (isset($ownerCalendars[$ev->getCalendar()])) {
-                $event = $ev;
-                break;
-            }
-        }
-
-        /* If not successful, try all calendars the user has access too. */
-        if (empty($event)) {
-            $readableCalendars = Kronolith::listCalendars(false, PERMS_READ);
-            foreach ($eventArray as $ev) {
-                if (isset($readableCalendars[$ev->getCalendar()])) {
-                    $event = $ev;
-                    break;
-                }
-            }
-        }
-
-        if (empty($event)) {
-            $event = $eventArray[0];
-        }
-
-        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.
-     */
-    public function saveEvent($event)
-    {
-        if ($event->isStored() || $event->exists()) {
-            $values = array();
-
-            $query = 'UPDATE ' . $this->_params['table'] . ' SET ';
-
-            foreach ($event->getProperties() as $key => $val) {
-                $query .= " $key = ?,";
-                $values[] = $val;
-            }
-            $query = substr($query, 0, -1);
-            $query .= ' WHERE event_id = ?';
-            $values[] = $event->getId();
-
-            /* Log the query at a DEBUG log level. */
-            Horde::logMessage(sprintf('Kronolith_Driver_sql::saveEvent(): user = "%s"; query = "%s"; values = "%s"',
-                                      Auth::getAuth(), $query, implode(',', $values)),
-                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-            $result = $this->_write_db->query($query, $values);
-            if (is_a($result, 'PEAR_Error')) {
-                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
-                return $result;
-            }
-
-            /* Log the modification of this item in the history log. */
-            if ($event->getUID()) {
-                $history = Horde_History::singleton();
-                $history->log('kronolith:' . $this->_calendar . ':' . $event->getUID(), array('action' => 'modify'), true);
-            }
-
-            /* Update tags */
-            $tagger = Kronolith::getTagger();
-            $tagger->replaceTags($event->getUID(), $event->tags, 'event');
-
-            /* Notify users about the changed event. */
-            $result = Kronolith::sendNotification($event, 'edit');
-            if (is_a($result, 'PEAR_Error')) {
-                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
-            }
-
-            return $event->getId();
-        } else {
-            if ($event->getId()) {
-                $id = $event->getId();
-            } else {
-                $id = hash('md5', uniqid(mt_rand(), true));
-                $event->setId($id);
-            }
-
-            if ($event->getUID()) {
-                $uid = $event->getUID();
-            } else {
-                $uid = $this->generateUID();
-                $event->setUID($uid);
-            }
-
-            $query = 'INSERT INTO ' . $this->_params['table'];
-            $cols_name = ' (event_id, event_uid,';
-            $cols_values = ' VALUES (?, ?,';
-            $values = array($id, $uid);
-
-            foreach ($event->getProperties() as $key => $val) {
-                $cols_name .= " $key,";
-                $cols_values .= ' ?,';
-                $values[] = $val;
-            }
-
-            $cols_name .= ' calendar_id)';
-            $cols_values .= ' ?)';
-            $values[] = $this->_calendar;
-
-            $query .= $cols_name . $cols_values;
-
-            /* Log the query at a DEBUG log level. */
-            Horde::logMessage(sprintf('Kronolith_Driver_sql::saveEvent(): user = "%s"; query = "%s"; values = "%s"',
-                                Auth::getAuth(), $query, implode(',', $values)),
-                                __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-            $result = $this->_write_db->query($query, $values);
-            if (is_a($result, 'PEAR_Error')) {
-                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
-                return $result;
-            }
-
-            /* Log the creation of this item in the history log. */
-            $history = Horde_History::singleton();
-            $history->log('kronolith:' . $this->_calendar . ':' . $uid, array('action' => 'add'), true);
-
-            /* Deal with any tags */
-            $tagger = Kronolith::getTagger();
-            $tagger->tag($event->getUID(), $event->tags, 'event');
-
-            /* Notify users about the new event. */
-            $result = Kronolith::sendNotification($event, 'add');
-            if (is_a($result, 'PEAR_Error')) {
-                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
-            }
-
-            return $id;
-        }
-    }
-
-    /**
-     * Move an event to a new calendar.
-     *
-     * @param string $eventId      The event to move.
-     * @param string $newCalendar  The new calendar.
-     */
-    public function move($eventId, $newCalendar)
-    {
-        /* Fetch the event for later use. */
-        $event = $this->getEvent($eventId);
-        if (is_a($event, 'PEAR_Error')) {
-            return $event;
-        }
-
-        $query = 'UPDATE ' . $this->_params['table'] . ' SET calendar_id = ? WHERE calendar_id = ? AND event_id = ?';
-        $values = array($newCalendar, $this->_calendar, $eventId);
-
-        /* Log the query at a DEBUG log level. */
-        Horde::logMessage(sprintf('Kronolith_Driver_sql::move(): %s; values = "%s"',
-                                  $query, implode(',', $values)),
-                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        /* Attempt the move query. */
-        $result = $this->_write_db->query($query, $values);
-        if (is_a($result, 'PEAR_Error')) {
-            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
-            return $result;
-        }
-
-        /* Log the moving of this item in the history log. */
-        $uid = $event->getUID();
-        if ($uid) {
-            $history = Horde_History::singleton();
-            $history->log('kronolith:' . $this->_calendar . ':' . $uid, array('action' => 'delete'), true);
-            $history->log('kronolith:' . $newCalendar . ':' . $uid, array('action' => 'add'), true);
-        }
-
-        return true;
-    }
-
-    /**
-     * Delete a calendar and all its events.
-     *
-     * @param string $calendar  The name of the calendar to delete.
-     *
-     * @return mixed  True or a PEAR_Error on failure.
-     */
-    public function delete($calendar)
-    {
-        $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE calendar_id = ?';
-        $values = array($calendar);
-
-        /* Log the query at a DEBUG log level. */
-        Horde::logMessage(sprintf('Kronolith_Driver_sql::delete(): user = "%s"; query = "%s"; values = "%s"',
-                                  Auth::getAuth(), $query, implode(',', $values)),
-                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        return $this->_write_db->query($query, $values);
-    }
-
-    /**
-     * Delete 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.
-     *
-     * @return mixed  True or a PEAR_Error on failure.
-     */
-    public function deleteEvent($eventId, $silent = false)
-    {
-        /* Fetch the event for later use. */
-        $event = $this->getEvent($eventId);
-        if (is_a($event, 'PEAR_Error')) {
-            return $event;
-        }
-
-        $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE event_id = ? AND calendar_id = ?';
-        $values = array($eventId, $this->_calendar);
-
-        /* Log the query at a DEBUG log level. */
-        Horde::logMessage(sprintf('Kronolith_Driver_sql::deleteEvent(): user = "%s"; query = "%s"; values = "%s"',
-                                  Auth::getAuth(), $query, implode(',', $values)),
-                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-        $result = $this->_write_db->query($query, $values);
-        if (is_a($result, 'PEAR_Error')) {
-            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
-            return $result;
-        }
-
-        /* Log the deletion of this item in the history log. */
-        if ($event->getUID()) {
-            $history = Horde_History::singleton();
-            $history->log('kronolith:' . $this->_calendar . ':' . $event->getUID(), array('action' => 'delete'), true);
-        }
-
-        /* Remove any pending alarms. */
-        if (@include_once 'Horde/Alarm.php') {
-            $alarm = Horde_Alarm::factory();
-            $alarm->delete($event->getUID());
-        }
-
-        /* Remove any tags */
-        $tagger = Kronolith::getTagger();
-        $tagger->replaceTags($event->getUID(), array(), 'event');
-
-        /* Notify about the deleted event. */
-        if (!$silent) {
-            $result = Kronolith::sendNotification($event, 'delete');
-            if (is_a($result, 'PEAR_Error')) {
-                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Attempts to open a connection to the SQL server.
-     *
-     * @return boolean True.
-     */
-    public function initialize()
-    {
-        Horde::assertDriverConfig($this->_params, 'calendar',
-            array('phptype'));
-
-        if (!isset($this->_params['database'])) {
-            $this->_params['database'] = '';
-        }
-        if (!isset($this->_params['username'])) {
-            $this->_params['username'] = '';
-        }
-        if (!isset($this->_params['hostspec'])) {
-            $this->_params['hostspec'] = '';
-        }
-        if (!isset($this->_params['table'])) {
-            $this->_params['table'] = 'kronolith_events';
-        }
-
-        /* Connect to the SQL server using the supplied parameters. */
-        $this->_write_db = DB::connect($this->_params,
-                                       array('persistent' => !empty($this->_params['persistent']),
-                                             'ssl' => !empty($this->_params['ssl'])));
-        if (is_a($this->_write_db, 'PEAR_Error')) {
-            return $this->_write_db;
-        }
-        $this->_initConn($this->_write_db);
-
-        /* Check if we need to set up the read DB connection
-         * seperately. */
-        if (!empty($this->_params['splitread'])) {
-            $params = array_merge($this->_params, $this->_params['read']);
-            $this->_db = DB::connect($params,
-                                     array('persistent' => !empty($params['persistent']),
-                                           'ssl' => !empty($params['ssl'])));
-            if (is_a($this->_db, 'PEAR_Error')) {
-                return $this->_db;
-            }
-            $this->_initConn($this->_db);
-        } else {
-            /* Default to the same DB handle for the writer too. */
-            $this->_db = $this->_write_db;
-        }
-
-        return true;
-    }
-
-    /**
-     */
-    private function _initConn(&$db)
-    {
-        // Set DB portability options.
-        switch ($db->phptype) {
-        case 'mssql':
-            $db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
-            break;
-        default:
-            $db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
-        }
-
-        /* Handle any database specific initialization code to run. */
-        switch ($db->dbsyntax) {
-        case 'oci8':
-            $query = "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'";
-
-            /* Log the query at a DEBUG log level. */
-            Horde::logMessage(sprintf('Kronolith_Driver_sql::_initConn(): user = "%s"; query = "%s"',
-                                      Auth::getAuth(), $query),
-                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-            $db->query($query);
-            break;
-
-        case 'pgsql':
-            $query = "SET datestyle TO 'iso'";
-
-            /* Log the query at a DEBUG log level. */
-            Horde::logMessage(sprintf('Kronolith_Driver_sql::_initConn(): user = "%s"; query = "%s"',
-                                      Auth::getAuth(), $query),
-                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
-            $db->query($query);
-            break;
-        }
-    }
-
-    /**
-     * 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 String::convertCharset($value, $this->_params['charset']);
-    }
-
-    /**
-     * 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 String::convertCharset($value, NLS::getCharset(), $this->_params['charset']);
-    }
-
-    /**
-     * Remove all events owned by the specified user in all calendars.
-     *
-     *
-     * @param string $user  The user name to delete events for.
-     *
-     * @param mixed  True | PEAR_Error
-     */
-    public function removeUserData($user)
-    {
-        if (!Auth::isAdmin()) {
-            return PEAR::raiseError(_("Permission Denied"));
-        }
-
-        $shares = $GLOBALS['kronolith_shares']->listShares($user, PERMS_EDIT);
-        if (is_a($shares, 'PEAR_Error')) {
-            return $shares;
-        }
-
-        foreach (array_keys($shares) as $calendar) {
-            $ids = Kronolith::listEventIds(null, null, $calendar);
-            if (is_a($ids, 'PEAR_Error')) {
-                return $ids;
-            }
-            $uids = array();
-            foreach ($ids as $cal) {
-                $uids = array_merge($uids, array_keys($cal));
-            }
-
-            foreach ($uids as $uid) {
-                $event = $this->getByUID($uid);
-                if (is_a($event, 'PEAR_Error')) {
-                    return $event;
-                }
-
-                $this->deleteEvent($event->getId());
-            }
-        }
-        return true;
-    }
-}
-
-/**
- * @package Kronolith
- */
-class Kronolith_Event_sql extends Kronolith_Event
-{
-    /**
-     * @var array
-     */
-    private $_properties = array();
-
-    public function fromDriver($SQLEvent)
-    {
-        $driver = $this->getDriver();
-
-        $this->allday = (bool)$SQLEvent['event_allday'];
-        if (!$this->allday && $driver->getParam('utc')) {
-            $tz_local = date_default_timezone_get();
-            $this->start = new Horde_Date($SQLEvent['event_start'], 'UTC');
-            $this->start->setTimezone($tz_local);
-            $this->end = new Horde_Date($SQLEvent['event_end'], 'UTC');
-            $this->end->setTimezone($tz_local);
-        } else {
-            $this->start = new Horde_Date($SQLEvent['event_start']);
-            $this->end = new Horde_Date($SQLEvent['event_end']);
-        }
-
-        $this->durMin = ($this->end->timestamp() - $this->start->timestamp()) / 60;
-
-        $this->title = $driver->convertFromDriver($SQLEvent['event_title']);
-        $this->eventID = $SQLEvent['event_id'];
-        $this->setUID($SQLEvent['event_uid']);
-        $this->creatorID = $SQLEvent['event_creator_id'];
-
-        if (!empty($SQLEvent['event_recurtype'])) {
-            $this->recurrence = new Horde_Date_Recurrence($this->start);
-            $this->recurrence->setRecurType((int)$SQLEvent['event_recurtype']);
-            $this->recurrence->setRecurInterval((int)$SQLEvent['event_recurinterval']);
-            if (isset($SQLEvent['event_recurenddate'])) {
-                if ($driver->getParam('utc')) {
-                    $recur_end = new Horde_Date($SQLEvent['event_recurenddate'], 'UTC');
-                    if ($recur_end->min == 0) {
-                        /* Old recurrence end date format. */
-                        $recur_end = new Horde_Date($SQLEvent['event_recurenddate']);
-                        $recur_end->hour = 23;
-                        $recur_end->min = 59;
-                        $recur_end->sec = 59;
-                    } else {
-                        $recur_end->setTimezone(date_default_timezone_get());
-                    }
-                } else {
-                    $recur_end = new Horde_Date($SQLEvent['event_recurenddate']);
-                    $recur_end->hour = 23;
-                    $recur_end->min = 59;
-                    $recur_end->sec = 59;
-                }
-                $this->recurrence->setRecurEnd($recur_end);
-            }
-            if (isset($SQLEvent['event_recurcount'])) {
-                $this->recurrence->setRecurCount((int)$SQLEvent['event_recurcount']);
-            }
-            if (isset($SQLEvent['event_recurdays'])) {
-                $this->recurrence->recurData = (int)$SQLEvent['event_recurdays'];
-            }
-            if (!empty($SQLEvent['event_exceptions'])) {
-                $this->recurrence->exceptions = explode(',', $SQLEvent['event_exceptions']);
-            }
-        }
-
-        if (isset($SQLEvent['event_location'])) {
-            $this->location = $driver->convertFromDriver($SQLEvent['event_location']);
-        }
-        if (isset($SQLEvent['event_private'])) {
-            $this->private = (bool)($SQLEvent['event_private']);
-        }
-        if (isset($SQLEvent['event_status'])) {
-            $this->status = $SQLEvent['event_status'];
-        }
-        if (isset($SQLEvent['event_attendees'])) {
-            $this->attendees = array_change_key_case($driver->convertFromDriver(unserialize($SQLEvent['event_attendees'])));
-        }
-        if (isset($SQLEvent['event_description'])) {
-            $this->description = $driver->convertFromDriver($SQLEvent['event_description']);
-        }
-        if (isset($SQLEvent['event_alarm'])) {
-            $this->alarm = (int)$SQLEvent['event_alarm'];
-        }
-        if (isset($SQLEvent['event_alarm_methods'])) {
-            $this->methods = $driver->convertFromDriver(unserialize($SQLEvent['event_alarm_methods']));
-        }
-        $this->initialized = true;
-        $this->stored = true;
-    }
-
-    public function toDriver()
-    {
-        $driver = $this->getDriver();
-
-        /* Basic fields. */
-        $this->_properties['event_creator_id'] = $driver->convertToDriver($this->getCreatorId());
-        $this->_properties['event_title'] = $driver->convertToDriver($this->title);
-        $this->_properties['event_description'] = $driver->convertToDriver($this->getDescription());
-        $this->_properties['event_location'] = $driver->convertToDriver($this->getLocation());
-        $this->_properties['event_private'] = (int)$this->isPrivate();
-        $this->_properties['event_status'] = $this->getStatus();
-        $this->_properties['event_attendees'] = serialize($driver->convertToDriver($this->getAttendees()));
-        $this->_properties['event_modified'] = $_SERVER['REQUEST_TIME'];
-
-        if ($this->isAllDay()) {
-            $this->_properties['event_start'] = $this->start->strftime('%Y-%m-%d %H:%M:%S');
-            $this->_properties['event_end'] = $this->end->strftime('%Y-%m-%d %H:%M:%S');
-            $this->_properties['event_allday'] = 1;
-        } else {
-            if ($driver->getParam('utc')) {
-                $start = clone $this->start;
-                $end = clone $this->end;
-                $start->setTimezone('UTC');
-                $end->setTimezone('UTC');
-            } else {
-                $start = $this->start;
-                $end = $this->end;
-            }
-            $this->_properties['event_start'] = $start->strftime('%Y-%m-%d %H:%M:%S');
-            $this->_properties['event_end'] = $end->strftime('%Y-%m-%d %H:%M:%S');
-            $this->_properties['event_allday'] = 0;
-        }
-
-        /* Alarm. */
-        $this->_properties['event_alarm'] = (int)$this->getAlarm();
-
-        /* Alarm Notification Methods. */
-        $this->_properties['event_alarm_methods'] = serialize($driver->convertToDriver($this->methods));
-
-        /* Recurrence. */
-        if (!$this->recurs()) {
-            $this->_properties['event_recurtype'] = 0;
-        } else {
-            $recur = $this->recurrence->getRecurType();
-            if ($this->recurrence->hasRecurEnd()) {
-                if ($driver->getParam('utc')) {
-                    $recur_end = clone $this->recurrence->recurEnd;
-                    $recur_end->setTimezone('UTC');
-                } else {
-                    $recur_end = $this->recurrence->recurEnd;
-                }
-            } else {
-                $recur_end = new Horde_Date(array('year' => 9999, 'month' => 12, 'mday' => 31, 'hour' => 23, 'min' => 59, 'sec' => 59));
-            }
-
-            $this->_properties['event_recurtype'] = $recur;
-            $this->_properties['event_recurinterval'] = $this->recurrence->getRecurInterval();
-            $this->_properties['event_recurenddate'] = $recur_end->format('Y-m-d H:i:s');
-            $this->_properties['event_recurcount'] = $this->recurrence->getRecurCount();
-
-            switch ($recur) {
-            case Horde_Date_Recurrence::RECUR_WEEKLY:
-                $this->_properties['event_recurdays'] = $this->recurrence->getRecurOnDays();
-                break;
-            }
-            $this->_properties['event_exceptions'] = implode(',', $this->recurrence->getExceptions());
-        }
-    }
-
-    public function getProperties()
-    {
-        return $this->_properties;
-    }
-
-}
diff --git a/kronolith/lib/Event.php b/kronolith/lib/Event.php
new file mode 100644 (file)
index 0000000..5b8e162
--- /dev/null
@@ -0,0 +1,2208 @@
+<?php
+/**
+ * Kronolith_Event defines a generic API for events.
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Kronolith
+ */
+class Kronolith_Event
+{
+    /**
+     * Flag that is set to true if this event has data from either a storage
+     * backend or a form or other import method.
+     *
+     * @var boolean
+     */
+    public $initialized = false;
+
+    /**
+     * Flag that is set to true if this event exists in a storage driver.
+     *
+     * @var boolean
+     */
+    public $stored = false;
+
+    /**
+     * The driver unique identifier for this event.
+     *
+     * @var string
+     */
+    public $eventID = null;
+
+    /**
+     * The UID for this event.
+     *
+     * @var string
+     */
+    protected $_uid = null;
+
+    /**
+     * The iCalendar SEQUENCE for this event.
+     *
+     * @var integer
+     */
+    protected $_sequence = null;
+
+    /**
+     * The user id of the creator of the event.
+     *
+     * @var string
+     */
+    public $creatorID = null;
+
+    /**
+     * The title of this event.
+     *
+     * @var string
+     */
+    public $title = '';
+
+    /**
+     * The location this event occurs at.
+     *
+     * @var string
+     */
+    public $location = '';
+
+    /**
+     * The status of this event.
+     *
+     * @var integer
+     */
+    public $status = Kronolith::STATUS_CONFIRMED;
+
+    /**
+     * The description for this event
+     *
+     * @var string
+     */
+    public $description = '';
+
+    /**
+     * Remote description of this event (URL).
+     *
+     * @var string
+     */
+    public $remoteUrl = '';
+
+    /**
+     * Remote calendar name.
+     *
+     * @var string
+     */
+    public $remoteCal = '';
+
+    /**
+     * Whether the event is private.
+     *
+     * @var boolean
+     */
+    public $private = false;
+
+    /**
+     * This tag's events.
+     *
+     * @var mixed  Array of tags or comma delimited string.
+     */
+    public $tags = array();
+
+    /**
+     * All the attendees of this event.
+     *
+     * This is an associative array where the keys are the email addresses
+     * of the attendees, and the values are also associative arrays with
+     * keys 'attendance' and 'response' pointing to the attendees' attendance
+     * and response values, respectively.
+     *
+     * @var array
+     */
+    public $attendees = array();
+
+    /**
+     * The start time of the event.
+     *
+     * @var Horde_Date
+     */
+    public $start;
+
+    /**
+     * The end time of the event.
+     *
+     * @var Horde_Date
+     */
+    public $end;
+
+    /**
+     * The duration of this event in minutes
+     *
+     * @var integer
+     */
+    public $durMin = 0;
+
+    /**
+     * Whether this is an all-day event.
+     *
+     * @var boolean
+     */
+    public $allday = false;
+
+    /**
+     * Number of minutes before the event starts to trigger an alarm.
+     *
+     * @var integer
+     */
+    public $alarm = 0;
+
+    /**
+     * The particular alarm methods overridden for this event.
+     *
+     * @var array
+     */
+    public $methods;
+
+    /**
+     * The identifier of the calender this event exists on.
+     *
+     * @var string
+     */
+    protected $_calendar;
+
+    /**
+     * The HTML background color to be used for this event.
+     *
+     * @var string
+     */
+    protected $_backgroundColor;
+
+    /**
+     * The HTML foreground color to be used for this event.
+     *
+     * @var string
+     */
+    protected $_foregroundColor;
+
+    /**
+     * The VarRenderer class to use for printing select elements.
+     *
+     * @var Horde_UI_VarRenderer
+     */
+    private $_varRenderer;
+
+    /**
+     * The Horde_Date_Recurrence class for this event.
+     *
+     * @var Horde_Date_Recurrence
+     */
+    public $recurrence;
+
+    /**
+     * Constructor.
+     *
+     * @param Kronolith_Driver $driver  The backend driver that this event is
+     *                                  stored in.
+     * @param mixed $eventObject        Backend specific event object
+     *                                  that this will represent.
+     */
+    public function __construct(&$driver, $eventObject = null)
+    {
+        static $alarm;
+
+        /* Set default alarm value. */
+        if (!isset($alarm) && isset($GLOBALS['prefs'])) {
+            $alarm = $GLOBALS['prefs']->getValue('default_alarm');
+        }
+        $this->alarm = $alarm;
+
+        $this->_calendar = $driver->getCalendar();
+        if (!empty($this->_calendar)) {
+            $share = $GLOBALS['all_calendars'][$this->_calendar];
+            $this->_backgroundColor = $share->get('color');
+            if (empty($this->_backgroundColor)) {
+                $this->_backgroundColor = '#dddddd';
+            }
+            $this->_foregroundColor = Horde_Image::brightness($this->_backgroundColor) < 128 ? '#f6f6f6' : '#000';
+        }
+
+        if ($eventObject !== null) {
+            $this->fromDriver($eventObject);
+            $tagger = Kronolith::getTagger();
+            $this->tags = $tagger->getTags($this->getUID(), 'event');
+        }
+    }
+
+    /**
+     * Returns a reference to a driver that's valid for this event.
+     *
+     * @return Kronolith_Driver  A driver that this event can use to save
+     *                           itself, etc.
+     */
+    public function getDriver()
+    {
+        global $kronolith_driver;
+        if ($kronolith_driver->getCalendar() != $this->_calendar) {
+            $kronolith_driver->open($this->_calendar);
+        }
+
+        return $kronolith_driver;
+    }
+
+    /**
+     * Returns the share this event belongs to.
+     *
+     * @return Horde_Share  This event's share.
+     */
+    public function getShare()
+    {
+        if (isset($GLOBALS['all_calendars'][$this->getCalendar()])) {
+            $share = $GLOBALS['all_calendars'][$this->getCalendar()];
+        } else {
+            $share = PEAR::raiseError('Share not found');
+        }
+        return $share;
+    }
+
+    /**
+     * Encapsulates permissions checking.
+     *
+     * @param integer $permission  The permission to check for.
+     * @param string $user         The user to check permissions for.
+     *
+     * @return boolean
+     */
+    public function hasPermission($permission, $user = null)
+    {
+        if ($user === null) {
+            $user = Auth::getAuth();
+        }
+
+        if ($this->remoteCal) {
+            switch ($permission) {
+            case PERMS_SHOW:
+            case PERMS_READ:
+            case PERMS_EDIT:
+                return true;
+
+            default:
+                return false;
+            }
+        }
+
+        return (!is_a($share = &$this->getShare(), 'PEAR_Error') &&
+                $share->hasPermission($user, $permission, $this->getCreatorId()));
+    }
+
+    /**
+     * Saves changes to this event.
+     *
+     * @return mixed  True or a PEAR_Error on failure.
+     */
+    public function save()
+    {
+        if (!$this->isInitialized()) {
+            return PEAR::raiseError('Event not yet initialized');
+        }
+
+        $this->toDriver();
+        $driver = &$this->getDriver();
+        $result = $driver->saveEvent($this);
+        if (!is_a($result, 'PEAR_Error') &&
+            !empty($GLOBALS['conf']['alarms']['driver'])) {
+            $alarm = $this->toAlarm(new Horde_Date($_SERVER['REQUEST_TIME']));
+            if ($alarm) {
+                $alarm['start'] = new Horde_Date($alarm['start']);
+                $alarm['end'] = new Horde_Date($alarm['end']);
+                $horde_alarm = Horde_Alarm::factory();
+                $horde_alarm->set($alarm);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Exports this event in iCalendar format.
+     *
+     * @param Horde_iCalendar &$calendar  A Horde_iCalendar object that acts as
+     *                                    a container.
+     *
+     * @return Horde_iCalendar_vevent  The vEvent object for this event.
+     */
+    public function toiCalendar(&$calendar)
+    {
+        $vEvent = &Horde_iCalendar::newComponent('vevent', $calendar);
+        $v1 = $calendar->getAttribute('VERSION') == '1.0';
+
+        if ($this->isAllDay()) {
+            $vEvent->setAttribute('DTSTART', $this->start, array('VALUE' => 'DATE'));
+            $vEvent->setAttribute('DTEND', $this->end, array('VALUE' => 'DATE'));
+        } else {
+            $vEvent->setAttribute('DTSTART', $this->start);
+            $vEvent->setAttribute('DTEND', $this->end);
+        }
+
+        $vEvent->setAttribute('DTSTAMP', $_SERVER['REQUEST_TIME']);
+        $vEvent->setAttribute('UID', $this->_uid);
+
+        /* Get the event's history. */
+        $history = &Horde_History::singleton();
+        $created = $modified = null;
+        $log = $history->getHistory('kronolith:' . $this->_calendar . ':' . $this->_uid);
+        if ($log && !is_a($log, 'PEAR_Error')) {
+            foreach ($log->getData() as $entry) {
+                switch ($entry['action']) {
+                case 'add':
+                    $created = $entry['ts'];
+                    break;
+
+                case 'modify':
+                    $modified = $entry['ts'];
+                    break;
+                }
+            }
+        }
+        if (!empty($created)) {
+            $vEvent->setAttribute($v1 ? 'DCREATED' : 'CREATED', $created);
+            if (empty($modified)) {
+                $modified = $created;
+            }
+        }
+        if (!empty($modified)) {
+            $vEvent->setAttribute('LAST-MODIFIED', $modified);
+        }
+
+        $vEvent->setAttribute('SUMMARY', $v1 ? $this->getTitle() : String::convertCharset($this->getTitle(), NLS::getCharset(), 'utf-8'));
+        $name = Kronolith::getUserName($this->getCreatorId());
+        if (!$v1) {
+            $name = String::convertCharset($name, NLS::getCharset(), 'utf-8');
+        }
+        $vEvent->setAttribute('ORGANIZER',
+                              'mailto:' . Kronolith::getUserEmail($this->getCreatorId()),
+                              array('CN' => $name));
+        if (!$this->isPrivate() || $this->getCreatorId() == Auth::getAuth()) {
+            if (!empty($this->description)) {
+                $vEvent->setAttribute('DESCRIPTION', $v1 ? $this->description : String::convertCharset($this->description, NLS::getCharset(), 'utf-8'));
+            }
+
+            // Tags
+            $tags = $this->tags;
+            if (is_array($tags)) {
+                $tags = implode(', ', $tags);
+            }
+            if (!empty($tags)) {
+                $vEvent->setAttribute('CATEGORIES', $v1 ? $tags : String::convertCharset($tags, NLS::getCharset(), 'utf-8'));
+            }
+
+            // Location
+            if (!empty($this->location)) {
+                $vEvent->setAttribute('LOCATION', $v1 ? $this->location : String::convertCharset($this->location, NLS::getCharset(), 'utf-8'));
+            }
+        }
+        $vEvent->setAttribute('CLASS', $this->isPrivate() ? 'PRIVATE' : 'PUBLIC');
+
+        // Status.
+        switch ($this->getStatus()) {
+        case Kronolith::STATUS_FREE:
+            // This is not an official iCalendar value, but we need it for
+            // synchronization.
+            $vEvent->setAttribute('STATUS', 'FREE');
+            $vEvent->setAttribute('TRANSP', $v1 ? 1 : 'TRANSPARENT');
+            break;
+        case Kronolith::STATUS_TENTATIVE:
+            $vEvent->setAttribute('STATUS', 'TENTATIVE');
+            $vEvent->setAttribute('TRANSP', $v1 ? 0 : 'OPAQUE');
+            break;
+        case Kronolith::STATUS_CONFIRMED:
+            $vEvent->setAttribute('STATUS', 'CONFIRMED');
+            $vEvent->setAttribute('TRANSP', $v1 ? 0 : 'OPAQUE');
+            break;
+        case Kronolith::STATUS_CANCELLED:
+            if ($v1) {
+                $vEvent->setAttribute('STATUS', 'DECLINED');
+                $vEvent->setAttribute('TRANSP', 1);
+            } else {
+                $vEvent->setAttribute('STATUS', 'CANCELLED');
+                $vEvent->setAttribute('TRANSP', 'TRANSPARENT');
+            }
+            break;
+        }
+
+        // Attendees.
+        foreach ($this->getAttendees() as $email => $status) {
+            $params = array();
+            switch ($status['attendance']) {
+            case Kronolith::PART_REQUIRED:
+                if ($v1) {
+                    $params['EXPECT'] = 'REQUIRE';
+                } else {
+                    $params['ROLE'] = 'REQ-PARTICIPANT';
+                }
+                break;
+
+            case Kronolith::PART_OPTIONAL:
+                if ($v1) {
+                    $params['EXPECT'] = 'REQUEST';
+                } else {
+                    $params['ROLE'] = 'OPT-PARTICIPANT';
+                }
+                break;
+
+            case Kronolith::PART_NONE:
+                if ($v1) {
+                    $params['EXPECT'] = 'FYI';
+                } else {
+                    $params['ROLE'] = 'NON-PARTICIPANT';
+                }
+                break;
+            }
+
+            switch ($status['response']) {
+            case Kronolith::RESPONSE_NONE:
+                if ($v1) {
+                    $params['STATUS'] = 'NEEDS ACTION';
+                    $params['RSVP'] = 'YES';
+                } else {
+                    $params['PARTSTAT'] = 'NEEDS-ACTION';
+                    $params['RSVP'] = 'TRUE';
+                }
+                break;
+
+            case Kronolith::RESPONSE_ACCEPTED:
+                if ($v1) {
+                    $params['STATUS'] = 'ACCEPTED';
+                } else {
+                    $params['PARTSTAT'] = 'ACCEPTED';
+                }
+                break;
+
+            case Kronolith::RESPONSE_DECLINED:
+                if ($v1) {
+                    $params['STATUS'] = 'DECLINED';
+                } else {
+                    $params['PARTSTAT'] = 'DECLINED';
+                }
+                break;
+
+            case Kronolith::RESPONSE_TENTATIVE:
+                if ($v1) {
+                    $params['STATUS'] = 'TENTATIVE';
+                } else {
+                    $params['PARTSTAT'] = 'TENTATIVE';
+                }
+                break;
+            }
+
+            if (strpos($email, '@') === false) {
+                $email = '';
+            }
+            if ($v1) {
+                if (!empty($status['name'])) {
+                    if (!empty($email)) {
+                        $email = ' <' . $email . '>';
+                    }
+                    $email = $status['name'] . $email;
+                    $email = Horde_Mime_Address::trimAddress($email);
+                }
+            } else {
+                if (!empty($status['name'])) {
+                    $params['CN'] = String::convertCharset($status['name'], NLS::getCharset(), 'utf-8');
+                }
+                if (!empty($email)) {
+                    $email = 'mailto:' . $email;
+                }
+            }
+
+            $vEvent->setAttribute('ATTENDEE', $email, $params);
+        }
+
+        // Alarms.
+        if (!empty($this->alarm)) {
+            if ($v1) {
+                $alarm = new Horde_Date($this->start);
+                $alarm->min -= $this->alarm;
+                $vEvent->setAttribute('AALARM', $alarm);
+            } else {
+                $vAlarm = &Horde_iCalendar::newComponent('valarm', $vEvent);
+                $vAlarm->setAttribute('ACTION', 'DISPLAY');
+                $vAlarm->setAttribute('TRIGGER;VALUE=DURATION', '-PT' . $this->alarm . 'M');
+                $vEvent->addComponent($vAlarm);
+            }
+        }
+
+        // Recurrence.
+        if ($this->recurs()) {
+            if ($v1) {
+                $rrule = $this->recurrence->toRRule10($calendar);
+            } else {
+                $rrule = $this->recurrence->toRRule20($calendar);
+            }
+            if (!empty($rrule)) {
+                $vEvent->setAttribute('RRULE', $rrule);
+            }
+
+            // Exceptions.
+            $exceptions = $this->recurrence->getExceptions();
+            foreach ($exceptions as $exception) {
+                if (!empty($exception)) {
+                    list($year, $month, $mday) = sscanf($exception, '%04d%02d%02d');
+                    $exdate = new Horde_Date(array(
+                        'year' => $year,
+                        'month' => $month,
+                        'mday' => $mday,
+                        'hour' => $this->start->hour,
+                        'min' => $this->start->min,
+                        'sec' => $this->start->sec,
+                    ));
+                    $vEvent->setAttribute('EXDATE', array($exdate));
+                }
+            }
+        }
+
+        return $vEvent;
+    }
+
+    /**
+     * Updates the properties of this event from a Horde_iCalendar_vevent
+     * object.
+     *
+     * @param Horde_iCalendar_vevent $vEvent  The iCalendar data to update
+     *                                        from.
+     */
+    public function fromiCalendar($vEvent)
+    {
+        // Unique ID.
+        $uid = $vEvent->getAttribute('UID');
+        if (!empty($uid) && !is_a($uid, 'PEAR_Error')) {
+            $this->setUID($uid);
+        }
+
+        // Sequence.
+        $seq = $vEvent->getAttribute('SEQUENCE');
+        if (is_int($seq)) {
+            $this->_sequence = $seq;
+        }
+
+        // Title, tags and description.
+        $title = $vEvent->getAttribute('SUMMARY');
+        if (!is_array($title) && !is_a($title, 'PEAR_Error')) {
+            $this->setTitle($title);
+        }
+
+        // Tags
+        $categories = $vEvent->getAttributeValues('CATEGORIES');
+        if (!is_a($categories, 'PEAR_Error')) {
+            $this->tags = $categories;
+        }
+
+        // Description
+        $desc = $vEvent->getAttribute('DESCRIPTION');
+        if (!is_array($desc) && !is_a($desc, 'PEAR_Error')) {
+            $this->setDescription($desc);
+        }
+
+        // Remote Url
+        $url = $vEvent->getAttribute('URL');
+        if (!is_array($url) && !is_a($url, 'PEAR_Error')) {
+            $this->remoteUrl = $url;
+        }
+
+        // Location
+        $location = $vEvent->getAttribute('LOCATION');
+        if (!is_array($location) && !is_a($location, 'PEAR_Error')) {
+            $this->setLocation($location);
+        }
+
+        // Class
+        $class = $vEvent->getAttribute('CLASS');
+        if (!is_array($class) && !is_a($class, 'PEAR_Error')) {
+            $class = String::upper($class);
+            if ($class == 'PRIVATE' || $class == 'CONFIDENTIAL') {
+                $this->setPrivate(true);
+            } else {
+                $this->setPrivate(false);
+            }
+        }
+
+        // Status.
+        $status = $vEvent->getAttribute('STATUS');
+        if (!is_array($status) && !is_a($status, 'PEAR_Error')) {
+            $status = String::upper($status);
+            if ($status == 'DECLINED') {
+                $status = 'CANCELLED';
+            }
+            if (defined('Kronolith::STATUS_' . $status)) {
+                $this->setStatus(constant('Kronolith::STATUS_' . $status));
+            }
+        }
+
+        // Start and end date.
+        $start = $vEvent->getAttribute('DTSTART');
+        if (!is_a($start, 'PEAR_Error')) {
+            if (!is_array($start)) {
+                // Date-Time field
+                $this->start = new Horde_Date($start);
+            } else {
+                // Date field
+                $this->start = new Horde_Date(
+                    array('year'  => (int)$start['year'],
+                          'month' => (int)$start['month'],
+                          'mday'  => (int)$start['mday']));
+            }
+        }
+        $end = $vEvent->getAttribute('DTEND');
+        if (!is_a($end, 'PEAR_Error')) {
+            if (!is_array($end)) {
+                // Date-Time field
+                $this->end = new Horde_Date($end);
+                // All day events are transferred by many device as
+                // DSTART: YYYYMMDDT000000 DTEND: YYYYMMDDT2359(59|00)
+                // Convert accordingly
+                if (is_object($this->start) && $this->start->hour == 0 &&
+                    $this->start->min == 0 && $this->start->sec == 0 &&
+                    $this->end->hour == 23 && $this->end->min == 59) {
+                    $this->end = new Horde_Date(
+                        array('year'  => (int)$this->end->year,
+                              'month' => (int)$this->end->month,
+                              'mday'  => (int)$this->end->mday + 1));
+                }
+            } elseif (is_array($end) && !is_a($end, 'PEAR_Error')) {
+                // Date field
+                $this->end = new Horde_Date(
+                    array('year'  => (int)$end['year'],
+                          'month' => (int)$end['month'],
+                          'mday'  => (int)$end['mday']));
+            }
+        } else {
+            $duration = $vEvent->getAttribute('DURATION');
+            if (!is_array($duration) && !is_a($duration, 'PEAR_Error')) {
+                $this->end = new Horde_Date($this->start);
+                $this->end->sec += $duration;
+            } else {
+                // End date equal to start date as per RFC 2445.
+                $this->end = new Horde_Date($this->start);
+                if (is_array($start)) {
+                    // Date field
+                    $this->end->mday++;
+                }
+            }
+        }
+
+        // vCalendar 1.0 alarms
+        $alarm = $vEvent->getAttribute('AALARM');
+        if (!is_array($alarm) &&
+            !is_a($alarm, 'PEAR_Error') &&
+            intval($alarm)) {
+            $this->alarm = intval(($this->start->timestamp() - $alarm) / 60);
+        }
+
+        // @TODO: vCalendar 2.0 alarms
+
+        // Attendance.
+        // Importing attendance may result in confusion: editing an imported
+        // copy of an event can cause invitation updates to be sent from
+        // people other than the original organizer. So we don't import by
+        // default. However to allow updates by SyncML replication, the custom
+        // X-ATTENDEE attribute is used which has the same syntax as
+        // ATTENDEE.
+        $attendee = $vEvent->getAttribute('X-ATTENDEE');
+        if (!is_a($attendee, 'PEAR_Error')) {
+
+            if (!is_array($attendee)) {
+                $attendee = array($attendee);
+            }
+            $params = $vEvent->getAttribute('X-ATTENDEE', true);
+            if (!is_array($params)) {
+                $params = array($params);
+            }
+            for ($i = 0; $i < count($attendee); ++$i) {
+                $attendee[$i] = str_replace(array('MAILTO:', 'mailto:'), '',
+                                            $attendee[$i]);
+                $email = Horde_Mime_Address::bareAddress($attendee[$i]);
+                // Default according to rfc2445:
+                $attendance = Kronolith::PART_REQUIRED;
+                // vCalendar 2.0 style:
+                if (!empty($params[$i]['ROLE'])) {
+                    switch($params[$i]['ROLE']) {
+                    case 'OPT-PARTICIPANT':
+                        $attendance = Kronolith::PART_OPTIONAL;
+                        break;
+
+                    case 'NON-PARTICIPANT':
+                        $attendance = Kronolith::PART_NONE;
+                        break;
+                    }
+                }
+                // vCalendar 1.0 style;
+                if (!empty($params[$i]['EXPECT'])) {
+                    switch($params[$i]['EXPECT']) {
+                    case 'REQUEST':
+                        $attendance = Kronolith::PART_OPTIONAL;
+                        break;
+
+                    case 'FYI':
+                        $attendance = Kronolith::PART_NONE;
+                        break;
+                    }
+                }
+                $response = Kronolith::RESPONSE_NONE;
+                if (empty($params[$i]['PARTSTAT']) &&
+                    !empty($params[$i]['STATUS'])) {
+                    $params[$i]['PARTSTAT']  = $params[$i]['STATUS'];
+                }
+
+                if (!empty($params[$i]['PARTSTAT'])) {
+                    switch($params[$i]['PARTSTAT']) {
+                    case 'ACCEPTED':
+                        $response = Kronolith::RESPONSE_ACCEPTED;
+                        break;
+
+                    case 'DECLINED':
+                        $response = Kronolith::RESPONSE_DECLINED;
+                        break;
+
+                    case 'TENTATIVE':
+                        $response = Kronolith::RESPONSE_TENTATIVE;
+                        break;
+                    }
+                }
+                $name = isset($params[$i]['CN']) ? $params[$i]['CN'] : null;
+
+                $this->addAttendee($email, $attendance, $response, $name);
+            }
+        }
+
+        // Recurrence.
+        $rrule = $vEvent->getAttribute('RRULE');
+        if (!is_array($rrule) && !is_a($rrule, 'PEAR_Error')) {
+            $this->recurrence = new Horde_Date_Recurrence($this->start);
+            if (strpos($rrule, '=') !== false) {
+                $this->recurrence->fromRRule20($rrule);
+            } else {
+                $this->recurrence->fromRRule10($rrule);
+            }
+
+            // Exceptions.
+            $exdates = $vEvent->getAttributeValues('EXDATE');
+            if (is_array($exdates)) {
+                foreach ($exdates as $exdate) {
+                    if (is_array($exdate)) {
+                        $this->recurrence->addException((int)$exdate['year'],
+                                                        (int)$exdate['month'],
+                                                        (int)$exdate['mday']);
+                    }
+                }
+            }
+        }
+
+        $this->initialized = true;
+    }
+
+    /**
+     * Imports the values for this event from an array of values.
+     *
+     * @param array $hash  Array containing all the values.
+     */
+    public function fromHash($hash)
+    {
+        // See if it's a new event.
+        if ($this->getId() === null) {
+            $this->setCreatorId(Auth::getAuth());
+        }
+        if (!empty($hash['title'])) {
+            $this->setTitle($hash['title']);
+        } else {
+            return PEAR::raiseError(_("Events must have a title."));
+        }
+        if (!empty($hash['description'])) {
+            $this->setDescription($hash['description']);
+        }
+        if (!empty($hash['location'])) {
+            $this->setLocation($hash['location']);
+        }
+        if (!empty($hash['start_date'])) {
+            $date = explode('-', $hash['start_date']);
+            if (empty($hash['start_time'])) {
+                $time = array(0, 0, 0);
+            } else {
+                $time = explode(':', $hash['start_time']);
+                if (count($time) == 2) {
+                    $time[2] = 0;
+                }
+            }
+            if (count($time) == 3 && count($date) == 3) {
+                $this->start = new Horde_Date(array('year' => $date[0],
+                                                    'month' => $date[1],
+                                                    'mday' => $date[2],
+                                                    'hour' => $time[0],
+                                                    'min' => $time[1],
+                                                    'sec' => $time[2]));
+            }
+        } else {
+            return PEAR::raiseError(_("Events must have a start date."));
+        }
+        if (empty($hash['duration'])) {
+            if (empty($hash['end_date'])) {
+                $hash['end_date'] = $hash['start_date'];
+            }
+            if (empty($hash['end_time'])) {
+                $hash['end_time'] = $hash['start_time'];
+            }
+        } else {
+            $weeks = str_replace('W', '', $hash['duration'][1]);
+            $days = str_replace('D', '', $hash['duration'][2]);
+            $hours = str_replace('H', '', $hash['duration'][4]);
+            $minutes = isset($hash['duration'][5]) ? str_replace('M', '', $hash['duration'][5]) : 0;
+            $seconds = isset($hash['duration'][6]) ? str_replace('S', '', $hash['duration'][6]) : 0;
+            $hash['duration'] = ($weeks * 60 * 60 * 24 * 7) + ($days * 60 * 60 * 24) + ($hours * 60 * 60) + ($minutes * 60) + $seconds;
+            $this->end = new Horde_Date($this->start);
+            $this->end->sec += $hash['duration'];
+        }
+        if (!empty($hash['end_date'])) {
+            $date = explode('-', $hash['end_date']);
+            if (empty($hash['end_time'])) {
+                $time = array(0, 0, 0);
+            } else {
+                $time = explode(':', $hash['end_time']);
+                if (count($time) == 2) {
+                    $time[2] = 0;
+                }
+            }
+            if (count($time) == 3 && count($date) == 3) {
+                $this->end = new Horde_Date(array('year' => $date[0],
+                                                  'month' => $date[1],
+                                                  'mday' => $date[2],
+                                                  'hour' => $time[0],
+                                                  'min' => $time[1],
+                                                  'sec' => $time[2]));
+            }
+        }
+        if (!empty($hash['alarm'])) {
+            $this->setAlarm($hash['alarm']);
+        } elseif (!empty($hash['alarm_date']) &&
+                  !empty($hash['alarm_time'])) {
+            $date = explode('-', $hash['alarm_date']);
+            $time = explode(':', $hash['alarm_time']);
+            if (count($time) == 2) {
+                $time[2] = 0;
+            }
+            if (count($time) == 3 && count($date) == 3) {
+                $alarm = new Horde_Date(array('hour'  => $time[0],
+                                              'min'   => $time[1],
+                                              'sec'   => $time[2],
+                                              'month' => $date[1],
+                                              'mday'  => $date[2],
+                                              'year'  => $date[0]));
+                $this->setAlarm(($this->start->timestamp() - $alarm->timestamp()) / 60);
+            }
+        }
+        if (!empty($hash['recur_type'])) {
+            $this->recurrence = new Horde_Date_Recurrence($this->start);
+            $this->recurrence->setRecurType($hash['recur_type']);
+            if (!empty($hash['recur_end_date'])) {
+                $date = explode('-', $hash['recur_end_date']);
+                $this->recurrence->setRecurEnd(new Horde_Date(array('year' => $date[0], 'month' => $date[1], 'mday' => $date[2])));
+            }
+            if (!empty($hash['recur_interval'])) {
+                $this->recurrence->setRecurInterval($hash['recur_interval']);
+            }
+            if (!empty($hash['recur_data'])) {
+                $this->recurrence->setRecurOnDay($hash['recur_data']);
+            }
+        }
+
+        $this->initialized = true;
+    }
+
+    /**
+     * Returns an alarm hash of this event suitable for Horde_Alarm.
+     *
+     * @param Horde_Date $time  Time of alarm.
+     * @param string $user      The user to return alarms for.
+     * @param Prefs $prefs      A Prefs instance.
+     *
+     * @return array  Alarm hash or null.
+     */
+    public function toAlarm($time, $user = null, $prefs = null)
+    {
+        if (!$this->getAlarm()) {
+            return;
+        }
+
+        if ($this->recurs()) {
+            $eventDate = $this->recurrence->nextRecurrence($time);
+            if ($eventDate && $this->recurrence->hasException($eventDate->year, $eventDate->month, $eventDate->mday)) {
+                return;
+            }
+        }
+
+        if (empty($user)) {
+            $user = Auth::getAuth();
+        }
+        if (empty($prefs)) {
+            $prefs = $GLOBALS['prefs'];
+        }
+
+        $methods = !empty($this->methods) ? $this->methods : @unserialize($prefs->getValue('event_alarms'));
+        $start = clone $this->start;
+        $start->min -= $this->getAlarm();
+        if (isset($methods['notify'])) {
+            $methods['notify']['show'] = array(
+                '__app' => $GLOBALS['registry']->getApp(),
+                'event' => $this->getId(),
+                'calendar' => $this->getCalendar());
+            if (!empty($methods['notify']['sound'])) {
+                if ($methods['notify']['sound'] == 'on') {
+                    // Handle boolean sound preferences.
+                    $methods['notify']['sound'] = $GLOBALS['registry']->get('themesuri') . '/sounds/theetone.wav';
+                } else {
+                    // Else we know we have a sound name that can be
+                    // served from Horde.
+                    $methods['notify']['sound'] = $GLOBALS['registry']->get('themesuri', 'horde') . '/sounds/' . $methods['notify']['sound'];
+                }
+            }
+        }
+        if (isset($methods['popup'])) {
+            $methods['popup']['message'] = $this->getTitle($user);
+            $description = $this->getDescription();
+            if (!empty($description)) {
+                $methods['popup']['message'] .= "\n\n" . $description;
+            }
+        }
+        if (isset($methods['mail'])) {
+            $methods['mail']['body'] = sprintf(
+                _("We would like to remind you of this upcoming event.\n\n%s\n\nLocation: %s\n\nDate: %s\nTime: %s\n\n%s"),
+                $this->getTitle($user),
+                $this->location,
+                $this->start->strftime($prefs->getValue('date_format')),
+                $this->start->format($prefs->getValue('twentyFour') ? 'H:i' : 'h:ia'),
+                $this->getDescription());
+        }
+
+        return array(
+            'id' => $this->getUID(),
+            'user' => $user,
+            'start' => $start->timestamp(),
+            'end' => $this->end->timestamp(),
+            'methods' => array_keys($methods),
+            'params' => $methods,
+            'title' => $this->getTitle($user),
+            'text' => $this->getDescription());
+    }
+
+    /**
+     * Returns a simple object suitable for json transport representing this
+     * event.
+     *
+     * @return object  A simple object.
+     */
+    public function toJSON()
+    {
+        $json = new stdClass;
+        $json->t = $this->getTitle();
+        $json->c = $this->getCalendar();
+        $json->bg = $this->_backgroundColor;
+        $json->fg = $this->_foregroundColor;
+        return $json;
+    }
+
+    /**
+     * TODO
+     */
+    public function isInitialized()
+    {
+        return $this->initialized;
+    }
+
+    /**
+     * TODO
+     */
+    public function isStored()
+    {
+        return $this->stored;
+    }
+
+    /**
+     * Checks if the current event is already present in the calendar.
+     *
+     * Does the check based on the uid.
+     *
+     * @return boolean  True if event exists, false otherwise.
+     */
+    public function exists()
+    {
+        if (!isset($this->_uid) || !isset($this->_calendar)) {
+            return false;
+        }
+
+        $eventID = $GLOBALS['kronolith_driver']->exists($this->_uid, $this->_calendar);
+        if (is_a($eventID, 'PEAR_Error') || !$eventID) {
+            return false;
+        } else {
+            $this->eventID = $eventID;
+            return true;
+        }
+    }
+
+    public function getDuration()
+    {
+        static $duration = null;
+        if (isset($duration)) {
+            return $duration;
+        }
+
+        if ($this->start && $this->end) {
+            $dur_day_match = Date_Calc::dateDiff($this->start->mday,
+                                                 $this->start->month,
+                                                 $this->start->year,
+                                                 $this->end->mday,
+                                                 $this->end->month,
+                                                 $this->end->year);
+            $dur_hour_match = $this->end->hour - $this->start->hour;
+            $dur_min_match = $this->end->min - $this->start->min;
+            while ($dur_min_match < 0) {
+                $dur_min_match += 60;
+                --$dur_hour_match;
+            }
+            while ($dur_hour_match < 0) {
+                $dur_hour_match += 24;
+                --$dur_day_match;
+            }
+            if ($dur_hour_match == 0 && $dur_min_match == 0 &&
+                $this->end->mday - $this->start->mday == 1) {
+                $dur_day_match = 1;
+                $dur_hour_match = 0;
+                $dur_min_match = 0;
+                $whole_day_match = true;
+            } else {
+                $whole_day_match = false;
+            }
+        } else {
+            $dur_day_match = 0;
+            $dur_hour_match = 1;
+            $dur_min_match = 0;
+            $whole_day_match = false;
+        }
+
+        $duration = new stdClass;
+        $duration->day = $dur_day_match;
+        $duration->hour = $dur_hour_match;
+        $duration->min = $dur_min_match;
+        $duration->wholeDay = $whole_day_match;
+
+        return $duration;
+    }
+
+    /**
+     * Returns whether this event is a recurring event.
+     *
+     * @return boolean  True if this is a recurring event.
+     */
+    public function recurs()
+    {
+        return isset($this->recurrence) &&
+            !$this->recurrence->hasRecurType(Horde_Date_Recurrence::RECUR_NONE);
+    }
+
+    /**
+     * Returns a description of this event's recurring type.
+     *
+     * @return string  Human readable recurring type.
+     */
+    public function getRecurName()
+    {
+        return $this->recurs()
+            ? $this->recurrence->getRecurName()
+            : _("No recurrence");
+    }
+
+    /**
+     * Returns a correcty formatted exception date for recurring events and a
+     * link to delete this exception.
+     *
+     * @param string $date  Exception in the format Ymd.
+     *
+     * @return string  The formatted date and delete link.
+     */
+    public function exceptionLink($date)
+    {
+        if (!preg_match('/(\d{4})(\d{2})(\d{2})/', $date, $match)) {
+            return '';
+        }
+        $horde_date = new Horde_Date(array('year' => $match[1],
+                                           'month' => $match[2],
+                                           'mday' => $match[3]));
+        $formatted = $horde_date->strftime($GLOBALS['prefs']->getValue('date_format'));
+        return $formatted
+            . Horde::link(Util::addParameter(Horde::applicationUrl('edit.php'), array('calendar' => $this->getCalendar(), 'eventID' => $this->eventID, 'del_exception' => $date, 'url' => Util::getFormData('url'))), sprintf(_("Delete exception on %s"), $formatted))
+            . Horde::img('delete-small.png', _("Delete"), '', $GLOBALS['registry']->getImageDir('horde'))
+            . '</a>';
+    }
+
+    /**
+     * Returns a list of exception dates for recurring events including links
+     * to delete them.
+     *
+     * @return string  List of exception dates and delete links.
+     */
+    public function exceptionsList()
+    {
+        return implode(', ', array_map(array($this, 'exceptionLink'), $this->recurrence->getExceptions()));
+    }
+
+    public function getCalendar()
+    {
+        return $this->_calendar;
+    }
+
+    public function setCalendar($calendar)
+    {
+        $this->_calendar = $calendar;
+    }
+
+    public function isRemote()
+    {
+        return (bool)$this->remoteCal;
+    }
+
+    /**
+     * Returns the locally unique identifier for this event.
+     *
+     * @return string  The local identifier for this event.
+     */
+    public function getId()
+    {
+        return $this->eventID;
+    }
+
+    /**
+     * Sets the locally unique identifier for this event.
+     *
+     * @param string $eventId  The local identifier for this event.
+     */
+    public function setId($eventId)
+    {
+        if (substr($eventId, 0, 10) == 'kronolith:') {
+            $eventId = substr($eventId, 10);
+        }
+        $this->eventID = $eventId;
+    }
+
+    /**
+     * Returns the global UID for this event.
+     *
+     * @return string  The global UID for this event.
+     */
+    public function getUID()
+    {
+        return $this->_uid;
+    }
+
+    /**
+     * Sets the global UID for this event.
+     *
+     * @param string $uid  The global UID for this event.
+     */
+    public function setUID($uid)
+    {
+        $this->_uid = $uid;
+    }
+
+    /**
+     * Returns the iCalendar SEQUENCE for this event.
+     *
+     * @return integer  The sequence for this event.
+     */
+    public function getSequence()
+    {
+        return $this->_sequence;
+    }
+
+    /**
+     * Returns the id of the user who created the event.
+     *
+     * @return string  The creator id
+     */
+    public function getCreatorId()
+    {
+        return !empty($this->creatorID) ? $this->creatorID : Auth::getAuth();
+    }
+
+    /**
+     * Sets the id of the creator of the event.
+     *
+     * @param string $creatorID  The user id for the user who created the event
+     */
+    public function setCreatorId($creatorID)
+    {
+        $this->creatorID = $creatorID;
+    }
+
+    /**
+     * Returns the title of this event.
+     *
+     * @param string $user  The current user.
+     *
+     * @return string  The title of this event.
+     */
+    public function getTitle($user = null)
+    {
+        if (isset($this->external) ||
+            isset($this->contactID) ||
+            $this->remoteCal) {
+            return !empty($this->title) ? $this->title : _("[Unnamed event]");
+        }
+
+        if (!$this->isInitialized()) {
+            return '';
+        }
+
+        if ($user === null) {
+            $user = Auth::getAuth();
+        }
+
+        $twentyFour = $GLOBALS['prefs']->getValue('twentyFour');
+        $start = $this->start->format($twentyFour ? 'G:i' : 'g:ia');
+        $end = $this->end->format($twentyFour ? 'G:i' : 'g:ia');
+
+        // We explicitly allow admin access here for the alarms notifications.
+        if (!Auth::isAdmin() && $this->isPrivate() &&
+            $this->getCreatorId() != $user) {
+            return sprintf(_("Private Event from %s to %s"), $start, $end);
+        } elseif (Auth::isAdmin() || $this->hasPermission(PERMS_READ, $user)) {
+            return strlen($this->title) ? $this->title : _("[Unnamed event]");
+        } else {
+            return sprintf(_("Event from %s to %s"), $start, $end);
+        }
+    }
+
+    /**
+     * Sets the title of this event.
+     *
+     * @param string  The new title for this event.
+     */
+    public function setTitle($title)
+    {
+        $this->title = $title;
+    }
+
+    /**
+     * Returns the description of this event.
+     *
+     * @return string  The description of this event.
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
+    /**
+     * Sets the description of this event.
+     *
+     * @param string $description  The new description for this event.
+     */
+    public function setDescription($description)
+    {
+        $this->description = $description;
+    }
+
+    /**
+     * Returns the location this event occurs at.
+     *
+     * @return string  The location of this event.
+     */
+    public function getLocation()
+    {
+        return $this->location;
+    }
+
+    /**
+     * Sets the location this event occurs at.
+     *
+     * @param string $location  The new location for this event.
+     */
+    public function setLocation($location)
+    {
+        $this->location = $location;
+    }
+
+    /**
+     * Returns whether this event is private.
+     *
+     * @return boolean  Whether this even is private.
+     */
+    public function isPrivate()
+    {
+        return $this->private;
+    }
+
+    /**
+     * Sets the private flag of this event.
+     *
+     * @param boolean $private  Whether this event should be marked private.
+     */
+    public function setPrivate($private)
+    {
+        $this->private = !empty($private);
+    }
+
+    /**
+     * Returns the event status.
+     *
+     * @return integer  The status of this event.
+     */
+    public function getStatus()
+    {
+        return $this->status;
+    }
+
+    /**
+     * Checks whether the events status is the same as the specified value.
+     *
+     * @param integer $status  The status value to check against.
+     *
+     * @return boolean  True if the events status is the same as $status.
+     */
+    public function hasStatus($status)
+    {
+        return ($status == $this->status);
+    }
+
+    /**
+     * Sets the status of this event.
+     *
+     * @param integer $status  The new event status.
+     */
+    public function setStatus($status)
+    {
+        $this->status = $status;
+    }
+
+    /**
+     * Returns the entire attendees array.
+     *
+     * @return array  A copy of the attendees array.
+     */
+    public function getAttendees()
+    {
+        return $this->attendees;
+    }
+
+    /**
+     * Checks to see whether the specified attendee is associated with the
+     * current event.
+     *
+     * @param string $email  The email address of the attendee.
+     *
+     * @return boolean  True if the specified attendee is present for this
+     *                  event.
+     */
+    public function hasAttendee($email)
+    {
+        $email = String::lower($email);
+        return isset($this->attendees[$email]);
+    }
+
+    /**
+     * Sets the entire attendee array.
+     *
+     * @param array $attendees  The new attendees array. This should be of the
+     *                          correct format to avoid driver problems.
+     */
+    public function setAttendees($attendees)
+    {
+        $this->attendees = array_change_key_case($attendees);
+    }
+
+    /**
+     * Adds a new attendee to the current event.
+     *
+     * This will overwrite an existing attendee if one exists with the same
+     * email address.
+     *
+     * @param string $email        The email address of the attendee.
+     * @param integer $attendance  The attendance code of the attendee.
+     * @param integer $response    The response code of the attendee.
+     * @param string $name         The name of the attendee.
+     */
+    public function addAttendee($email, $attendance, $response, $name = null)
+    {
+        $email = String::lower($email);
+        if ($attendance == Kronolith::PART_IGNORE) {
+            if (isset($this->attendees[$email])) {
+                $attendance = $this->attendees[$email]['attendance'];
+            } else {
+                $attendance = Kronolith::PART_REQUIRED;
+            }
+        }
+        if (empty($name) && isset($this->attendees[$email]) &&
+            !empty($this->attendees[$email]['name'])) {
+            $name = $this->attendees[$email]['name'];
+        }
+
+        $this->attendees[$email] = array(
+            'attendance' => $attendance,
+            'response' => $response,
+            'name' => $name
+        );
+    }
+
+    /**
+     * Removes the specified attendee from the current event.
+     *
+     * @param string $email  The email address of the attendee.
+     */
+    public function removeAttendee($email)
+    {
+        $email = String::lower($email);
+        if (isset($this->attendees[$email])) {
+            unset($this->attendees[$email]);
+        }
+    }
+
+    public function isAllDay()
+    {
+        return $this->allday ||
+            ($this->start->hour == 0 && $this->start->min == 0 && $this->start->sec == 0 &&
+             (($this->end->hour == 0 && $this->end->min == 0 && $this->end->sec == 0) ||
+              ($this->end->hour == 23 && $this->end->min == 59)) &&
+             ($this->end->mday > $this->start->mday ||
+              $this->end->month > $this->start->month ||
+              $this->end->year > $this->start->year));
+    }
+
+    public function getAlarm()
+    {
+        return $this->alarm;
+    }
+
+    public function setAlarm($alarm)
+    {
+        $this->alarm = $alarm;
+    }
+
+    public function readForm()
+    {
+        global $prefs, $cManager;
+
+        // Event owner.
+        $targetcalendar = Util::getFormData('targetcalendar');
+        if (strpos($targetcalendar, ':')) {
+            list(, $creator) = explode(':', $targetcalendar, 2);
+        } else {
+            $creator = isset($this->eventID) ? $this->getCreatorId() : Auth::getAuth();
+        }
+        $this->setCreatorId($creator);
+
+        // Basic fields.
+        $this->setTitle(Util::getFormData('title', $this->title));
+        $this->setDescription(Util::getFormData('description', $this->description));
+        $this->setLocation(Util::getFormData('location', $this->location));
+        $this->setPrivate(Util::getFormData('private'));
+
+        // Status.
+        $this->setStatus(Util::getFormData('status', $this->status));
+
+        // Attendees.
+        if (isset($_SESSION['kronolith']['attendees']) && is_array($_SESSION['kronolith']['attendees'])) {
+            $this->setAttendees($_SESSION['kronolith']['attendees']);
+        }
+
+        // Event start.
+        $start = Util::getFormData('start');
+        $start_year = $start['year'];
+        $start_month = $start['month'];
+        $start_day = $start['day'];
+        $start_hour = Util::getFormData('start_hour');
+        $start_min = Util::getFormData('start_min');
+        $am_pm = Util::getFormData('am_pm');
+
+        if (!$prefs->getValue('twentyFour')) {
+            if ($am_pm == 'PM') {
+                if ($start_hour != 12) {
+                    $start_hour += 12;
+                }
+            } elseif ($start_hour == 12) {
+                $start_hour = 0;
+            }
+        }
+
+        if (Util::getFormData('end_or_dur') == 1) {
+            if (Util::getFormData('whole_day') == 1) {
+                $start_hour = 0;
+                $start_min = 0;
+                $dur_day = 0;
+                $dur_hour = 24;
+                $dur_min = 0;
+            } else {
+                $dur_day = (int)Util::getFormData('dur_day');
+                $dur_hour = (int)Util::getFormData('dur_hour');
+                $dur_min = (int)Util::getFormData('dur_min');
+            }
+        }
+
+        $this->start = new Horde_Date(array('hour' => $start_hour,
+                                            'min' => $start_min,
+                                            'month' => $start_month,
+                                            'mday' => $start_day,
+                                            'year' => $start_year));
+
+        if (Util::getFormData('end_or_dur') == 1) {
+            // Event duration.
+            $this->end = new Horde_Date(array('hour' => $start_hour + $dur_hour,
+                                              'min' => $start_min + $dur_min,
+                                              'month' => $start_month,
+                                              'mday' => $start_day + $dur_day,
+                                              'year' => $start_year));
+        } else {
+            // Event end.
+            $end = Util::getFormData('end');
+            $end_year = $end['year'];
+            $end_month = $end['month'];
+            $end_day = $end['day'];
+            $end_hour = Util::getFormData('end_hour');
+            $end_min = Util::getFormData('end_min');
+            $end_am_pm = Util::getFormData('end_am_pm');
+
+            if (!$prefs->getValue('twentyFour')) {
+                if ($end_am_pm == 'PM') {
+                    if ($end_hour != 12) {
+                        $end_hour += 12;
+                    }
+                } elseif ($end_hour == 12) {
+                    $end_hour = 0;
+                }
+            }
+
+            $this->end = new Horde_Date(array('hour' => $end_hour,
+                                              'min' => $end_min,
+                                              'month' => $end_month,
+                                              'mday' => $end_day,
+                                              'year' => $end_year));
+            if ($this->end->compareDateTime($this->start) < 0) {
+                $this->end = new Horde_Date($this->start);
+            }
+        }
+
+        // Alarm.
+        if (Util::getFormData('alarm') == 1) {
+            $this->setAlarm(Util::getFormData('alarm_value') * Util::getFormData('alarm_unit'));
+            // Notification.
+            if (Util::getFormData('alarm_change_method')) {
+                $types = Util::getFormData('event_alarms');
+                if (!empty($types)) {
+                    $methods = array();
+                    foreach ($types as $type) {
+                        $methods[$type] = array();
+                        switch ($type){
+                        case 'notify':
+                            $methods[$type]['sound'] = Util::getFormData('event_alarms_sound');
+                            break;
+                        case 'mail':
+                            $methods[$type]['email'] = Util::getFormData('event_alarms_email');
+                            break;
+                        case 'popup':
+                            break;
+                        }
+                    }
+                    $this->methods = $methods;
+                }
+            } else {
+                $this->methods = array();
+            }
+        } else {
+            $this->setAlarm(0);
+            $this->methods = array();
+        }
+
+        // Recurrence.
+        $recur = Util::getFormData('recur');
+        if ($recur !== null && $recur !== '') {
+            if (!isset($this->recurrence)) {
+                $this->recurrence = new Horde_Date_Recurrence($this->start);
+            }
+            if (Util::getFormData('recur_enddate_type') == 'date') {
+                $recur_enddate = Util::getFormData('recur_enddate');
+                if ($this->recurrence->hasRecurEnd()) {
+                    $recurEnd = $this->recurrence->recurEnd;
+                    $recurEnd->month = $recur_enddate['month'];
+                    $recurEnd->mday = $recur_enddate['day'];
+                    $recurEnd->year = $recur_enddate['year'];
+                } else {
+                    $recurEnd = new Horde_Date(
+                        array('hour' => 23,
+                              'min' => 59,
+                              'sec' => 59,
+                              'month' => $recur_enddate['month'],
+                              'mday' => $recur_enddate['day'],
+                              'year' => $recur_enddate['year']));
+                }
+                $this->recurrence->setRecurEnd($recurEnd);
+            } elseif (Util::getFormData('recur_enddate_type') == 'count') {
+                $this->recurrence->setRecurCount(Util::getFormData('recur_count'));
+            } elseif (Util::getFormData('recur_enddate_type') == 'none') {
+                $this->recurrence->setRecurCount(0);
+                $this->recurrence->setRecurEnd(null);
+            }
+
+            $this->recurrence->setRecurType($recur);
+            switch ($recur) {
+            case Horde_Date_Recurrence::RECUR_DAILY:
+                $this->recurrence->setRecurInterval(Util::getFormData('recur_daily_interval', 1));
+                break;
+
+            case Horde_Date_Recurrence::RECUR_WEEKLY:
+                $weekly = Util::getFormData('weekly');
+                $weekdays = 0;
+                if (is_array($weekly)) {
+                    foreach ($weekly as $day) {
+                        $weekdays |= $day;
+                    }
+                }
+
+                if ($weekdays == 0) {
+                    // Sunday starts at 0.
+                    switch ($this->start->dayOfWeek()) {
+                    case 0: $weekdays |= Horde_Date::MASK_SUNDAY; break;
+                    case 1: $weekdays |= Horde_Date::MASK_MONDAY; break;
+                    case 2: $weekdays |= Horde_Date::MASK_TUESDAY; break;
+                    case 3: $weekdays |= Horde_Date::MASK_WEDNESDAY; break;
+                    case 4: $weekdays |= Horde_Date::MASK_THURSDAY; break;
+                    case 5: $weekdays |= Horde_Date::MASK_FRIDAY; break;
+                    case 6: $weekdays |= Horde_Date::MASK_SATURDAY; break;
+                    }
+                }
+
+                $this->recurrence->setRecurInterval(Util::getFormData('recur_weekly_interval', 1));
+                $this->recurrence->setRecurOnDay($weekdays);
+                break;
+
+            case Horde_Date_Recurrence::RECUR_MONTHLY_DATE:
+                $this->recurrence->setRecurInterval(Util::getFormData('recur_day_of_month_interval', 1));
+                break;
+
+            case Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY:
+                $this->recurrence->setRecurInterval(Util::getFormData('recur_week_of_month_interval', 1));
+                break;
+
+            case Horde_Date_Recurrence::RECUR_YEARLY_DATE:
+                $this->recurrence->setRecurInterval(Util::getFormData('recur_yearly_interval', 1));
+                break;
+
+            case Horde_Date_Recurrence::RECUR_YEARLY_DAY:
+                $this->recurrence->setRecurInterval(Util::getFormData('recur_yearly_day_interval', 1));
+                break;
+
+            case Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY:
+                $this->recurrence->setRecurInterval(Util::getFormData('recur_yearly_weekday_interval', 1));
+                break;
+            }
+        }
+
+        // Tags.
+        $this->tags = Util::getFormData('tags');
+
+        $this->initialized = true;
+    }
+
+    public function html($property)
+    {
+        global $prefs;
+
+        $options = array();
+        $attributes = '';
+        $sel = false;
+        $label = '';
+
+        switch ($property) {
+        case 'start[year]':
+            return  '<label for="' . $this->_formIDEncode($property) . '" class="hidden">' . _("Start Year") . '</label>' .
+                '<input name="' . $property . '" value="' . $this->start->year .
+                '" type="text" onchange="' . $this->js($property) .
+                '" id="' . $this->_formIDEncode($property) . '" size="4" maxlength="4" />';
+
+        case 'start[month]':
+            $sel = $this->start->month;
+            for ($i = 1; $i < 13; ++$i) {
+                $options[$i] = strftime('%b', mktime(1, 1, 1, $i, 1));
+            }
+            $attributes = ' onchange="' . $this->js($property) . '"';
+            $label = _("Start Month");
+            break;
+
+        case 'start[day]':
+            $sel = $this->start->mday;
+            for ($i = 1; $i < 32; ++$i) {
+                $options[$i] = $i;
+            }
+            $attributes = ' onchange="' . $this->js($property) . '"';
+            $label = _("Start Day");
+            break;
+
+        case 'start_hour':
+            $sel = $this->start->format($prefs->getValue('twentyFour') ? 'G' : 'g');
+            $hour_min = $prefs->getValue('twentyFour') ? 0 : 1;
+            $hour_max = $prefs->getValue('twentyFour') ? 24 : 13;
+            for ($i = $hour_min; $i < $hour_max; ++$i) {
+                $options[$i] = $i;
+            }
+            $attributes = ' onchange="document.eventform.whole_day.checked = false; KronolithEventForm.updateEndDate();"';
+            $label = _("Start Hour");
+            break;
+
+        case 'start_min':
+            $sel = sprintf('%02d', $this->start->min);
+            for ($i = 0; $i < 12; ++$i) {
+                $min = sprintf('%02d', $i * 5);
+                $options[$min] = $min;
+            }
+            $attributes = ' onchange="document.eventform.whole_day.checked = false; KronolithEventForm.updateEndDate();"';
+            $label = _("Start Minute");
+            break;
+
+        case 'end[year]':
+            return  '<label for="' . $this->_formIDEncode($property) . '" class="hidden">' . _("End Year") . '</label>' .
+                '<input name="' . $property . '" value="' . $this->end->year .
+                '" type="text" onchange="' . $this->js($property) .
+                '" id="' . $this->_formIDEncode($property) . '" size="4" maxlength="4" />';
+
+        case 'end[month]':
+            $sel = $this->isInitialized() ? $this->end->month : $this->start->month;
+            for ($i = 1; $i < 13; ++$i) {
+                $options[$i] = strftime('%b', mktime(1, 1, 1, $i, 1));
+            }
+            $attributes = ' onchange="' . $this->js($property) . '"';
+            $label = _("End Month");
+            break;
+
+        case 'end[day]':
+            $sel = $this->isInitialized() ? $this->end->mday : $this->start->mday;
+            for ($i = 1; $i < 32; ++$i) {
+                $options[$i] = $i;
+            }
+            $attributes = ' onchange="' . $this->js($property) . '"';
+            $label = _("End Day");
+            break;
+
+        case 'end_hour':
+            $sel = $this->isInitialized() ?
+                $this->end->format($prefs->getValue('twentyFour') ? 'G' : 'g') :
+                $this->start->format($prefs->getValue('twentyFour') ? 'G' : 'g') + 1;
+            $hour_min = $prefs->getValue('twentyFour') ? 0 : 1;
+            $hour_max = $prefs->getValue('twentyFour') ? 24 : 13;
+            for ($i = $hour_min; $i < $hour_max; ++$i) {
+                $options[$i] = $i;
+            }
+            $attributes = ' onchange="KronolithEventForm.updateDuration(); document.eventform.end_or_dur[0].checked = true"';
+            $label = _("End Hour");
+            break;
+
+        case 'end_min':
+            $sel = $this->isInitialized() ? $this->end->min : $this->start->min;
+            $sel = sprintf('%02d', $sel);
+            for ($i = 0; $i < 12; ++$i) {
+                $min = sprintf('%02d', $i * 5);
+                $options[$min] = $min;
+            }
+            $attributes = ' onchange="KronolithEventForm.updateDuration(); document.eventform.end_or_dur[0].checked = true"';
+            $label = _("End Minute");
+            break;
+
+        case 'dur_day':
+            $dur = $this->getDuration();
+            return  '<label for="' . $property . '" class="hidden">' . _("Duration Day") . '</label>' .
+                '<input name="' . $property . '" value="' . $dur->day .
+                '" type="text" onchange="' . $this->js($property) .
+                '" id="' . $property . '" size="4" maxlength="4" />';
+
+        case 'dur_hour':
+            $dur = $this->getDuration();
+            $sel = $dur->hour;
+            for ($i = 0; $i < 24; ++$i) {
+                $options[$i] = $i;
+            }
+            $attributes = ' onchange="' . $this->js($property) . '"';
+            $label = _("Duration Hour");
+            break;
+
+        case 'dur_min':
+            $dur = $this->getDuration();
+            $sel = $dur->min;
+            for ($i = 0; $i < 13; ++$i) {
+                $min = sprintf('%02d', $i * 5);
+                $options[$min] = $min;
+            }
+            $attributes = ' onchange="' . $this->js($property) . '"';
+            $label = _("Duration Minute");
+            break;
+
+        case 'recur_enddate[year]':
+            if ($this->isInitialized()) {
+                $end = ($this->recurs() && $this->recurrence->hasRecurEnd())
+                        ? $this->recurrence->recurEnd->year
+                        : $this->end->year;
+            } else {
+                $end = $this->start->year;
+            }
+            return  '<label for="' . $this->_formIDEncode($property) . '" class="hidden">' . _("Recurrence End Year") . '</label>' .
+                '<input name="' . $property . '" value="' . $end .
+                '" type="text" onchange="' . $this->js($property) .
+                '" id="' . $this->_formIDEncode($property) . '" size="4" maxlength="4" />';
+
+        case 'recur_enddate[month]':
+            if ($this->isInitialized()) {
+                $sel = ($this->recurs() && $this->recurrence->hasRecurEnd())
+                    ? $this->recurrence->recurEnd->month
+                    : $this->end->month;
+            } else {
+                $sel = $this->start->month;
+            }
+            for ($i = 1; $i < 13; ++$i) {
+                $options[$i] = strftime('%b', mktime(1, 1, 1, $i, 1));
+            }
+            $attributes = ' onchange="' . $this->js($property) . '"';
+            $label = _("Recurrence End Month");
+            break;
+
+        case 'recur_enddate[day]':
+            if ($this->isInitialized()) {
+                $sel = ($this->recurs() && $this->recurrence->hasRecurEnd())
+                    ? $this->recurrence->recurEnd->mday
+                    : $this->end->mday;
+            } else {
+                $sel = $this->start->mday;
+            }
+            for ($i = 1; $i < 32; ++$i) {
+                $options[$i] = $i;
+            }
+            $attributes = ' onchange="' . $this->js($property) . '"';
+            $label = _("Recurrence End Day");
+            break;
+        }
+
+        if (!$this->_varRenderer) {
+            $this->_varRenderer = Horde_UI_VarRenderer::factory('html');
+        }
+
+        return '<label for="' . $this->_formIDEncode($property) . '" class="hidden">' . $label . '</label>' .
+            '<select name="' . $property . '"' . $attributes . ' id="' . $this->_formIDEncode($property) . '">' .
+            $this->_varRenderer->_selectOptions($options, $sel) .
+            '</select>';
+    }
+
+    public function js($property)
+    {
+        switch ($property) {
+        case 'start[month]':
+        case 'start[year]':
+        case 'start[day]':
+        case 'start':
+            return 'KronolithEventForm.updateWday(\'start_wday\'); document.eventform.whole_day.checked = false; KronolithEventForm.updateEndDate();';
+
+        case 'end[month]':
+        case 'end[year]':
+        case 'end[day]':
+        case 'end':
+            return 'KronolithEventForm.updateWday(\'end_wday\'); updateDuration(); document.eventform.end_or_dur[0].checked = true;';
+
+        case 'recur_enddate[month]':
+        case 'recur_enddate[year]':
+        case 'recur_enddate[day]':
+        case 'recur_enddate':
+            return 'KronolithEventForm.updateWday(\'recur_end_wday\'); document.eventform.recur_enddate_type[1].checked = true;';
+
+        case 'dur_day':
+        case 'dur_hour':
+        case 'dur_min':
+            return 'document.eventform.whole_day.checked = false; KronolithEventForm.updateEndDate(); document.eventform.end_or_dur[1].checked = true;';
+        }
+    }
+
+    /**
+     * @param array $params
+     *
+     * @return string
+     */
+    public function getViewUrl($params = array(), $full = false)
+    {
+        $params['eventID'] = $this->eventID;
+        if ($this->remoteUrl) {
+            return $this->remoteUrl;
+        } elseif ($this->remoteCal) {
+            $params['calendar'] = '**remote';
+            $params['remoteCal'] = $this->remoteCal;
+        } else {
+            $params['calendar'] = $this->getCalendar();
+        }
+
+        return Horde::applicationUrl(Util::addParameter('event.php', $params), $full);
+    }
+
+    /**
+     * @param array $params
+     *
+     * @return string
+     */
+    public function getEditUrl($params = array())
+    {
+        $params['view'] = 'EditEvent';
+        $params['eventID'] = $this->eventID;
+        if ($this->remoteCal) {
+            $params['calendar'] = '**remote';
+            $params['remoteCal'] = $this->remoteCal;
+        } else {
+            $params['calendar'] = $this->getCalendar();
+        }
+
+        return Horde::applicationUrl(Util::addParameter('event.php', $params));
+    }
+
+    /**
+     * @param array $params
+     *
+     * @return string
+     */
+    public function getDeleteUrl($params = array())
+    {
+        $params['view'] = 'DeleteEvent';
+        $params['eventID'] = $this->eventID;
+        $params['calendar'] = $this->getCalendar();
+        return Horde::applicationUrl(Util::addParameter('event.php', $params));
+    }
+
+    /**
+     * @param array $params
+     *
+     * @return string
+     */
+    public function getExportUrl($params = array())
+    {
+        $params['view'] = 'ExportEvent';
+        $params['eventID'] = $this->eventID;
+        if ($this->remoteCal) {
+            $params['calendar'] = '**remote';
+            $params['remoteCal'] = $this->remoteCal;
+        } else {
+            $params['calendar'] = $this->getCalendar();
+        }
+
+        return Horde::applicationUrl(Util::addParameter('event.php', $params));
+    }
+
+    public function getLink($datetime = null, $icons = true, $from_url = null, $full = false)
+    {
+        global $prefs, $registry;
+
+        if (is_null($datetime)) {
+            $datetime = $this->start;
+        }
+        if (is_null($from_url)) {
+            $from_url = Horde::selfUrl(true, false, true);
+        }
+
+        $link = '';
+        $event_title = $this->getTitle();
+        if (isset($this->external)) {
+            $link = $registry->link($this->external . '/show', $this->external_params);
+            $link = Horde::linkTooltip(Horde::url($link), '', 'event-tentative', '', '', String::wrap($this->description));
+        } elseif (isset($this->eventID) && $this->hasPermission(PERMS_READ)) {
+            $link = Horde::linkTooltip($this->getViewUrl(array('datetime' => $datetime->strftime('%Y%m%d%H%M%S'), 'url' => $from_url), $full),
+                                       $event_title,
+                                       $this->getStatusClass(), '', '',
+                                       $this->getTooltip(),
+                                       '',
+                                       array('style' => $this->getCSSColors(false)));
+        }
+
+        $link .= @htmlspecialchars($event_title, ENT_QUOTES, NLS::getCharset());
+
+        if ($this->hasPermission(PERMS_READ) &&
+            (isset($this->eventID) ||
+             isset($this->external))) {
+            $link .= '</a>';
+        }
+
+        if ($icons && $prefs->getValue('show_icons')) {
+            $icon_color = $this->_foregroundColor == '#000' ? '000' : 'fff';
+            $status = '';
+            if ($this->alarm) {
+                if ($this->alarm % 10080 == 0) {
+                    $alarm_value = $this->alarm / 10080;
+                    $title = $alarm_value == 1 ?
+                        _("Alarm 1 week before") :
+                        sprintf(_("Alarm %d weeks before"), $alarm_value);
+                } elseif ($this->alarm % 1440 == 0) {
+                    $alarm_value = $this->alarm / 1440;
+                    $title = $alarm_value == 1 ?
+                        _("Alarm 1 day before") :
+                        sprintf(_("Alarm %d days before"), $alarm_value);
+                } elseif ($this->alarm % 60 == 0) {
+                    $alarm_value = $this->alarm / 60;
+                    $title = $alarm_value == 1 ?
+                        _("Alarm 1 hour before") :
+                        sprintf(_("Alarm %d hours before"), $alarm_value);
+                } else {
+                    $alarm_value = $this->alarm;
+                    $title = $alarm_value == 1 ?
+                        _("Alarm 1 minute before") :
+                        sprintf(_("Alarm %d minutes before"), $alarm_value);
+                }
+                $status .= Horde::img('alarm-' . $icon_color . '.png', $title,
+                                      array('title' => $title,
+                                            'class' => 'iconAlarm'),
+                                      Horde::url($registry->getImageDir(), true, -1));
+            }
+
+            if ($this->recurs()) {
+                $title = Kronolith::recurToString($this->recurrence->getRecurType());
+                $status .= Horde::img('recur-' . $icon_color . '.png', $title,
+                                      array('title' => $title,
+                                            'class' => 'iconRecur'),
+                                      Horde::url($registry->getImageDir(), true, -1));
+            }
+
+            if ($this->isPrivate()) {
+                $title = _("Private event");
+                $status .= Horde::img('private-' . $icon_color . '.png', $title,
+                                      array('title' => $title,
+                                            'class' => 'iconPrivate'),
+                                      Horde::url($registry->getImageDir(), true, -1));
+            }
+
+            if (!empty($this->attendees)) {
+                $title = count($this->attendees) == 1
+                    ? _("1 attendee")
+                    : sprintf(_("%s attendees"), count($this->attendees));
+                $status .= Horde::img('attendees.png', $title,
+                                      array('title' => $title,
+                                            'class' => 'iconPeople'),
+                                      Horde::url($registry->getImageDir(), true, -1));
+            }
+
+            if (!empty($status)) {
+                $link .= ' ' . $status;
+            }
+
+            if (!$this->eventID || !empty($this->external)) {
+                return $link;
+            }
+
+            $edit = '';
+            $delete = '';
+            if ((!$this->isPrivate() || $this->getCreatorId() == Auth::getAuth())
+                && $this->hasPermission(PERMS_EDIT)) {
+                $editurl = $this->getEditUrl(array('datetime' => $datetime->strftime('%Y%m%d%H%M%S'),
+                                                   'url' => $from_url));
+                $edit = Horde::link($editurl, sprintf(_("Edit %s"), $event_title), 'iconEdit')
+                    . Horde::img('edit-' . $icon_color . '.png', _("Edit"), '', Horde::url($registry->getImageDir(), true, -1))
+                    . '</a>';
+            }
+            if ($this->hasPermission(PERMS_DELETE)) {
+                $delurl = $this->getDeleteUrl(array('datetime' => $datetime->strftime('%Y%m%d%H%M%S'),
+                                                    'url' => $from_url));
+                $delete = Horde::link($delurl, sprintf(_("Delete %s"), $event_title), 'iconDelete')
+                    . Horde::img('delete-' . $icon_color . '.png', _("Delete"), '', Horde::url($registry->getImageDir(), true, -1))
+                    . '</a>';
+            }
+
+            if ($edit || $delete) {
+                $link .= $edit . $delete;
+            }
+        }
+
+        return $link;
+    }
+
+    /**
+     * Returns the CSS color definition for this event.
+     *
+     * @param boolean $with_attribute  Whether to wrap the colors inside a
+     *                                 "style" attribute.
+     *
+     * @return string  A CSS string with color definitions.
+     */
+    public function getCSSColors($with_attribute = true)
+    {
+        $css = 'background-color:' . $this->_backgroundColor . ';color:' . $this->_foregroundColor;
+        if ($with_attribute) {
+            $css = ' style="' . $css . '"';
+        }
+        return $css;
+    }
+
+    /**
+     * @return string  A tooltip for quick descriptions of this event.
+     */
+    public function getTooltip()
+    {
+        $tooltip = $this->getTimeRange()
+            . "\n" . sprintf(_("Owner: %s"), ($this->getCreatorId() == Auth::getAuth() ?
+                                              _("Me") : Kronolith::getUserName($this->getCreatorId())));
+
+        if (!$this->isPrivate() || $this->getCreatorId() == Auth::getAuth()) {
+            if ($this->location) {
+                $tooltip .= "\n" . _("Location") . ': ' . $this->location;
+            }
+
+            if ($this->description) {
+                $tooltip .= "\n\n" . String::wrap($this->description);
+            }
+        }
+
+        return $tooltip;
+    }
+
+    /**
+     * @return string  The time range of the event ("All Day", "1:00pm-3:00pm",
+     *                 "08:00-22:00").
+     */
+    public function getTimeRange()
+    {
+        if ($this->isAllDay()) {
+            return _("All day");
+        } elseif (($cmp = $this->start->compareDate($this->end)) > 0) {
+            $df = $GLOBALS['prefs']->getValue('date_format');
+            if ($cmp > 0) {
+                return $this->end->strftime($df) . '-'
+                    . $this->start->strftime($df);
+            } else {
+                return $this->start->strftime($df) . '-'
+                    . $this->end->strftime($df);
+            }
+        } else {
+            $twentyFour = $GLOBALS['prefs']->getValue('twentyFour');
+            return $this->start->format($twentyFour ? 'G:i' : 'g:ia')
+                . '-'
+                . $this->end->format($twentyFour ? 'G:i' : 'g:ia');
+        }
+    }
+
+    /**
+     * @return string  The CSS class for the event based on its status.
+     */
+    public function getStatusClass()
+    {
+        switch ($this->status) {
+        case Kronolith::STATUS_CANCELLED:
+            return 'event-cancelled';
+
+        case Kronolith::STATUS_TENTATIVE:
+        case Kronolith::STATUS_FREE:
+            return 'event-tentative';
+        }
+
+        return 'event';
+    }
+
+    private function _formIDEncode($id)
+    {
+        return str_replace(array('[', ']'),
+                           array('_', ''),
+                           $id);
+    }
+
+}
diff --git a/kronolith/lib/Event/Holidays.php b/kronolith/lib/Event/Holidays.php
new file mode 100644 (file)
index 0000000..97fe409
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Copyright 2006-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @see     http://pear.php.net/packages/Date_Holidays
+ * @author  Stephan Hohmann <webmaster@dasourcerer.net>
+ * @package Kronolith
+ */
+class Kronolith_Event_Holidays extends Kronolith_Event
+{
+    /**
+     * The status of this event.
+     *
+     * @var integer
+     */
+    public $status = Kronolith::STATUS_FREE;
+
+    /**
+     * Whether this is an all-day event.
+     *
+     * @var boolean
+     */
+    public $allday = true;
+
+    /**
+     * Parse in an event from the driver.
+     *
+     * @param Date_Holidays_Holiday $dhEvent  A holiday returned
+     *                                        from the driver
+     */
+    public function fromDriver($dhEvent)
+    {
+        $this->stored = true;
+        $this->initialized = true;
+        $this->setTitle(String::convertCharset($dhEvent->getTitle(), 'UTF-8'));
+        $this->setId($dhEvent->getInternalName());
+
+        $this->start = new Horde_Date($dhEvent->_date->getTime());
+        $this->end = new Horde_Date($this->start);
+        $this->end->mday++;
+    }
+
+    /**
+     * Return this events title.
+     *
+     * @return string The title of this event
+     */
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    /**
+     * Is this event an all-day event?
+     *
+     * Since there are no holidays lasting only a few hours, this is always
+     * true.
+     *
+     * @return boolean <code>true</code>
+     */
+    public function isAllDay()
+    {
+        return true;
+    }
+
+}
diff --git a/kronolith/lib/Event/Ical.php b/kronolith/lib/Event/Ical.php
new file mode 100644 (file)
index 0000000..89efe74
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Kronolith
+ */
+class Kronolith_Event_Ical extends Kronolith_Event
+{
+
+    public function fromDriver($vEvent)
+    {
+        $this->fromiCalendar($vEvent);
+        $this->initialized = true;
+        $this->stored = true;
+    }
+
+    public function toDriver()
+    {
+        return $this->toiCalendar();
+    }
+
+}
diff --git a/kronolith/lib/Event/Kolab.php b/kronolith/lib/Event/Kolab.php
new file mode 100644 (file)
index 0000000..2856cad
--- /dev/null
@@ -0,0 +1,244 @@
+<?php
+/**
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @author  Gunnar Wrobel <wrobel@pardus.de>
+ * @author  Stuart Binge <omicron@mighty.co.za>
+ * @package Kronolith
+ */
+class Kronolith_Event_Kolab extends Kronolith_Event
+{
+
+    public function fromDriver($event)
+    {
+        $this->eventID = $event['uid'];
+        $this->setUID($this->eventID);
+
+        if (isset($event['summary'])) {
+            $this->title = $event['summary'];
+        }
+        if (isset($event['body'])) {
+            $this->description = $event['body'];
+        }
+        if (isset($event['location'])) {
+            $this->location = $event['location'];
+        }
+
+        if (isset($event['sensitivity']) &&
+            ($event['sensitivity'] == 'private' || $event['sensitivity'] == 'confidential')) {
+            $this->setPrivate(true);
+        }
+
+        if (isset($event['organizer']['smtp-address'])) {
+            if (Kronolith::isUserEmail(Auth::getAuth(), $event['organizer']['smtp-address'])) {
+                $this->creatorID = Auth::getAuth();
+            } else {
+                $this->creatorID = $event['organizer']['smtp-address'];
+            }
+        }
+
+        if (isset($event['alarm'])) {
+            $this->alarm = $event['alarm'];
+        }
+
+        $this->start = new Horde_Date($event['start-date']);
+        $this->end = new Horde_Date($event['end-date']);
+        $this->durMin = ($this->end->timestamp() - $this->start->timestamp()) / 60;
+
+        if (isset($event['show-time-as'])) {
+            switch ($event['show-time-as']) {
+                case 'free':
+                    $this->status = Kronolith::STATUS_FREE;
+                    break;
+
+                case 'tentative':
+                    $this->status = Kronolith::STATUS_TENTATIVE;
+                    break;
+
+                case 'busy':
+                case 'outofoffice':
+                default:
+                    $this->status = Kronolith::STATUS_CONFIRMED;
+            }
+        } else {
+            $this->status = Kronolith::STATUS_CONFIRMED;
+        }
+
+        // Recurrence
+        if (isset($event['recurrence'])) {
+            $this->recurrence = new Horde_Date_Recurrence($this->start);
+            $this->recurrence->fromHash($event['recurrence']);
+        }
+
+        // Attendees
+        $attendee_count = 0;
+        foreach($event['attendee'] as $attendee) {
+            $name = $attendee['display-name'];
+            $email = $attendee['smtp-address'];
+
+            $role = $attendee['role'];
+            switch ($role) {
+            case 'optional':
+                $role = Kronolith::PART_OPTIONAL;
+                break;
+
+            case 'resource':
+                $role = Kronolith::PART_NONE;
+                break;
+
+            case 'required':
+            default:
+                $role = Kronolith::PART_REQUIRED;
+                break;
+            }
+
+            $status = $attendee['status'];
+            switch ($status) {
+            case 'accepted':
+                $status = Kronolith::RESPONSE_ACCEPTED;
+                break;
+
+            case 'declined':
+                $status = Kronolith::RESPONSE_DECLINED;
+                break;
+
+            case 'tentative':
+                $status = Kronolith::RESPONSE_TENTATIVE;
+                break;
+
+            case 'none':
+            default:
+                $status = Kronolith::RESPONSE_NONE;
+                break;
+            }
+
+            // Attendees without an email address get added as incremented number
+            if (empty($email)) {
+                $email = $attendee_count;
+                $attendee_count++;
+            }
+
+            $this->addAttendee($email, $role, $status, $name);
+        }
+
+        $this->initialized = true;
+        $this->stored = true;
+    }
+
+    public function toDriver()
+    {
+        $event = array();
+        $event['uid'] = $this->getUID();
+        $event['summary'] = $this->title;
+        $event['body']  = $this->description;
+        $event['location'] = $this->location;
+
+        if ($this->isPrivate()) {
+            $event['sensitivity'] = 'private';
+        } else {
+            $event['sensitivity'] = 'public';
+        }
+
+        // Only set organizer if this is a new event
+        if ($this->getID() == null) {
+            $organizer = array(
+                            'display-name' => Kronolith::getUserName($this->getCreatorId()),
+                            'smtp-address' => Kronolith::getUserEmail($this->getCreatorId())
+                         );
+            $event['organizer'] = $organizer;
+        }
+
+        if ($this->alarm != 0) {
+            $event['alarm'] = $this->alarm;
+        }
+
+        $event['start-date'] = $this->start->timestamp();
+        $event['end-date'] = $this->end->timestamp();
+        $event['_is_all_day'] = $this->isAllDay();
+
+        switch ($this->status) {
+        case Kronolith::STATUS_FREE:
+        case Kronolith::STATUS_CANCELLED:
+            $event['show-time-as'] = 'free';
+            break;
+
+        case Kronolith::STATUS_TENTATIVE:
+            $event['show-time-as'] = 'tentative';
+            break;
+
+        // No mapping for outofoffice
+        case Kronolith::STATUS_CONFIRMED:
+        default:
+            $event['show-time-as'] = 'busy';
+        }
+
+        // Recurrence
+        if ($this->recurs()) {
+            $event['recurrence'] = $this->recurrence->toHash();
+        } else {
+            $event['recurrence'] = array();
+        }
+
+
+        // Attendees
+        $event['attendee'] = array();
+        $attendees = $this->getAttendees();
+
+        foreach($attendees as $email => $attendee) {
+            $new_attendee = array();
+            $new_attendee['display-name'] = $attendee['name'];
+
+            // Attendee without an email address
+            if (is_int($email)) {
+                $new_attendee['smtp-address'] = '';
+            } else {
+                $new_attendee['smtp-address'] = $email;
+            }
+
+            switch ($attendee['attendance']) {
+            case Kronolith::PART_OPTIONAL:
+                $new_attendee['role'] = 'optional';
+                break;
+
+            case Kronolith::PART_NONE:
+                $new_attendee['role'] = 'resource';
+                break;
+
+            case Kronolith::PART_REQUIRED:
+            default:
+                $new_attendee['role'] = 'required';
+                break;
+            }
+
+            $new_attendee['request-response'] = '0';
+
+            switch ($attendee['response']) {
+            case Kronolith::RESPONSE_ACCEPTED:
+                $new_attendee['status'] = 'accepted';
+                break;
+
+            case Kronolith::RESPONSE_DECLINED:
+                $new_attendee['status'] = 'declined';
+                break;
+
+            case Kronolith::RESPONSE_TENTATIVE:
+                $new_attendee['status'] = 'tentative';
+                break;
+
+            case Kronolith::RESPONSE_NONE:
+            default:
+                $new_attendee['status'] = 'none';
+                break;
+            }
+
+            $event['attendee'][] = $new_attendee;
+        }
+
+        return $event;
+    }
+
+}
diff --git a/kronolith/lib/Event/Sql.php b/kronolith/lib/Event/Sql.php
new file mode 100644 (file)
index 0000000..0c3930c
--- /dev/null
@@ -0,0 +1,177 @@
+<?php
+/**
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Luc Saillard <luc.saillard@fr.alcove.com>
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Kronolith
+ */
+class Kronolith_Event_Sql extends Kronolith_Event
+{
+    /**
+     * @var array
+     */
+    private $_properties = array();
+
+    public function fromDriver($SQLEvent)
+    {
+        $driver = $this->getDriver();
+
+        $this->allday = (bool)$SQLEvent['event_allday'];
+        if (!$this->allday && $driver->getParam('utc')) {
+            $tz_local = date_default_timezone_get();
+            $this->start = new Horde_Date($SQLEvent['event_start'], 'UTC');
+            $this->start->setTimezone($tz_local);
+            $this->end = new Horde_Date($SQLEvent['event_end'], 'UTC');
+            $this->end->setTimezone($tz_local);
+        } else {
+            $this->start = new Horde_Date($SQLEvent['event_start']);
+            $this->end = new Horde_Date($SQLEvent['event_end']);
+        }
+
+        $this->durMin = ($this->end->timestamp() - $this->start->timestamp()) / 60;
+
+        $this->title = $driver->convertFromDriver($SQLEvent['event_title']);
+        $this->eventID = $SQLEvent['event_id'];
+        $this->setUID($SQLEvent['event_uid']);
+        $this->creatorID = $SQLEvent['event_creator_id'];
+
+        if (!empty($SQLEvent['event_recurtype'])) {
+            $this->recurrence = new Horde_Date_Recurrence($this->start);
+            $this->recurrence->setRecurType((int)$SQLEvent['event_recurtype']);
+            $this->recurrence->setRecurInterval((int)$SQLEvent['event_recurinterval']);
+            if (isset($SQLEvent['event_recurenddate'])) {
+                if ($driver->getParam('utc')) {
+                    $recur_end = new Horde_Date($SQLEvent['event_recurenddate'], 'UTC');
+                    if ($recur_end->min == 0) {
+                        /* Old recurrence end date format. */
+                        $recur_end = new Horde_Date($SQLEvent['event_recurenddate']);
+                        $recur_end->hour = 23;
+                        $recur_end->min = 59;
+                        $recur_end->sec = 59;
+                    } else {
+                        $recur_end->setTimezone(date_default_timezone_get());
+                    }
+                } else {
+                    $recur_end = new Horde_Date($SQLEvent['event_recurenddate']);
+                    $recur_end->hour = 23;
+                    $recur_end->min = 59;
+                    $recur_end->sec = 59;
+                }
+                $this->recurrence->setRecurEnd($recur_end);
+            }
+            if (isset($SQLEvent['event_recurcount'])) {
+                $this->recurrence->setRecurCount((int)$SQLEvent['event_recurcount']);
+            }
+            if (isset($SQLEvent['event_recurdays'])) {
+                $this->recurrence->recurData = (int)$SQLEvent['event_recurdays'];
+            }
+            if (!empty($SQLEvent['event_exceptions'])) {
+                $this->recurrence->exceptions = explode(',', $SQLEvent['event_exceptions']);
+            }
+        }
+
+        if (isset($SQLEvent['event_location'])) {
+            $this->location = $driver->convertFromDriver($SQLEvent['event_location']);
+        }
+        if (isset($SQLEvent['event_private'])) {
+            $this->private = (bool)($SQLEvent['event_private']);
+        }
+        if (isset($SQLEvent['event_status'])) {
+            $this->status = $SQLEvent['event_status'];
+        }
+        if (isset($SQLEvent['event_attendees'])) {
+            $this->attendees = array_change_key_case($driver->convertFromDriver(unserialize($SQLEvent['event_attendees'])));
+        }
+        if (isset($SQLEvent['event_description'])) {
+            $this->description = $driver->convertFromDriver($SQLEvent['event_description']);
+        }
+        if (isset($SQLEvent['event_alarm'])) {
+            $this->alarm = (int)$SQLEvent['event_alarm'];
+        }
+        if (isset($SQLEvent['event_alarm_methods'])) {
+            $this->methods = $driver->convertFromDriver(unserialize($SQLEvent['event_alarm_methods']));
+        }
+        $this->initialized = true;
+        $this->stored = true;
+    }
+
+    public function toDriver()
+    {
+        $driver = $this->getDriver();
+
+        /* Basic fields. */
+        $this->_properties['event_creator_id'] = $driver->convertToDriver($this->getCreatorId());
+        $this->_properties['event_title'] = $driver->convertToDriver($this->title);
+        $this->_properties['event_description'] = $driver->convertToDriver($this->getDescription());
+        $this->_properties['event_location'] = $driver->convertToDriver($this->getLocation());
+        $this->_properties['event_private'] = (int)$this->isPrivate();
+        $this->_properties['event_status'] = $this->getStatus();
+        $this->_properties['event_attendees'] = serialize($driver->convertToDriver($this->getAttendees()));
+        $this->_properties['event_modified'] = $_SERVER['REQUEST_TIME'];
+
+        if ($this->isAllDay()) {
+            $this->_properties['event_start'] = $this->start->strftime('%Y-%m-%d %H:%M:%S');
+            $this->_properties['event_end'] = $this->end->strftime('%Y-%m-%d %H:%M:%S');
+            $this->_properties['event_allday'] = 1;
+        } else {
+            if ($driver->getParam('utc')) {
+                $start = clone $this->start;
+                $end = clone $this->end;
+                $start->setTimezone('UTC');
+                $end->setTimezone('UTC');
+            } else {
+                $start = $this->start;
+                $end = $this->end;
+            }
+            $this->_properties['event_start'] = $start->strftime('%Y-%m-%d %H:%M:%S');
+            $this->_properties['event_end'] = $end->strftime('%Y-%m-%d %H:%M:%S');
+            $this->_properties['event_allday'] = 0;
+        }
+
+        /* Alarm. */
+        $this->_properties['event_alarm'] = (int)$this->getAlarm();
+
+        /* Alarm Notification Methods. */
+        $this->_properties['event_alarm_methods'] = serialize($driver->convertToDriver($this->methods));
+
+        /* Recurrence. */
+        if (!$this->recurs()) {
+            $this->_properties['event_recurtype'] = 0;
+        } else {
+            $recur = $this->recurrence->getRecurType();
+            if ($this->recurrence->hasRecurEnd()) {
+                if ($driver->getParam('utc')) {
+                    $recur_end = clone $this->recurrence->recurEnd;
+                    $recur_end->setTimezone('UTC');
+                } else {
+                    $recur_end = $this->recurrence->recurEnd;
+                }
+            } else {
+                $recur_end = new Horde_Date(array('year' => 9999, 'month' => 12, 'mday' => 31, 'hour' => 23, 'min' => 59, 'sec' => 59));
+            }
+
+            $this->_properties['event_recurtype'] = $recur;
+            $this->_properties['event_recurinterval'] = $this->recurrence->getRecurInterval();
+            $this->_properties['event_recurenddate'] = $recur_end->format('Y-m-d H:i:s');
+            $this->_properties['event_recurcount'] = $this->recurrence->getRecurCount();
+
+            switch ($recur) {
+            case Horde_Date_Recurrence::RECUR_WEEKLY:
+                $this->_properties['event_recurdays'] = $this->recurrence->getRecurOnDays();
+                break;
+            }
+            $this->_properties['event_exceptions'] = implode(',', $this->recurrence->getExceptions());
+        }
+    }
+
+    public function getProperties()
+    {
+        return $this->_properties;
+    }
+
+}
index ae05140..9a47008 100644 (file)
@@ -1,5 +1,10 @@
 <?php
 /**
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
  * @package Kronolith
  */
 
@@ -820,7 +825,7 @@ class Kronolith
                               __FILE__, __LINE__, PEAR_LOG_ERR);
             return array();
         } else {
-            $dhDriver = Kronolith_Driver::factory('holidays');
+            $dhDriver = Kronolith_Driver::factory('Holidays');
             return $dhDriver->listEvents($startDate, $endDate);
         }
     }
@@ -951,7 +956,7 @@ class Kronolith
                             continue;
                         }
 
-                        $event = &$kronolith_driver->getEvent();
+                        $event = $kronolith_driver->getEvent();
                         $event->eventID = '_' . $api . $eventsListItem['id'];
                         $event->external = $api;
                         $event->external_params = $eventsListItem['params'];
@@ -994,7 +999,7 @@ class Kronolith
 
             /* Remote Calendars. */
             foreach ($GLOBALS['display_remote_calendars'] as $url) {
-                $driver = self::getDriver('ical', array('url' => $url));
+                $driver = self::getDriver('Ical', array('url' => $url));
                 $events = $driver->listEvents($startOfPeriod, $endOfPeriod);
                 if (!is_a($events, 'PEAR_Error')) {
                     $kronolith_driver->open(Kronolith::getDefaultCalendar(PERMS_SHOW));
@@ -2073,7 +2078,7 @@ class Kronolith
 
         if (!isset(self::$_instances[$sig])) {
             switch ($driver) {
-            case 'ical':
+            case 'Ical':
                 /* Check for HTTP proxy configuration */
                 if (!empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) {
                     $params['proxy'] = $GLOBALS['conf']['http']['proxy'];
@@ -2128,13 +2133,13 @@ class Kronolith
             require_once KRONOLITH_BASE . '/lib/Views/Event.php';
 
             if (Util::getFormData('calendar') == '**remote') {
-                $driver = self::getDriver('ical', array('url' => Util::getFormData('remoteCal')));
+                $driver = self::getDriver('Ical', array('url' => Util::getFormData('remoteCal')));
                 $event = $driver->getEvent(Util::getFormData('eventID'));
             } elseif ($uid = Util::getFormData('uid')) {
-                $event = &$GLOBALS['kronolith_driver']->getByUID($uid);
+                $event = $GLOBALS['kronolith_driver']->getByUID($uid);
             } else {
                 $GLOBALS['kronolith_driver']->open(Util::getFormData('calendar'));
-                $event = &$GLOBALS['kronolith_driver']->getEvent(
+                $event = $GLOBALS['kronolith_driver']->getEvent(
                     Util::getFormData('eventID'));
             }
             if (!is_a($event, 'PEAR_Error') &&
@@ -2148,11 +2153,11 @@ class Kronolith
             require_once KRONOLITH_BASE . '/lib/Views/EditEvent.php';
 
             if (Util::getFormData('calendar') == '**remote') {
-                $driver = self::getDriver('ical', array('url' => Util::getFormData('remoteCal')));
+                $driver = self::getDriver('Ical', array('url' => Util::getFormData('remoteCal')));
                 $event = $driver->getEvent(Util::getFormData('eventID'));
             } else {
                 $GLOBALS['kronolith_driver']->open(Util::getFormData('calendar'));
-                $event = &$GLOBALS['kronolith_driver']->getEvent(
+                $event = $GLOBALS['kronolith_driver']->getEvent(
                     Util::getFormData('eventID'));
             }
             if (!is_a($event, 'PEAR_Error') &&
@@ -2166,7 +2171,7 @@ class Kronolith
             require_once KRONOLITH_BASE . '/lib/Views/DeleteEvent.php';
 
             $GLOBALS['kronolith_driver']->open(Util::getFormData('calendar'));
-            $event = &$GLOBALS['kronolith_driver']->getEvent
+            $event = $GLOBALS['kronolith_driver']->getEvent
                 (Util::getFormData('eventID'));
             if (!is_a($event, 'PEAR_Error') &&
                 !$event->hasPermission(PERMS_DELETE)) {
@@ -2179,13 +2184,13 @@ class Kronolith
             require_once KRONOLITH_BASE . '/lib/Views/ExportEvent.php';
 
             if (Util::getFormData('calendar') == '**remote') {
-                $driver = self::getDriver('ical', array('url' => Util::getFormData('remoteCal')));
+                $driver = self::getDriver('Ical', array('url' => Util::getFormData('remoteCal')));
                 $event = $driver->getEvent(Util::getFormData('eventID'));
             } elseif ($uid = Util::getFormData('uid')) {
-                $event = &$GLOBALS['kronolith_driver']->getByUID($uid);
+                $event = $GLOBALS['kronolith_driver']->getByUID($uid);
             } else {
                 $GLOBALS['kronolith_driver']->open(Util::getFormData('calendar'));
-                $event = &$GLOBALS['kronolith_driver']->getEvent(
+                $event = $GLOBALS['kronolith_driver']->getEvent(
                     Util::getFormData('eventID'));
             }
             if (!is_a($event, 'PEAR_Error') &&
index 6a2b022..35d16ba 100644 (file)
@@ -56,7 +56,7 @@ $object = array(
 var_dump($kolab->_storage->save($object));
                           
 // Check that the driver can be created
-$kron = Kronolith_Driver::factory('kolab');
+$kron = Kronolith_Driver::factory('Kolab');
 $kron->open('wrobel@example.org');
 
 $start = &new Horde_Date(86400);