This commit is contained in:
fenris 2025-09-21 18:49:03 +02:00
parent baea0378fb
commit 93abd2dfd8
17 changed files with 582 additions and 391 deletions

39
source/auths/_factory.php Normal file
View file

@ -0,0 +1,39 @@
<?php
namespace davina\auths;
require_once(DIR_LOGIC . '/auths/_interface.php');
require_once(DIR_LOGIC . '/auths/pass_through.php');
require_once(DIR_LOGIC . '/auths/static.php');
/**
*/
function make(
string $kind,
$data
) : \davina\auths\interface_auth
{
switch ($kind)
{
case 'pass_through':
{
return (new \davina\auths\class_auth_pass_through(
));
break;
}
case 'static':
{
return (new \davina\auths\class_auth_static(
$data['password']
));
break;
}
default:
{
throw (new \Exception(\sprintf('unhandled auth kind: %s', $kind)));
break;
}
}
}
?>

View file

@ -0,0 +1,42 @@
<?php
namespace davina\auths;
require_once('vendor/autoload.php');
/**
*/
interface interface_auth
{
/**
* @param array $credentials {
* record<
* username:(null|string),
* password:string
* >
* }
*/
function check(
array $credentials
) : bool
;
/**
* @param array $credentials {
* record<
* username:(null|string),
* password:string
* >
* }
*/
function determine_parameters(
array $credentials
) : array
;
}
?>

View file

@ -0,0 +1,39 @@
<?php
namespace davina\auths;
require_once('vendor/autoload.php');
/**
*/
class class_auth_pass_through implements interface_auth
{
/**
* [implementation]
*/
function check(
array $credentials
) : bool
{
return true;
}
/**
* [implementation]
*/
function determine_parameters(
array $credentials
) : array
{
return [
'auth_username' => $credentials['username'],
'auth_password' => $credentials['password'],
];
}
}
?>

51
source/auths/static.php Normal file
View file

@ -0,0 +1,51 @@
<?php
namespace davina\auths;
require_once('vendor/autoload.php');
/**
*/
class class_auth_static implements interface_auth
{
/**
*/
private string $password;
/**
*/
public function __construct(
string $password
)
{
$this->password = $password;
}
/**
* [implementation]
*/
function check(
array $credentials
) : bool
{
return ($credentials['password'] === $this->password);
}
/**
* [implementation]
*/
function determine_parameters(
array $credentials
) : array
{
return [];
}
}
?>

View file

