⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.97
Server IP:
41.128.143.86
Server:
Linux host.raqmix.cloud 6.8.0-1025-azure #30~22.04.1-Ubuntu SMP Wed Mar 12 15:28:20 UTC 2025 x86_64
Server Software:
Apache
PHP Version:
8.3.23
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
usr
/
share
/
psa-horde
/
kronolith
/
lib
/
Edit File: Event.php
<?php /** * Kronolith_Event defines a generic API for events. * * Copyright 1999-2017 Horde LLC (http://www.horde.org/) * * See the enclosed file COPYING for license information (GPL). If you * did not receive this file, see http://www.horde.org/licenses/gpl. * * @author Chuck Hagenbuch <chuck@horde.org> * @author Jan Schneider <jan@horde.org> * @package Kronolith */ abstract 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 */ protected $_id = null; /** * The UID for this event. * * @var string */ public $uid = null; /** * The iCalendar SEQUENCE for this event. * * @var integer */ public $sequence = null; /** * The user id of the creator of the event. * * @var string */ protected $_creator = null; /** * The title of this event. * * For displaying in the interface use getTitle() instead. * * @var string */ public $title = ''; /** * The location this event occurs at. * * @var string */ public $location = ''; /** * The timezone of this event. * * @var string */ public $timezone; /** * The status of this event. * * @var integer */ public $status = Kronolith::STATUS_CONFIRMED; /** * URL to an icon of this event. * * @var string */ public $icon = ''; /** * The description for this event. * * @var string */ public $description = ''; /** * URL of this event. * * @var string */ public $url = ''; /** * Whether the event is private. * * @var boolean */ public $private = false; /** * Event tags from the storage backend (e.g. Kolab) * * @var array */ protected $_internaltags; /** * This tag's events. * * @var array|string */ protected $_tags = null; /** * Geolocation * * @var array */ protected $_geoLocation; /** * Whether this is the event on the first day of a multi-day event. * * @var boolen */ public $first = true; /** * Whether this is the event on the last day of a multi-day event. * * @var boolen */ public $last = true; /** * 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(); /** * All resources of this event. * * This is an associative array where keys are resource uids, values are * associative arrays with keys attendance and response. * * @var array */ protected $_resources = array(); /** * The start time of the event. * * @var Horde_Date */ public $start; /** * The end time of the event. * * @var Horde_Date */ public $end; /** * The original start time of the event. * * This may differ from $start on multi-day events where $start is the * start time on the current day. For recurring events this is the start * time of the current recurrence. * * @var Horde_Date */ protected $_originalStart; /** * The original end time of the event. * * @see $_originalStart for details. * * @var Horde_Date */ protected $_originalEnd; /** * The duration of this event in minutes * * @var integer */ public $durMin = 0; /** * Whether this is an all-day event. * * @var boolean */ public $allday = false; /** * The creation time. * * @see loadHistory() * @var Horde_Date */ public $created; /** * The creator string. * * @see loadHistory() * @var string */ public $createdby; /** * The last modification time. * * @see loadHistory() * @var Horde_Date */ public $modified; /** * The last-modifier string. * * @see loadHistory() * @var string */ public $modifiedby; /** * Number of minutes before the event starts to trigger an alarm. * * @var integer */ public $alarm = 0; /** * Snooze minutes for this event's alarm. * * @see Horde_Alarm::snooze() * * @var integer */ protected $_snooze; /** * The particular alarm methods overridden for this event. * * @var array */ public $methods; /** * The identifier of the calender this event exists on. * * @var string */ public $calendar; /** * The type of the calender this event exists on. * * @var string */ public $calendarType; /** * The HTML background color to be used for this event. * * @var string */ protected $_backgroundColor = '#dddddd'; /** * The HTML foreground color to be used for this event. * * @var string */ protected $_foregroundColor = '#000000'; /** * The VarRenderer class to use for printing select elements. * * @var Horde_Core_Ui_VarRenderer */ private $_varRenderer; /** * The Horde_Date_Recurrence class for this event. * * @var Horde_Date_Recurrence */ public $recurrence; /** * Used in view renderers. * * @var integer */ protected $_overlap; /** * Used in view renderers. * * @var integer */ protected $_indent; /** * Used in view renderers. * * @var integer */ protected $_span; /** * Used in view renderers. * * @var integer */ protected $_rowspan; /** * The baseid. For events that represent exceptions this is the UID of the * original, recurring event. * * @var string */ public $baseid; /** * For exceptions, the date of the original recurring event that this is an * exception for. * * @var Horde_Date */ public $exceptionoriginaldate; /** * The cached event duration, split up in time units. * * @see getDuration() * @var stdClass */ protected $_duration; /** * 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(Kronolith_Driver $driver, $eventObject = null) { $this->calendar = $driver->calendar; list($this->_backgroundColor, $this->_foregroundColor) = $driver->colors(); if (!is_null($eventObject)) { $this->fromDriver($eventObject); } } /** * Retrieves history information for this event from the history backend. */ public function loadHistory() { try { $log = $GLOBALS['injector']->getInstance('Horde_History') ->getHistory('kronolith:' . $this->calendar . ':' . $this->uid); $userId = $GLOBALS['registry']->getAuth(); foreach ($log as $entry) { switch ($entry['action']) { case 'add': $this->created = new Horde_Date($entry['ts']); if ($userId != $entry['who']) { $this->createdby = sprintf(_("by %s"), Kronolith::getUserName($entry['who'])); } else { $this->createdby = _("by me"); } break; case 'modify': if ($this->modified && $this->modified->timestamp() >= $entry['ts']) { break; } $this->modified = new Horde_Date($entry['ts']); if ($userId != $entry['who']) { $this->modifiedby = sprintf(_("by %s"), Kronolith::getUserName($entry['who'])); } else { $this->modifiedby = _("by me"); } break; } } } catch (Horde_Exception $e) { } } /** * Setter. * * Sets the 'id' and 'creator' properties. * * @param string $name Property name. * @param mixed $value Property value. */ public function __set($name, $value) { switch ($name) { case 'id': if (substr($value, 0, 10) == 'kronolith:') { $value = substr($value, 10); } // Fall through. case 'creator': case 'geoLocation': case 'indent': case 'originalStart': case 'originalEnd': case 'overlap': case 'rowspan': case 'span': case 'tags': $this->{'_' . $name} = $value; return; } $trace = debug_backtrace(); trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE); } /** * Getter. * * Returns the 'id' and 'creator' properties. * * @param string $name Property name. * * @return mixed Property value. */ public function __get($name) { switch ($name) { case 'id': case 'indent': case 'overlap': case 'rowspan': case 'span': return $this->{'_' . $name}; case 'creator': if (empty($this->_creator)) { $this->_creator = $GLOBALS['registry']->getAuth(); } return $this->_creator; break; case 'originalStart': if (empty($this->_originalStart)) { $this->_originalStart = $this->start; } return $this->_originalStart; break; case 'originalEnd': if (empty($this->_originalEnd)) { $this->_originalEnd = $this->start; } return $this->_originalEnd; break; case 'tags': if (!isset($this->_tags)) { $this->synchronizeTags(Kronolith::getTagger()->getTags($this->uid, Kronolith_Tagger::TYPE_EVENT)); } return $this->_tags; case 'geoLocation': if (!isset($this->_geoLocation)) { try { $this->_geoLocation = $GLOBALS['injector']->getInstance('Kronolith_Geo')->getLocation($this->id); } catch (Kronolith_Exception $e) {} } return $this->_geoLocation; } $trace = debug_backtrace(); trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE); return null; } /** * 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() { return Kronolith::getDriver(str_replace('Kronolith_Event_', '', get_class($this)), $this->calendar); } /** * Returns the share this event belongs to. * * @return Horde_Share This event's share. * @throws Kronolith_Exception */ public function getShare() { if ($GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $this->calendar) !== false) { return $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $this->calendar)->share(); } throw new LogicException('Share not found'); } /** * 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 = $GLOBALS['registry']->getAuth(); } try { $share = $this->getShare(); } catch (Exception $e) { return false; } return $share->hasPermission($user, $permission, $this->creator); } /** * Saves changes to this event. * * @return integer The event id. * @throws Kronolith_Exception */ public function save() { if (!$this->initialized) { throw new LogicException('Event not yet initialized'); } /* Check for acceptance/denial of this event's resources. */ $accepted_resources = array(); $locks = $GLOBALS['injector']->getInstance('Horde_Lock'); $lock = array(); // Don't waste time with resource acceptance if the status is cancelled, // the event will be removed from the resource calendar anyway. if ($this->status != Kronolith::STATUS_CANCELLED) { foreach (array_keys($this->getResources()) as $id) { /* Get the resource and protect against infinite recursion in * case someone is silly enough to add a resource to it's own * event.*/ $resource = Kronolith::getDriver('Resource')->getResource($id); $rcal = $resource->get('calendar'); if ($rcal == $this->calendar) { continue; } Kronolith::getDriver('Resource')->open($rcal); /* Lock the resource and get the response */ if ($resource->get('response_type') == Kronolith_Resource::RESPONSETYPE_AUTO) { $principle = 'calendar/' . $rcal; $lock[$resource->getId()] = $locks->setLock($GLOBALS['registry']->getAuth(), 'kronolith', $principle, 5, Horde_Lock::TYPE_EXCLUSIVE); $haveLock = true; } else { $haveLock = false; } if ($haveLock && !$lock[$resource->getId()]) { // Already locked // For now, just fail. Not sure how else to capture the // locked resources and notify the user. throw new Kronolith_Exception(sprintf(_("The resource \"%s\" was locked. Please try again."), $resource->get('name'))); } else { $response = $resource->getResponse($this); } /* Remember accepted resources so we can add the event to their * calendars. Otherwise, clear the lock. */ if ($response == Kronolith::RESPONSE_ACCEPTED) { $accepted_resources[] = $resource; } elseif ($haveLock) { $locks->clearLock($lock[$resource->getId()]); } if ($response == Kronolith::RESPONSE_DECLINED && $this->uid) { $r_driver = Kronolith::getDriver('Resource'); $r_event = $r_driver->getByUID($this->uid, array($resource->get('calendar'))); $r_driver->deleteEvent($r_event, true, true); } /* Add the resource to the event */ $this->addResource($resource, $response); } } else { // If event is cancelled, and actually exists, we need to mark it // as cancelled in resource calendar. foreach (array_keys($this->getResources()) as $id) { $resource = Kronolith::getDriver('Resource')->getResource($id); $rcal = $resource->get('calendar'); if ($rcal == $this->calendar) { continue; } try { Kronolith::getDriver('Resource')->open($rcal); $resource->addEvent($this); } catch (Exception $e) { } } } /* Save */ $result = $this->getDriver()->saveEvent($this); /* Now that the event is definitely commited to storage, we can add * the event to each resource that has accepted. Not very efficient, * but this also solves the problem of not having a GUID for the event * until after it's saved. If we add the event to the resources * calendar before it is saved, they will have different GUIDs, and * hence no longer refer to the same event. */ foreach ($accepted_resources as $resource) { $resource->addEvent($this); if ($resource->get('response_type') == Kronolith_Resource::RESPONSETYPE_AUTO) { $locks->clearLock($lock[$resource->getId()]); } } $hordeAlarm = $GLOBALS['injector']->getInstance('Horde_Alarm'); if ($alarm = $this->toAlarm(new Horde_Date($_SERVER['REQUEST_TIME']))) { $hordeAlarm->set($alarm); if ($this->_snooze) { $hordeAlarm->snooze($this->uid, $GLOBALS['registry']->getAuth(), $this->_snooze); } } else { $hordeAlarm->delete($this->uid); } return $result; } /** * Imports a backend specific event object. * * @param mixed $eventObject Backend specific event object that this * object will represent. */ public function fromDriver($event) { } /** * Exports this event in iCalendar format. * * @param Horde_Icalendar $calendar A Horde_Icalendar object that acts as * a container. * * @return array An array of Horde_Icalendar_Vevent objects for this event. */ public function toiCalendar($calendar) { $vEvent = Horde_Icalendar::newComponent('vevent', $calendar); $v1 = $calendar->getAttribute('VERSION') == '1.0'; $vEvents = array(); // For certain recur types, we must output in the event's timezone // so that the BYDAY values do not get out of sync with the UTC // date-time. See Bug: 11339 if ($this->recurs()) { switch ($this->recurrence->getRecurType()) { case Horde_Date_Recurrence::RECUR_WEEKLY: case Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY: case Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY: if (!$this->timezone) { $this->timezone = date_default_timezone_get(); } } } if ($this->isAllDay()) { $vEvent->setAttribute('DTSTART', $this->start, array('VALUE' => 'DATE')); $vEvent->setAttribute('DTEND', $this->end, array('VALUE' => 'DATE')); $vEvent->setAttribute('X-FUNAMBOL-ALLDAY', 1); } else { $this->setTimezone(true); $params = array(); if ($this->timezone) { try { if (!$this->baseid) { $tz = $GLOBALS['injector']->getInstance('Horde_Timezone'); $vEvents[] = $tz->getZone($this->timezone)->toVtimezone(); } $params['TZID'] = $this->timezone; } catch (Horde_Exception $e) { Horde::log('Unable to locate the tz database.', 'WARN'); } } $vEvent->setAttribute('DTSTART', clone $this->start, $params); $vEvent->setAttribute('DTEND', clone $this->end, $params); } $vEvent->setAttribute('DTSTAMP', $_SERVER['REQUEST_TIME']); $vEvent->setAttribute('UID', $this->uid); /* Get the event's create and last modify date. */ $created = $modified = null; try { $history = $GLOBALS['injector']->getInstance('Horde_History'); $created = $history->getActionTimestamp( 'kronolith:' . $this->calendar . ':' . $this->uid, 'add'); $modified = $history->getActionTimestamp( 'kronolith:' . $this->calendar . ':' . $this->uid, 'modify'); /* The history driver returns 0 for not found. If 0 or null does * not matter, strip this. */ if ($created == 0) { $created = null; } if ($modified == 0) { $modified = null; } } catch (Exception $e) { } 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', $this->getTitle()); // Organizer if (count($this->attendees)) { $name = Kronolith::getUserName($this->creator); $email = Kronolith::getUserEmail($this->creator); $params = array(); if ($v1) { $tmp = new Horde_Mail_Rfc822_Address($email); if (!empty($name)) { $tmp->personal = $name; } $email = strval($tmp); } else { if (!empty($name)) { $params['CN'] = $name; } if (!empty($email)) { $email = 'mailto:' . $email; } } $vEvent->setAttribute('ORGANIZER', $email, $params); } if (!$this->isPrivate()) { if (!empty($this->description)) { $vEvent->setAttribute('DESCRIPTION', $this->description); } // Tags if ($this->tags) { $vEvent->setAttribute('CATEGORIES', '', array(), true, array_values($this->tags)); } // Location if (!empty($this->location)) { $vEvent->setAttribute('LOCATION', $this->location); } if ($this->geoLocation) { $vEvent->setAttribute('GEO', array('latitude' => $this->geoLocation['lat'], 'longitude' => $this->geoLocation['lon'])); } // URL if (!empty($this->url)) { $vEvent->setAttribute('URL', $this->url); } } $vEvent->setAttribute('CLASS', $this->private ? 'PRIVATE' : 'PUBLIC'); // Status. switch ($this->status) { 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->attendees 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($email)) { if (!empty($status['name'])) { $email = $status['name']; } } else { $tmp = new Horde_Mail_Rfc822_Address($email); if (!empty($status['name'])) { $tmp->personal = $status['name']; } $email = strval($tmp); } } else { if (!empty($status['name'])) { $params['CN'] = $status['name']; } 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('DESCRIPTION', $this->getTitle()); $vAlarm->setAttribute( 'TRIGGER;VALUE=DURATION', ($this->alarm > 0 ? '-' : '') . 'PT' . abs($this->alarm) . 'M' ); $vEvent->addComponent($vAlarm); } $hordeAlarm = $GLOBALS['injector']->getInstance('Horde_Alarm'); if ($hordeAlarm->exists($this->uid, $GLOBALS['registry']->getAuth()) && $hordeAlarm->isSnoozed($this->uid, $GLOBALS['registry']->getAuth())) { $vEvent->setAttribute('X-MOZ-LASTACK', new Horde_Date($_SERVER['REQUEST_TIME'])); $alarm = $hordeAlarm->get($this->uid, $GLOBALS['registry']->getAuth()); if (!empty($alarm['snooze'])) { $alarm['snooze']->setTimezone(date_default_timezone_get()); $vEvent->setAttribute('X-MOZ-SNOOZE-TIME', $alarm['snooze']); } } } // 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. An exception with no replacement event is represented // by EXDATE, and those with replacement events are represented by // a new vEvent element. We get all known replacement events first, // then remove the exceptionoriginaldate from the list of the event // exceptions. Any exceptions left should represent exceptions with // no replacement. $exceptions = $this->recurrence->getExceptions(); $search = new stdClass(); $search->baseid = $this->uid; $results = $this->getDriver()->search($search); foreach ($results as $days) { foreach ($days as $exceptionEvent) { // Need to change the UID so it links to the original // recurring event, but only if not using $v1. If using $v1, // we add the date to EXDATE and do NOT change the UID. if (!$v1) { $exceptionEvent->uid = $this->uid; } $vEventException = $exceptionEvent->toiCalendar($calendar); // This should never happen, but protect against it anyway. if (count($vEventException) > 2 || (count($vEventException) > 1 && !($vEventException[0] instanceof Horde_Icalendar_Vtimezone) && !($vEventException[1] instanceof Horde_Icalendar_Vtimezone))) { throw new Kronolith_Exception(_("Unable to parse event.")); } $vEventException = array_pop($vEventException); // If $v1, need to add to EXDATE if (!$this->isAllDay()) { $exceptionEvent->setTimezone(true); } if (!$v1) { $vEventException->setAttribute('RECURRENCE-ID', $exceptionEvent->exceptionoriginaldate); } else { $vEvent->setAttribute('EXDATE', array($exceptionEvent->exceptionoriginaldate), array('VALUE' => 'DATE')); } $originaldate = $exceptionEvent->exceptionoriginaldate->format('Ymd'); $key = array_search($originaldate, $exceptions); if ($key !== false) { unset($exceptions[$key]); } $vEvents[] = $vEventException; } } /* The remaining exceptions represent deleted recurrences */ foreach ($exceptions as $exception) { if (!empty($exception)) { // Use multiple EXDATE attributes instead of EXDATE // attributes with multiple values to make Apple iCal // happy. list($year, $month, $mday) = sscanf($exception, '%04d%02d%02d'); if ($this->isAllDay()) { $vEvent->setAttribute('EXDATE', array(new Horde_Date($year, $month, $mday)), array('VALUE' => 'DATE')); } else { // Another Apple iCal/Calendar fix. EXDATE is only // recognized if the full datetime is present and matches // the time part given in DTSTART. $params = array(); if ($this->timezone) { $params['TZID'] = $this->timezone; } $exdate = clone $this->start; $exdate->year = $year; $exdate->month = $month; $exdate->mday = $mday; $vEvent->setAttribute('EXDATE', array($exdate), $params); } } } } array_unshift($vEvents, $vEvent); $this->setTimezone(false); return $vEvents; } /** * Updates the properties of this event from a Horde_Icalendar_Vevent * object. * * @param Horde_Icalendar_Vevent $vEvent The iCalendar data to update * from. * @param boolean $parseAttendees Parse attendees too? * @since Kronolith 4.2 */ public function fromiCalendar($vEvent, $parseAttendees = false) { // Unique ID. try { $uid = $vEvent->getAttribute('UID'); if (!empty($uid)) { $this->uid = $uid; } } catch (Horde_Icalendar_Exception $e) {} // Sequence. try { $seq = $vEvent->getAttribute('SEQUENCE'); if (is_int($seq)) { $this->sequence = $seq; } } catch (Horde_Icalendar_Exception $e) {} // Title, tags and description. try { $title = $this->_ensureUtf8($vEvent->getAttribute('SUMMARY')); if (!is_array($title)) { $this->title = $title; } } catch (Horde_Icalendar_Exception $e) {} // Tags try { $this->_tags = $vEvent->getAttributeValues('CATEGORIES'); } catch (Horde_Icalendar_Exception $e) {} // Description try { $desc = $this->_ensureUtf8($vEvent->getAttribute('DESCRIPTION')); if (!is_array($desc)) { $this->description = $desc; } } catch (Horde_Icalendar_Exception $e) {} // Remote Url try { $url = $vEvent->getAttribute('URL'); if (!is_array($url)) { $this->url = $url; } } catch (Horde_Icalendar_Exception $e) {} // Location try { $location = $this->_ensureUtf8($vEvent->getAttribute('LOCATION')); if (!is_array($location)) { $this->location = $location; } } catch (Horde_Icalendar_Exception $e) {} try { $geolocation = $vEvent->getAttribute('GEO'); $this->geoLocation = array( 'lat' => $geolocation['latitude'], 'lon' => $geolocation['longitude'] ); } catch (Horde_Icalendar_Exception $e) {} // Class try { $class = $vEvent->getAttribute('CLASS'); if (!is_array($class)) { $class = Horde_String::upper($class); $this->private = $class == 'PRIVATE' || $class == 'CONFIDENTIAL'; } } catch (Horde_Icalendar_Exception $e) {} // Status. try { $status = $vEvent->getAttribute('STATUS'); if (!is_array($status)) { $status = Horde_String::upper($status); if ($status == 'DECLINED') { $status = 'CANCELLED'; } if (defined('Kronolith::STATUS_' . $status)) { $this->status = constant('Kronolith::STATUS_' . $status); } } } catch (Horde_Icalendar_Exception $e) {} // Reset allday flag in case this has changed. Will be recalculated // next time isAllDay() is called. $this->allday = false; // Start and end date. $tzid = null; try { $start = $vEvent->getAttribute('DTSTART'); $startParams = $vEvent->getAttribute('DTSTART', true); // We don't support different timezones for different attributes, // so use the DTSTART timezone for the complete event. if (isset($startParams[0]['TZID'])) { // Horde_Date supports timezone aliases, so try that first. $tz = $startParams[0]['TZID']; try { // Check if the timezone name is supported by PHP natively. new DateTimeZone($tz); $this->timezone = $tzid = $tz; } catch (Exception $e) { } } if (!is_array($start)) { // Date-Time field $this->start = new Horde_Date($start, $tzid); } else { // Date field $this->start = new Horde_Date( array('year' => (int)$start['year'], 'month' => (int)$start['month'], 'mday' => (int)$start['mday']), $tzid ); } } catch (Horde_Icalendar_Exception $e) { throw new Kronolith_Exception($e); } catch (Horde_Date_Exception $e) { throw new Kronolith_Exception($e); } try { $end = $vEvent->getAttribute('DTEND'); if (!is_array($end)) { // Date-Time field $this->end = new Horde_Date($end, $tzid); // 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), $tzid); } } else { // Date field $this->end = new Horde_Date( array('year' => (int)$end['year'], 'month' => (int)$end['month'], 'mday' => (int)$end['mday']), $tzid); } } catch (Horde_Icalendar_Exception $e) { $end = null; } if (is_null($end)) { try { $duration = $vEvent->getAttribute('DURATION'); if (!is_array($duration)) { $this->end = new Horde_Date($this->start); $this->end->sec += $duration; $end = 1; } } catch (Horde_Icalendar_Exception $e) {} if (is_null($end)) { // 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 try { $alarm = $vEvent->getAttribute('AALARM'); if (!is_array($alarm) && intval($alarm)) { $this->alarm = intval(($this->start->timestamp() - $alarm) / 60); } } catch (Horde_Icalendar_Exception $e) {} // vCalendar 2.0 alarms foreach ($vEvent->getComponents() as $alarm) { if (!($alarm instanceof Horde_Icalendar_Valarm)) { continue; } try { if ($alarm->getAttribute('ACTION') == 'NONE') { continue; } } catch (Horde_Icalendar_Exception $e) { } try { // @todo consider implementing different ACTION types. // $action = $alarm->getAttribute('ACTION'); $trigger = $alarm->getAttribute('TRIGGER'); $triggerParams = $alarm->getAttribute('TRIGGER', true); } catch (Horde_Icalendar_Exception $e) { continue; } if (!is_array($triggerParams)) { $triggerParams = array($triggerParams); } $haveTrigger = false; foreach ($triggerParams as $tp) { if (isset($tp['VALUE']) && $tp['VALUE'] == 'DATE-TIME') { if (isset($tp['RELATED']) && $tp['RELATED'] == 'END') { $this->alarm = intval(($this->end->timestamp() - $trigger) / 60); } else { $this->alarm = intval(($this->start->timestamp() - $trigger) / 60); } $haveTrigger = true; break; } elseif (isset($tp['RELATED']) && $tp['RELATED'] == 'END') { $this->alarm = -intval($trigger / 60); $this->alarm -= $this->durMin; $haveTrigger = true; break; } } if (!$haveTrigger) { $this->alarm = -intval($trigger / 60); } break; } // Alarm snoozing/dismissal if ($this->alarm) { try { // If X-MOZ-LASTACK is set, this event is either dismissed or // snoozed. $vEvent->getAttribute('X-MOZ-LASTACK'); try { // If X-MOZ-SNOOZE-TIME is set, this event is snoozed. $snooze = $vEvent->getAttribute('X-MOZ-SNOOZE-TIME'); $this->_snooze = intval(($snooze - time()) / 60); } catch (Horde_Icalendar_Exception $e) { // If X-MOZ-SNOOZE-TIME is not set, this event is dismissed. $this->_snooze = -1; } } catch (Horde_Icalendar_Exception $e) { } } // 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 synchronization, this behavior // can be overriden. // X-ATTENDEE is there for historical reasons. @todo remove in // Kronolith 5. $attendee = null; if ($parseAttendees) { try { $attendee = $vEvent->getAttribute('ATTENDEE'); $params = $vEvent->getAttribute('ATTENDEE', true); } catch (Horde_Icalendar_Exception $e) { try { $attendee = $vEvent->getAttribute('X-ATTENDEE'); $params = $vEvent->getAttribute('X-ATTENDEE', true); } catch (Horde_Icalendar_Exception $e) { } } } if ($attendee) { if (!is_array($attendee)) { $attendee = array($attendee); } if (!is_array($params)) { $params = array($params); } // Clear the attendees since we might be editing/replacing the event $this->attendees = array(); for ($i = 0; $i < count($attendee); ++$i) { $attendee[$i] = str_replace(array('MAILTO:', 'mailto:'), '', $attendee[$i]); $tmp = new Horde_Mail_Rfc822_Address($attendee[$i]); $email = $tmp->bare_address; // 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']) ? $this->_ensureUtf8($params[$i]['CN']) : null; $this->addAttendee($email, $attendance, $response, $name); } } $this->_handlevEventRecurrence($vEvent); $this->initialized = true; } /** * Handle parsing recurrence related fields. * * @param Horde_Icalendar $vEvent * @throws Kronolith_Exception */ protected function _handlevEventRecurrence($vEvent) { // Recurrence. try { $rrule = $vEvent->getAttribute('RRULE'); if (!is_array($rrule)) { $this->recurrence = new Horde_Date_Recurrence($this->start); if (strpos($rrule, '=') !== false) { $this->recurrence->fromRRule20($rrule); } else { $this->recurrence->fromRRule10($rrule); } // Exceptions. EXDATE represents deleted events, just add the // exception, no new event is needed. $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']); } } } } } catch (Horde_Icalendar_Exception $e) {} // RECURRENCE-ID indicates that this event represents an exception try { $recurrenceid = $vEvent->getAttribute('RECURRENCE-ID'); $originaldt = new Horde_Date($recurrenceid); $this->exceptionoriginaldate = $originaldt; $this->baseid = $this->uid; $this->uid = null; try { $originalEvent = $this->getDriver()->getByUID($this->baseid); if ($originalEvent->recurrence) { $originalEvent->recurrence->addException( $originaldt->format('Y'), $originaldt->format('m'), $originaldt->format('d') ); $originalEvent->save(); } } catch (Horde_Exception_NotFound $e) { throw new Kronolith_Exception(_("Unable to locate original event series.")); } } catch (Horde_Icalendar_Exception $e) {} } /** * Imports the values for this event from a MS ActiveSync Message. * * @see Horde_ActiveSync_Message_Appointment */ public function fromASAppointment(Horde_ActiveSync_Message_Appointment $message) { /* New event? */ if ($this->id === null) { $this->creator = $GLOBALS['registry']->getAuth(); } if (!$message->isGhosted('subject') && strlen($title = $message->getSubject())) { $this->title = $title; } if ($message->getProtocolVersion() == Horde_ActiveSync::VERSION_TWOFIVE && !$message->isGhosted('body') && strlen($description = $message->getBody())) { $this->description = $description; } elseif ($message->getProtocolVersion() > Horde_ActiveSync::VERSION_TWOFIVE && !$message->isGhosted('airsyncbasebody')) { if ($message->airsyncbasebody->type == Horde_ActiveSync::BODYPREF_TYPE_HTML) { $this->description = Horde_Text_Filter::filter($message->airsyncbasebody->data, 'Html2text'); } else { $this->description = $message->airsyncbasebody->data; } } if (!$message->isGhosted('location') && strlen($location = $message->getLocation())) { $this->location = $location; } /* Date/times */ $tz = !$message->isGhosted('timezone') ? $message->getTimezone() : $this->timezone; $dates = $message->getDatetime(); $this->start = !$message->isGhosted('starttime') ? clone($dates['start']) : $this->start; $this->start->setTimezone($tz); $this->end = !$message->isGhosted('endtime') ? clone($dates['end']) : $this->end; $this->end->setTimezone($tz); if (!$message->isGhosted('alldayevent')) { $this->allday = $dates['allday']; } if ($tz != date_default_timezone_get()) { $this->timezone = $tz; } /* Sensitivity */ if (!$message->isGhosted('sensitivity')) { $this->private = ($message->getSensitivity() == Horde_ActiveSync_Message_Appointment::SENSITIVITY_PRIVATE || $message->getSensitivity() == Horde_ActiveSync_Message_Appointment::SENSITIVITY_CONFIDENTIAL) ? true : false; } /* Busy Status */ if (!$message->isGhosted('meetingstatus')) { if ($message->getMeetingStatus() == Horde_ActiveSync_Message_Appointment::MEETING_CANCELLED) { $status = Kronolith::STATUS_CANCELLED; } else { $status = $message->getBusyStatus(); switch ($status) { case Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY: $status = Kronolith::STATUS_CONFIRMED; break; case Horde_ActiveSync_Message_Appointment::BUSYSTATUS_FREE: $status = Kronolith::STATUS_FREE; break; case Horde_ActiveSync_Message_Appointment::BUSYSTATUS_TENTATIVE: $status = Kronolith::STATUS_TENTATIVE; break; // @TODO: not sure how "Out" should show in kronolith... case Horde_ActiveSync_Message_Appointment::BUSYSTATUS_OUT: $status = Kronolith::STATUS_CONFIRMED; default: // EAS Specifies default should be free. $status = Kronolith::STATUS_FREE; } } $this->status = $status; } /* Alarm */ if (!$message->isGhosted('reminder') && ($alarm = $message->getReminder())) { $this->alarm = $alarm; } /* Recurrence */ if (!$message->isGhosted('recurrence') && ($rrule = $message->getRecurrence())) { /* Exceptions */ $kronolith_driver = $this->getDriver(); /* Since AS keeps exceptions as part of the original event, we need * to delete all existing exceptions and re-create them. The only * drawback to this is that the UIDs will change. */ $this->recurrence = $rrule; if (!empty($this->uid)) { $search = new StdClass(); $search->baseid = $this->uid; $results = $kronolith_driver->search($search); foreach ($results as $days) { foreach ($days as $exception) { $kronolith_driver->deleteEvent($exception->id); } } } $erules = $message->getExceptions(); foreach ($erules as $rule){ /* Add exception to recurrence obj*/ $original = $rule->getExceptionStartTime(); $original->setTimezone($tz); $this->recurrence->addException($original->format('Y'), $original->format('m'), $original->format('d')); /* Readd the exception event, if not deleted */ if (!$rule->deleted) { $event = $kronolith_driver->getEvent(); $times = $rule->getDatetime(); $event->start = $times['start']; $event->end = $times['end']; $event->start->setTimezone($tz); $event->end->setTimezone($tz); $event->allday = $times['allday']; $event->title = $rule->getSubject(); $event->title = empty($event->title) ? $this->title : $event->title; $event->description = $rule->getBody(); $event->description = empty($event->description) ? $this->description : $event->description; $event->baseid = $this->uid; $event->exceptionoriginaldate = $original; $event->initialized = true; if ($tz != date_default_timezone_get()) { $event->timezone = $tz; } $event->save(); } } } /* Attendees */ if (!$message->isGhosted('attendees')) { $attendees = $message->getAttendees(); foreach ($attendees as $attendee) { switch ($attendee->status) { case Horde_ActiveSync_Message_Attendee::STATUS_ACCEPT: $response_code = Kronolith::RESPONSE_ACCEPTED; break; case Horde_ActiveSync_Message_Attendee::STATUS_DECLINE: $response_code = Kronolith::RESPONSE_DECLINED; break; case Horde_ActiveSync_Message_Attendee::STATUS_TENTATIVE: $response_code = Kronolith::RESPONSE_TENTATIVE; break; default: $response_code = Kronolith::RESPONSE_NONE; } switch ($attendee->type) { case Horde_ActiveSync_Message_Attendee::TYPE_REQUIRED: $part_type = Kronolith::PART_REQUIRED; break; case Horde_ActiveSync_Message_Attendee::TYPE_OPTIONAL: $part_type = Kronolith::PART_OPTIONAL; break; case Horde_ActiveSync_Message_Attendee::TYPE_RESOURCE: $part_type = Kronolith::PART_REQUIRED; } $this->addAttendee($attendee->email, $part_type, $response_code, $attendee->name); } } /* Categories (Tags) */ if (!$message->isGhosted('categories')) { $this->_tags = $message->getCategories(); } // 14.1 if ($message->getProtocolVersion() >= Horde_ActiveSync::VERSION_FOURTEENONE && !$message->isGhosted('onlinemeetingexternallink')) { $this->url = $message->onlinemeetingexternallink; } /* Flag that we are initialized */ $this->initialized = true; } /** * Export this event as a MS ActiveSync Message * * @param array $options Options: * - protocolversion: (float) The EAS version to support * DEFAULT: 2.5 * - bodyprefs: (array) A BODYPREFERENCE array. * DEFAULT: none (No body prefs enforced). * - truncation: (integer) Truncate event body to this length * DEFAULT: none (No truncation). * * @return Horde_ActiveSync_Message_Appointment */ public function toASAppointment(array $options = array()) { global $prefs, $registry; $message = new Horde_ActiveSync_Message_Appointment( array( 'logger' => $GLOBALS['injector']->getInstance('Horde_Log_Logger'), 'protocolversion' => $options['protocolversion'] ) ); if (!$this->isPrivate()) { // Handle body/truncation if (!empty($options['bodyprefs'])) { if (Horde_String::length($this->description) > 0) { $bp = $options['bodyprefs']; $note = new Horde_ActiveSync_Message_AirSyncBaseBody(); // No HTML supported. Always use plaintext. $note->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; if (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { $truncation = $bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']; } elseif (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_HTML])) { $truncation = $bp[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize']; $this->description = Horde_Text_Filter::filter($this->description, 'Text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO)); } else { $truncation = false; } if ($truncation && Horde_String::length($this->description) > $truncation) { $note->data = Horde_String::substr($this->description, 0, $truncation); $note->truncated = 1; } else { $note->data = $this->description; } $note->estimateddatasize = Horde_String::length($this->description); $message->airsyncbasebody = $note; } } else { $message->setBody($this->description); } $message->setLocation($this->location); } $message->setSubject($this->getTitle()); $message->setDatetime(array( 'start' => $this->start, 'end' => $this->end, 'allday' => $this->isAllDay()) ); $message->setTimezone($this->start); // Organizer if (count($this->attendees)) { if ($this->creator == $registry->getAuth()) { $as_ident = $prefs->getValue('activesync_identity') == 'horde' ? $prefs->getValue('default_identity') : $prefs->getValue('activesync_identity'); $name = $GLOBALS['injector'] ->getInstance('Horde_Core_Factory_Identity') ->create($this->creator)->getValue('fullname', $as_ident); $email = $GLOBALS['injector'] ->getInstance('Horde_Core_Factory_Identity') ->create($this->creator)->getValue('from_addr', $as_ident); } else { $name = Kronolith::getUserName($this->creator); $email = Kronolith::getUserEmail($this->creator); } $message->setOrganizer(array( 'name' => $name, 'email' => $email) ); } // Privacy $message->setSensitivity($this->private ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_PRIVATE : Horde_ActiveSync_Message_Appointment::SENSITIVITY_NORMAL); // Busy Status switch ($this->status) { case Kronolith::STATUS_CANCELLED: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_FREE; break; case Kronolith::STATUS_CONFIRMED: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY; break; case Kronolith::STATUS_TENTATIVE: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_TENTATIVE; case Kronolith::STATUS_FREE: case Kronolith::STATUS_NONE: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_FREE; } $message->setBusyStatus($status); // DTStamp $message->setDTStamp($_SERVER['REQUEST_TIME']); // Recurrence if ($this->recurs()) { $message->setRecurrence($this->recurrence, $GLOBALS['prefs']->getValue('week_start_monday')); /* Exceptions are tricky. Exceptions, even those that represent * deleted instances of a recurring event, must be added. To do this * we query the storage for all the events that represent exceptions * (those with the baseid == $this->uid) and then remove the * exceptionoriginaldate from the list of exceptions we know about. * Any dates left in this list when we are done, must represent * deleted instances of this recurring event.*/ if (!empty($this->recurrence) && $exceptions = $this->recurrence->getExceptions()) { $results = $this->boundExceptions(); foreach ($results as $exception) { $e = new Horde_ActiveSync_Message_Exception(array( 'protocolversion' => $options['protocolversion'])); $e->setDateTime(array( 'start' => $exception->start, 'end' => $exception->end, 'allday' => $exception->isAllDay())); // The start time of the *original* recurring event $e->setExceptionStartTime($exception->exceptionoriginaldate); $originaldate = $exception->exceptionoriginaldate->format('Ymd'); $key = array_search($originaldate, $exceptions); if ($key !== false) { unset($exceptions[$key]); } // Remaining properties that could be different $e->setSubject($exception->getTitle()); if (!$exception->isPrivate()) { $e->setLocation($exception->location); $e->setBody($exception->description); } $e->setSensitivity($exception->private ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_PRIVATE : Horde_ActiveSync_Message_Appointment::SENSITIVITY_NORMAL); $e->setReminder($exception->alarm); $e->setDTStamp($_SERVER['REQUEST_TIME']); if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWELVEONE) { switch ($exception->status) { case Kronolith::STATUS_TENTATIVE; $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_TENTATIVE; break; case Kronolith::STATUS_NONE: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NORESPONSE; break; case Kronolith::STATUS_CONFIRMED: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_ACCEPTED; break; default: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NONE; } } // Tags/Categories if (!$exception->isPrivate()) { foreach ($exception->tags as $tag) { $e->addCategory($tag); } } $message->addexception($e); } // Any dates left in $exceptions must be deleted exceptions foreach ($exceptions as $deleted) { $e = new Horde_ActiveSync_Message_Exception(array( 'protocolversion' => $options['protocolversion'])); // Kronolith stores the date only, but some AS clients need // the datetime. list($year, $month, $mday) = sscanf($deleted, '%04d%02d%02d'); $st = clone $this->start; $st->year = $year; $st->month = $month; $st->mday = $mday; $e->setExceptionStartTime($st); $e->deleted = true; $message->addException($e); } } } // Attendees if (!$this->isPrivate() && count($this->attendees)) { $message->setMeetingStatus( $this->status == Kronolith::STATUS_CANCELLED ? Horde_ActiveSync_Message_Appointment::MEETING_CANCELLED : Horde_ActiveSync_Message_Appointment::MEETING_IS_MEETING ); foreach ($this->attendees as $email => $properties) { $attendee = new Horde_ActiveSync_Message_Attendee(array( 'protocolversion' => $options['protocolversion'])); $adr_obj = new Horde_Mail_Rfc822_Address($email); $attendee->name = $adr_obj->label; $attendee->email = $adr_obj->bare_address; // AS only has required or optional, and only EAS Version > 2.5 if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) { $attendee->type = ($properties['attendance'] !== Kronolith::PART_REQUIRED ? Horde_ActiveSync_Message_Attendee::TYPE_OPTIONAL : Horde_ActiveSync_Message_Attendee::TYPE_REQUIRED); switch ($properties['response']) { case Kronolith::RESPONSE_NONE: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_NORESPONSE; break; case Kronolith::RESPONSE_ACCEPTED: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_ACCEPT; break; case Kronolith::RESPONSE_DECLINED: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_DECLINE; break; case Kronolith::RESPONSE_TENTATIVE: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_TENTATIVE; break; default: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_UNKNOWN; } } $message->addAttendee($attendee); } } elseif ($this->status == Kronolith::STATUS_CANCELLED) { $message->setMeetingStatus(Horde_ActiveSync_Message_Appointment::MEETING_CANCELLED); } else { $message->setMeetingStatus(Horde_ActiveSync_Message_Appointment::MEETING_NOT_MEETING); } // Resources if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) { $r = $this->getResources(); foreach ($r as $id => $data) { $resource = Kronolith::getDriver('Resource')->getResource($id); // EAS *REQUIRES* an email field for Resources. If it is missing // a number of clients will fail, losing push. if ($resource->get('email')) { $attendee = new Horde_ActiveSync_Message_Attendee(array( 'protocolversion' => $options['protocolversion'])); $attendee->email = $resource->get('email'); $attendee->type = Horde_ActiveSync_Message_Attendee::TYPE_RESOURCE; $attendee->name = $data['name']; $attendee->status = $data['response']; $message->addAttendee($attendee); } } } // Reminder if ($this->alarm) { $message->setReminder($this->alarm); } // Categories (tags) if (!$this->isPrivate()) { foreach ($this->tags as $tag) { $message->addCategory($tag); } } // EAS 14 if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWELVEONE) { // We don't track the actual responses we sent to other's invitations. // Set this based on the status flag. switch ($this->status) { case Kronolith::STATUS_TENTATIVE; $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_TENTATIVE; break; case Kronolith::STATUS_NONE: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NORESPONSE; break; case Kronolith::STATUS_CONFIRMED: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_ACCEPTED; break; default: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NONE; } } // 14.1 if ($options['protocolversion'] >= Horde_ActiveSync::VERSION_FOURTEENONE) { $message->onlinemeetingexternallink = $this->url; } return $message; } /** * Imports the values for this event from an array of values. * * @param array $hash Array containing all the values. * * @throws Kronolith_Exception */ public function fromHash($hash) { // See if it's a new event. if ($this->id === null) { $this->creator = $GLOBALS['registry']->getAuth(); } if (!empty($hash['title'])) { $this->title = $hash['title']; } else { throw new Kronolith_Exception(_("Events must have a title.")); } $this->start = null; if (!empty($hash['start_date'])) { $date = array_map('intval', explode('-', $hash['start_date'])); if (empty($hash['start_time'])) { $time = array(0, 0, 0); } else { $time = array_map('intval', explode(':', $hash['start_time'])); if (count($time) == 2) { $time[2] = 0; } } if (count($time) == 3 && count($date) == 3 && !empty($date[1]) && !empty($date[2])) { if ($date[0] < 100) { $date[0] += (date('Y') / 100 | 0) * 100; } $this->start = new Horde_Date( array( 'year' => $date[0], 'month' => $date[1], 'mday' => $date[2], 'hour' => $time[0], 'min' => $time[1], 'sec' => $time[2] ), isset($hash['timezone']) ? $hash['timezone'] : null ); } } if (!isset($this->start)) { throw new Kronolith_Exception(_("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 = array_map('intval', explode('-', $hash['end_date'])); if (empty($hash['end_time'])) { $time = array(0, 0, 0); } else { $time = array_map('intval', explode(':', $hash['end_time'])); if (count($time) == 2) { $time[2] = 0; } } if (count($time) == 3 && count($date) == 3 && !empty($date[1]) && !empty($date[2])) { if ($date[0] < 100) { $date[0] += (date('Y') / 100 | 0) * 100; } $this->end = new Horde_Date( array( 'year' => $date[0], 'month' => $date[1], 'mday' => $date[2], 'hour' => $time[0], 'min' => $time[1], 'sec' => $time[2] ), isset($hash['timezone']) ? $hash['timezone'] : null ); } } if (!empty($hash['alarm'])) { $this->alarm = (int)$hash['alarm']; } elseif (!empty($hash['alarm_date']) && !empty($hash['alarm_time'])) { $date = array_map('intval', explode('-', $hash['alarm_date'])); $time = array_map('intval', explode(':', $hash['alarm_time'])); if (count($time) == 2) { $time[2] = 0; } if (count($time) == 3 && count($date) == 3 && !empty($date[1]) && !empty($date[2])) { $alarm = new Horde_Date( array( 'year' => $date[0], 'month' => $date[1], 'mday' => $date[2], 'hour' => $time[0], 'min' => $time[1], 'sec' => $time[2] ), isset($hash['timezone']) ? $hash['timezone'] : null ); $this->alarm = ($this->start->timestamp() - $alarm->timestamp()) / 60; } } $this->allday = !empty($hash['allday']); if (!empty($hash['description'])) { $this->description = $hash['description']; } if (!empty($hash['location'])) { $this->location = $hash['location']; } // Import once we support organizers. /* if (!empty($hash['organizer'])) { $this->organizer = $hash['organizer']; } */ if (!empty($hash['private'])) { $this->private = true; } if (!empty($hash['recur_type'])) { $this->recurrence = new Horde_Date_Recurrence($this->start); $this->recurrence->setRecurType($hash['recur_type']); if (!empty($hash['recur_count'])) { $this->recurrence->setRecurCount($hash['recur_count']); } elseif (!empty($hash['recur_end_date'])) { $date = array_map('intval', explode('-', $hash['recur_end_date'])); if (count($date) == 3 && !empty($date[1]) && !empty($date[2])) { $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']); } if (!empty($hash['recur_exceptions'])) { foreach ($hash['recur_exceptions'] as $exception) { $parts = explode('-', $exception); if (count($parts) == 3) { $this->recurrence->addException($parts[0], $parts[1], $parts[2]); } } } } if (isset($hash['sequence'])) { $this->sequence = $hash['sequence']; } if (!empty($hash['tags'])) { $this->tags = $hash['tags']; } if (!empty($hash['timezone'])) { $this->timezone = $hash['timezone']; } if (!empty($hash['uid'])) { $this->uid = $hash['uid']; } $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->alarm || $this->status == Kronolith::STATUS_CANCELLED) { return; } if ($this->recurs()) { $eventDate = $this->recurrence->nextRecurrence($time); if (!$eventDate || ($eventDate && $this->recurrence->hasException($eventDate->year, $eventDate->month, $eventDate->mday))) { return; } $start = clone $eventDate; $diff = Date_Calc::dateDiff( $this->start->mday, $this->start->month, $this->start->year, $this->end->mday, $this->end->month, $this->end->year ); if ($diff == -1) { $diff = 0; } $end = new Horde_Date(array( 'year' => $start->year, 'month' => $start->month, 'mday' => $start->mday + $diff, 'hour' => $this->end->hour, 'min' => $this->end->min, 'sec' => $this->end->sec) ); } else { $start = clone $this->start; $end = clone $this->end; } $serverName = $_SERVER['SERVER_NAME']; $serverConf = $GLOBALS['conf']['server']['name']; if (!empty($GLOBALS['conf']['reminder']['server_name'])) { $_SERVER['SERVER_NAME'] = $GLOBALS['conf']['server']['name'] = $GLOBALS['conf']['reminder']['server_name']; } if (empty($user)) { $user = $GLOBALS['registry']->getAuth(); } if (empty($prefs)) { $prefs = $GLOBALS['prefs']; } $methods = !empty($this->methods) ? $this->methods : @unserialize($prefs->getValue('event_alarms')); if (isset($methods['notify'])) { $methods['notify']['show'] = array( '__app' => $GLOBALS['registry']->getApp(), 'event' => $this->id, 'calendar' => $this->calendar); $methods['notify']['ajax'] = 'event:' . $this->calendarType . '|' . $this->calendar . ':' . $this->id . ':' . $start->dateString(); if (!empty($methods['notify']['sound'])) { if ($methods['notify']['sound'] == 'on') { // Handle boolean sound preferences. $methods['notify']['sound'] = (string)Horde_Themes::sound('theetone.wav'); } else { // Else we know we have a sound name that can be // served from Horde. $methods['notify']['sound'] = (string)Horde_Themes::sound($methods['notify']['sound']); } } if ($this->isAllDay()) { if ($start->compareDate($end) == 0) { $methods['notify']['subtitle'] = sprintf(_("On %s"), '<strong>' . $start->strftime($prefs->getValue('date_format')) . '</strong>'); } else { $methods['notify']['subtitle'] = sprintf(_("From %s to %s"), '<strong>' . $start->strftime($prefs->getValue('date_format')) . '</strong>', '<strong>' . $end->strftime($prefs->getValue('date_format')) . '</strong>'); } } else { $methods['notify']['subtitle'] = sprintf(_("From %s at %s to %s at %s"), '<strong>' . $start->strftime($prefs->getValue('date_format')), $start->format($prefs->getValue('twentyFour') ? 'H:i' : 'h:ia') . '</strong>', '<strong>' . $end->strftime($prefs->getValue('date_format')), $this->end->format($prefs->getValue('twentyFour') ? 'H:i' : 'h:ia') . '</strong>'); } } if (isset($methods['mail'])) { $image = Kronolith::getImagePart('big_alarm.png'); $view = new Horde_View(array('templatePath' => KRONOLITH_TEMPLATES . '/alarm', 'encoding' => 'UTF-8')); new Horde_View_Helper_Text($view); $view->event = $this; $view->imageId = $image->getContentId(); $view->user = $user; $view->dateFormat = $prefs->getValue('date_format'); $view->timeFormat = $prefs->getValue('twentyFour') ? 'H:i' : 'h:ia'; $view->start = $start; if (!$prefs->isLocked('event_reminder')) { $view->prefsUrl = Horde::url($GLOBALS['registry']->getServiceLink('prefs', 'kronolith'), true)->remove(session_name()); } if ($this->attendees) { $view->attendees = Kronolith::getAttendeeEmailList($this->attendees)->addresses; } $methods['mail']['mimepart'] = Kronolith::buildMimeMessage($view, 'mail', $image); } if (isset($methods['desktop'])) { if ($this->isAllDay()) { if ($this->start->compareDate($this->end) == 0) { $methods['desktop']['subtitle'] = sprintf(_("On %s"), $start->strftime($prefs->getValue('date_format'))); } else { $methods['desktop']['subtitle'] = sprintf(_("From %s to %s"), $start->strftime($prefs->getValue('date_format')), $end->strftime($prefs->getValue('date_format'))); } } else { $methods['desktop']['subtitle'] = sprintf(_("From %s at %s to %s at %s"), $start->strftime($prefs->getValue('date_format')), $start->format($prefs->getValue('twentyFour') ? 'H:i' : 'h:ia'), $end->strftime($prefs->getValue('date_format')), $this->end->format($prefs->getValue('twentyFour') ? 'H:i' : 'h:ia')); } $methods['desktop']['url'] = strval($this->getViewUrl(array(), true, false)); } $alarmStart = clone $start; $alarmStart->min -= $this->alarm; $alarm = array( 'id' => $this->uid, 'user' => $user, 'start' => $alarmStart, 'end' => $end, 'methods' => array_keys($methods), 'params' => $methods, 'title' => $this->getTitle($user), 'text' => $this->description, 'instanceid' => $this->recurs() ? $eventDate->dateString() : null); $_SERVER['SERVER_NAME'] = $serverName; $GLOBALS['conf']['server']['name'] = $serverConf; return $alarm; } /** * Returns a simple object suitable for json transport representing this * event. * * Possible properties are: * - t: title * - d: description * - c: calendar id * - s: start date * - e: end date * - fi: first day of a multi-day event * - la: last day of a multi-day event * - x: status (Kronolith::STATUS_* constant) * - al: all-day? * - bg: background color * - fg: foreground color * - pe: edit permissions? * - pd: delete permissions? * - vl: variable, i.e. editable length? * - a: alarm text or minutes * - r: recurrence type (Horde_Date_Recurrence::RECUR_* constant) * - bid: The baseid for an event representing an exception * - eod: The original date that an exception is replacing * - ic: icon * - ln: link * - aj: ajax link * - id: event id * - ty: calendar type (driver) * - l: location * - u: url * - sd: formatted start date * - st: formatted start time * - ed: formatted end date * - et: formatted end time * - at: attendees * - rs: resources * - tg: tag list, * - mt: meeting (Boolean true if event has attendees, false otherwise). * * @param boolean $allDay If not null, overrides whether the event is * an all-day event. * @param boolean $full Whether to return all event details. * @param string $time_format The date() format to use for time formatting. * * @return stdClass A simple object. */ public function toJson($allDay = null, $full = false, $time_format = 'H:i') { $json = new stdClass; $json->uid = $this->uid; $json->t = $this->getTitle(); $json->c = $this->calendar; $json->s = $this->start->toJson(); $json->e = $this->end->toJson(); $json->fi = $this->first; $json->la = $this->last; $json->x = (int)$this->status; $json->al = is_null($allDay) ? $this->isAllDay() : $allDay; $json->pe = $this->hasPermission(Horde_Perms::EDIT); $json->pd = $this->hasPermission(Horde_Perms::DELETE); $json->l = $this->getLocation(); $json->mt = !empty($this->attendees); $json->sort = sprintf( '%010s%06s', $this->originalStart->timestamp(), 240000 - $this->end->format('His') ); if ($this->icon) { $json->ic = $this->icon; } if ($this->alarm) { if ($this->alarm % 10080 == 0) { $alarm_value = $this->alarm / 10080; $json->a = sprintf(ngettext("%d week", "%d weeks", $alarm_value), $alarm_value); } elseif ($this->alarm % 1440 == 0) { $alarm_value = $this->alarm / 1440; $json->a = sprintf(ngettext("%d day", "%d days", $alarm_value), $alarm_value); } elseif ($this->alarm % 60 == 0) { $alarm_value = $this->alarm / 60; $json->a = sprintf(ngettext("%d hour", "%d hours", $alarm_value), $alarm_value); } else { $alarm_value = $this->alarm; $json->a = sprintf(ngettext("%d minute", "%d minutes", $alarm_value), $alarm_value); } } if ($this->recurs()) { $json->r = $this->recurrence->getRecurType(); } elseif ($this->baseid) { $json->bid = $this->baseid; if ($this->exceptionoriginaldate) { $json->eod = sprintf(_("%s at %s"), $this->exceptionoriginaldate->strftime($GLOBALS['prefs']->getValue('date_format')), $this->exceptionoriginaldate->strftime(($GLOBALS['prefs']->getValue('twentyFour') ? '%H:%M' : '%I:%M %p'))); } } if ($this->_resources) { $json->rs = $this->_resources; } if ($full) { $json->id = $this->id; $json->ty = $this->calendarType; $json->sd = $this->start->strftime('%x'); $json->st = $this->start->format($time_format); $json->ed = $this->end->strftime('%x'); $json->et = $this->end->format($time_format); $json->tz = $this->timezone; $json->a = $this->alarm; $json->pv = $this->private; if ($this->recurs()) { $json->r = $this->recurrence->toJson(); } if (!$this->isPrivate()) { $json->d = $this->description; $json->u = htmlentities($this->url); $json->uhl = $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter( $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($this->url, 'linkurls'), 'Xss' ); $json->tg = array_values($this->tags); $json->gl = $this->geoLocation; if ($this->attendees) { $attendees = array(); foreach ($this->attendees as $email => $info) { $tmp = new Horde_Mail_Rfc822_Address($email); if (!empty($info['name'])) { $tmp->personal = $info['name']; } $attendees[] = array( 'a' => intval($info['attendance']), 'e' => $tmp->bare_address, 'r' => intval($info['response']), 'l' => strval($tmp) ); $json->at = $attendees; } } } if ($this->methods) { $json->m = $this->methods; } } return $json; } /** * 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; } try { $eventID = $this->getDriver()->exists($this->uid, $this->calendar); if (!$eventID) { return false; } } catch (Exception $e) { return false; } $this->id = $eventID; return true; } /** * Converts this event between the event's and the local timezone. * * @param boolean $to_orginal If true converts to the event's timezone. */ public function setTimezone($to_original) { if (!$this->timezone || !$this->getDriver()->supportsTimezones()) { return; } $timezone = $to_original ? $this->timezone : date_default_timezone_get(); $this->start->setTimezone($timezone); $this->end->setTimezone($timezone); if ($this->recurs() && $this->recurrence->hasRecurEnd()) { /* @todo Check if have to go through all recurrence exceptions too. */ $this->recurrence->start->setTimezone($timezone); $this->recurrence->recurEnd->setTimezone($timezone); } } public function getDuration() { if (isset($this->_duration)) { return $this->_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; } } else { $dur_day_match = 0; $dur_hour_match = 1; $dur_min_match = 0; } $this->_duration = new stdClass; $this->_duration->day = $dur_day_match; $this->_duration->hour = $dur_hour_match; $this->_duration->min = $dur_min_match; $this->_duration->wholeDay = $this->isAllDay(); return $this->_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) && empty($this->baseid); } /** * Returns a description of this event's recurring type. * * @return string Human readable recurring type. */ public function getRecurName() { if (empty($this->baseid)) { return $this->recurs() ? $this->recurrence->getRecurName() : _("No recurrence"); } else { return _("Exception"); } } /** * 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::url('edit.php') ->add(array('calendar' => $this->calendarType . '_' .$this->calendar, 'eventID' => $this->id, 'del_exception' => $date, 'url' => Horde_Util::getFormData('url'))) ->link(array('title' => sprintf(_("Delete exception on %s"), $formatted))) . Horde::img('delete-small.png', _("Delete")) . '</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() { $exceptions = $this->recurrence->getExceptions(); asort($exceptions); return implode(', ', array_map(array($this, 'exceptionLink'), $exceptions)); } /** * Returns a list of events that represent exceptions to this event's * recurrence series, if any. If this event does not recur, an empty array * is returned. * * @param boolean $flat If true (the default), returns a flat array * containing Kronolith_Event objects. If false, * results are in the format of listEvents calls. @see * Kronolith::listEvents(). * * @return array An array of Kronolith_Event objects whose baseid property * is equal to this event's uid. I.e., it is a bound * exception. * * @since 4.2.2 */ public function boundExceptions($flat = true) { if (!$this->recurrence || !$this->uid) { return array(); } $return = array(); $search = new stdClass(); $search->baseid = $this->uid; $results = $this->getDriver()->search($search); if (!$flat) { return $results; } foreach ($results as $days) { foreach ($days as $exception) { $return[] = $exception; } } return $return; } /** * Returns whether the event should be considered private. * * @param string $user The current user. If omitted, uses the current user. * * @return boolean Whether to consider the event as private. */ public function isPrivate($user = null) { global $registry; if ($user === null) { $user = $registry->getAuth(); } // Never private if private is not true or if the current user is the // event creator. if ((!$this->private || $this->creator == $user) && $this->hasPermission(Horde_Perms::READ, $user)) { return false; } return true; } /** * Returns the title of this event, considering private flags. * * @param string $user The current user. * * @return string The title of this event. */ public function getTitle($user = null) { if (!$this->initialized) { return ''; } return $this->isPrivate($user) ? _("busy") : (strlen($this->title) ? $this->title : _("[Unnamed event]")); } /** * Returns the location of this event, considering private flags. * * @param string $user The current user. * * @return string The location of this event. */ public function getLocation($user = null) { return $this->isPrivate($user) ? '' : $this->location; } /** * 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) { return isset($this->attendees[Horde_String::lower($email)]); } /** * 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) { 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 ); } /** * Adds a single resource to this event. * * No validation or acceptence/denial is done here...it should be done * when saving the event. * * @param Kronolith_Resource $resource The resource to add. */ public function addResource($resource, $response) { $this->_resources[$resource->getId()] = array( 'attendance' => Kronolith::PART_REQUIRED, 'response' => $response, 'name' => $resource->get('name'), 'calendar' => $resource->get('calendar') ); } /** * Removes a resource from this event. * * @param Kronolith_Resource $resource The resource to remove. */ public function removeResource($resource) { if (isset($this->_resources[$resource->getId()])) { unset($this->_resources[$resource->getId()]); } } /** * Returns all resources. * * @return array A copy of the resources array. */ public function getResources() { return $this->_resources; } /** * Set the entire resource array. Only used when copying an Event. * * @param array $resources The resource array. * @since 4.2.6 */ public function setResources(array $resources) { $this->_resources = $resources; } public function isAllDay() { return $this->allday || ($this->start->hour == 0 && $this->start->min == 0 && $this->start->sec == 0 && (($this->end->hour == 23 && $this->end->min == 59) || ($this->end->hour == 0 && $this->end->min == 0 && $this->end->sec == 0 && ($this->end->mday > $this->start->mday || $this->end->month > $this->start->month || $this->end->year > $this->start->year)))); } /** * Syncronizes tags from the tagging backend with the task storage backend, * if necessary. * * @param array $tags Tags from the tagging backend. */ public function synchronizeTags($tags) { if (isset($this->_internaltags)) { $lower_internaltags = array_map('Horde_String::lower', $this->_internaltags); $lower_tags = array_map('Horde_String::lower', $tags); usort($lower_tags, 'strcoll'); if (array_diff($lower_internaltags, $lower_tags)) { Kronolith::getTagger()->replaceTags( $this->uid, $this->_internaltags, $this->_creator, Kronolith_Tagger::TYPE_EVENT ); } $this->_tags = $this->_internaltags; } else { $this->_tags = $tags; } } /** * Reads form/post data and updates this event's properties. * * @param Kronolith_Event|null $existing If this is an exception event * this is taken as the base event. * @since 4.2.6 * */ public function readForm(Kronolith_Event $existing = null) { global $prefs, $session; // Event owner. $targetcalendar = Horde_Util::getFormData('targetcalendar'); if (strpos($targetcalendar, '\\')) { list(, $this->creator) = explode('\\', $targetcalendar, 2); } elseif (!isset($this->_id)) { $this->creator = $GLOBALS['registry']->getAuth(); } // Basic fields. $this->title = Horde_Util::getFormData('title', $this->title); $this->description = Horde_Util::getFormData('description', $this->description); $this->location = Horde_Util::getFormData('location', $this->location); $this->timezone = Horde_Util::getFormData('timezone', $this->timezone); $this->private = (bool)Horde_Util::getFormData('private'); // URL. $url = Horde_Util::getFormData('eventurl', $this->url); if (strlen($url)) { // Analyze and re-construct. $url = @parse_url($url); if ($url) { if (function_exists('http_build_url')) { if (empty($url['path'])) { $url['path'] = '/'; } $url = http_build_url($url); } else { $new_url = ''; if (isset($url['scheme'])) { $new_url .= $url['scheme'] . '://'; } if (isset($url['user'])) { $new_url .= $url['user']; if (isset($url['pass'])) { $new_url .= ':' . $url['pass']; } $new_url .= '@'; } if (isset($url['host'])) { // Convert IDN hosts to ASCII. if (function_exists('idn_to_ascii')) { $url['host'] = @idn_to_ascii($url['host']); } elseif (Horde_Mime::is8bit($url['host'])) { //throw new Kronolith_Exception(_("Invalid character in URL.")); $url['host'] = ''; } $new_url .= $url['host']; } if (isset($url['path'])) { $new_url .= $url['path']; } if (isset($url['query'])) { $new_url .= '?' . $url['query']; } if (isset($url['fragment'])) { $new_url .= '#' . $url['fragment']; } $url = $new_url; } } } $this->url = $url; // Status. $this->status = Horde_Util::getFormData('status', $this->status); // Attendees. $attendees = $session->get('kronolith', 'attendees', Horde_Session::TYPE_ARRAY); if (!is_null($newattendees = Horde_Util::getFormData('attendees'))) { $newattendees = Kronolith::parseAttendees(trim($newattendees)); foreach ($newattendees as $email => $attendee) { if (!isset($attendees[$email])) { $attendees[$email] = $attendee; } } foreach (array_keys($attendees) as $email) { if (!isset($newattendees[$email])) { unset($attendees[$email]); } } } $this->attendees = $attendees; // Event start. $allDay = Horde_Util::getFormData('whole_day'); if ($start_date = Horde_Util::getFormData('start_date')) { // From ajax interface. $this->start = Kronolith::parseDate($start_date . ' ' . Horde_Util::getFormData('start_time'), true, $this->timezone); if ($allDay) { $this->start->hour = $this->start->min = $this->start->sec = 0; } } elseif ($start = Horde_Util::getFormData('start')) { // From traditional interface. $start_year = $start['year']; $start_month = $start['month']; $start_day = $start['day']; $start_hour = Horde_Util::getFormData('start_hour'); $start_min = Horde_Util::getFormData('start_min'); $am_pm = Horde_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 (Horde_Util::getFormData('end_or_dur') == 1) { if ($allDay) { $start_hour = 0; $start_min = 0; $dur_day = 0; $dur_hour = 24; $dur_min = 0; } else { $dur_day = (int)Horde_Util::getFormData('dur_day'); $dur_hour = (int)Horde_Util::getFormData('dur_hour'); $dur_min = (int)Horde_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->timezone); } // Event end. if ($end_date = Horde_Util::getFormData('end_date')) { // From ajax interface. $this->end = Kronolith::parseDate($end_date . ' ' . Horde_Util::getFormData('end_time'), true, $this->timezone); if ($allDay) { $this->end->hour = $this->end->min = $this->end->sec = 0; $this->end->mday++; } } elseif (Horde_Util::getFormData('end_or_dur') == 1) { // Event duration from traditional interface. $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)); } elseif ($end = Horde_Util::getFormData('end')) { // From traditional interface. $end_year = $end['year']; $end_month = $end['month']; $end_day = $end['day']; $end_hour = Horde_Util::getFormData('end_hour'); $end_min = Horde_Util::getFormData('end_min'); $end_am_pm = Horde_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->timezone); if ($this->end->compareDateTime($this->start) < 0) { $this->end = new Horde_Date($this->start); } } $this->allday = false; // Alarm. if (!is_null($alarm = Horde_Util::getFormData('alarm'))) { if ($alarm) { $value = Horde_Util::getFormData('alarm_value'); $unit = Horde_Util::getFormData('alarm_unit'); if ($value == 0) { $value = $unit = 1; } $this->alarm = $value * $unit; // Notification. if (Horde_Util::getFormData('alarm_change_method')) { $types = Horde_Util::getFormData('event_alarms'); $methods = array(); if (!empty($types)) { foreach ($types as $type) { $methods[$type] = array(); switch ($type){ case 'notify': $methods[$type]['sound'] = Horde_Util::getFormData('event_alarms_sound'); break; case 'mail': $methods[$type]['email'] = Horde_Util::getFormData('event_alarms_email'); break; case 'popup': break; } } } $this->methods = $methods; } else { $this->methods = array(); } } else { $this->alarm = 0; $this->methods = array(); } } // Recurrence. $this->recurrence = $this->readRecurrenceForm( $this->start, $this->timezone, $this->recurrence); // Convert to local timezone. $this->setTimezone(false); $this->_handleResources($existing); // Tags. $this->tags = Horde_Util::getFormData('tags', $this->tags); // Geolocation if (Horde_Util::getFormData('lat') && Horde_Util::getFormData('lon')) { $this->geoLocation = array('lat' => Horde_Util::getFormData('lat'), 'lon' => Horde_Util::getFormData('lon'), 'zoom' => Horde_Util::getFormData('zoom')); } $this->initialized = true; } static public function readRecurrenceForm($start, $timezone, $recurrence = null) { $recur = Horde_Util::getFormData('recur'); if (!strlen($recur)) { return $recurrence; } if (!isset($recurrence)) { $recurrence = new Horde_Date_Recurrence($start); } else { $recurrence->setRecurStart($start); } if (Horde_Util::getFormData('recur_end_type') == 'date') { $end_date = Horde_Util::getFormData('recur_end_date', false); if ($end_date !== false) { // From ajax interface. if (empty($end_date)) { throw new Kronolith_Exception("Missing required end date of recurrence."); } $date_ob = Kronolith::parseDate($end_date, false); $recur_enddate = array( 'year' => $date_ob->year, 'month' => $date_ob->month, 'day' => $date_ob->mday); } else { // From traditional interface. $recur_enddate = Horde_Util::getFormData('recur_end'); } if ($recurrence->hasRecurEnd()) { $recurEnd = $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']), $timezone); } $recurrence->setRecurEnd($recurEnd); } elseif (Horde_Util::getFormData('recur_end_type') == 'count') { $recurrence->setRecurCount(Horde_Util::getFormData('recur_count')); } elseif (Horde_Util::getFormData('recur_end_type') == 'none') { $recurrence->setRecurCount(0); $recurrence->setRecurEnd(null); } $recurrence->setRecurType($recur); switch ($recur) { case Horde_Date_Recurrence::RECUR_DAILY: $recurrence->setRecurInterval(Horde_Util::getFormData('recur_daily_interval', 1)); break; case Horde_Date_Recurrence::RECUR_WEEKLY: $weekly = Horde_Util::getFormData('weekly'); $weekdays = 0; if (is_array($weekly)) { foreach ($weekly as $day) { $weekdays |= $day; } } if ($weekdays == 0) { // Sunday starts at 0. switch ($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; } } $recurrence->setRecurInterval(Horde_Util::getFormData('recur_weekly_interval', 1)); $recurrence->setRecurOnDay($weekdays); break; case Horde_Date_Recurrence::RECUR_MONTHLY_DATE: switch (Horde_Util::getFormData('recur_monthly_scheme')) { case Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY: $recurrence->setRecurType(Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY); case Horde_Date_Recurrence::RECUR_MONTHLY_DATE: $recurrence->setRecurInterval( Horde_Util::getFormData('recur_monthly') ? 1 : Horde_Util::getFormData('recur_monthly_interval', 1) ); break; default: $recurrence->setRecurInterval(Horde_Util::getFormData('recur_day_of_month_interval', 1)); break; } break; case Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY: $recurrence->setRecurInterval(Horde_Util::getFormData('recur_week_of_month_interval', 1)); break; case Horde_Date_Recurrence::RECUR_YEARLY_DATE: switch (Horde_Util::getFormData('recur_yearly_scheme')) { case Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY: case Horde_Date_Recurrence::RECUR_YEARLY_DAY: $recurrence->setRecurType(Horde_Util::getFormData('recur_yearly_scheme')); case Horde_Date_Recurrence::RECUR_YEARLY_DATE: $recurrence->setRecurInterval( Horde_Util::getFormData('recur_yearly') ? 1 : Horde_Util::getFormData('recur_yearly_interval', 1) ); break; default: $recurrence->setRecurInterval(Horde_Util::getFormData('recur_yearly_interval', 1)); break; } break; case Horde_Date_Recurrence::RECUR_YEARLY_DAY: $recurrence->setRecurInterval(Horde_Util::getFormData('recur_yearly_day_interval', $yearly_interval)); break; case Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY: $recurrence->setRecurInterval(Horde_Util::getFormData('recur_yearly_weekday_interval', $yearly_interval)); break; } foreach (array('exceptions', 'completions') as $what) { if ($data = Horde_Util::getFormData($what)) { if (!is_array($data)) { $data = explode(',', $data); } foreach ($data as $date) { list($year, $month, $mday) = sscanf($date, '%04d%02d%02d'); if ($what == 'exceptions') { $recurrence->addException($year, $month, $mday); } else { $recurrence->addCompletion($year, $month, $mday); } } } } return $recurrence; } /** * Handles updating/saving this event's resources. Unless this event recurs, * this will delete this event from any resource calendars that are no * longer needed (as when a resource is removed from an existing event). If * this event is an exception, i.e., contains a baseid, AND $existing is * provided, the resources from the original event are used for purposes * of determining any resources that need to be removed. * * * @param Kronolith_Event|null $existing An existing base event. * @since 4.2.6 */ protected function _handleResources(Kronolith_Event $existing = null) { global $session; if (Horde_Util::getFormData('isajax', false)) { $resources = array(); } else { $resources = $session->get('kronolith', 'resources', Horde_Session::TYPE_ARRAY); } $existingResources = $this->_resources; $newresources = Horde_Util::getFormData('resources'); if (!empty($newresources)) { foreach (explode(',', $newresources) as $id) { try { $resource = Kronolith::getDriver('Resource')->getResource($id); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push($e->getMessage(), 'horde.error'); continue; } if (!($resource instanceof Kronolith_Resource_Group) || $resource->isFree($this)) { $resources[$resource->getId()] = array( 'attendance' => Kronolith::PART_REQUIRED, 'response' => Kronolith::RESPONSE_NONE, 'name' => $resource->get('name') ); } else { $GLOBALS['notification']->push(_("No resources from this group were available"), 'horde.error'); } } } $this->_resources = $resources; // Have the base event, and this is an exception so we must // match the recurrence in the resource's copy of the base event. if (!empty($existing) && $existing->recurs() && !$this->recurs()) { foreach ($existing->getResources() as $rid => $data) { $resource = Kronolith::getDriver('Resource')->getResource($key); $r_event = Kronolith::getDriver('Resource')->getByUID($existing->uid, $resource->calendar); $r_event->recurrence = $event->recurrence; $r_event->save(); } } // If we don't recur, check for removal of any resources so we can // update those resources' calendars. if (!$this->recurs()) { $merged = $existingResources + $this->_resources; $delete = array_diff(array_keys($existingResources), array_keys($this->_resources)); foreach ($delete as $key) { // Resource might be declined, in which case it won't have the event // on it's calendar. if ($merged[$key]['response'] != Kronolith::RESPONSE_DECLINED) { try { Kronolith::getDriver('Resource') ->getResource($key) ->removeEvent($this); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push('foo', 'horde.error'); } } } } } 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"' . ' 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)); } $label = _("Start Month"); break; case 'start[day]': $sel = $this->start->mday; for ($i = 1; $i < 32; ++$i) { $options[$i] = $i; } $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; } $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; } $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"' . ' id="' . $this->_formIDEncode($property) . '" size="4" maxlength="4" />'; case 'end[month]': $sel = $this->end ? $this->end->month : $this->start->month; for ($i = 1; $i < 13; ++$i) { $options[$i] = strftime('%b', mktime(1, 1, 1, $i, 1)); } $label = _("End Month"); break; case 'end[day]': $sel = $this->end ? $this->end->mday : $this->start->mday; for ($i = 1; $i < 32; ++$i) { $options[$i] = $i; } $label = _("End Day"); break; case 'end_hour': $sel = $this->end ? $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; } $label = _("End Hour"); break; case 'end_min': $sel = $this->end ? $this->end->min : $this->start->min; $sel = sprintf('%02d', $sel); for ($i = 0; $i < 12; ++$i) { $min = sprintf('%02d', $i * 5); $options[$min] = $min; } $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"' . ' id="' . $property . '" size="4" maxlength="4" />'; case 'dur_hour': $dur = $this->getDuration(); $sel = $dur->hour; for ($i = 0; $i < 24; ++$i) { $options[$i] = $i; } $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; } $label = _("Duration Minute"); break; case 'recur_end[year]': if ($this->end) { $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"' . ' id="' . $this->_formIDEncode($property) . '" size="4" maxlength="4" />'; case 'recur_end[month]': if ($this->end) { $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)); } $label = _("Recurrence End Month"); break; case 'recur_end[day]': if ($this->end) { $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; } $label = _("Recurrence End Day"); break; } if (!$this->_varRenderer) { $this->_varRenderer = Horde_Core_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>'; } /** * @param array $params * * @return Horde_Url */ public function getViewUrl($params = array(), $full = false, $encoded = true) { $params['eventID'] = $this->id; $params['calendar'] = $this->calendar; $params['type'] = $this->calendarType; return Horde::url('event.php', $full)->setRaw(!$encoded)->add($params); } /** * @param array $params * * @return Horde_Url */ public function getEditUrl($params = array(), $full = false) { $params['view'] = 'EditEvent'; $params['eventID'] = $this->id; $params['calendar'] = $this->calendar; $params['type'] = $this->calendarType; return Horde::url('event.php', $full)->add($params); } /** * @param array $params * * @return Horde_Url */ public function getDeleteUrl($params = array(), $full = false) { $params['view'] = 'DeleteEvent'; $params['eventID'] = $this->id; $params['calendar'] = $this->calendar; $params['type'] = $this->calendarType; return Horde::url('event.php', $full)->add($params); } /** * @param array $params * * @return Horde_Url */ public function getExportUrl($params = array(), $full = false) { $params['view'] = 'ExportEvent'; $params['eventID'] = $this->id; $params['calendar'] = $this->calendar; $params['type'] = $this->calendarType; return Horde::url('event.php', $full)->add($params); } public function getLink($datetime = null, $icons = true, $from_url = null, $full = false, $encoded = true) { global $prefs; if (is_null($datetime)) { $datetime = $this->start; } if (is_null($from_url)) { $from_url = Horde::selfUrl(true, false, true); } $event_title = $this->getTitle(); $view_url = $this->getViewUrl(array('datetime' => $datetime->strftime('%Y%m%d%H%M%S'), 'url' => $from_url), $full, $encoded); $read_permission = $this->hasPermission(Horde_Perms::READ); $link = '<span' . $this->getCSSColors() . '>'; if ($read_permission && $view_url) { $link .= Horde::linkTooltip($view_url, $event_title, $this->getStatusClass(), '', '', $this->getTooltip(), '', array('style' => $this->getCSSColors(false))); } $link .= htmlspecialchars($event_title); if ($read_permission && $view_url) { $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 = sprintf(ngettext("Alarm %d week before", "Alarm %d weeks before", $alarm_value), $alarm_value); } elseif ($this->alarm % 1440 == 0) { $alarm_value = $this->alarm / 1440; $title = sprintf(ngettext("Alarm %d day before", "Alarm %d days before", $alarm_value), $alarm_value); } elseif ($this->alarm % 60 == 0) { $alarm_value = $this->alarm / 60; $title = sprintf(ngettext("Alarm %d hour before", "Alarm %d hours before", $alarm_value), $alarm_value); } else { $alarm_value = $this->alarm; $title = sprintf(ngettext("Alarm %d minute before", "Alarm %d minutes before", $alarm_value), $alarm_value); } $status .= Horde::fullSrcImg('alarm-' . $icon_color . '.png', array('attr' => array('alt' => $title, 'title' => $title, 'class' => 'iconAlarm'))); } if ($this->recurs()) { $title = Kronolith::recurToString($this->recurrence->getRecurType()); $status .= Horde::fullSrcImg('recur-' . $icon_color . '.png', array('attr' => array('alt' => $title, 'title' => $title, 'class' => 'iconRecur'))); } elseif ($this->baseid) { $title = _("Exception"); $status .= Horde::fullSrcImg('exception-' . $icon_color . '.png', array('attr' => array('alt' => $title, 'title' => $title, 'class' => 'iconRecur'))); } if ($this->private) { $title = _("Private event"); $status .= Horde::fullSrcImg('private-' . $icon_color . '.png', array('attr' => array('alt' => $title, 'title' => $title, 'class' => 'iconPrivate'))); } if (!empty($this->attendees)) { $status .= Horde::fullSrcImg('attendees-' . $icon_color . '.png', array('attr' => array('alt' => _("Meeting"), 'title' => _("Meeting"), 'class' => 'iconPeople'))); } $space = ' '; if (!empty($this->icon)) { $link = $status . ' <img class="kronolithEventIcon" src="' . $this->icon . '" /> ' . $link; } elseif (!empty($status)) { $link .= ' ' . $status; $space = ''; } if ((!$this->private || $this->creator == $GLOBALS['registry']->getAuth()) && Kronolith::getDefaultCalendar(Horde_Perms::EDIT)) { $url = $this->getEditUrl( array('datetime' => $datetime->strftime('%Y%m%d%H%M%S'), 'url' => $from_url), $full); if ($url) { $link .= $space . $url->link(array('title' => sprintf(_("Edit %s"), $event_title), 'class' => 'iconEdit')) . Horde::fullSrcImg('edit-' . $icon_color . '.png', array('attr' => array('alt' => _("Edit")))) . '</a>'; $space = ''; } } if ($this->hasPermission(Horde_Perms::DELETE)) { $url = $this->getDeleteUrl( array('datetime' => $datetime->strftime('%Y%m%d%H%M%S'), 'url' => $from_url), $full); if ($url) { $link .= $space . $url->link(array('title' => sprintf(_("Delete %s"), $event_title), 'class' => 'iconDelete')) . Horde::fullSrcImg('delete-' . $icon_color . '.png', array('attr' => array('alt' => _("Delete")))) . '</a>'; } } } return $link . '</span>'; } /** * 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->creator == $GLOBALS['registry']->getAuth() ? _("Me") : Kronolith::getUserName($this->creator))); if (!$this->isPrivate()) { if ($this->location) { $tooltip .= "\n" . _("Location") . ': ' . $this->location; } if ($this->description) { $tooltip .= "\n\n" . Horde_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 'kronolith-event-cancelled'; case Kronolith::STATUS_TENTATIVE: case Kronolith::STATUS_FREE: return 'kronolith-event-tentative'; } } protected function _formIDEncode($id) { return str_replace(array('[', ']'), array('_', ''), $id); } /** * Ensure the given string is valid UTF-8. * * @param string $text The string to ensure contains no invalid UTF-8 sequences. * * @return string|boolean The valid UTF-8 string, possibly with illegal sequences removed. */ protected function _ensureUtf8($text) { if (Horde_String::validUtf8($text)) { return $text; } return preg_replace('/[^\x09\x0A\x0D\x20-\x7E]/', '', $text); } }
Simpan