diff --git a/source/auths/_factory.php b/source/auths/_factory.php new file mode 100644 index 0000000..a5a9452 --- /dev/null +++ b/source/auths/_factory.php @@ -0,0 +1,39 @@ + diff --git a/source/auths/_interface.php b/source/auths/_interface.php new file mode 100644 index 0000000..cfaeff2 --- /dev/null +++ b/source/auths/_interface.php @@ -0,0 +1,42 @@ + + * } + */ + function check( + array $credentials + ) : bool + ; + + + /** + * @param array $credentials { + * record< + * username:(null|string), + * password:string + * > + * } + */ + function determine_parameters( + array $credentials + ) : array + ; + +} + + ?> diff --git a/source/auths/pass_through.php b/source/auths/pass_through.php new file mode 100644 index 0000000..673663d --- /dev/null +++ b/source/auths/pass_through.php @@ -0,0 +1,39 @@ + $credentials['username'], + 'auth_password' => $credentials['password'], + ]; + } + +} + + ?> diff --git a/source/auths/static.php b/source/auths/static.php new file mode 100644 index 0000000..6b62cc1 --- /dev/null +++ b/source/auths/static.php @@ -0,0 +1,51 @@ +password = $password; + } + + + /** + * [implementation] + */ + function check( + array $credentials + ) : bool + { + return ($credentials['password'] === $this->password); + } + + + /** + * [implementation] + */ + function determine_parameters( + array $credentials + ) : array + { + return []; + } + +} + + ?> diff --git a/source/conf.php b/source/conf.php index 95ce744..910b45a 100644 --- a/source/conf.php +++ b/source/conf.php @@ -5,72 +5,22 @@ namespace davina\conf; 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} - */ - public array $sources; - public struct_settings $settings; -} - - /** */ class _state { - public static ?struct_root $data = null; + public static $data = null; } /** */ -function validate_source_name( - string $source_name +function validate_realm_name( + string $realm_name ) : string { $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); } @@ -87,54 +37,86 @@ function load( ), true ); - { - $data = new struct_root(); - // auth - { - $auth = new struct_auth(); - $auth->kind = (($data_raw['auth'] ?? [])['kind'] ?? 'none'); - $auth->data = (($data_raw['auth'] ?? [])['data'] ?? null); - $data->auth = $auth; - } - // sources - { - $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; - } + _state::$data = [ + /** + * @todo check for name duplicates + */ + 'realms' => \davina\helpers\list_\map( + $data_raw['realms'], + function ($realm_raw) { + if (! validate_realm_name($realm_raw['name'])) + { + throw (new \Exception(\sprintf('invalid realm name: "%s"', $realm_raw['name']))); } - ); - } - // settings - { - $settings = new struct_settings(); - $settings->timezone = (($data_raw['settings'] ?? [])['timezone'] ?? 'UTC'); - $data->settings = $settings; - } - _state::$data = $data; - } + else + { + return [ + 'name' => $realm_raw['name'], + 'auth' => (function ($auth_raw) use ($realm_raw) { + switch ($auth_raw['kind']) + { + case 'pass_through': + { + 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( -) : struct_root +) { if (_state::$data === null) { diff --git a/source/helpers/string.php b/source/helpers/string.php index ce04ae4..b6a066e 100644 --- a/source/helpers/string.php +++ b/source/helpers/string.php @@ -13,7 +13,18 @@ function coin( $result = $template; 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; } diff --git a/source/main.php b/source/main.php index 7bf36c2..9913ac9 100644 --- a/source/main.php +++ b/source/main.php @@ -7,9 +7,11 @@ define("DIR_LOGIC", "logic"); require_once('vendor/autoload.php'); require_once(DIR_LOGIC . '/base.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 . '/sources/_factory.php'); +require_once(DIR_LOGIC . '/auths/_factory.php'); +require_once(DIR_LOGIC . '/model.php'); require_once(DIR_LOGIC . '/conf.php'); @@ -20,21 +22,28 @@ function main( \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 = []; - foreach (\davina\conf\get()->sources as $source_raw) + $realms = []; + foreach (\davina\conf\get()['realms'] as $realm_raw) { - $sources[$source_raw->name] = \davina\sources\make( - $source_raw->kind, - $source_raw->data + $realms[$realm_raw['name']] = new \davina\model\struct_realm( + $realm_raw['name'], + \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( [ @@ -43,7 +52,7 @@ function main( ), new \Sabre\CalDAV\CalendarRoot( $principal_backend, - new \davina\overwrites\class_caldav_backend($sources) + new \davina\overwrites\class_caldav_backend($realms) ), ] ); @@ -52,8 +61,8 @@ function main( $server->addPlugin( new \Sabre\DAV\Auth\Plugin( - new \davina\overwrites\class_auth_backend_basic( - $sources + new \davina\overwrites\class_auth_backend( + $realms ) ) ); @@ -70,7 +79,7 @@ function main( /** * not required */ - $server->addPlugin(new \Sabre\DAV\Browser\Plugin()); + // $server->addPlugin(new \Sabre\DAV\Browser\Plugin()); $server->start(); } diff --git a/source/model.php b/source/model.php index 069e347..176e2b1 100644 --- a/source/model.php +++ b/source/model.php @@ -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; + } + +} + ?> diff --git a/source/overwrites/auth_backend.php b/source/overwrites/auth_backend.php new file mode 100644 index 0000000..80c6be1 --- /dev/null +++ b/source/overwrites/auth_backend.php @@ -0,0 +1,86 @@ +} + */ + 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); + } + } + } + +} + + ?> diff --git a/source/overwrites/auths/_factory.php b/source/overwrites/auths/_factory.php deleted file mode 100644 index b24f76f..0000000 --- a/source/overwrites/auths/_factory.php +++ /dev/null @@ -1,37 +0,0 @@ - diff --git a/source/overwrites/auths/basic.php b/source/overwrites/auths/basic.php deleted file mode 100644 index cee97aa..0000000 --- a/source/overwrites/auths/basic.php +++ /dev/null @@ -1,74 +0,0 @@ -} - */ - 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); - } - } - -} - - ?> diff --git a/source/overwrites/auths/none.php b/source/overwrites/auths/none.php deleted file mode 100644 index dd2939d..0000000 --- a/source/overwrites/auths/none.php +++ /dev/null @@ -1,39 +0,0 @@ - diff --git a/source/overwrites/caldav_backend.php b/source/overwrites/caldav_backend.php index 833e188..3efc67c 100644 --- a/source/overwrites/caldav_backend.php +++ b/source/overwrites/caldav_backend.php @@ -7,7 +7,6 @@ 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 . '/sources/_interface.php'); require_once(DIR_LOGIC . '/conf.php'); @@ -20,25 +19,25 @@ class class_caldav_backend /** - * @var array {map} + * @var array {map} */ - private array $sources; + private array $realms; /** */ - private ?string $selected_source_name; + private ?string $selected_realm_name; /** */ public function __construct( - array $sources + array $realms ) { // parent::__construct(); - $this->sources = $sources; - $this->selected_source_name = null; + $this->realms = $realms; + $this->selected_realm_name = null; } @@ -104,30 +103,37 @@ class class_caldav_backend /** */ private function encode_tag( - string $tag - ) : string + ?string $tag + ) : ?string { - 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), - ] - ); + 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< - * source_name:string, - * calendar_name:string, + * realm_name:string, + * calendar_name:(null|string), * > * } */ @@ -135,19 +141,27 @@ class class_caldav_backend array $parameters ) : string { - return \sprintf( - '%s-%s', - $parameters['source_name'], - $parameters['calendar_name'] - ); + $str = $parameters['realm_name']; + if ($parameters['calendar_name'] === null) + { + // do nothing + } + else + { + $str .= \sprintf( + '-%s', + $parameters['calendar_name'] + ); + } + return $str; } /** * @return array { * record< - * source_name:string, - * calendar_name:string, + * realm_name:string, + * calendar_name:(null|string), * > * } */ @@ -157,8 +171,14 @@ class class_caldav_backend { $parts = \explode('-', $str, 2); return [ - 'source_name' => $parts[0], - 'calendar_name' => $parts[1], + 'realm_name' => $parts[0], + 'calendar_name' => ( + (count($parts) < 2) + ? + null + : + $parts[1] + ), ]; } @@ -166,23 +186,23 @@ class class_caldav_backend /** */ private function get_source( - $source_name + string $realm_name ) : \davina\sources\interface_source { 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; - if (! \array_key_exists($source_name, $this->sources)) + $this->selected_realm_name = $realm_name; + 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 { - return $this->sources[$source_name]; + return $this->realms[$realm_name]->source; } } else @@ -200,9 +220,10 @@ class class_caldav_backend ) { // $source = $this->source; - $source_name = \explode('/', $principalUri)[1]; - $source = $this->get_source($source_name); - $calendar = $source->read([]); + // $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) { @@ -211,23 +232,42 @@ class class_caldav_backend $tags[$tag] = null; } } - $result = \array_map( - fn($tag) => [ - 'id' => $this->encode_calendar_identifier( - [ - 'source_name' => $source_name, - 'calendar_name' => $this->encode_tag($tag), + 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']), ] ), - '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); - $source_name = $calendar_identifier['source_name']; - $source = $this->get_source($source_name); + // $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([]); + $calendar = $source->read(); - $result = \array_map( - 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, - ], - \array_values( - \array_filter( - $calendar->events, - fn($event) => \in_array( - $tag, - \array_map( - fn($x) => $this->encode_tag($x), - $event->tags + 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, + ], + ), + ] ); - return $result; } @@ -307,16 +355,15 @@ class class_caldav_backend { $id = $objectUri; $calendar_identifier = $this->decode_calendar_identifier($calendarId); - $source_name = $calendar_identifier['source_name']; - $source = $this->get_source($source_name); + // $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([]); + $calendar = $source->read(); - $events = \array_values( - \array_filter( - $calendar->events, - fn($event) => ($event->id === $id) - ) + $events = \davina\helpers\list_\filter( + $calendar->events, + fn($event) => ($event->id === $id) ); if (\count($events) < 1) { diff --git a/source/overwrites/principal_backend.php b/source/overwrites/principal_backend.php index d4194e2..d704b39 100644 --- a/source/overwrites/principal_backend.php +++ b/source/overwrites/principal_backend.php @@ -13,18 +13,18 @@ class class_principle_backend { /** - * @var array {map} + * @var array {map} */ - private array $sources; + private array $realms; /** */ public function __construct( - array $sources + array $realms ) { - $this->sources = $sources; + $this->realms = $realms; } @@ -34,18 +34,12 @@ class class_principle_backend $prefixPath ) { - return \davina\helpers\list_\map( - \array_keys($this->sources), - fn($source_name) => [ - 'uri' => \davina\helpers\string_\coin( - 'principals/{{name}}', - [ - 'name' => $source_name, - ] - ), - '{DAV:}displayname' => $source_name, - ] - ); + return [ + [ + 'uri' => 'principals/-', + '{DAV:}displayname' => 'default', + ], + ]; } diff --git a/source/sources/_factory.php b/source/sources/_factory.php index fe04e0f..78d0484 100644 --- a/source/sources/_factory.php +++ b/source/sources/_factory.php @@ -2,6 +2,7 @@ namespace davina\sources; +require_once(DIR_LOGIC . '/sources/_interface.php'); require_once(DIR_LOGIC . '/sources/ics_feed.php'); @@ -19,8 +20,8 @@ function make( return ( new class_source_ics_feed( $data['url'], - ($data['lifetime'] ?? (60 * 15)), - ($data['combine'] ?? false) + $data['lifetime'], + $data['conflate'] ) ); } diff --git a/source/sources/_interface.php b/source/sources/_interface.php index bcb414d..4179fee 100644 --- a/source/sources/_interface.php +++ b/source/sources/_interface.php @@ -9,15 +9,8 @@ interface interface_source { /** - * @param array $parameters { - * record< - * username:(null|string), - * password:(null|string), - * > - * } */ public function read( - array $parameters ) : \davina\model\struct_calendar ; diff --git a/source/sources/ics_feed.php b/source/sources/ics_feed.php index 35df9e6..711c824 100644 --- a/source/sources/ics_feed.php +++ b/source/sources/ics_feed.php @@ -7,6 +7,7 @@ require_once(DIR_LOGIC . '/helpers/cache.php'); require_once(DIR_LOGIC . '/helpers/pit.php'); require_once(DIR_LOGIC . '/helpers/ics.php'); require_once(DIR_LOGIC . '/base.php'); +require_once(DIR_LOGIC . '/sources/_interface.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( string $url_template, ?int $lifetime, - bool $combine + bool $conflate ) { $this->url_template = $url_template; $this->lifetime = $lifetime; - $this->combine = $combine; + $this->conflate = $conflate; $this->cache_file = \davina\helpers\call\convey( new \davina\helpers\cache\class_cache_file('data'), [ @@ -68,7 +69,10 @@ class class_source_ics_feed { return \davina\helpers\string_\coin( $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( - \davina\helpers\ics\struct_vcalendar $vcalendar + \davina\helpers\ics\struct_vcalendar $vcalendar, + ?string $realm_name ) : \davina\model\struct_calendar { return ( @@ -87,8 +92,10 @@ class class_source_ics_feed $vevent->uid, // title ( - $this->combine + (! $this->conflate) ? + $vevent->summary + : \sprintf( '%s%s', $vevent->summary, @@ -100,8 +107,6 @@ class class_source_ics_feed ) ) ) - : - $vevent->summary ), // begin \davina\helpers\ics\datetime_to_unix_timestamp($vevent->dtstart->value), @@ -119,11 +124,11 @@ class class_source_ics_feed $vevent->description, // 'tags ( - $this->combine + (! $this->conflate) ? - ['all'] - : $vevent->categories + : + [] ) )), $vcalendar->events @@ -136,7 +141,8 @@ class class_source_ics_feed /** */ private function retrieve( - string $url + string $url, + ?string $realm_name ) { $client = new \Sabre\HTTP\Client(); @@ -154,7 +160,7 @@ class class_source_ics_feed { $ics = $response->getBody(); $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); return $calendar_raw; break; @@ -179,13 +185,18 @@ class class_source_ics_feed * [implementation] */ public function read( - array $parameters ) : \davina\model\struct_calendar { - $url = $this->url(\davina\get_parameters()); - $key = $url; + $parameters = \davina\get_parameters(); + $url = $this->url($parameters); + $key = \sprintf( + '%s:%u', + $url, + $this->conflate + ); $f1 = fn() => $this->retrieve( - $url + $url, + $parameters['realm_name'] ); $f2 = fn() => \davina\helpers\cache\get( $this->cache_file,