@ -5,72 +5,22 @@ namespace davina\conf;
require_once(DIR_LOGIC . '/helpers/list.php'); require_once(DIR_LOGIC . '/helpers/list.php');
/**
*/
class struct_auth
{
public string $kind;
public $data;
}
/**
*/
class struct_source_data_ics_feed
{
public string $url;
public bool $combined;
public int $lifetime;
}
/**
*/
class struct_source
{
public string $name;
public string $kind;
public $data;
}
/**
*/
class struct_settings
{
public string $timezone;
}
/**
*/
class struct_root
{
public struct_auth $auth;
/**
* @var array {list<struct_source>}
*/
public array $sources;
public struct_settings $settings;
}
/** /**
*/ */
class _state class _state
{ {
public static ?struct_root $data = null; public static $data = null;
} }
/** /**
*/ */
function validate_source_name( function validate_realm_name(
string $source_name string $realm_name
) : string ) : string
{ {
$matchs = null; $matchs = null;
$result = preg_match('/^[0-9a-zA-Z]+$/', $source_name, $matches); $result = preg_match('/^[0-9a-zA-Z]+$/', $realm_name, $matches);
return ($result === 1); return ($result === 1);
} }
@ -87,54 +37,86 @@ function load(
), ),
true true
); );
{ _state::$data = [
$data = new struct_root(); /**
// auth * @todo check for name duplicates
{ */
$auth = new struct_auth(); 'realms' => \davina\helpers\list_\map(
$auth->kind = (($data_raw['auth'] ?? [])['kind'] ?? 'none'); $data_raw['realms'],
$auth->data = (($data_raw['auth'] ?? [])['data'] ?? null); function ($realm_raw) {
$data->auth = $auth; if (! validate_realm_name($realm_raw['name']))
} {
// sources throw (new \Exception(\sprintf('invalid realm name: "%s"', $realm_raw['name'])));
{
$sources = [];
/**
* @todo check for name duplicates
*/
$data->sources = \davina\helpers\list_\map(
$data_raw['sources'],
function ($source_raw) {
if (! validate_source_name($source_raw['name']))
{
throw (new \Exception(\sprintf('invalid source name: "%s"', $source_raw['name'])));
}
else
{
$source = new struct_source();
$source->name = $source_raw['name'];
$source->kind = $source_raw['kind'];
$source->data = ($source_raw['data'] ?? null);
return $source;
}
} }
); else
} {
// settings return [
{ 'name' => $realm_raw['name'],
$settings = new struct_settings(); 'auth' => (function ($auth_raw) use ($realm_raw) {
$settings->timezone = (($data_raw['settings'] ?? [])['timezone'] ?? 'UTC'); switch ($auth_raw['kind'])
$data->settings = $settings; {
} case 'pass_through':
_state::$data = $data; {
} return [
'kind' => 'pass_through',
'data' => null
];
break;
}
case 'static':
{
return [
'kind' => 'static',
'data' => [
'username' => ($auth_raw['data']['username'] ?? $realm_raw['name']),
'password' => ($auth_raw['data']['password'] ?? $realm_raw['name']),
]
];
break;
}
default:
{
throw (new \Exception(\sprintf('invalid auth kind: %s', $auth_raw['kind'])));
break;
}
}
}) ($realm_raw['auth']),
'source' => (function ($source_raw) {
switch ($source_raw['kind'])
{
case 'ics_feed':
{
return [
'kind' => 'ics_feed',
'data' => [
'url' => $source_raw['data']['url'],
'lifetime' => ($source_raw['data']['lifetime'] ?? (60 * 15)),
'conflate' => ($source_raw['data']['conflate'] ?? true),
]
];
break;
}
default:
{
throw (new \Exception(\sprintf('invalid source kind: %s', $source_raw['kind'])));
}
}
}) ($realm_raw['source']),
];
}
}
),
'settings' => [
'timezone' => (($data_raw['settings'] ?? [])['timezone'] ?? 'UTC'),
]
];
} }
/** /**
*/ */
function get( function get(
) : struct_root )
{ {
if (_state::$data === null) if (_state::$data === null)
{ {

View file

@ -13,7 +13,18 @@ function coin(
$result = $template; $result = $template;
foreach ($arguments as $key => $value) foreach ($arguments as $key => $value)
{ {
$result = \str_replace(\sprintf('{{%s}}', $key), $value, $result); if ($value === null)
{
// do nothing
}
else
{
$result = \str_replace(
\sprintf('{{%s}}', $key),
$value,
$result
);
}
} }
return $result; return $result;
} }

View file

@ -7,9 +7,11 @@ define("DIR_LOGIC", "logic");
require_once('vendor/autoload.php'); require_once('vendor/autoload.php');
require_once(DIR_LOGIC . '/base.php'); require_once(DIR_LOGIC . '/base.php');
require_once(DIR_LOGIC . '/overwrites/principal_backend.php'); require_once(DIR_LOGIC . '/overwrites/principal_backend.php');
require_once(DIR_LOGIC . '/overwrites/auths/_factory.php'); require_once(DIR_LOGIC . '/overwrites/auth_backend.php');
require_once(DIR_LOGIC . '/overwrites/caldav_backend.php'); require_once(DIR_LOGIC . '/overwrites/caldav_backend.php');
require_once(DIR_LOGIC . '/sources/_factory.php'); require_once(DIR_LOGIC . '/sources/_factory.php');
require_once(DIR_LOGIC . '/auths/_factory.php');
require_once(DIR_LOGIC . '/model.php');
require_once(DIR_LOGIC . '/conf.php'); require_once(DIR_LOGIC . '/conf.php');
@ -20,21 +22,28 @@ function main(
\davina\set_parameters([]); \davina\set_parameters([]);
\date_default_timezone_set(\davina\conf\get()->settings->timezone); \date_default_timezone_set(\davina\conf\get()['settings']['timezone']);
/** /**
* @todo nicht einfach die source names als keys verwenden * @todo nicht einfach die realm names als keys verwenden
*/ */
$sources = []; $realms = [];
foreach (\davina\conf\get()->sources as $source_raw) foreach (\davina\conf\get()['realms'] as $realm_raw)
{ {
$sources[$source_raw->name] = \davina\sources\make( $realms[$realm_raw['name']] = new \davina\model\struct_realm(
$source_raw->kind, $realm_raw['name'],
$source_raw->data \davina\auths\make(
$realm_raw['auth']['kind'],
$realm_raw['auth']['data']
),
\davina\sources\make(
$realm_raw['source']['kind'],
$realm_raw['source']['data']
)
); );
} }
$principal_backend = new \davina\overwrites\class_principle_backend($sources); $principal_backend = new \davina\overwrites\class_principle_backend($realms);
$server = new \Sabre\DAV\Server( $server = new \Sabre\DAV\Server(
[ [
@ -43,7 +52,7 @@ function main(
), ),
new \Sabre\CalDAV\CalendarRoot( new \Sabre\CalDAV\CalendarRoot(
$principal_backend, $principal_backend,
new \davina\overwrites\class_caldav_backend($sources) new \davina\overwrites\class_caldav_backend($realms)
), ),
] ]
); );
@ -52,8 +61,8 @@ function main(
$server->addPlugin( $server->addPlugin(
new \Sabre\DAV\Auth\Plugin( new \Sabre\DAV\Auth\Plugin(
new \davina\overwrites\class_auth_backend_basic( new \davina\overwrites\class_auth_backend(
$sources $realms
) )
) )
); );
@ -70,7 +79,7 @@ function main(
/** /**
* not required * not required
*/ */
$server->addPlugin(new \Sabre\DAV\Browser\Plugin()); // $server->addPlugin(new \Sabre\DAV\Browser\Plugin());
$server->start(); $server->start();
} }

View file

@ -116,4 +116,40 @@ function calendar_from_raw(
)); ));
} }
/**
*/
class struct_realm
{
/**
*/
public string $name;
/**
*/
public \davina\auths\interface_auth $auth;
/**
*/
public \davina\sources\interface_source $source;
/**
*/
public function __construct(
string $name,
\davina\auths\interface_auth $auth,
\davina\sources\interface_source $source
)
{
$this->name = $name;
$this->auth = $auth;
$this->source = $source;
}
}
?> ?>

