This program 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 (at your option) any later
version.
This program 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 this program. If not, see
.
*/
namespace davina\helpers\ics;
require_once('vendor/autoload.php');
require_once(DIR_LOGIC . '/helpers/string.php');
require_once(DIR_LOGIC . '/helpers/pit.php');
/**
*/
class struct_rrule
{
public ?string $freq;
public ?string $byday;
public ?string $bymonth;
}
/**
*/
// type type_offset = string;
/**
*/
// type type_timestamp = string;
/**
*/
enum enum_class
{
case public_/* = 'public'*/;
case private_/* = 'private'*/;
case confidential/* = 'confidential'*/;
}
/**
*/
enum enum_event_status
{
case tentative/* = 'tentative'*/;
case confirmed/* = 'confirmed'*/;
case cancelled/* = 'cancelled'*/;
}
/**
*/
enum enum_transp
{
case opaque/* = 'opaque'*/;
case transparent/* = 'transparent'*/;
}
/**
*/
// type type_tzid = string;
/**
*/
class struct_date
{
public int $year;
public int $month;
public int $day;
public function __construct(
int $year,
int $month,
int $day,
)
{
$this->year = $year;
$this->month = $month;
$this->day = $day;
}
}
/**
*/
class struct_time
{
public int $hour;
public int $minute;
public int $second;
public bool $utc;
public function __construct(
int $hour,
int $minute,
int $second,
bool $utc
)
{
$this->hour = $hour;
$this->minute = $minute;
$this->second = $second;
$this->utc = $utc;
}
}
/**
*/
class struct_datetime
{
public struct_date $date;
public ?struct_time $time;
public function __construct(
struct_date $date,
?struct_time $time
)
{
$this->date = $date;
$this->time = $time;
}
}
/**
*/
class struct_dt
{
public /*type_tzid*/string $tzid;
public struct_datetime $value;
public function __construct(
string $tzid,
struct_datetime $value
)
{
$this->tzid = $tzid;
$this->value = $value;
}
}
/**
*/
class struct_duration
{
public bool $negative;
public ?int $weeks;
public ?int $days;
public ?int $hours;
public ?int $minutes;
public ?int $seconds;
}
/**
*/
class struct_vtimezone_entry
{
public struct_datetime $dtstart;
public struct_rrule $rrule;
public ?/*type_offset*/string $tzoffsetfrom;
public ?/*type_offset*/string $tzoffsetto;
}
/**
*/
class struct_vtimezone
{
public ?string/*type_tzid*/ $tzid;
public ?struct_vtimezone_entry $standard;
public ?struct_vtimezone_entry $daylight;
}
/**
*/
class struct_geo
{
public float $latitude;
public float $longitude;
}
/**
*/
class struct_organizer
{
public ?string $value;
public ?string $cn;
public ?string $dir;
public ?string $sent_by;
}
/**
* @see https://www.rfc-editor.org/rfc/rfc5545#section-3.6.1
*/
class struct_vevent
{
// required
public string $uid;
public struct_datetime $dtstamp;
// required if "method" is not specified in the parent
public ?struct_dt $dtstart;
// optional
public ?enum_class $class;
public ?struct_datetime $created;
public ?string $description;
public ?struct_geo $geo;
public ?struct_datetime $last_modified;
public ?string $location;
/**
* @see https://www.rfc-editor.org/rfc/rfc5545#section-3.8.4.3
*/
public ?struct_organizer $organizer;
public ?int $priority;
public ?int $sequence;
public ?enum_event_status $status;
public ?string $summary;
public ?enum_transp $transp;
public ?string $url;
public $recurid;
public ?type_rrule $rrule;
// either or
public ?struct_dt $dtend;
public ?type_duration $duration;
// optional
public $attach;
public ?string $attendee;
public /*list*/?array $categories;
public $comment;
public $contact;
public $exdate;
public $rstatus;
public $related;
public $resources;
public $rdate;
// extra
public /*map*/?array $x_props;
public /*map*/?array $iana_props;
}
/**
* @see https://www.rfc-editor.org/rfc/rfc5545#section-3.4
*/
class struct_vcalendar
{
// required
public string $version;
public string $prodid;
public /*list*/array $vevents;
// optional
public ?string $calscale;
public ?string $method;
public ?struct_vtimezone $vtimezone;
// extra
public /*map*/?array $x_props;
public /*map*/?array $iana_props;
};
/**
*/
function date_decode(
string $date_encoded
) : struct_date
{
return (new struct_date(
\intval(\substr($date_encoded, 0, 4)),
\intval(\substr($date_encoded, 4, 2)),
\intval(\substr($date_encoded, 6, 2))
));
}
/**
*/
function time_decode(
string $time_encoded
) : struct_time
{
return (new struct_time(
\intval(\substr($time_encoded, 0, 2)),
\intval(\substr($time_encoded, 2, 2)),
\intval(\substr($time_encoded, 4, 2)),
((\strlen($time_encoded) >= 7) && ($time_encoded[6] === 'Z'))
));
}
/**
*/
function datetime_decode(
string $datetime_encoded
) : struct_datetime
{
$parts = \explode('T', $datetime_encoded, 2);
return (new struct_datetime(
date_decode($parts[0]),
((\count($parts) >= 2) ? time_decode($parts[1]) : null)
));
}
/**
*/
enum enum_decode_state_label {
case expect_vcalendar_begin/* = "expect_vcalendar_begin"*/;
case expect_vcalendar_property/* = "expect_vcalendar_property"*/;
case expect_vevent_property/* = "expect_vevent_property"*/;
case done/* = "done"*/;
}
/**
*/
function class_encode(
enum_class $class
) : string
{
switch ($class)
{
case enum_class::private_: {return 'PRIVATE'; break;}
case enum_class::public_: {return 'PUBLIC'; break;}
case enum_class::confidential: {return 'CONFIDENTIAL'; break;}
}
}
/**
*/
function class_decode(
string $class_encoded
) : enum_class
{
return [
'PRIVATE' => enum_class::private_,
'PUBLIC' => enum_class::public_,
'CONFIDENTIAL' => enum_class::confidential,
][$class_encoded];
}
/**
*/
function event_status_encode(
enum_event_status $event_status
) : string
{
switch ($event_status)
{
case enum_event_status::tentative: {return 'TENTATIVE'; break;}
case enum_event_status::confirmed: {return 'CONFIRMED'; break;}
case enum_event_status::cancelled: {return 'CANCELLED'; break;}
}
}
/**
*/
function event_status_decode(
string $event_status_encoded
) : enum_event_status
{
return [
'TENTATIVE' => enum_event_status::tentative,
'CONFIRMED' => enum_event_status::confirmed,
'CANCELLED' => enum_event_status::cancelled,
][$event_status_encoded];
}
/**
*/
function transp_encode(
enum_transp $transp
) : string
{
switch (transp)
{
case enum_transp::opaque: {return 'OPAQUE'; break;}
case enum_transp::transparent: {return 'TRANSPARENT'; break;}
}
}
/**
*/
function transp_decode(
string $transp_encoded
) : enum_transp
{
return [
'OPAQUE' => enum_transp::opaque,
'TRANSPARENT' => enum_transp::transparent,
][$transp_encoded];
}
/**
*/
/*
function datetime_to_unixtimestamp(
struct_datetime $datetime
) : int
{
if (($datetime->time !== null) && (! $datetime->time->utc))
{
throw (new \Exception('can not convert not utc time values'));
}
else
{
return lib_plankton.pit.from_datetime(
{
"timezone_shift": 0,
"date": {
"year": datetime.date.year,
"month": datetime.date.month,
"day": datetime.date.day,
},
"time": {
"hour": ((datetime.time === null) ? 0 : datetime.time.hour),
"minute": ((datetime.time === null) ? 0 : datetime.time.minute),
"second": ((datetime.time === null) ? 0 : datetime.time.second),
}
}
);
}
}
*/
/**
*/
function vcalendar_decode(
string $ics
) : struct_vcalendar
{
$path = \sprintf('/tmp/foo.ics');
\file_put_contents($path, $ics);
$ical = new \ICal\ICal(
$path,
[
'defaultSpan' => 2, // Default value
'defaultTimeZone' => 'UTC',
'defaultWeekStart' => 'MO', // Default value
'disableCharacterReplacement' => false, // Default value
'filterDaysAfter' => null, // Default value
'filterDaysBefore' => null, // Default value
'httpUserAgent' => null, // Default value
'skipRecurrence' => false, // Default value
]
);
// $ical->initFile($path);
/**
* @todo transform correctly
*/
$result = new struct_vcalendar();
$result->events = \array_map(
function ($event_raw) {
$vevent = new struct_vevent();
$vevent->uid = $event_raw->uid;
$vevent->summary = $event_raw->summary;
$vevent->dtstart = (new struct_dt(
'',
datetime_decode($event_raw->dtstart)
));
$vevent->dtend = (
($event_raw->dtend === null)
?
null
:
(new struct_dt(
'',
datetime_decode($event_raw->dtend)
))
);
$vevent->location = $event_raw->location;
$vevent->description = $event_raw->description;
$vevent->categories = \array_reduce(
\array_map(
fn($x) => \explode(',', $x),
\array_values(
\array_filter(
($event_raw->additionalProperties['categories_array'] ?? []),
fn($x) => \is_string($x)
)
)
),
fn($x, $y) => \array_merge($x, $y),
[]
);
return $vevent;
},
$ical->events()
);
return $result;
}
/**
* @see https://www.rfc-editor.org/rfc/rfc5545
* @see https://icalendar.org/iCalendar-RFC-5545/
*/
function date_encode(
struct_date $date
) : string
{
return \davina\helpers\string_\coin(
'{{year}}{{month}}{{day}}',
[
'year' => \str_pad(\sprintf('%u', $date->year), 4, '0', \STR_PAD_LEFT),
'month' => \str_pad(\sprintf('%u', $date->month), 2, '0', \STR_PAD_LEFT),
'day' => \str_pad(\sprintf('%u', $date->day), 2, '0', \STR_PAD_LEFT),
]
);
}
/**
*/
function time_encode(
struct_time $time
) : string
{
return \davina\helpers\string_\coin(
'{{hour}}{{minute}}{{second}}{{utc}}',
[
'hour' => \str_pad(\sprintf('%u', $time->hour), 2, '0', \STR_PAD_LEFT),
'minute' => \str_pad(\sprintf('%u', $time->minute), 2, '0', \STR_PAD_LEFT),
'second' => \str_pad(\sprintf('%u', $time->second), 2, '0', \STR_PAD_LEFT),
'utc' => ($time->utc ? 'Z' : ''),
]
);
}
/**
*/
function datetime_encode(
struct_datetime $datetime
) : string
{
return \davina\helpers\string_\coin(
'{{date}}T{{time}}',
[
'date' => date_encode($datetime->date),
'time' => time_encode($datetime->time),
]
);
}
/**
* @todo complete
*/
function vcalendar_encode(
struct_vcalendar $vcalendar
) : string
{
$content_lines = [];
\array_push($content_lines, 'BEGIN:VCALENDAR');
\array_push($content_lines, \davina\helpers\string_\coin('VERSION:{{version}}', ['version' => $vcalendar->version]));
\array_push($content_lines, \davina\helpers\string_\coin('PRODID:{{prodid}}', ['prodid' => $vcalendar->prodid]));
\array_push($content_lines, \davina\helpers\string_\coin('METHOD:{{method}}', ['method' => $vcalendar->method]));
foreach ($vcalendar->events as $vevent)
{
\array_push($content_lines, 'BEGIN:VEVENT');
{
// uid
\array_push(
$content_lines,
\davina\helpers\string_\coin(
'UID:{{uid}}',
[
'uid' => $vevent->uid,
]
)
);
// dtstart
\array_push(
$content_lines,
\davina\helpers\string_\coin(
'DTSTART:{{dtstart}}',
[
'dtstart' => datetime_encode($vevent->dtstart->value),
]
)
);
// dtend
if ($vevent->dtend !== null)
{
\array_push(
$content_lines,
\davina\helpers\string_\coin(
'DTEND:{{dtend}}',
[
'dtend' => datetime_encode($vevent->dtend->value),
]
)
);
}
// dtstamp
\array_push(
$content_lines,
\davina\helpers\string_\coin(
'DTSTAMP:{{dtstamp}}',
[
'dtstamp' => datetime_encode($vevent->dtstamp),
]
)
);
// class
if ($vevent->class !== null)
{
\array_push(
$content_lines,
\davina\helpers\string_\coin(
'CLASS:{{class}}',
[
'class' => class_encode($vevent->class),
]
)
);
}
// summary
\array_push(
$content_lines,
\davina\helpers\string_\coin(
'SUMMARY:{{summary}}',
[
'summary' => $vevent->summary,
]
)
);
// description
if ($vevent->description !== null)
{
\array_push(
$content_lines,
\davina\helpers\string_\coin(
'DESCRIPTION:{{description}}',
[
'description' => $vevent->description,
]
)
);
}
// location
if ($vevent->location !== null)
{
\array_push(
$content_lines,
\davina\helpers\string_\coin(
'LOCATION:{{location}}',
[
'location' => $vevent->location,
]
)
);
}
/*
// geo
if (vevent.geo !== undefined) {
content_lines.push(
lib_plankton.string.coin(
"GEO:{{geo_latitude}};{{geo_longitude}}",
{
"geo_latitude": vevent.geo.latitude.toFixed(4),
"geo_longitude": vevent.geo.longitude.toFixed(4),
}
)
);
}
*/
/*
// url
if (vevent.url !== undefined) {
content_lines.push(
lib_plankton.string.coin(
"URL:{{url}}",
{
"url": vevent.url,
}
)
);
}
*/
// categories
if (
($vevent->categories !== null)
&&
(\count($vevent->categories) > 0)
)
{
\array_push(
$content_lines,
\davina\helpers\string_\coin(
'CATEGORIES:{{categories}}',
[
'categories' => \implode(',', $vevent->categories),
]
)
);
}
}
\array_push($content_lines, 'END:VEVENT');
}
\array_push($content_lines, 'END:VCALENDAR');
$lines = [];
foreach ($content_lines as $content_line)
{
$slices = \davina\helpers\string_\slice($content_line, 75 - 1);
\array_push($lines, $slices[0]);
foreach (\array_slice($slices, 1) as $slice)
{
\array_push($lines, ' ' . $slice);
}
}
return \implode("\r\n", $lines);
}
/**
*/
function datetime_to_unix_timestamp(
struct_datetime $datetime
) : int
{
return \davina\helpers\pit\pit_to_unix_timestamp(
\davina\helpers\pit\pit_from_datetime(
new \davina\helpers\pit\struct_datetime(
0,
new \davina\helpers\pit\struct_date(
$datetime->date->year,
$datetime->date->month,
$datetime->date->day
),
(
($datetime->time === null)
?
null
:
new \davina\helpers\pit\struct_time(
$datetime->time->hour,
$datetime->time->minute,
$datetime->time->second
)
)
)
)
);
}
/**
*/
function datetime_from_unix_timestamp(
int $unix_timestamp
) : struct_datetime
{
$pit = \davina\helpers\pit\pit_from_unix_timestamp($unix_timestamp);
$datetime = \davina\helpers\pit\pit_to_datetime($pit);
return (new struct_datetime(
new struct_date(
$datetime->date->year,
$datetime->date->month,
$datetime->date->day
),
(
($datetime->time === null)
?
null
:
new struct_time(
$datetime->time->hour,
$datetime->time->minute,
$datetime->time->second,
true
)
)
));
}
?>