mirror of
https://github.com/YunoHost-Apps/agendav_ynh.git
synced 2024-09-03 20:36:12 +02:00
1038 lines
34 KiB
PHP
1038 lines
34 KiB
PHP
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
|
|
|
|
/*
|
|
* Copyright 2011-2012 Jorge López Pérez <jorge@adobo.org>
|
|
*
|
|
* This file is part of AgenDAV.
|
|
*
|
|
* AgenDAV is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* any later version.
|
|
*
|
|
* AgenDAV is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with AgenDAV. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
class Icshelper {
|
|
private $config; // for iCalCreator
|
|
|
|
private $tz;
|
|
|
|
private $date_format; // Date format given by lang file
|
|
|
|
private $date_frontend_format, $time_frontend_format;
|
|
|
|
|
|
function __construct() {
|
|
|
|
$this->CI =& get_instance();
|
|
|
|
// Timezone
|
|
$this->tz = $this->CI->timezonemanager->getTz(
|
|
$this->CI->config->item('default_timezone'));
|
|
|
|
$this->date_format = $this->CI->config->item('format_full_date');
|
|
$this->date_frontend_format = $this->CI->dates->date_format_string('date');
|
|
$this->time_frontend_format = $this->CI->dates->time_format_string('date');
|
|
|
|
$this->config = array(
|
|
'unique_id' =>
|
|
$this->CI->config->item('icalendar_unique_id'),
|
|
);
|
|
|
|
require_once('iCalcreator.class.php');
|
|
}
|
|
|
|
/**
|
|
* Creates a new iCalendar resource
|
|
*
|
|
* Property keys can be lowercase
|
|
*
|
|
* Returns generated guid, FALSE on error. $generated will be filled with
|
|
* new generated resource (iCalComponent object)
|
|
*/
|
|
function new_resource($properties, &$generated, $tz, $reminders =
|
|
array()) {
|
|
$properties = array_change_key_case($properties, CASE_UPPER);
|
|
|
|
$contents = '';
|
|
$ical = new vcalendar($this->config);
|
|
// Default CALSCALE in standard
|
|
$ical->setProperty('calscale', 'GREGORIAN');
|
|
|
|
$allday = (isset($properties['ALLDAY']) && $properties['ALLDAY'] ==
|
|
'true');
|
|
|
|
if ($allday) {
|
|
// Discard timezone
|
|
$tz = $this->CI->timezonemanager->getTz('UTC');
|
|
}
|
|
|
|
// Add VTIMEZONE
|
|
$this->add_vtimezone($ical, $tz->getName());
|
|
|
|
$vevent =& $ical->newComponent('vevent');
|
|
|
|
$now = $this->CI->dates->datetime2idt();
|
|
$uid = $this->generate_guid();
|
|
|
|
$vevent->setProperty('CREATED', $now);
|
|
$vevent->setProperty('LAST-MODIFIED', $now);
|
|
$vevent->setProperty('DTSTAMP', $now);
|
|
$vevent->setProperty('UID', $uid);
|
|
$vevent->setProperty('SEQUENCE', '0'); // RFC5545, 3.8.7.4
|
|
$vevent->setProperty('SUMMARY', $properties['SUMMARY']);
|
|
|
|
// Rest of properties
|
|
$add_prop = array('DTSTART', 'DTEND', 'DESCRIPTION', 'LOCATION',
|
|
'DURATION', 'RRULE', 'TRANSP', 'CLASS');
|
|
|
|
foreach ($add_prop as $p) {
|
|
if (isset($properties[$p]) && !empty($properties[$p])) {
|
|
$params = FALSE;
|
|
|
|
// Generate DTSTART/DTEND
|
|
if ($p == 'DTSTART' || $p == 'DTEND') {
|
|
if ($tz->getName() != 'UTC') {
|
|
$params = array('TZID' => $tz->getName());
|
|
}
|
|
$properties[$p] = $this->CI->dates->datetime2idt(
|
|
$properties[$p], $tz);
|
|
// All day: use parameter VALUE=DATE
|
|
if ($allday) {
|
|
$params['VALUE'] = 'DATE';
|
|
}
|
|
}
|
|
$vevent->setProperty($p, $properties[$p], $params);
|
|
}
|
|
}
|
|
|
|
// VALARM components (reminders)
|
|
$vevent = $this->set_valarms($vevent, $reminders);
|
|
|
|
|
|
$generated = $ical;
|
|
return $uid;
|
|
}
|
|
|
|
|
|
/**
|
|
* Generates a new GUID
|
|
*
|
|
* Found on phunction PHP framework
|
|
* (http://sourceforge.net/projects/phunction/)
|
|
*/
|
|
function generate_guid()
|
|
{
|
|
if (function_exists('com_create_guid') === true)
|
|
{
|
|
return trim(com_create_guid(), '{}');
|
|
}
|
|
|
|
return sprintf('%04X%04X-%04X-%04X-%04X-%04X%04X%04X',
|
|
mt_rand(0, 65535), mt_rand(0, 65535),
|
|
mt_rand(0, 65535), mt_rand(16384, 20479),
|
|
mt_rand(32768, 49151), mt_rand(0, 65535),
|
|
mt_rand(0, 65535), mt_rand(0, 65535));
|
|
}
|
|
|
|
/**
|
|
* Expands a list of resources to repeated events, depending on
|
|
* recurrence rules and recurrence exceptions/modifications
|
|
*
|
|
* @param array() $resources Resources returned by GetEvents
|
|
* @param int $start Start timestamp
|
|
* @param int $end End timestamp
|
|
* @param string $calendar Current calendar
|
|
*/
|
|
function expand_and_parse_events($resources, $start, $end, $calendar) {
|
|
$result = array();
|
|
|
|
// Dates
|
|
$utc = $this->CI->timezonemanager->getTz('UTC');
|
|
$date_start = new DateTime($start, $utc);
|
|
$date_end = new DateTime($end, $utc);
|
|
|
|
foreach ($resources as $r) {
|
|
$event_href = $r['href'];
|
|
$event_etag = $r['etag'];
|
|
|
|
$ical = new vcalendar($this->config);
|
|
$res = $ical->parse($r['data']);
|
|
if ($res === FALSE) {
|
|
$this->CI->extended_logs->message('ERROR',
|
|
"Couldn't parse event with href=" . $calendar . '/'
|
|
.$event_href);
|
|
}
|
|
$ical->sort();
|
|
|
|
$timezones = $this->get_timezones($ical);
|
|
|
|
$sy = intval($date_start->format('Y'));
|
|
$sm = intval($date_start->format('m'));
|
|
$sd = intval($date_start->format('d'));
|
|
$ey = intval($date_end->format('Y'));
|
|
$em = intval($date_end->format('m'));
|
|
$ed = intval($date_end->format('d'));
|
|
|
|
/*
|
|
log_message('INTERNALS', 'Pidiendo expansión para ' . $sy . '-'
|
|
. $sm . '-' . $sd . ' a ' . $ey . '-' . $em . '-' .
|
|
$ed);
|
|
log_message('INTERNALS', $event_href);
|
|
log_message('INTERNALS', $r['data']);
|
|
*/
|
|
|
|
$expand = $ical->selectComponents($sy, $sm, $sd, $ey, $em, $ed,
|
|
'vevent', false, true, false);
|
|
|
|
if ($expand !== FALSE) {
|
|
foreach( $expand as $year => $year_arr ) {
|
|
foreach( $year_arr as $month => $month_arr ) {
|
|
foreach( $month_arr as $day => $day_arr ) {
|
|
foreach( $day_arr as $event ) {
|
|
$tz = $this->detect_tz($event, $timezones);
|
|
$result[] =
|
|
$this->parse_vevent_fullcalendar($event,
|
|
$event_href, $event_etag, $calendar,
|
|
$tz, $timezones);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$expand = $ical->selectComponents($sy, $sm, $sd, $ey, $em, $ed,
|
|
'vevent', true, true, false);
|
|
if ($expand === FALSE) {
|
|
$this->CI->extended_logs->message('ERROR',
|
|
"Server sent an event which doesn't fit in our dates interval");
|
|
} else {
|
|
foreach($expand as $event) {
|
|
$tz = $this->detect_tz($event, $timezones);
|
|
$result[] =
|
|
$this->parse_vevent_fullcalendar($event,
|
|
$event_href, $event_etag, $calendar,
|
|
$tz, $timezones);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
/**
|
|
* Parses an VEVENT for Fullcalendar
|
|
*/
|
|
function parse_vevent_fullcalendar($vevent,
|
|
$href, $etag, $calendar = 'calendario', $tz, $timezones) {
|
|
|
|
$this_event = array(
|
|
'href' => $href,
|
|
'calendar' => $calendar,
|
|
'etag' => $etag,
|
|
'disableDragging' => FALSE,
|
|
'disableResizing' => FALSE,
|
|
'ignoreTimezone' => TRUE,
|
|
'timezone' => $tz->getName(),
|
|
);
|
|
|
|
// Start and end date
|
|
$dtstart = $this->extract_date($vevent, 'DTSTART', $tz);
|
|
$dtend = $this->extract_date($vevent, 'DTEND', $tz);
|
|
|
|
// We have for sure DTSTART
|
|
$start = $dtstart['result'];
|
|
|
|
// Do we have DTEND?
|
|
if (!is_null($dtend)) {
|
|
$end = $dtend['result'];
|
|
} else {
|
|
$duration = $vevent->getProperty('duration',
|
|
false, false, true);
|
|
|
|
// Calculate dtend if not present
|
|
if ($duration !== FALSE) {
|
|
$end = $this->CI->dates->idt2datetime($duration,
|
|
$tz);
|
|
} else {
|
|
// RFC 2445, p52
|
|
if ($dtstart['value'] == 'DATE-TIME') {
|
|
$end = clone $start;
|
|
} else {
|
|
$end = clone $start;
|
|
$end->add(new DateInterval('P1D'));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Is this a recurrent event?
|
|
if (FALSE !== ($current_dtstart =
|
|
$vevent->getProperty('x-current-dtstart'))) {
|
|
// Is this a multiday event? In that case, ignore this event
|
|
|
|
// Hack to avoid getProperty() ignore next getProperty() on
|
|
// RRULE.
|
|
if (FALSE === $vevent->rrule) {
|
|
return FALSE;
|
|
}
|
|
|
|
$this_event['expanded'] = TRUE;
|
|
|
|
// Format depends on DTSTART
|
|
if (!isset($dtstart['property']['value']['hour'])) {
|
|
$current_dtstart[1] .= ' 00:00:00';
|
|
}
|
|
|
|
// Keep a copy
|
|
$orig_start = clone $start;
|
|
|
|
$start = $this->CI->dates->x_current2datetime($current_dtstart[1], $tz);
|
|
unset($this_event['end']);
|
|
|
|
$current_dtend = $vevent->getProperty('x-current-dtend');
|
|
if ($current_dtend !== FALSE) {
|
|
if (!isset($dtstart['property']['value']['hour'])) {
|
|
$current_dtend[1] .= ' 00:00:00';
|
|
}
|
|
|
|
$orig_end = clone $end;
|
|
$end =
|
|
$this->CI->dates->x_current2datetime($current_dtend[1],
|
|
$tz);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
$interesting_props = array(
|
|
'summary', 'uid', 'description', 'rrule',
|
|
'duration', 'location', 'class', 'recurrence-id',
|
|
'transp',
|
|
);
|
|
|
|
foreach ($interesting_props as $p) {
|
|
// TODO: more properties
|
|
// TODO multiple ocurrences of the same property?
|
|
// TODO current-dtstart
|
|
$prop = $vevent->getProperty($p, FALSE, TRUE);
|
|
|
|
if ($prop === FALSE) {
|
|
continue;
|
|
}
|
|
|
|
$val = $prop['value'];
|
|
$params = $prop['params'];
|
|
switch ($p) {
|
|
case 'summary':
|
|
$this_event['title'] = $val;
|
|
break;
|
|
case 'uid':
|
|
$this_event['uid'] = $val;
|
|
break;
|
|
case 'description':
|
|
$description = $val;
|
|
$this_event['description'] =
|
|
preg_replace('/\\\n|\\\r/', "\n", $description);
|
|
|
|
// Format
|
|
$this_event['formatted_description'] =
|
|
preg_replace('/\\\n|\\\r/', '<br />', $description);
|
|
break;
|
|
case 'rrule':
|
|
$this_event['rrule_serialized'] =
|
|
base64_encode(serialize($val));
|
|
$new_val = trim($vevent->_format_recur('RRULE',
|
|
array($prop)));
|
|
$this_event['rrule'] = $new_val;
|
|
|
|
$explanation =
|
|
$this->CI->recurrence->rrule_explain($val,
|
|
$unused);
|
|
if ($explanation !== FALSE) {
|
|
$this_event['rrule_explained'] = $explanation;
|
|
}
|
|
// TODO make it editable when able to parse it
|
|
$this_event['editable'] = FALSE;
|
|
break;
|
|
case 'duration':
|
|
$this_event['duration'] =
|
|
iCalUtilityFunctions::_format_duration($val);
|
|
break;
|
|
case 'location':
|
|
$this_event['location'] = $val;
|
|
break;
|
|
case 'class':
|
|
$this_event['icalendar_class'] = $val;
|
|
break;
|
|
case 'transp':
|
|
$this_event['transp'] = $val;
|
|
break;
|
|
case 'recurrence-id':
|
|
// TODO parse a little bit
|
|
$this_event['recurrence_id'] = $val;
|
|
break;
|
|
default:
|
|
$this->CI->extended_logs->message('ERROR',
|
|
'Attempt to parse iCalendar property ' . $p
|
|
. ' on VEVENT which is not developed '
|
|
.'yet');
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Internal fullCalendar id
|
|
$this_event['id'] = $calendar . ':' . $this_event['uid'];
|
|
|
|
|
|
|
|
|
|
// Is this an all day event?
|
|
$this_event['allDay'] = FALSE;
|
|
|
|
if (isset($dtstart['value']) &&
|
|
$dtstart['value'] == 'DATE') {
|
|
$this_event['allDay'] = TRUE;
|
|
} else if ($start->diff($end)->format('s') == '86400') {
|
|
if ($start->format('Hi') == '0000') {
|
|
$this_event['allDay'] = TRUE;
|
|
}
|
|
|
|
// Check using UTC and local time
|
|
if ($start->getTimeZone()->getName() == 'UTC') {
|
|
$test_start = clone $start;
|
|
$test_start->setTimeZone($this->tz);
|
|
if ($test_start->format('Hi') == '0000') {
|
|
$this_event['allDay'] = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($this_event['allDay'] === TRUE) {
|
|
// Fool fullcalendar (dates are inclusive).
|
|
// For expanded events have special care,
|
|
// iCalcreator expands them using start_day=end_day, which
|
|
// confuses fullCalendar
|
|
|
|
$start->setTime(0, 0, 0);
|
|
$end->setTime(0, 0, 0);
|
|
|
|
$end->sub(new DateInterval('P1D'))->add(new
|
|
DateInterval('PT1H'));
|
|
|
|
if (isset($this_event['expanded'])) {
|
|
$orig_start->setTime(0, 0, 0);
|
|
$orig_end->setTime(0, 0, 0);
|
|
|
|
$orig_end->sub(new DateInterval('P1D'))->add(new
|
|
DateInterval('PT1H'));
|
|
}
|
|
|
|
|
|
$this_event['orig_allday'] = TRUE;
|
|
|
|
} else {
|
|
$this_event['orig_allday'] = FALSE;
|
|
}
|
|
|
|
|
|
// To be used with strftime()
|
|
$ts_start = $start->getTimestamp();
|
|
$ts_end = $end->getTimestamp();
|
|
|
|
// Needed for some conversions (Fullcalendar timestamp and am/pm
|
|
// indicator)
|
|
if (!isset($this_event['allDay'])
|
|
|| $this_event['allDay'] !== TRUE) {
|
|
$start->setTimeZone($this->tz);
|
|
$end->setTimeZone($this->tz);
|
|
}
|
|
|
|
// Expanded events
|
|
if (isset($orig_start)) {
|
|
$orig_start->setTimeZone($this->tz);
|
|
$orig_end->setTimeZone($this->tz);
|
|
$this_event['orig_start'] = $orig_start->format(DateTime::ISO8601);
|
|
$this_event['orig_end'] = $orig_end->format(DateTime::ISO8601);
|
|
}
|
|
|
|
// Readable dates for start and end
|
|
|
|
// Keep all day events as they are (UTC)
|
|
$system_tz = date_default_timezone_get();
|
|
if (!isset($this_event['allDay'])
|
|
|| $this_event['allDay'] !== TRUE) {
|
|
date_default_timezone_set($this->tz->getName());
|
|
}
|
|
|
|
$this_event['formatted_start'] = strftime($this->date_format, $ts_start);
|
|
|
|
if (isset($this_event['allDay']) && $this_event['allDay'] == TRUE) {
|
|
// Next day?
|
|
if ($start->format('Ymd') == $end->format('Ymd')) {
|
|
$this_event['formatted_end'] =
|
|
'('.$this->CI->i18n->_('labels', 'allday').')';
|
|
} else {
|
|
$this_event['formatted_end'] = strftime($this->date_format, $ts_end);
|
|
}
|
|
} else {
|
|
// Are they in the same day?
|
|
$this_event['formatted_start'] .= ' '
|
|
. $this->CI->dates->strftime_time($ts_start, $start);
|
|
if ($start->format('Ymd') == $end->format('Ymd')) {
|
|
$this_event['formatted_end'] =
|
|
$this->CI->dates->strftime_time($ts_end, $end);
|
|
} else {
|
|
$this_event['formatted_end'] =
|
|
strftime($this->date_format, $ts_end) . ' ' .
|
|
$this->CI->dates->strftime_time($ts_end, $end);
|
|
}
|
|
}
|
|
|
|
// Restore TZ
|
|
date_default_timezone_set($system_tz);
|
|
|
|
// Empty title?
|
|
if (!isset($this_event['title'])) {
|
|
$this_event['title'] = $this->CI->i18n->_('labels', 'untitled');
|
|
}
|
|
|
|
$this_event['start'] = $start->format(DateTime::ISO8601);
|
|
$this_event['end'] = $end->format(DateTime::ISO8601);
|
|
|
|
// Reminders for this event
|
|
$this_event['visible_reminders'] = array();
|
|
$this_event['reminders'] = array();
|
|
|
|
$valarms = $this->parse_valarms($vevent, $timezones);
|
|
|
|
foreach ($valarms as $order => $reminder) {
|
|
$this_event['visible_reminders'][] = $order;
|
|
$this_event['reminders'][] = $reminder;
|
|
}
|
|
|
|
return $this_event;
|
|
}
|
|
|
|
/**
|
|
* Parses an iCalendar resource
|
|
*/
|
|
function parse_icalendar($data) {
|
|
$vcalendar = new vcalendar($this->config);
|
|
$vcalendar->parse($data);
|
|
|
|
return $vcalendar;
|
|
}
|
|
|
|
|
|
/**
|
|
* Collects all timezones (VTIMEZONE) present in a resource
|
|
*
|
|
* Returns an associative array with 'tzid' => DateTimeZone('real tz
|
|
* name')
|
|
*/
|
|
function get_timezones($icalendar) {
|
|
$result = array();
|
|
while ($vt = $icalendar->getComponent('vtimezone')) {
|
|
$tzid = $vt->getProperty('TZID');
|
|
// Contains (usually) the time zone name
|
|
$tzval = $vt->getProperty('X-LIC-LOCATION');
|
|
|
|
if ($tzval === FALSE || empty($tzval)) {
|
|
// Try to extract it from TZID name
|
|
$tzval = $tzid;
|
|
} else {
|
|
$tzval = $tzval[1];
|
|
}
|
|
|
|
// Do we have tzval?
|
|
if ($tzval !== FALSE && !empty($tzval)) {
|
|
$result[$tzid] = $this->CI->timezonemanager->getTz($tzval);
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Finds a component within a resource, and returns its index in the
|
|
* components array.
|
|
*
|
|
* Useful for replacing existing components by using GetComponents() to
|
|
* save resources directly
|
|
*
|
|
* @param iCalComponent $reosurce Full iCalComponent VCALENDAR
|
|
* @param string $type VEVENT, VTIMEZONE, etc
|
|
* @param conditions Associative array. Possible keys:
|
|
* - RECURRENCE-ID
|
|
* - ?
|
|
* @param iCalComponent The found object
|
|
*/
|
|
function find_component_position($resource, $type,
|
|
$conditions = array(), &$comp) {
|
|
|
|
// Position
|
|
$i = 1;
|
|
$found = FALSE;
|
|
$comp = null;
|
|
|
|
while ($found === FALSE && ($c = $resource->getComponent($type))) {
|
|
// Check conditions
|
|
if (isset($conditions['recurrence-id'])) {
|
|
$recurr_id = $c->getProperty('recurrence-id');
|
|
if ($recurr_id !== FALSE && $recurr_id ==
|
|
$conditions['recurrence-id']) {
|
|
$found = $i;
|
|
}
|
|
} else if (!isset($conditions['recurrence-id'])) {
|
|
$found = $i;
|
|
}
|
|
|
|
if ($found !== FALSE) {
|
|
$comp = $c;
|
|
}
|
|
}
|
|
|
|
return $found;
|
|
}
|
|
|
|
/**
|
|
* Replaces a component in the n-th position
|
|
*/
|
|
function replace_component($resource, $type, $n, $new) {
|
|
$resource->setComponent($new, $type, $n);
|
|
return $resource;
|
|
}
|
|
|
|
|
|
/**
|
|
* Applies a LAST-MODIFIED change on the iCalendar component
|
|
* (VEVENT, etc)
|
|
*/
|
|
function set_last_modified($component) {
|
|
$now = $this->CI->dates->datetime2idt();
|
|
|
|
$component->setProperty('last-modified', $now);
|
|
|
|
// SEQUENCE
|
|
$seq = $component->getProperty('sequence');
|
|
if ($seq !== FALSE) {
|
|
$seq = intval($seq);
|
|
$seq++;
|
|
$component->setProperty('sequence', $seq);
|
|
}
|
|
|
|
return $component;
|
|
}
|
|
|
|
/**
|
|
* Gets DTSTART/other property timezone from a component
|
|
*
|
|
*/
|
|
function detect_tz($component, $tzs, $prop = 'dtstart') {
|
|
$dtstart = $component->getProperty($prop, FALSE, TRUE);
|
|
$val = $dtstart['value'];
|
|
$params = $dtstart['params'];
|
|
$has_z = isset($val['tz']) ? ($val['tz']=='Z') : FALSE;
|
|
$value = $this->paramvalue($params, 'value');;
|
|
$used_tz = null;
|
|
if ($has_z || $value == 'DATE') {
|
|
$used_tz = $this->CI->timezonemanager->getTz('UTC');
|
|
} else {
|
|
$tzid = $this->paramvalue($params, 'tzid');;
|
|
|
|
if ($tzid !== FALSE && isset($tzs[$tzid])) {
|
|
$used_tz = $tzs[$tzid];
|
|
} else {
|
|
// Not UTC but no TZID/invalid TZID?!
|
|
$used_tz = $this->CI->timezonemanager->getTz(
|
|
$this->CI->config->item('default_timezone'));
|
|
}
|
|
}
|
|
|
|
return $used_tz;
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets a component DTSTART value
|
|
*
|
|
* @param iCalComponent $component
|
|
* @param DateTimeZone $tz Used TZ
|
|
* @param DateTime $new_start
|
|
* @param string $increment
|
|
* @param string $force_new_value_type
|
|
* @param string $force_new_tzid
|
|
*/
|
|
function make_start($component, $tz,
|
|
$new_start = null,
|
|
$increment = null,
|
|
$force_new_value_type = null,
|
|
$force_new_tzid = null) {
|
|
|
|
$value = null;
|
|
$format = null;
|
|
$params = array();
|
|
|
|
$info = $this->extract_date($component, 'DTSTART', $tz);
|
|
// No current DTSTART?
|
|
if (is_null($info)) {
|
|
$params = array('VALUE' => (is_null($force_new_value_type) ?
|
|
'DATE-TIME' : $force_new_value_type));
|
|
$value = new DateTime('now', $tz);
|
|
} else {
|
|
$params = $info['property']['params'];
|
|
if (!is_null($force_new_value_type)) {
|
|
$params['VALUE'] = $force_new_value_type;
|
|
} elseif (!isset($params['VALUE'])) {
|
|
$params['VALUE'] = 'DATE-TIME';
|
|
}
|
|
|
|
$value = $this->CI->dates->idt2datetime($info['property']['value'],
|
|
$tz);
|
|
}
|
|
|
|
// DATE values can't have TZID
|
|
if ($params['VALUE'] == 'DATE') {
|
|
unset($params['TZID']);
|
|
} else if (!is_null($force_new_tzid)) {
|
|
$params['TZID'] = $force_new_tzid;
|
|
}
|
|
|
|
$format = $this->CI->dates->format_for($params['VALUE'], $tz);
|
|
|
|
// Use current DTSTART
|
|
if (!is_null($new_start)) {
|
|
$value = $new_start;
|
|
}
|
|
|
|
// Increment
|
|
if (!is_null($increment)) {
|
|
$value->add($this->CI->dates->duration2di($increment));
|
|
}
|
|
|
|
$component->setProperty('dtstart', $this->CI->dates->datetime2idt(
|
|
$value, $tz, $format), $params);
|
|
|
|
return $component;
|
|
}
|
|
|
|
/**
|
|
* Sets a component end value
|
|
*
|
|
* @param iCalComponent $component
|
|
* @param DateTimeZone $tz Used TZ
|
|
* @param DateTime $new_start
|
|
* @param string $increment
|
|
* @param string $force_new_value_type
|
|
*/
|
|
function make_end($component, $tz,
|
|
$new_end = null,
|
|
$increment = null,
|
|
$force_new_value_type = null,
|
|
$force_new_tzid = null) {
|
|
|
|
$value = null;
|
|
$format = null;
|
|
$params = array();
|
|
|
|
$dtend_info = $this->extract_date($component, 'DTEND', $tz);
|
|
|
|
if (is_null($dtend_info)) {
|
|
// No DTEND in event
|
|
if (is_null($new_end)) {
|
|
// Event has DURATION defined. Generate DTEND and remove
|
|
// DURATION property
|
|
$dtend_info = $this->getProperty('duration', FALSE, FALSE, TRUE);
|
|
|
|
if ($dtend_info === FALSE) {
|
|
// Something is wrong . No DTEND nor DURATION
|
|
// Return the component as is
|
|
$this->CI->extended_logs->message('ERROR',
|
|
'Event with uid=' . $component->getProperty('uid')
|
|
.' has neither DTEND nor DURATION properties');
|
|
return $component;
|
|
}
|
|
|
|
$value = $this->CI->dates->idt2datetime($dtend, $tz);
|
|
|
|
}
|
|
|
|
// Get current DTSTART params
|
|
$dtstart_info = $this->extract_date($component, 'DTSTART', $tz);
|
|
if (is_null($dtend_info)) {
|
|
// Neither DTSTART nor DTEND!?
|
|
$params = array('VALUE' => 'DATE-TIME');
|
|
} else {
|
|
$params = $dtstart_info['property']['params'];
|
|
}
|
|
|
|
// We prefer DTEND to DURATION
|
|
$component->deleteProperty('duration');
|
|
} else {
|
|
$params = $dtend_info['property']['params'];
|
|
$value = $this->CI->dates->idt2datetime($dtend_info['property']['value'],
|
|
$tz);
|
|
}
|
|
|
|
// VALUE parameter
|
|
if (!is_null($force_new_value_type)) {
|
|
$params['VALUE'] = $force_new_value_type;
|
|
} elseif (!isset($params['VALUE'])) {
|
|
$params['VALUE'] = 'DATE-TIME';
|
|
}
|
|
|
|
|
|
// Use retrieved DTEND (or calculated)
|
|
if (!is_null($new_end)) {
|
|
$value = $new_end;
|
|
}
|
|
|
|
// Increment
|
|
if (!is_null($increment)) {
|
|
$value->add($this->CI->dates->duration2di($increment));
|
|
}
|
|
|
|
// DATE values can't have TZID
|
|
if ($params['VALUE'] == 'DATE') {
|
|
unset($params['TZID']);
|
|
} else if (!is_null($force_new_tzid)) {
|
|
$params['TZID'] = $force_new_tzid;
|
|
}
|
|
|
|
$format = $this->CI->dates->format_for($params['VALUE'], $tz);
|
|
|
|
// Save new value
|
|
$component->setProperty('dtend',
|
|
$this->CI->dates->datetime2idt($value, $tz, $format),
|
|
$params);
|
|
|
|
return $component;
|
|
}
|
|
|
|
/**
|
|
* Make easy to parse a DTSTART/DTEND
|
|
*/
|
|
function extract_date($component, $name = 'DTSTART', $tz) {
|
|
$p = $component->getProperty($name, FALSE, TRUE);
|
|
if ($p === FALSE) {
|
|
return null;
|
|
} else {
|
|
$val = $p['value'];
|
|
$params = $p['params'];
|
|
}
|
|
|
|
$obj = $this->CI->dates->idt2datetime(
|
|
$val,
|
|
$tz);
|
|
|
|
$value_parameter = $this->paramvalue($params, 'value', 'DATE-TIME');
|
|
|
|
return array(
|
|
'property' => $p,
|
|
'value' => $value_parameter,
|
|
'result' => $obj,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Changes every property passed as an associative array (key will be
|
|
* uppercased) on given component. DTSTART, DTEND and
|
|
* DURATION are ignored, use make_start and make_end instead
|
|
*
|
|
* @param iCalComponent $component
|
|
* @param array $properties
|
|
*/
|
|
|
|
function change_properties($component, $properties) {
|
|
$properties = array_change_key_case($properties, CASE_UPPER);
|
|
|
|
foreach ($properties as $p => $v) {
|
|
if ($p == 'DTSTART' || $p == 'DTEND' || $p == 'DURATION') {
|
|
continue;
|
|
}
|
|
// TODO: multivalued properties?
|
|
|
|
// TRANSP
|
|
if ($p == 'TRANSP') {
|
|
if ($v != 'OPAQUE' && $v != 'TRANSPARENT') {
|
|
log_message('ERROR', 'Invalid TRANSP value ('.$v.'). Ignoring.');
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$component->deleteProperty($p);
|
|
if (!empty($v)) {
|
|
$component->setProperty($p, $v);
|
|
}
|
|
}
|
|
|
|
return $component;
|
|
}
|
|
|
|
|
|
/**
|
|
* Changes an event to have all its components with a new timezone
|
|
*
|
|
* Affected properties: DTSTART, DTEND, DUE, EXDATE, RDATE
|
|
*
|
|
* Only changed if VALUE is DATE-TIME or TIME
|
|
* Information extracted from RFC 2445, 4.2.19
|
|
*/
|
|
function change_tz($component, $old_tz, $new_tzid, $new_tz) {
|
|
$change = array('DTSTART', 'DTEND', 'DUE', 'EXDATE', 'RDATE');
|
|
foreach ($change as $c) {
|
|
$new_prop = array();
|
|
$prop = $component->GetProperties($c);
|
|
foreach ($prop as $p) {
|
|
$valuep = $p->GetParameterValue('VALUE');
|
|
if (!is_null($valuep) && $valuep == 'DATE') {
|
|
$new_prop[] = $p;
|
|
continue;
|
|
}
|
|
$tzid = $p->GetParameterValue('TZID');
|
|
if (!is_null($tzid) && $tzid == $new_tzid) {
|
|
// Keep untouched
|
|
$new_prop[] = $p;
|
|
continue;
|
|
}
|
|
|
|
// No TZ or different TZ
|
|
$val = $p->Value();
|
|
$multiple = preg_split('/,/', $val);
|
|
foreach ($multiple as $v) {
|
|
$new_p = clone $p;
|
|
$current = $this->CI->dates->idt2datetime($v,
|
|
$this->CI->dates->format_for($valuep, $old_tz),
|
|
$old_tz);
|
|
$new_p->SetParameterValue('TZID', $new_tzid);
|
|
$new_p->Value($this->CI->dates->datetime2idt($current,
|
|
$new_tz));
|
|
$new_prop[] = $new_p;
|
|
}
|
|
} // end foreach $prop
|
|
|
|
// Set new properties
|
|
$component->SetProperties($new_prop, $c);
|
|
}
|
|
|
|
return $component;
|
|
}
|
|
|
|
|
|
/**
|
|
* Make it easy to access parameters
|
|
*/
|
|
function paramvalue($params, $name, $default_val = FALSE) {
|
|
$name = strtoupper($name);
|
|
return (isset($params[$name]) ? $params[$name] : $default_val);
|
|
}
|
|
|
|
/**
|
|
* Add a VTIMEZONE using the specified TZID
|
|
* If VTIMEZONE was already added, do nothing
|
|
*
|
|
* @param iCalcomponent
|
|
* @param string Timezone id to add
|
|
* @param array (Optional) result from get_timezones()
|
|
* @return Used TZID, even when it was not added
|
|
*/
|
|
|
|
function add_vtimezone(&$resource, $tzid, $timezones = array()) {
|
|
if ($tzid != 'UTC' && !isset($timezones[$tzid])) {
|
|
$res = iCalUtilityFunctions::createTimezone($resource,
|
|
$tzid, array( 'X-LIC-LOCATION' => $tzid));
|
|
if ($res === FALSE) {
|
|
$this->CI->extended_logs->message('ERROR',
|
|
"Couldn't create vtimezone with tzid=" . $tzid
|
|
.' Defaulting to UTC');
|
|
$tzid = 'UTC';
|
|
}
|
|
}
|
|
|
|
return $tzid;
|
|
}
|
|
|
|
/**
|
|
* Parses a VEVENT resource VALARM definitions
|
|
*
|
|
* Returns an associative array ('n1#' => new Reminder, 'n2#' => new
|
|
* Reminder...), where 'n#' is the order where this VALARM was found
|
|
*/
|
|
function parse_valarms($vevent, $timezones = array()) {
|
|
$parsed_reminders = array();
|
|
|
|
$order = 0;
|
|
while ($valarm = $vevent->getComponent('valarm')) {
|
|
$order++;
|
|
// TODO parse more actions
|
|
$action = $valarm->getProperty('action');
|
|
if ($action == 'DISPLAY') {
|
|
$trigger = $valarm->getProperty('trigger');
|
|
$reminder = null;
|
|
|
|
if (isset($trigger['before'])) {
|
|
// Related to event start/end
|
|
$reminder = Reminder::createFrom($trigger);
|
|
} else {
|
|
// Absolute date-time trigger
|
|
$tz = $this->detect_tz($valarm, $timezones, 'trigger');
|
|
$datetime = $this->CI->dates->idt2datetime($trigger, $tz);
|
|
// Use default timezone
|
|
$datetime->setTimezone($this->tz);
|
|
|
|
$reminder = Reminder::createFrom($datetime);
|
|
$reminder->tdate =
|
|
$datetime->format($this->date_frontend_format);
|
|
$reminder->ttime =
|
|
$datetime->format($this->time_frontend_format);
|
|
}
|
|
|
|
if ($reminder !== null) {
|
|
$reminder->order = $order;
|
|
$parsed_reminders[$order] = $reminder;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $parsed_reminders;
|
|
}
|
|
|
|
/**
|
|
* Adds or replaces VALARM components (reminders) for a given VEVENT
|
|
* resource. Removes VALARMs that were deleted by user
|
|
*/
|
|
function set_valarms(&$resource, $reminders, $old_visible_reminders =
|
|
array()) {
|
|
foreach ($reminders as $r) {
|
|
$valarm = new valarm();
|
|
$valarm = $r->assign_properties($valarm);
|
|
if ($r->order !== FALSE) {
|
|
$resource = $this->replace_component($resource,
|
|
'valarm', $r->order, $valarm);
|
|
unset($old_visible_reminders[$r->order]);
|
|
} else {
|
|
$resource->setComponent($valarm);
|
|
}
|
|
}
|
|
|
|
// Any VALARMs left that was not present?
|
|
$remove_valarms = array_keys($old_visible_reminders);
|
|
foreach ($remove_valarms as $n) {
|
|
$resource->deleteComponent('valarm', $n);
|
|
}
|
|
|
|
return $resource;
|
|
}
|
|
|
|
|
|
}
|
|
|