View file

@ -0,0 +1,86 @@
<?php
namespace davina\overwrites;
require_once('vendor/autoload.php');
require_once(DIR_LOGIC . '/base.php');
/**
*/
class class_auth_backend
extends \Sabre\DAV\Auth\Backend\AbstractBasic
implements \Sabre\DAV\Auth\Backend\BackendInterface
{
/**
* @var array {list<\davina\sources\interface_source>}
*/
private array $realms;
/**
*/
public function __construct(
array $realms
)
{
// parent::__construct();
$this->realms = $realms;
$this->setRealm('davina');
}
/**
*/
protected function validateUserPass(
/*string */$identifier,
/*string */$password
)/* : bool*/
{
$parts = \explode('-', $identifier, 2);
$realm_name = $parts[0];
$credentials = [
'username' => (
(\count($parts) >= 2)
?
$parts[1]
:
null
),
'password' => $password,
];
if (! \array_key_exists($realm_name, $this->realms))
{
return false;
}
else
{
$realm = $this->realms[$realm_name];
if (! $realm->auth->check($credentials))
{
return false;
}
else
{
/**
* @todo check for security
*/
\davina\set_parameters(
\array_merge(
$realm->auth->determine_parameters($credentials),
[
'realm_name' => $realm_name,
]
)
);
$data = $realm->source->read();
return ($data !== null);
}
}
}
}
?>

View file

@ -1,37 +0,0 @@
<?php
namespace davina\overwrites;
require_once('vendor/autoload.php');
require_once(DIR_LOGIC . '/sources/_interface.php');
require_once(DIR_LOGIC . '/overwrites/auths/none.php');
require_once(DIR_LOGIC . '/overwrites/auths/basic.php');
/**
*/
function make_auth_backend(
\davina\sources\interface_source $source,
array $descriptor
) : \Sabre\DAV\Auth\Backend\BackendInterface
{
switch ($descriptor['kind'])
{
case 'none':
{
return (new \davina\overwrites\class_auth_backend_none());
break;
}
case 'basic':
{
return (new \davina\overwrites\class_auth_backend_basic($source));
break;
}
default:
{
throw (new \Exception(\sprintf('unhandled auth backend kind: %s', $descriptor['kind'])));
break;
}
}
}
?>

