* @since Kronolith 0.1 * @package Kronolith */ class Kronolith_Driver { /** * A hash containing any parameters for the current driver. * * @var array */ var $_params = array(); /** * The current calendar. * * @var string */ var $_calendar; /** * Constructor - just store the $params in our newly-created * object. All other work is done by open(). * * @param array $params Any parameters needed for this driver. */ function Kronolith_Driver($params = array()) { $this->_params = $params; } /** * Get the currently open calendar. * * @return string The current calendar name. */ function getCalendar() { return $this->_calendar; } /** * Generate a universal / unique identifier for a task. This is * NOT something that we expect to be able to parse into a * tasklist and a taskId. * * @return string A nice unique string (should be 255 chars or less). */ function generateUID() { return date('YmdHis') . '.' . substr(base_convert(microtime(), 10, 36), -16) . '@' . $GLOBALS['conf']['server']['name']; } /** * 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. */ function rename($from, $to) { return true; } /** * Search a calendar. * * @param object Kronolith_Event $query A Kronolith_Event object with the * criteria to search for. * * @return mixed An array of Kronolith_Events or a PEAR_Error. */ function search($query) { /* Our default implementation first gets all events in * a specific period, and then filters based on the actual * values that are filled in. Drivers can optimize this * behavior if they have the ability. */ $results = array(); $events = &$this->listEvents($query->start, $query->end); if (is_a($events, 'PEAR_Error')) { return $events; } if (isset($query->start)) { $startTime = $query->start->timestamp(); } else { $startTime = null; } if (isset($query->end)) { $endTime = $query->end->timestamp(); } else { $endTime = null; } foreach ($events as $eventid) { $event = &$this->getEvent($eventid); if (is_a($event, 'PEAR_Error')) { return $event; } $evStartTime = $event->start->timestamp(); $evEndTime = $event->end->timestamp(); if (((($evEndTime > $startTime || !isset($startTime)) && ($evStartTime < $endTime || !isset($endTime))) || ($event->getRecurType() != KRONOLITH_RECUR_NONE && $evEndTime >= $startTime && $evStartTime <= $endTime)) && (empty($query->title) || stristr($event->getTitle(), $query->title)) && (empty($query->location) || stristr($event->getLocation(), $query->location)) && (empty($query->description) || stristr($event->getDescription(), $query->description)) && (!isset($query->category) || $event->getCategory() == $query->category) && (!isset($query->status) || $event->getStatus() == $query->status)) { $results[] = $event; } } return $results; } /** * Find the next recurrence of $eventId that's after $afterDate. * * @param string $eventId The ID of the event to fetch. * @param Horde_Date $afterDate Return events after this date. * * @return Horde_Date | boolean The date of the next recurrence or false * if the event does not recur after $afterDate. */ function nextRecurrence($eventId, $afterDate) { $event = &$this->getEvent($eventId); if (is_a($event, 'PEAR_Error')) { return $event; } return $event->nextRecurrence($afterDate); } /** * Attempts to return a concrete Kronolith_Driver instance based * on $driver. * * @param string $driver The type of concrete Kronolith_Driver subclass * to return. * * @param array $params A hash containing any additional configuration or * connection parameters a subclass might need. * * @return Kronolith_Driver The newly created concrete Kronolith_Driver * instance, or a PEAR_Error on error. */ function &factory($driver = null, $params = null) { if (is_null($driver)) { $driver = $GLOBALS['conf']['calendar']['driver']; } $driver = basename($driver); if (is_null($params)) { $params = Horde::getDriverConfig('calendar', $driver); } include_once dirname(__FILE__) . '/Driver/' . $driver . '.php'; $class = 'Kronolith_Driver_' . $driver; if (class_exists($class)) { $driver = &new $class($params); } else { $driver = PEAR::raiseError(sprintf(_("Unable to load the definition of %s."), $class)); } return $driver; } } /** * Kronolith_Event:: defines a generic API for events. * * @author Chuck Hagenbuch * @since Kronolith 0.1 * @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 */ var $initialized = false; /** * Flag that is set to true if this event exists in a storage driver. * * @var boolean */ var $stored = false; /** * The driver unique identifier for this event. * * @var string */ var $eventID = null; /** * The UID for this event. * * @var string */ var $_uid = null; /** * The user id of the creator of the event. * * @var string */ var $creatorID = null; /** * The title of this event. * * @var string */ var $title = ''; /** * The identifier of the category this event belongs to. * * @var integer */ var $category = 0; /** * The location this event occurs at. * * @var string */ var $location = ''; /** * The status of this event. * * @var integer */ var $status = KRONOLITH_STATUS_CONFIRMED; /** * The description for this event * * @var string */ var $description = ''; /** * 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 */ var $attendees = array(); /** * All the key words associtated with this event. * * @var array */ var $keywords = array(); /** * All the exceptions from recurrence for this event. * * @var array */ var $exceptions = array(); /** * The start time of the event. * * @var Horde_Date */ var $start; /** * The end time of the event. * * @var Horde_Date */ var $end; /** * The end time of the recurrence interval. * * @var Horde_Date */ var $recurEnd = null; /** * The duration of this event in minutes * * @var integer */ var $durMin = 0; /** * Number of minutes before the event starts to trigger an alarm. * * @var integer */ var $alarm = 0; /** * The type of recurrence this event follows. KRONOLITH_RECUR_* constant. * * @var integer */ var $recurType = KRONOLITH_RECUR_NONE; /** * TODO The length of time between recurrences in seconds? * * @var integer */ var $recurInterval = null; /** * Any additional recurrence data. * * @var integer */ var $recurData = null; /** * The identifier of the calender this event exists on. * * @var string */ var $_calendar; /** * The VarRenderer class to use for printing select elements. * * @var Horde_UI_VarRenderer */ var $_varRenderer; /** * Constructor * * @param Kronolith_Driver $driver The backend driver that this * event is stored in. * @param Kronolith_Event $eventObject Backend specific event object * that this will represent. */ function Kronolith_Event(&$driver, $eventObject = null) { $this->_calendar = $driver->getCalendar(); if (isset($eventObject)) { $this->fromDriver($eventObject); } } /** * Return 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. */ function &getDriver() { global $kronolith; if ($kronolith->getCalendar() != $this->_calendar) { $kronolith->close(); $kronolith->open($this->_calendar); } return $kronolith; } /** * Export this event in iCalendar format. * * @param Horde_iCalendar &$calendar A Horde_iCalendar object that acts as * a container. * @param Identity $identity The Identity object of the organizer. * * @return Horde_iCalendar_vevent The vEvent object for this event. */ function &toiCalendar(&$calendar, $identity) { global $prefs; // Get a reference to the calendar driver. $kronolith = &$this->getDriver(); $vEvent = &Horde_iCalendar::newComponent('vevent', $calendar); 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', time()); $vEvent->setAttribute('UID', $this->_uid); $vEvent->setAttribute('SUMMARY', $this->title); $vEvent->setAttribute('TRANSP', 'OPAQUE'); $name = $identity->getValue('fullname'); $vEvent->setAttribute('ORGANIZER', 'MAILTO:' . $identity->getValue('from_addr'), empty($name) ? null : array('CN' => $name)); if (!empty($this->description)) { $vEvent->setAttribute('DESCRIPTION', $this->description); } $categories = $this->getCategory(); if (!empty($categories)) { $vEvent->setAttribute('CATEGORIES', $categories); } if (!empty($this->location)) { $vEvent->setAttribute('LOCATION', $this->location); } // Attendees. foreach ($this->getAttendees() as $email => $status) { $params = array(); switch ($status['attendance']) { case KRONOLITH_PART_REQUIRED: $params['ROLE'] = 'REQ-PARTICIPANT'; break; case KRONOLITH_PART_OPTIONAL: $params['ROLE'] = 'OPT-PARTICIPANT'; break; case KRONOLITH_PART_NONE: $params['ROLE'] = 'NON-PARTICIPANT'; break; } switch ($status['response']) { case KRONOLITH_RESPONSE_NONE: $params['PARTSTAT'] = 'NEEDS-ACTION'; $params['RSVP'] = 'TRUE'; break; case KRONOLITH_RESPONSE_ACCEPTED: $params['PARTSTAT'] = 'ACCEPTED'; break; case KRONOLITH_RESPONSE_DECLINED: $params['PARTSTAT'] = 'DECLINED'; break; case KRONOLITH_RESPONSE_TENTATIVE: $params['PARTSTAT'] = 'TENTATIVE'; break; } $vEvent->setAttribute('ATTENDEE', 'MAILTO:' . $email, $params); } // Alarm. if (!empty($this->alarm)) { $vEvent->setAttribute('AALARM', $this->start->timestamp() - $this->alarm * 60); } // Recurrence. if ($this->recurType) { $rrule = ''; switch ($this->recurType) { case KRONOLITH_RECUR_NONE: break; case KRONOLITH_RECUR_DAILY: $rrule = 'FREQ=DAILY;INTERVAL=' . $this->recurInterval; break; case KRONOLITH_RECUR_WEEKLY: $rrule = 'FREQ=WEEKLY;INTERVAL=' . $this->recurInterval . ';BYDAY='; $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); for ($i = $flag = 0; $i <= 7 ; ++$i) { if ($this->recurOnDay(pow(2, $i))) { if ($flag) { $rrule .= ','; } $rrule .= $vcaldays[$i]; $flag = true; } } break; case KRONOLITH_RECUR_DAY_OF_MONTH: $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval; break; case KRONOLITH_RECUR_WEEK_OF_MONTH: $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval . ';BYDAY=' . ($this->start->weekOfYear() - date('W', mktime(0, 0, 0, $this->start->month, 1, $this->start->year)) + 1) . $vcaldays[$this->start->dayOfWeek()]; break; case KRONOLITH_RECUR_YEARLY: $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval; break; } if (!empty($rrule) && !empty($this->recurEnd)) { $rrule .= ';UNTIL=' . $calendar->_exportDate($this->recurEnd); } if (!empty($rrule)) { $vEvent->setAttribute('RRULE', $rrule); } } // Exceptions. $exceptions = $this->getExceptions(); $exdates = array(); foreach ($exceptions as $exception) { if (!empty($exception)) { list($year, $month, $mday) = sscanf($exception, '%04d%02d%02d'); $exdates[] = &new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $mday)); } } if (count($exdates)) { $vEvent->setAttribute('EXDATE', $exdates, array('VALUE' => 'DATE')); } return $vEvent; } /** * Update the properties of this event from a * Horde_iCalendar_vevent object. * * @param Horde_iCalendar_vevent $vEvent The iCalendar data to update * from. */ function fromiCalendar($vEvent) { // Unique ID. $uid = $vEvent->getAttribute('UID'); if (!empty($uid) && !is_a($uid, 'PEAR_Error')) { $this->setUID($uid); } // Title, category and description. $title = $vEvent->getAttribute('SUMMARY'); if (!is_array($title) && !is_a($title, 'PEAR_Error')) { $this->setTitle($title); } $categories = $vEvent->getAttribute('CATEGORIES'); if (!is_array($categories) && !is_a($categories, 'PEAR_Error')) { // The CATEGORY attribute is delimited by commas, so split // it up. $categories = explode(',', $categories); // We only support one category per event right now, so // arbitrarily take the last one. foreach ($categories as $category) { $this->setCategory($category); } } $desc = $vEvent->getAttribute('DESCRIPTION'); if (!is_array($desc) && !is_a($desc, 'PEAR_Error')) { $this->setDescription($desc); } // Location. $location = $vEvent->getAttribute('LOCATION'); if (!is_array($location) && !is_a($location, 'PEAR_Error')) { $this->setLocation($location); } // Start and end date. $start = $vEvent->getAttribute('DTSTART'); if (!is_array($start) && !is_a($start, 'PEAR_Error')) { $this->start = &new Horde_Date($start); } elseif (is_array($start) && !is_a($start, 'PEAR_Error')) { $this->start = &new Horde_Date(array('year' => (int)$start['year'], 'month' => (int)$start['month'], 'mday' => (int)$start['mday'])); } $end = $vEvent->getAttribute('DTEND'); if (!is_array($end) && !is_a($end, 'PEAR_Error')) { $this->end = &new Horde_Date($end); } elseif (is_array($end) && !is_a($end, 'PEAR_Error')) { $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->timestamp() + $duration); } else { // End date equal to start date as per RFC 2445. $this->end = Util::cloneObject($this->start); } } $alarm = $vEvent->getAttribute('AALARM'); if (!is_array($alarm) && !is_a($alarm, 'PEAR_Error') && intval($alarm)) { $this->alarm = intval(($this->start->timestamp() - $alarm) / 60); } /* 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')) { require_once 'Horde/MIME.php'; 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('MAILTO:', '', $attendee[$i]); $email = MIME::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; } } $this->addAttendee($email, $attendance, $response); } } // Recurrence. $rrule = $vEvent->getAttribute('RRULE'); if (!is_array($rrule) && !is_a($rrule, 'PEAR_Error') && strpos($rrule, '=') !== false) { // Parse the recurrence rule into keys and values. $parts = explode(';', $rrule); foreach ($parts as $part) { list($key, $value) = explode('=', $part, 2); $rdata[$key] = $value; } if (isset($rdata['FREQ'])) { // Always default the recurInterval to 1. $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1); $frequency = String::upper($rdata['FREQ']); switch ($frequency) { case 'DAILY': $this->setRecurType(KRONOLITH_RECUR_DAILY); break; case 'WEEKLY': $this->setRecurType(KRONOLITH_RECUR_WEEKLY); if (isset($rdata['BYDAY'])) { $maskdays = array('SU' => HORDE_DATE_MASK_SUNDAY, 'MO' => HORDE_DATE_MASK_MONDAY, 'TU' => HORDE_DATE_MASK_TUESDAY, 'WE' => HORDE_DATE_MASK_WEDNESDAY, 'TH' => HORDE_DATE_MASK_THURSDAY, 'FR' => HORDE_DATE_MASK_FRIDAY, 'SA' => HORDE_DATE_MASK_SATURDAY); $days = explode(',', $rdata['BYDAY']); $mask = 0; foreach ($days as $day) { $mask |= $maskdays[$day]; } $this->setRecurOnDay($mask); } else { // Recur on the day of the week of the // original recurrence. $maskdays = array(HORDE_DATE_SUNDAY => HORDE_DATE_MASK_SUNDAY, HORDE_DATE_MONDAY => HORDE_DATE_MASK_MONDAY, HORDE_DATE_TUESDAY => HORDE_DATE_MASK_TUESDAY, HORDE_DATE_WEDNESDAY => HORDE_DATE_MASK_WEDNESDAY, HORDE_DATE_THURSDAY => HORDE_DATE_MASK_THURSDAY, HORDE_DATE_FRIDAY => HORDE_DATE_MASK_FRIDAY, HORDE_DATE_SATURDAY => HORDE_DATE_MASK_SATURDAY); $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]); } break; case 'MONTHLY': if (isset($rdata['BYDAY'])) { $this->setRecurType(KRONOLITH_RECUR_WEEK_OF_MONTH); } else { $this->setRecurType(KRONOLITH_RECUR_DAY_OF_MONTH); } break; case 'YEARLY': $this->setRecurType(KRONOLITH_RECUR_YEARLY); break; } if (isset($rdata['UNTIL'])) { list($year, $month, $mday) = sscanf($rdata['UNTIL'], '%04d%02d%02d'); $this->recurEnd = &new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $mday)); } } else { // No recurrence data - event does not recur. $this->setRecurType(KRONOLITH_RECUR_NONE); } } // Exceptions. $exdates = $vEvent->getAttribute('EXDATE'); if (is_array($exdates)) { foreach ($exdates as $exdate) { if (is_array($exdate)) { $this->addException((int)$exdate['year'], (int)$exdate['month'], (int)$exdate['mday']); } } } $this->initialized = true; } /** * Import the values for this event from an array of values. * * @param array $hash Array containing all the values. */ function fromHash($hash) { // See if it's a new event. if (is_null($this->getId())) { $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['category'])) { global $cManager; $categories = $cManager->get(); if (!in_array($hash['category'], $categories)) { $cManager->add($hash['category']); } $this->setCategory($hash['category']); } if (!empty($hash['location'])) { $this->setLocation($hash['location']); } if (!empty($hash['keywords'])) { $this->setKeywords(explode(',', $hash['keywords'])); } 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']) && empty($hash['end_date'])) { $hash['end_date'] = $hash['start_date']; } if (!empty($hash['duration'])) { $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->timestamp() + $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) { $this->setAlarm(($this->start->timestamp() - mktime($time[0], $time[1], $time[2], $date[1], $date[2], $date[0])) / 60); } } if (!empty($hash['recur_type'])) { $this->setRecurType($hash['recur_type']); if (!empty($hash['recur_end_date'])) { $date = explode('-', $hash['recur_end_date']); $this->recurEnd = &new Horde_Date(array('year' => $date[0], 'month' => $date[1], 'mday' => $date[2])); } if (!empty($hash['recur_interval'])) { $this->setRecurInterval($hash['recur_interval']); } if (!empty($hash['recur_data'])) { $this->setRecurOnDay($hash['recur_data']); } } $this->initialized = true; } /** * Save changes to this event. * * @return mixed True or a PEAR_Error on failure. */ function save() { if (!$this->isInitialized()) { return PEAR::raiseError('Event not yet initialized'); } $this->toDriver(); $driver = &$this->getDriver(); return $driver->saveEvent($this); } /** * Add an exception to a recurring event. * * @param integer $year The year of the execption. * @param integer $month The month of the execption. * @param integer $mday The day of the month of the exception. */ function addException($year, $month, $mday) { $this->exceptions[] = sprintf('%04d%02d%02d', $year, $month, $mday); } /** * Check if an exception exists for a given reccurence of an event. * * @param integer $year The year of the reucrance. * @param integer $month The month of the reucrance. * @param integer $mday The day of the month of the reucrance. * * @return boolean True if an exception exists for the given date. */ function hasException($year, $month, $mday) { return in_array(sprintf('%04d%02d%02d', $year, $month, $mday), $this->getExceptions()); } /** * Retrieve all the exceptions for this event. * * @return array Array containing the dates of all the exceptions in * YYYYMMDD form. */ function getExceptions() { return $this->exceptions; } /** * TODO */ function isInitialized() { return $this->initialized; } /** * TODO */ function isStored() { return $this->stored; } /** * Check if the current event is already present in the calendar. * Do the check based on the uid. * * @return boolean True if event exists, false otherwise. */ function exists() { if (!isset($this->_uid) || !isset($this->_calendar)) { return false; } $eventID = $GLOBALS['kronolith']->exists($this->_uid, $this->_calendar); if (is_a($eventID, 'PEAR_Error') || !$eventID) { return false; } else { $this->eventID = $eventID; return true; } } /** * Check if this event recurs on a given day of the week. * * @param integer $dayMask A mask specifying the day(s) to check. * * @return boolean True if this event recurs on the given day(s). */ function recurOnDay($dayMask) { return ($this->recurData & $dayMask); } /** * Specify the days this event recurs on. * * @param integer $dayMask A mask specifying the day(s) to recur on. */ function setRecurOnDay($dayMask) { $this->recurData = $dayMask; } /** * Return the days this event recurs on. * * @return integer A mask specifying the day(s) this event recurs on. */ function getRecurOnDays() { return $this->recurData; } function getRecurType() { return $this->recurType; } function hasRecurType($recurrence) { return ($recurrence === $this->recurType); } function setRecurType($recurrence) { $this->recurType = $recurrence; } /** * Set the length of time between recurrences of this event. * * @param integer $interval The number of seconds between recurrences. */ function setRecurInterval($interval) { if ($interval > 0) { $this->recurInterval = $interval; } } /** * Retrieve the length of time between recurrences fo this event. * * @return integer The number of seconds between recurrences. */ function getRecurInterval() { return $this->recurInterval; } /** * Retrieve the locally unique identifier for this event. * * @return string The local identifier for this event. */ function getId() { return $this->eventID; } /** * Set the locally unique identifier for this event. * * @param string $eventId The local identifier for this event. */ function setId($eventId) { if (substr($eventId, 0, 10) == 'kronolith:') { $eventId = substr($eventId, 10); } $this->eventID = $eventId; } /** * Retrieve the global UID for this event. * * @return string The global UID for this event. */ function getUID() { return $this->_uid; } /** * Set the global UID for this event. * * @param string $uid The global UID for this event. */ function setUID($uid) { $this->_uid = $uid; } /** * Retrieve the id of the user who created the event. * * @return string The creator id */ function getCreatorId() { return !empty($this->creatorID) ? $this->creatorID : Auth::getAuth(); } /** * Set the id of the creator of the event. * * @param string $creatorID The user id for the user who created the event */ function setCreatorId($creatorID) { $this->creatorID = $creatorID; } /** * Retrieve the title of this event. * * @return string The title of this event. */ function getTitle() { if (isset($this->taskID) || isset($this->contactID) || isset($this->remoteCal)) { return !empty($this->title) ? $this->title : _("[Unnamed event]"); } if (!$this->isInitialized()) { return ''; } if (isset($GLOBALS['all_calendars'][$this->getCalendar()]) && !is_a($share = $GLOBALS['all_calendars'][$this->getCalendar()], 'PEAR_Error') && $share->hasPermission(Auth::getAuth(), PERMS_READ, $this->getCreatorId())) { return !empty($this->title) ? $this->title : _("[Unnamed event]"); } else { global $prefs; return sprintf(_("Event from %s to %s"), date($prefs->getValue('twentyFour') ? 'G:i' : 'g:ia', $this->start->timestamp()), date($prefs->getValue('twentyFour') ? 'G:i' : 'g:ia', $this->end->timestamp())); } } /** * Set the title of this event. * * @param string The new title for this event. */ function setTitle($title) { $this->title = $title; } /** * Retieve the description of this event. * * @return string The description of this event. */ function getDescription() { return $this->description; } /** * Set the description of this event. * * @param string $description The new description for this event. */ function setDescription($description) { $this->description = $description; } /** * Set the category this event belongs to. * * @param integer $category The identifier of the category this event * belongs to. */ function setCategory($category) { $this->category = $category; } /** * Retrieve the category this event belongs to. * * @return integer The identifier of the category this event belongs to. */ function getCategory() { return $this->category; } /** * Set the location this event occurs at. * * @param string $location The new location for this event. */ function setLocation($location) { $this->location = $location; } /** * Retrieve the location this event occurs at. * * @return string The location of this event. */ function getLocation() { return $this->location; } /** * Retrieve the event status. * * @return integer The status of this event. */ 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. */ function hasStatus($status) { return ($status == $this->status); } /** * Set the status of this event. * * @param integer $status The new event status. */ function setStatus($status) { $this->status = $status; } /** * Sets the entire attendee array. * * @param array $attendees The new attendees array. This should be of the * correct format to avoid driver problems. */ function setAttendees($attendees) { $this->attendees = $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. */ function addAttendee($email, $attendance, $response) { if ($attendance == KRONOLITH_PART_IGNORE) { if (isset($this->attendees[$email])) { $attendance = $this->attendees[$email]['attendance']; } else { $attendance = KRONOLITH_PART_REQUIRED; } } $this->attendees[$email] = array( 'attendance' => $attendance, 'response' => $response ); } /** * Removes the specified attendee from the current event. * * @param string $email The email address of the attendee. */ function removeAttendee($email) { if (isset($this->attendees[$email])) { unset($this->attendees[$email]); } } /** * Returns the entire attendees array. * * @return array A copy of the attendees array. */ 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. */ function hasAttendee($email) { return isset($this->attendees[$email]); } function setKeywords($keywords) { $this->keywords = $keywords; } function getKeywords() { return $this->keywords; } function hasKeyword($keyword) { return in_array($keyword, $this->keywords); } function hasRecurEnd() { return (isset($this->recurEnd) && isset($this->recurEnd->year) && $this->recurEnd->year != 9999); } function isAllDay() { return ($this->start->hour == 0 && $this->start->min == 0 && $this->start->sec == 0 && $this->end->hour == 0 && $this->end->min == 0 && $this->start->sec == 0 && ($this->end->mday > $this->start->mday || $this->end->month > $this->start->month || $this->end->year > $this->start->year)); } function setAlarm($alarm) { $this->alarm = $alarm; } function getAlarm() { return $this->alarm; } function readForm() { global $prefs, $cManager; // See if it's a new event. if (!$this->isInitialized()) { $this->setCreatorId(Auth::getAuth()); } // Basic fields. $this->setTitle(Util::getFormData('title', $this->title)); $this->setDescription(Util::getFormData('description', $this->description)); $this->setLocation(Util::getFormData('location', $this->location)); $this->setKeywords(Util::getFormData('keywords', $this->keywords)); // Category. if ($new_category = Util::getFormData('new_category')) { $new_category = $cManager->add($new_category); if ($new_category) { $category = $new_category; } } else { $category = Util::getFormData('category', $this->category); } $this->setCategory($category); // Status. $this->setStatus(Util::getFormData('status', $this->status)); // Attendees. if (isset($_SESSION['attendees']) && is_array($_SESSION['attendees'])) { $this->setAttendees($_SESSION['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)); $this->start->correct(); 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)); $this->end->correct(); } 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)); $this->end->correct(); if ($this->end->timestamp() < $this->start->timestamp()) { $this->end = Util::cloneObject($this->start); } } // Alarm. if (Util::getFormData('alarm') == 1) { $this->setAlarm(Util::getFormData('alarm_value') * Util::getFormData('alarm_unit')); } else { $this->setAlarm(0); } // Recurrence. $recur = Util::getFormData('recur'); if (!is_null($recur) && $recur !== '') { if (Util::getFormData('recur_enddate_type') == 'none') { $this->recurEnd = null; } else { $recur_enddate = Util::getFormData('recur_enddate'); $recur_enddate_year = $recur_enddate['year']; $recur_enddate_month = $recur_enddate['month']; $recur_enddate_day = $recur_enddate['day']; $this->recurEnd = &new Horde_Date(array('hour' => 1, 'min' => 1, 'sec' => 1, 'month' => $recur_enddate_month, 'mday' => $recur_enddate_day, 'year' => $recur_enddate_year)); $this->recurEnd->correct(); } $this->setRecurType($recur); switch ($recur) { case KRONOLITH_RECUR_DAILY: $this->setRecurInterval(Util::getFormData('recur_daily_interval', 1)); break; case KRONOLITH_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->setRecurInterval(Util::getFormData('recur_weekly_interval', 1)); $this->setRecurOnDay($weekdays); break; case KRONOLITH_RECUR_DAY_OF_MONTH: $this->setRecurInterval(Util::getFormData('recur_day_of_month_interval', 1)); break; case KRONOLITH_RECUR_WEEK_OF_MONTH: $this->setRecurInterval(Util::getFormData('recur_week_of_month_interval', 1)); break; case KRONOLITH_RECUR_YEARLY: $this->setRecurInterval(Util::getFormData('recur_yearly_interval', 1)); break; } } $this->initialized = true; } function getDuration() { static $duration = null; if (isset($duration)) { return $duration; } if ($this->isInitialized()) { $dur_day_match = $this->end->mday - $this->start->mday; $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 = 0; $dur_hour_match = 23; $dur_min_match = 60; $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; } function html($property) { global $prefs; $options = array(); $attributes = ''; $sel = false; switch ($property) { case 'start[year]': return ''; case 'start[month]': $sel = $this->start->month; for ($i = 1; $i < 13; ++$i) { //edit by Tommy //$options[$i] = strftime('%b', mktime(1, 1, 1, $i, 1)); $options[$i] = strftime('%m', mktime(1, 1, 1, $i, 1)); } $attributes = ' onchange="' . $this->js($property) . '"'; break; case 'start[day]': $sel = $this->start->mday; for ($i = 1; $i < 32; ++$i) { $options[$i] = $i; } $attributes = ' onchange="' . $this->js($property) . '"'; break; case 'start_hour': $sel = (int)date($prefs->getValue('twentyFour') ? 'G' : 'g', $this->start->timestamp()); $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.event.whole_day.checked = false; updateEndDate();"'; 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.event.whole_day.checked = false; updateEndDate();"'; break; case 'end[year]': return ''; case 'end[month]': $sel = $this->isInitialized() ? $this->end->month : $this->start->month; for ($i = 1; $i < 13; ++$i) { //edit by Tommy //$options[$i] = strftime('%b', mktime(1, 1, 1, $i, 1)); $options[$i] = strftime('%m', mktime(1, 1, 1, $i, 1)); } $attributes = ' onchange="' . $this->js($property) . '"'; 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) . '"'; break; case 'end_hour': $sel = $this->isInitialized() ? (int)date($prefs->getValue('twentyFour') ? 'G' : 'g', $this->end->timestamp()) : (int)date($prefs->getValue('twentyFour') ? 'G' : 'g', $this->start->timestamp()) + 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="updateDuration(); document.event.end_or_dur[0].checked = true"'; 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="updateDuration(); document.event.end_or_dur[0].checked = true"'; break; case 'dur_day': $dur = $this->getDuration(); $sel = $dur->day; for ($i = 0; $i < 366; ++$i) { $options[$i] = $i; } $attributes = ' onchange="document.event.whole_day.checked = false; updateEndDate(); document.event.end_or_dur[1].checked = true;"'; break; case 'dur_hour': $dur = $this->getDuration(); $sel = $dur->hour; for ($i = 0; $i < 24; ++$i) { $options[$i] = $i; } $attributes = ' onchange="document.event.whole_day.checked = false; updateEndDate(); document.event.end_or_dur[1].checked = true;"'; 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="document.event.whole_day.checked = false;updateEndDate();document.event.end_or_dur[1].checked=true"'; break; case 'recur_enddate[year]': if ($this->isInitialized()) { $end = $this->hasRecurEnd() ? $this->recurEnd->year : $this->end->year; } else { $end = $this->start->year; } return ''; case 'recur_enddate[month]': if ($this->isInitialized()) { $sel = $this->hasRecurEnd() ? $this->recurEnd->month : $this->end->month; } else { $sel = $this->start->month; } for ($i = 1; $i < 13; ++$i) { //edit by Tommy //$options[$i] = strftime('%b', mktime(1, 1, 1, $i, 1)); $options[$i] = strftime('%m', mktime(1, 1, 1, $i, 1)); } $attributes = ' onchange="' . $this->js($property) . '"'; break; case 'recur_enddate[day]': if ($this->isInitialized()) { $sel = $this->hasRecurEnd() ? $this->recurEnd->mday : $this->end->mday; } else { $sel = $this->start->mday; } for ($i = 1; $i < 32; ++$i) { $options[$i] = $i; } $attributes = ' onchange="' . $this->js($property) . '"'; break; } if (!$this->_varRenderer) { require_once 'Horde/UI/VarRenderer.php'; $this->_varRenderer = &Horde_UI_VarRenderer::factory('html'); } return ''; } function js($property) { switch ($property) { case 'start[month]': case 'start[year]': case 'start[day]': case 'start': return 'updateWday(\'start_wday\'); document.event.whole_day.checked = false; updateEndDate();'; case 'end[month]': case 'end[year]': case 'end[day]': case 'end': return 'updateWday(\'end_wday\'); updateDuration(); document.event.end_or_dur[0].checked = true;'; case 'recur_enddate[month]': case 'recur_enddate[year]': case 'recur_enddate[day]': case 'recur_enddate': return 'updateWday(\'recur_end_wday\'); document.event.recur_enddate_type[1].checked = true;'; } } function getLink($timestamp = 0, $icons = true, $from_url = null) { global $print_view, $prefs, $registry, $cManager; if ($from_url === null) { $from_url = Horde::selfUrl(true, false, true); } if (isset($this->remoteCal)) { $share = false; $url = Util::addParameter('viewevent.php', array('eventID' => $this->eventID, 'calendar' => '**remote', 'remoteCal' => $this->remoteCal, 'timestamp' => $timestamp, 'url' => $from_url)); $url = Horde::applicationUrl($url); $link = Horde::linkTooltip($url, $this->getTitle(), $this->getStatusClass(), '', '', $this->getTooltip()); } else { $share =& $GLOBALS['all_calendars'][$this->getCalendar()]; if (is_a($share, 'PEAR_Error') || !$share->hasPermission(Auth::getAuth(), PERMS_READ, $this->getCreatorId())) { $link = ''; } elseif (isset($this->eventID)) { $url = Util::addParameter('viewevent.php', array('eventID' => $this->eventID, 'calendar' => $this->getCalendar(), 'timestamp' => $timestamp, 'url' => $from_url)); $url = Horde::applicationUrl($url); $link = Horde::linkTooltip($url, $this->title, $this->getStatusClass(), '', '', $this->getTooltip()); } elseif (isset($this->taskID)) { $link = $GLOBALS['registry']->link('tasks/show', array('task' => $this->taskID, 'tasklist' => $this->tasklistID)); $link = Horde::link(Horde::url($link), '', 'event'); } else { $link = ''; } } $link .= @htmlspecialchars($this->getTitle(), ENT_QUOTES, NLS::getCharset()); if (isset($this->remoteCal) || (!is_a($share, 'PEAR_Error') && $share->hasPermission(Auth::getAuth(), PERMS_READ, $this->getCreatorId()) && (isset($this->eventID) || isset($this->taskID)))) { $link .= ''; } if ($icons && $prefs->getValue('show_icons')) { $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_small.png', $title, array('title' => $title)); } if (!$this->hasRecurType(KRONOLITH_RECUR_NONE)) { $title = Kronolith::recurToString($this->recurType); $status .= Horde::img('recur.png', $title, array('title' => $title)); } 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)); } if (!empty($status)) { $link .= ' ' . $status; } if ($print_view || is_a($share, 'PEAR_Error') || !isset($this->eventID)) { return $link; } if (isset($this->remoteCal) || $share->hasPermission(Auth::getAuth(), PERMS_EDIT, $this->getCreatorId())) { $url = Util::addParameter('editevent.php', array('eventID' => $this->eventID, 'calendar' => isset($this->remoteCal) ? '**remote' : $this->getCalendar(), 'timestamp' => $timestamp, 'url' => $from_url)); if (isset($this->remoteCal)) { $url = Util::addParameter($url, 'remoteCal', $this->remoteCal); } $link .= ' ' . Horde::link(Horde::applicationUrl($url), sprintf(_("Edit %s"), $this->title)) . Horde::img('edit-small.png', _("Edit"), '', $registry->getImageDir('horde')) . ''; } if (!isset($this->remoteCal) && $share->hasPermission(Auth::getAuth(), PERMS_DELETE, $this->getCreatorId())) { $url = Util::addParameter('delevent.php', array('eventID' => $this->eventID, 'calendar' => $this->getCalendar(), 'timestamp' => $timestamp, 'url' => $from_url)); $link .= ' ' . Horde::link(Horde::applicationUrl($url), sprintf(_("Delete %s"), $this->title)) . Horde::img('delete-small.png', _("Delete"), '', $registry->getImageDir('horde')) . ''; } } return $link; } /** * @return string A tooltip for quick descriptions of this event. */ function getTooltip() { global $prefs; $tooltip = ''; if ($this->isAllDay()) { $tooltip = _("All day"); } elseif (($cmp = $this->start->compareDate($this->end)) > 0) { $df = $prefs->getValue('date_format'); if ($cmp > 0) { $tooltip = strftime($df, $this->end->timestamp()) . '-' . strftime($df, $this->start->timestamp()); } else { $tooltip = strftime($df, $this->start->timestamp()) . '-' . strftime($df, $this->end->timestamp()); } } else { $tooltip = date($prefs->getValue('twentyFour') ? 'G:i' : 'g:ia', $this->start->timestamp()) . '-' . date($prefs->getValue('twentyFour') ? 'G:i' : 'g:ia', $this->end->timestamp()); } $tooltip .= "\n" . sprintf(_("Owner: %s"), ($this->getCreatorId() == Auth::getAuth() ? _("Me") : Kronolith::getUserName($this->getCreatorId()))); if ($this->location) { $tooltip .= "\n" . _("Location") . ': ' . $this->location; } if ($this->description) { $tooltip .= "\n\n" . $this->description; } return $tooltip; } /** * @return string The CSS class for the event based on its status. */ 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'; } /** * Find the next recurrence of this event that's after $afterDate. * * @param Horde_Date $afterDate Return events after this date. * * @return Horde_Date|boolean The date of the next recurrence or false * if the event does not recur after * $afterDate. */ function nextRecurrence($afterDate) { $after = &new Horde_Date($afterDate); $after->correct(); if ($this->start->compareDate($after) >= 0) { return new Horde_Date($this->start); } if ($this->recurInterval == 0) { return false; } if ($this->hasRecurEnd()) { $this->recurEnd->hour = 23; $this->recurEnd->min = 59; $this->recurEnd->sec = 59; } switch ($this->getRecurType()) { case KRONOLITH_RECUR_DAILY: $diff = Date_Calc::dateDiff($this->start->mday, $this->start->month, $this->start->year, $after->mday, $after->month, $after->year); $recur = ceil($diff / $this->recurInterval) * $this->recurInterval; $next = $this->start; list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::daysToDate(Date_Calc::dateToDays($next->mday, $next->month, $next->year) + $recur, '%e/%m/%Y')); if ((!$this->hasRecurEnd() || $next->compareDate($this->recurEnd) <= 0) && $next->compareDate($after) >= 0) { return new Horde_Date($next); } break; case KRONOLITH_RECUR_WEEKLY: if (empty($this->recurData)) { return false; } list($start_week->mday, $start_week->month, $start_week->year) = explode('/', Date_Calc::beginOfWeek($this->start->mday, $this->start->month, $this->start->year, '%e/%m/%Y')); $start_week->hour = $this->start->hour; $start_week->min = $this->start->min; $start_week->sec = $this->start->sec; list($after_week->mday, $after_week->month, $after_week->year) = explode('/', Date_Calc::beginOfWeek($after->mday, $after->month, $after->year, '%e/%m/%Y')); $after_week_end = &new Horde_Date($after_week); $after_week_end->mday += 7; $after_week_end->correct(); $diff = Date_Calc::dateDiff($start_week->mday, $start_week->month, $start_week->year, $after_week->mday, $after_week->month, $after_week->year); $recur = $diff + ($diff % ($this->recurInterval * 7)); $next = $start_week; list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::daysToDate(Date_Calc::dateToDays($next->mday, $next->month, $next->year) + $recur, '%e/%m/%Y')); $next = &new Horde_Date($next); while ($next->compareDate($after) < 0 && $next->compareDate($after_week_end) < 0) { ++$next->mday; $next->correct(); } if (!$this->hasRecurEnd() || $next->compareDate($this->recurEnd) <= 0) { if ($next->compareDate($after_week_end) >= 0) { return $this->nextRecurrence($after_week_end); } while (!$this->recurOnDay((int)pow(2, $next->dayOfWeek())) && $next->compareDate($after_week_end) < 0) { ++$next->mday; $next->correct(); } if (!$this->hasRecurEnd() || $next->compareDate($this->recurEnd) <= 0) { if ($next->compareDate($after_week_end) >= 0) { return $this->nextRecurrence($after_week_end); } else { return $next; } } } break; case KRONOLITH_RECUR_DAY_OF_MONTH: $start = Util::cloneObject(new Horde_Date($this->start)); if ($after->compareDate($start) < 0) { $after = $start; } // If we're starting past this month's recurrence of the // event, look in the next month on the day the event // recurs. if ($after->mday > $start->mday) { ++$after->month; $after->mday = $start->mday; $after->correct(); } // Adjust $start to be the first match. $offset = ($after->month - $start->month) + ($after->year - $start->year) * 12; $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; $start->month += $offset; do { // Don't correct for day overflow; we just skip // February 30th, for example. $start->correct(HORDE_DATE_MASK_MONTH); if ($start->isValid()) { return $start; } // If the interval is 12, and the date isn't valid, // then we need to see if February 29th is an // option. If not, then the event will _never_ recur, // and we need to stop checking to avoid an infinite // loop. if ($this->recurInterval == 12 && ($start->month != 2 || $start->mday > 29)) { return false; } // Add the recurrence interval. $start->month += $this->recurInterval; // Bail if we've gone past the end of recurrence. if ($this->hasRecurEnd() && $this->recurEnd->compareDate($start) < 0) { return false; } } while (true); break; case KRONOLITH_RECUR_WEEK_OF_MONTH: // Start with the start date of the event. $estart = Util::cloneObject(new Horde_Date($this->start)); // What day of the week, and week of the month, do we // recur on? $nth = ceil($this->start->mday / 7); $weekday = $estart->dayOfWeek(); // Adjust $estart to be the first candidate. $offset = ($after->month - $estart->month) + ($after->year - $estart->year) * 12; $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; // Adjust our working date until it's after $after. $estart->month += $offset - $this->recurInterval; do { $estart->month += $this->recurInterval; $estart->correct(); $next = &Util::cloneObject($estart); $next->setNthWeekday($weekday, $nth); if ($next->compareDate($after) < 0) { // We haven't made it past $after yet, try // again. continue; } if ($this->hasRecurEnd() && $next->compareDate($this->recurEnd) > 0) { // We've gone past the end of recurrence; we can // give up now. return false; } // We have a candidate to return. break; } while (true); return $next; case KRONOLITH_RECUR_YEARLY: // Start with the start date of the event. $estart = Util::cloneObject(new Horde_Date($this->start)); // We probably need a seperate case here for February 29th // and leap years, but until we're absolutely sure it's a // bug, we'll leave it out. if ($after->month > $estart->month || ($after->month == $estart->month && $after->mday > $estart->mday)) { ++$after->year; $after->month = $estart->month; $after->mday = $estart->mday; } // Adjust $estart to be the first candidate. $offset = $after->year - $estart->year; if ($offset > 0) { $offset = (($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; $estart->year += $offset; } // We've gone past the end of recurrence; give up. if ($this->hasRecurEnd() && $this->recurEnd->compareDate($estart) < 0) { return false; } return $estart; } // We didn't find anything, the recurType was bad, or // something else went wrong - return false. return false; } function hasActiveRecurrence() { if (!$this->hasRecurEnd()) { return true; } $next = $this->nextRecurrence(Util::cloneObject($this->start)); while (is_object($next)) { if (!$this->hasException($next->year, $next->month, $next->mday)) { return true; } $next = $this->nextRecurrence(array('year' => $next->year, 'month' => $next->month, 'mday' => $next->mday + 1, 'hour' => $next->hour, 'min' => $next->min, 'sec' => $next->sec)); } return false; } function getCalendar() { return $this->_calendar; } function setCalendar($calendar) { $this->_calendar = $calendar; } }