455 lines
9.1 KiB
PHP
455 lines
9.1 KiB
PHP
<?php
|
|
/*
|
|
davina — Calendar data CalDAV conditioner
|
|
Copyright (C) 2025 Fenris <fenris@folksprak.org>
|
|
|
|
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
|
|
<https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
namespace davina\overwrites;
|
|
|
|
require_once('vendor/autoload.php');
|
|
require_once(DIR_LOGIC . '/helpers/call.php');
|
|
require_once(DIR_LOGIC . '/helpers/list.php');
|
|
require_once(DIR_LOGIC . '/helpers/ics.php');
|
|
require_once(DIR_LOGIC . '/model.php');
|
|
require_once(DIR_LOGIC . '/conf.php');
|
|
|
|
|
|
/**
|
|
*/
|
|
class class_caldav_backend
|
|
extends \Sabre\CalDAV\Backend\AbstractBackend
|
|
implements \Sabre\CalDAV\Backend\BackendInterface
|
|
{
|
|
|
|
|
|
/**
|
|
* @var array {map<string, \davina\model\struct_realm>}
|
|
*/
|
|
private array $realms;
|
|
|
|
|
|
/**
|
|
*/
|
|
private ?string $selected_realm_name;
|
|
|
|
|
|
/**
|
|
*/
|
|
public function __construct(
|
|
array $realms
|
|
)
|
|
{
|
|
// parent::__construct();
|
|
$this->realms = $realms;
|
|
$this->selected_realm_name = null;
|
|
}
|
|
|
|
|
|
/**
|
|
* @todo outsource
|
|
*/
|
|
private function event_to_vevent(
|
|
\davina\model\struct_event $event
|
|
) : \davina\helpers\ics\struct_vevent
|
|
{
|
|
$vevent = new \davina\helpers\ics\struct_vevent();
|
|
{
|
|
$vevent->uid = $event->id;
|
|
$vevent->dtstamp = \davina\helpers\ics\datetime_from_unix_timestamp($event->begin);
|
|
$vevent->dtstart = new \davina\helpers\ics\struct_dt(
|
|
'',
|
|
\davina\helpers\ics\datetime_from_unix_timestamp($event->begin)
|
|
);
|
|
$vevent->dtend = (
|
|
($event->end === null)
|
|
?
|
|
null
|
|
:
|
|
new \davina\helpers\ics\struct_dt(
|
|
'',
|
|
\davina\helpers\ics\datetime_from_unix_timestamp($event->end)
|
|
)
|
|
);
|
|
$vevent->summary = $event->title;
|
|
$vevent->location = $event->location;
|
|
$vevent->description = $event->description;
|
|
$vevent->class = \davina\helpers\ics\enum_class::public_;
|
|
$vevent->categories = $event->tags;
|
|
}
|
|
return $vevent;
|
|
}
|
|
|
|
|
|
/**
|
|
* @todo outsource
|
|
*/
|
|
private function events_to_vcalendar(
|
|
array $events
|
|
) : \davina\helpers\ics\struct_vcalendar
|
|
{
|
|
$vcalendar = new \davina\helpers\ics\struct_vcalendar();
|
|
{
|
|
$vcalendar->version = '2.0';
|
|
/**
|
|
* @todo conf
|
|
*/
|
|
$vcalendar->prodid = 'davina';
|
|
$vcalendar->method = 'PUBLISH';
|
|
$vcalendar->events = \array_map(
|
|
fn($event) => $this->event_to_vevent($event),
|
|
$events
|
|
);
|
|
}
|
|
return $vcalendar;
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
private function encode_tag(
|
|
?string $tag
|
|
) : ?string
|
|
{
|
|
if ($tag === null)
|
|
{
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
return \davina\helpers\call\convey(
|
|
$tag,
|
|
[
|
|
fn($x) => \strtolower($x),
|
|
fn($x) => \preg_replace('/ä/', 'ae', $x),
|
|
fn($x) => \preg_replace('/ö/', 'oe', $x),
|
|
fn($x) => \preg_replace('/ü/', 'ue', $x),
|
|
fn($x) => \preg_replace('/ß/', 'sz', $x),
|
|
fn($x) => \preg_replace('/\-/', '', $x),
|
|
fn($x) => \preg_replace('/ /', '-', $x),
|
|
fn($x) => \preg_replace('/[^a-zA-Z0-9_\-]/s', '_', $x),
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @param array $parameters {
|
|
* record<
|
|
* realm_name:string,
|
|
* calendar_name:(null|string),
|
|
* >
|
|
* }
|
|
*/
|
|
private function encode_calendar_identifier(
|
|
array $parameters
|
|
) : string
|
|
{
|
|
$str = $parameters['realm_name'];
|
|
if ($parameters['calendar_name'] === null)
|
|
{
|
|
// do nothing
|
|
}
|
|
else
|
|
{
|
|
$str .= \sprintf(
|
|
'-%s',
|
|
$parameters['calendar_name']
|
|
);
|
|
}
|
|
return $str;
|
|
}
|
|
|
|
|
|
/**
|
|
* @return array {
|
|
* record<
|
|
* realm_name:string,
|
|
* calendar_name:(null|string),
|
|
* >
|
|
* }
|
|
*/
|
|
private function decode_calendar_identifier(
|
|
string $str
|
|
) : array
|
|
{
|
|
$parts = \explode('-', $str, 2);
|
|
return [
|
|
'realm_name' => $parts[0],
|
|
'calendar_name' => (
|
|
(count($parts) < 2)
|
|
?
|
|
null
|
|
:
|
|
$parts[1]
|
|
),
|
|
];
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
private function get_source(
|
|
string $realm_name
|
|
) : \davina\sources\interface_source
|
|
{
|
|
if (
|
|
($this->selected_realm_name === null)
|
|
||
|
|
($this->selected_realm_name === $realm_name)
|
|
)
|
|
{
|
|
$this->selected_realm_name = $realm_name;
|
|
if (! \array_key_exists($realm_name, $this->realms))
|
|
{
|
|
throw (new \Exception(\sprintf('no such realm: %s', $realm_name)));
|
|
}
|
|
else
|
|
{
|
|
return $this->realms[$realm_name]->source;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw (new \Exception('may not change source'));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* [implementation]
|
|
*/
|
|
public function getCalendarsForUser(
|
|
$principalUri
|
|
)
|
|
{
|
|
// $source = $this->source;
|
|
// $realm_name = \explode('/', $principalUri)[1];
|
|
$realm_name = \davina\get_parameters()['realm_name'];
|
|
$source = $this->get_source($realm_name);
|
|
$calendar = $source->read();
|
|
$tags = [];
|
|
foreach ($calendar->events as $event)
|
|
{
|
|
foreach ($event->tags as $tag)
|
|
{
|
|
$tags[$tag] = null;
|
|
}
|
|
}
|
|
return \davina\helpers\call\convey(
|
|
$tags,
|
|
[
|
|
fn($x) => (
|
|
(\count($x) <= 0)
|
|
?
|
|
[null]
|
|
:
|
|
\array_keys($x)
|
|
),
|
|
fn($x) => \davina\helpers\list_\map(
|
|
$x,
|
|
fn($tag) => [
|
|
'id' => $this->encode_calendar_identifier(
|
|
[
|
|
'realm_name' => $realm_name,
|
|
'calendar_name' => $this->encode_tag($tag),
|
|
]
|
|
),
|
|
// 'uri' => $this->encode_tag($tag),
|
|
'uri' => $this->encode_calendar_identifier(
|
|
[
|
|
'realm_name' => $realm_name,
|
|
'calendar_name' => $this->encode_tag($tag),
|
|
]
|
|
),
|
|
'principaluri' => $principalUri,
|
|
'{DAV:}displayname' => ($tag ?? $realm_name),
|
|
\sprintf(
|
|
'{%s}supported-calendar-component-set',
|
|
\Sabre\CalDAV\Plugin::NS_CALDAV
|
|
) => new \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet(['VEVENT']),
|
|
]
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* [implementation]
|
|
*/
|
|
public function createCalendar(
|
|
$principalUri,
|
|
$calendarUri,
|
|
array $properties
|
|
)
|
|
{
|
|
throw (new \Exception('not implemented: createCalendar'));
|
|
}
|
|
|
|
|
|
/**
|
|
* [implementation]
|
|
*/
|
|
public function deleteCalendar(
|
|
$calendarId
|
|
)
|
|
{
|
|
throw (new \Exception('not implemented: deleteCalendar'));
|
|
}
|
|
|
|
|
|
/**
|
|
* [implementation]
|
|
*/
|
|
public function getCalendarObjects(
|
|
$calendarId
|
|
)
|
|
{
|
|
$calendar_identifier = $this->decode_calendar_identifier($calendarId);
|
|
// $realm_name = $calendar_identifier['realm_name'];
|
|
$realm_name = \davina\get_parameters()['realm_name'];
|
|
$source = $this->get_source($realm_name);
|
|
$tag = $calendar_identifier['calendar_name'];
|
|
$calendar = $source->read();
|
|
|
|
return \davina\helpers\call\convey(
|
|
$calendar->events,
|
|
[
|
|
fn($x) => \davina\helpers\list_\filter(
|
|
$x,
|
|
fn($event) => (
|
|
(\count($event->tags) <= 0)
|
|
||
|
|
\in_array(
|
|
$tag,
|
|
\davina\helpers\list_\map(
|
|
$event->tags,
|
|
fn($x) => $this->encode_tag($x)
|
|
)
|
|
)
|
|
)
|
|
),
|
|
fn($x) => \davina\helpers\list_\map(
|
|
$x,
|
|
fn($event) => [
|
|
'calendarid' => $calendarId,
|
|
'id' => $event->id,
|
|
// 'uri' => \sprintf('%s.ics', $entry['id']),
|
|
'uri' => $event->id,
|
|
'lastmodified' => \time(),
|
|
// 'etag' => null,
|
|
// 'size' => null,
|
|
'component' => 'vevent',
|
|
'{DAV:}displayname' => $event->title,
|
|
],
|
|
),
|
|
]
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* [implementation]
|
|
*/
|
|
public function getCalendarObject(
|
|
$calendarId,
|
|
$objectUri
|
|
)
|
|
{
|
|
$id = $objectUri;
|
|
$calendar_identifier = $this->decode_calendar_identifier($calendarId);
|
|
// $realm_name = $calendar_identifier['realm_name'];
|
|
$realm_name = \davina\get_parameters()['realm_name'];
|
|
$source = $this->get_source($realm_name);
|
|
$tag = $calendar_identifier['calendar_name'];
|
|
$calendar = $source->read();
|
|
|
|
$events = \davina\helpers\list_\filter(
|
|
$calendar->events,
|
|
fn($event) => ($event->id === $id)
|
|
);
|
|
if (\count($events) < 1)
|
|
{
|
|
throw (new \Exception(\sprintf('not found: %s', $objectUri)));
|
|
}
|
|
else if (\count($events) > 1)
|
|
{
|
|
throw (new \Exception(\sprintf('ambiguous: %s', $objectUri)));
|
|
}
|
|
else
|
|
{
|
|
$vcalendar = $this->events_to_vcalendar($events);
|
|
$ics = \davina\helpers\ics\vcalendar_encode($vcalendar);
|
|
return [
|
|
'calendardata' => $ics,
|
|
'uri' => $objectUri,
|
|
/**
|
|
* @todo
|
|
*/
|
|
'lastmodified' => \time(),
|
|
/**
|
|
* @todo
|
|
*/
|
|
// 'etag' => '""',
|
|
/**
|
|
* @todo
|
|
*/
|
|
// 'size' => 1,
|
|
'component' => 'vcalendar',
|
|
];
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* [implementation]
|
|
*/
|
|
public function createCalendarObject(
|
|
$calendarId,
|
|
$objectUri,
|
|
$calendarData
|
|
)
|
|
{
|
|
throw (new \Exception('not implemented: createCalendarObject'));
|
|
}
|
|
|
|
|
|
/**
|
|
* [implementation]
|
|
*/
|
|
public function updateCalendarObject(
|
|
$calendarId,
|
|
$objectUri,
|
|
$calendarData
|
|
)
|
|
{
|
|
throw (new \Exception('not implemented: updateCalendarObject'));
|
|
}
|
|
|
|
|
|
/**
|
|
* [implementation]
|
|
*/
|
|
public function deleteCalendarObject(
|
|
$calendarId,
|
|
$objectUri
|
|
)
|
|
{
|
|
throw (new \Exception('not implemented: deleteCalendarObject'));
|
|
}
|
|
|
|
}
|
|
|
|
?>
|