View file

@ -1,74 +0,0 @@
<?php
namespace davina\overwrites;
require_once('vendor/autoload.php');
require_once(DIR_LOGIC . '/base.php');
require_once(DIR_LOGIC . '/sources/_interface.php');
/**
*/
class class_auth_backend_basic
extends \Sabre\DAV\Auth\Backend\AbstractBasic
implements \Sabre\DAV\Auth\Backend\BackendInterface
{
/**
* @var array {list<\davina\sources\interface_source>}
*/
private array $sources;
/**
*/
public function __construct(
array $sources
)
{
// parent::__construct();
$this->sources = $sources;
$this->setRealm('davina');
}
/**
*/
protected function validateUserPass(
/*string */$identifier,
/*string */$password
)/* : bool*/
{
$parts = \explode('-', $identifier, 2);
$source_name = $parts[0];
$username = (
(\count($parts) >= 2)
?
$parts[1]
:
$source_name
);
$parameters = [
'username' => $username,
'password' => $password,
];
if (! \array_key_exists($source_name, $this->sources))
{
return false;
}
else
{
$source = $this->sources[$source_name];
/**
* @todo check for security
*/
\davina\set_parameters($parameters);
$data = $source->read(/*$parameters*/[]);
return ($data !== null);
}
}
}
?>

View file

@ -1,39 +0,0 @@
<?php
namespace davina\overwrites;
require_once('vendor/autoload.php');
require_once(DIR_LOGIC . '/sources/_interface.php');
/**
*/
class class_auth_backend_none
implements \Sabre\DAV\Auth\Backend\BackendInterface
{
/**
* @todo other principal uri?
*/
public function check(
\Sabre\HTTP\RequestInterface $request,
\Sabre\HTTP\ResponseInterface $response
)
{
return [true, 'principals/dummy'];
}
/**
*/
public function challenge(
\Sabre\HTTP\RequestInterface $request,
\Sabre\HTTP\ResponseInterface $response
)
{
// do nothing
}
}
?>

View file

@ -7,7 +7,6 @@ require_once(DIR_LOGIC . '/helpers/call.php');
require_once(DIR_LOGIC . '/helpers/list.php'); require_once(DIR_LOGIC . '/helpers/list.php');
require_once(DIR_LOGIC . '/helpers/ics.php'); require_once(DIR_LOGIC . '/helpers/ics.php');
require_once(DIR_LOGIC . '/model.php'); require_once(DIR_LOGIC . '/model.php');
require_once(DIR_LOGIC . '/sources/_interface.php');
require_once(DIR_LOGIC . '/conf.php'); require_once(DIR_LOGIC . '/conf.php');
@ -20,25 +19,25 @@ class class_caldav_backend
/** /**
* @var array {map<string, \davina\sources\interface_source>} * @var array {map<string, \davina\model\struct_realm>}
*/ */
private array $sources; private array $realms;
/** /**
*/ */
private ?string $selected_source_name; private ?string $selected_realm_name;
/** /**
*/ */
public function __construct( public function __construct(
array $sources array $realms
) )
{ {
// parent::__construct(); // parent::__construct();
$this->sources = $sources; $this->realms = $realms;
$this->selected_source_name = null; $this->selected_realm_name = null;
} }
@ -104,30 +103,37 @@ class class_caldav_backend
/** /**
*/ */
private function encode_tag( private function encode_tag(
string $tag ?string $tag
) : string ) : ?string
{ {
return \davina\helpers\call\convey( if ($tag === null)
$tag, {
[ return null;
fn($x) => \strtolower($x), }
fn($x) => \preg_replace('/ä/', 'ae', $x), else
fn($x) => \preg_replace('/ö/', 'oe', $x), {
fn($x) => \preg_replace('/ü/', 'ue', $x), return \davina\helpers\call\convey(
fn($x) => \preg_replace('/ß/', 'sz', $x), $tag,
fn($x) => \preg_replace('/\-/', '', $x), [
fn($x) => \preg_replace('/ /', '-', $x), fn($x) => \strtolower($x),
fn($x) => \preg_replace('/[^a-zA-Z0-9_\-]/s', '_', $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 { * @param array $parameters {
* record< * record<
* source_name:string, * realm_name:string,
* calendar_name:string, * calendar_name:(null|string),
* > * >
* } * }
*/ */
@ -135,19 +141,27 @@ class class_caldav_backend
array $parameters array $parameters
) : string ) : string
{ {
return \sprintf( $str = $parameters['realm_name'];
'%s-%s', if ($parameters['calendar_name'] === null)
$parameters['source_name'], {
$parameters['calendar_name'] // do nothing
); }
else
{
$str .= \sprintf(
'-%s',
$parameters['calendar_name']
);
}
return $str;
} }
/** /**
* @return array { * @return array {
* record< * record<
* source_name:string, * realm_name:string,
* calendar_name:string, * calendar_name:(null|string),
* > * >
* } * }
*/ */
@ -157,8 +171,14 @@ class class_caldav_backend
{ {
$parts = \explode('-', $str, 2); $parts = \explode('-', $str, 2);
return [ return [
'source_name' => $parts[0], 'realm_name' => $parts[0],
'calendar_name' => $parts[1], 'calendar_name' => (
(count($parts) < 2)
?
null
:
$parts[1]
),
]; ];
} }
@ -166,23 +186,23 @@ class class_caldav_backend
/** /**
*/ */
private function get_source( private function get_source(
$source_name string $realm_name
) : \davina\sources\interface_source ) : \davina\sources\interface_source
{ {
if ( if (
($this->selected_source_name === null) ($this->selected_realm_name === null)
|| ||
($this->selected_source_name === $source_name) ($this->selected_realm_name === $realm_name)
) )
{ {
$this->selected_source_name = $source_name; $this->selected_realm_name = $realm_name;
if (! \array_key_exists($source_name, $this->sources)) if (! \array_key_exists($realm_name, $this->realms))
{ {
throw (new \Exception(\sprintf('no such source: %s', $source_name))); throw (new \Exception(\sprintf('no such realm: %s', $realm_name)));
} }
else else
{ {
return $this->sources[$source_name]; return $this->realms[$realm_name]->source;
} }
} }
else else
@ -200,9 +220,10 @@ class class_caldav_backend
) )
{ {
// $source = $this->source; // $source = $this->source;
$source_name = \explode('/', $principalUri)[1]; // $realm_name = \explode('/', $principalUri)[1];
$source = $this->get_source($source_name); $realm_name = \davina\get_parameters()['realm_name'];
$calendar = $source->read([]); $source = $this->get_source($realm_name);
$calendar = $source->read();
$tags = []; $tags = [];
foreach ($calendar->events as $event) foreach ($calendar->events as $event)
{ {
@ -211,23 +232,42 @@ class class_caldav_backend
$tags[$tag] = null; $tags[$tag] = null;
} }
} }
$result = \array_map( return \davina\helpers\call\convey(
fn($tag) => [ $tags,
'id' => $this->encode_calendar_identifier( [
[ fn($x) => (
'source_name' => $source_name, (\count($x) <= 0)
'calendar_name' => $this->encode_tag($tag), ?
[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']),
] ]
), ),
'uri' => $this->encode_tag($tag),
'principaluri' => $principalUri,
'{DAV:}displayname' => $tag,
\sprintf('{%s}supported-calendar-component-set', \Sabre\CalDAV\Plugin::NS_CALDAV) => new \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet(['VEVENT']),
], ],
\array_keys($tags)
); );
// \error_log(\json_encode($result, \JSON_PRETTY_PRINT));
return $result;
} }
@ -263,37 +303,45 @@ class class_caldav_backend
) )
{ {
$calendar_identifier = $this->decode_calendar_identifier($calendarId); $calendar_identifier = $this->decode_calendar_identifier($calendarId);
$source_name = $calendar_identifier['source_name']; // $realm_name = $calendar_identifier['realm_name'];
$source = $this->get_source($source_name); $realm_name = \davina\get_parameters()['realm_name'];
$source = $this->get_source($realm_name);
$tag = $calendar_identifier['calendar_name']; $tag = $calendar_identifier['calendar_name'];
$calendar = $source->read([]); $calendar = $source->read();
$result = \array_map( return \davina\helpers\call\convey(
fn($event) => [ $calendar->events,
'calendarid' => $calendarId, [
'id' => $event->id, fn($x) => \davina\helpers\list_\filter(
// 'uri' => \sprintf('%s.ics', $entry['id']), $x,
'uri' => $event->id, fn($event) => (
'lastmodified' => \time(), (\count($event->tags) <= 0)
// 'etag' => null, ||
// 'size' => null, \in_array(
'component' => 'vevent', $tag,
'{DAV:}displayname' => $event->title, \davina\helpers\list_\map(
], $event->tags,
\array_values( fn($x) => $this->encode_tag($x)
\array_filter( )
$calendar->events,
fn($event) => \in_array(
$tag,
\array_map(
fn($x) => $this->encode_tag($x),
$event->tags
) )
) )
) ),
) 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,
],
),
]
); );
return $result;
} }
@ -307,16 +355,15 @@ class class_caldav_backend
{ {
$id = $objectUri; $id = $objectUri;
$calendar_identifier = $this->decode_calendar_identifier($calendarId); $calendar_identifier = $this->decode_calendar_identifier($calendarId);
$source_name = $calendar_identifier['source_name']; // $realm_name = $calendar_identifier['realm_name'];
$source = $this->get_source($source_name); $realm_name = \davina\get_parameters()['realm_name'];
$source = $this->get_source($realm_name);
$tag = $calendar_identifier['calendar_name']; $tag = $calendar_identifier['calendar_name'];
$calendar = $source->read([]); $calendar = $source->read();
$events = \array_values( $events = \davina\helpers\list_\filter(
\array_filter( $calendar->events,
$calendar->events, fn($event) => ($event->id === $id)
fn($event) => ($event->id === $id)
)
); );
if (\count($events) < 1) if (\count($events) < 1)
{ {

View file

@ -13,18 +13,18 @@ class class_principle_backend
{ {
/** /**
* @var array {map<string, \davina\sources\interface_source>} * @var array {map<string, \davina\model\struct_realm>}
*/ */
private array $sources; private array $realms;
/** /**
*/ */
public function __construct( public function __construct(
array $sources array $realms
) )
{ {
$this->sources = $sources; $this->realms = $realms;
} }
@ -34,18 +34,12 @@ class class_principle_backend
$prefixPath $prefixPath
) )
{ {
return \davina\helpers\list_\map( return [
\array_keys($this->sources), [
fn($source_name) => [ 'uri' => 'principals/-',
'uri' => \davina\helpers\string_\coin( '{DAV:}displayname' => 'default',
'principals/{{name}}', ],
[ ];
'name' => $source_name,
]
),
'{DAV:}displayname' => $source_name,
]
);
} }

View file

@ -2,6 +2,7 @@
namespace davina\sources; namespace davina\sources;
require_once(DIR_LOGIC . '/sources/_interface.php');
require_once(DIR_LOGIC . '/sources/ics_feed.php'); require_once(DIR_LOGIC . '/sources/ics_feed.php');
@ -19,8 +20,8 @@ function make(
return ( return (
new class_source_ics_feed( new class_source_ics_feed(
$data['url'], $data['url'],
($data['lifetime'] ?? (60 * 15)), $data['lifetime'],
($data['combine'] ?? false) $data['conflate']
) )
); );
} }

View file

@ -9,15 +9,8 @@ interface interface_source
{ {
/** /**
* @param array $parameters {
* record<
* username:(null|string),
* password:(null|string),
* >
* }
*/ */
public function read( public function read(
array $parameters
) : \davina\model\struct_calendar ) : \davina\model\struct_calendar
; ;

View file

@ -7,6 +7,7 @@ require_once(DIR_LOGIC . '/helpers/cache.php');
require_once(DIR_LOGIC . '/helpers/pit.php'); require_once(DIR_LOGIC . '/helpers/pit.php');
require_once(DIR_LOGIC . '/helpers/ics.php'); require_once(DIR_LOGIC . '/helpers/ics.php');
require_once(DIR_LOGIC . '/base.php'); require_once(DIR_LOGIC . '/base.php');
require_once(DIR_LOGIC . '/sources/_interface.php');
require_once(DIR_LOGIC . '/model.php'); require_once(DIR_LOGIC . '/model.php');
@ -28,7 +29,7 @@ class class_source_ics_feed
/** /**
*/ */
private bool $combine; private bool $conflate;
/** /**
@ -36,12 +37,12 @@ class class_source_ics_feed
public function __construct( public function __construct(
string $url_template, string $url_template,
?int $lifetime, ?int $lifetime,
bool $combine bool $conflate
) )
{ {
$this->url_template = $url_template; $this->url_template = $url_template;
$this->lifetime = $lifetime; $this->lifetime = $lifetime;
$this->combine = $combine; $this->conflate = $conflate;
$this->cache_file = \davina\helpers\call\convey( $this->cache_file = \davina\helpers\call\convey(
new \davina\helpers\cache\class_cache_file('data'), new \davina\helpers\cache\class_cache_file('data'),
[ [
@ -68,7 +69,10 @@ class class_source_ics_feed
{ {
return \davina\helpers\string_\coin( return \davina\helpers\string_\coin(
$this->url_template, $this->url_template,
$parameters [
'username' => ($parameters['auth_username'] ?? null),
'password' => ($parameters['auth_password'] ?? null),
]
); );
} }
@ -76,7 +80,8 @@ class class_source_ics_feed
/** /**
*/ */
private function vcalendar_to_calendar( private function vcalendar_to_calendar(
\davina\helpers\ics\struct_vcalendar $vcalendar \davina\helpers\ics\struct_vcalendar $vcalendar,
?string $realm_name
) : \davina\model\struct_calendar ) : \davina\model\struct_calendar
{ {
return ( return (
@ -87,8 +92,10 @@ class class_source_ics_feed
$vevent->uid, $vevent->uid,
// title // title
( (
$this->combine (! $this->conflate)
? ?
$vevent->summary
:
\sprintf( \sprintf(
'%s%s', '%s%s',
$vevent->summary, $vevent->summary,
@ -100,8 +107,6 @@ class class_source_ics_feed
) )
) )
) )
:
$vevent->summary
), ),
// begin // begin
\davina\helpers\ics\datetime_to_unix_timestamp($vevent->dtstart->value), \davina\helpers\ics\datetime_to_unix_timestamp($vevent->dtstart->value),
@ -119,11 +124,11 @@ class class_source_ics_feed
$vevent->description, $vevent->description,
// 'tags // 'tags
( (
$this->combine (! $this->conflate)
? ?
['all']
:
$vevent->categories $vevent->categories
:
[]
) )
)), )),
$vcalendar->events $vcalendar->events
@ -136,7 +141,8 @@ class class_source_ics_feed
/** /**
*/ */
private function retrieve( private function retrieve(
string $url string $url,
?string $realm_name
) )
{ {
$client = new \Sabre\HTTP\Client(); $client = new \Sabre\HTTP\Client();
@ -154,7 +160,7 @@ class class_source_ics_feed
{ {
$ics = $response->getBody(); $ics = $response->getBody();
$vcalendar = \davina\helpers\ics\vcalendar_decode($ics); $vcalendar = \davina\helpers\ics\vcalendar_decode($ics);
$calendar = $this->vcalendar_to_calendar($vcalendar); $calendar = $this->vcalendar_to_calendar($vcalendar, $realm_name);
$calendar_raw = \davina\model\calendar_to_raw($calendar); $calendar_raw = \davina\model\calendar_to_raw($calendar);
return $calendar_raw; return $calendar_raw;
break; break;
@ -179,13 +185,18 @@ class class_source_ics_feed
* [implementation] * [implementation]
*/ */
public function read( public function read(
array $parameters
) : \davina\model\struct_calendar ) : \davina\model\struct_calendar
{ {
$url = $this->url(\davina\get_parameters()); $parameters = \davina\get_parameters();
$key = $url; $url = $this->url($parameters);
$key = \sprintf(
'%s:%u',
$url,
$this->conflate
);
$f1 = fn() => $this->retrieve( $f1 = fn() => $this->retrieve(
$url $url,
$parameters['realm_name']
); );
$f2 = fn() => \davina\helpers\cache\get( $f2 = fn() => \davina\helpers\cache\get(
$this->cache_file, $this->cache_file,