diff --git a/source/conf.php b/source/conf.php index fbd147f..95ce744 100644 --- a/source/conf.php +++ b/source/conf.php @@ -2,6 +2,8 @@ namespace davina\conf; +require_once(DIR_LOGIC . '/helpers/list.php'); + /** */ @@ -26,6 +28,7 @@ class struct_source_data_ics_feed */ class struct_source { + public string $name; public string $kind; public $data; } @@ -44,7 +47,10 @@ class struct_settings class struct_root { public struct_auth $auth; - public struct_source $source; + /** + * @var array {list} + */ + public array $sources; public struct_settings $settings; } @@ -57,6 +63,18 @@ class _state } +/** + */ +function validate_source_name( + string $source_name +) : string +{ + $matchs = null; + $result = preg_match('/^[0-9a-zA-Z]+$/', $source_name, $matches); + return ($result === 1); +} + + /** */ function load( @@ -78,12 +96,29 @@ function load( $auth->data = (($data_raw['auth'] ?? [])['data'] ?? null); $data->auth = $auth; } - // source + // sources { - $source = new struct_source(); - $source->kind = $data_raw['source']['kind']; - $source->data = ($data_raw['source']['data'] ?? null); - $data->source = $source; + $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; + } + } + ); } // settings { diff --git a/source/helpers/list.php b/source/helpers/list.php new file mode 100644 index 0000000..89a59c1 --- /dev/null +++ b/source/helpers/list.php @@ -0,0 +1,35 @@ + diff --git a/source/main.php b/source/main.php index c4b4bdd..7bf36c2 100644 --- a/source/main.php +++ b/source/main.php @@ -22,19 +22,29 @@ function main( \date_default_timezone_set(\davina\conf\get()->settings->timezone); - $source = \davina\sources\make( - [ - 'kind' => \davina\conf\get()->source->kind, - 'data' => \davina\conf\get()->source->data, - ] - ); + /** + * @todo nicht einfach die source names als keys verwenden … + */ + $sources = []; + foreach (\davina\conf\get()->sources as $source_raw) + { + $sources[$source_raw->name] = \davina\sources\make( + $source_raw->kind, + $source_raw->data + ); + } - $principal_backend = new \davina\overwrites\class_principle_backend(); + $principal_backend = new \davina\overwrites\class_principle_backend($sources); $server = new \Sabre\DAV\Server( [ - new \Sabre\CalDAV\Principal\Collection($principal_backend), - new \Sabre\CalDAV\CalendarRoot($principal_backend, new \davina\overwrites\class_caldav_backend($source)), + new \Sabre\CalDAV\Principal\Collection( + $principal_backend + ), + new \Sabre\CalDAV\CalendarRoot( + $principal_backend, + new \davina\overwrites\class_caldav_backend($sources) + ), ] ); @@ -42,12 +52,8 @@ function main( $server->addPlugin( new \Sabre\DAV\Auth\Plugin( - \davina\overwrites\make_auth_backend( - $source, - [ - 'kind' => \davina\conf\get()->auth->kind, - 'data' => \davina\conf\get()->auth->data, - ] + new \davina\overwrites\class_auth_backend_basic( + $sources ) ) ); diff --git a/source/overwrites/auths/basic.php b/source/overwrites/auths/basic.php index 6d42754..cee97aa 100644 --- a/source/overwrites/auths/basic.php +++ b/source/overwrites/auths/basic.php @@ -15,18 +15,19 @@ class class_auth_backend_basic { /** + * @var array {list<\davina\sources\interface_source>} */ - private \davina\sources\interface_source $source; + private array $sources; /** */ public function __construct( - \davina\sources\interface_source $source + array $sources ) { // parent::__construct(); - $this->source = $source; + $this->sources = $sources; $this->setRealm('davina'); } @@ -34,20 +35,38 @@ class class_auth_backend_basic /** */ protected function validateUserPass( - /*string */$username, + /*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, ]; - /** - * @todo check for security - */ - \davina\set_parameters($parameters); - $data = $this->source->get(/*$parameters*/[]); - return ($data !== null); + 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/caldav_backend.php b/source/overwrites/caldav_backend.php index 7353735..833e188 100644 --- a/source/overwrites/caldav_backend.php +++ b/source/overwrites/caldav_backend.php @@ -4,6 +4,7 @@ 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 . '/sources/_interface.php'); @@ -19,52 +20,27 @@ class class_caldav_backend /** + * @var array {map} */ - private \davina\sources\interface_source $source; + private array $sources; + + + /** + */ + private ?string $selected_source_name; /** */ public function __construct( - \davina\sources\interface_source $source + array $sources ) { // parent::__construct(); - $this->source = $source; + $this->sources = $sources; + $this->selected_source_name = null; } - - /** - */ - private function hash_tag( - string $tag - ) : string - { - return \hash('sha256', $tag); - } - - - /** - */ - private function encode_tag( - 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), - ] - ); - } - /** * @todo outsource @@ -123,6 +99,97 @@ class class_caldav_backend } return $vcalendar; } + + + /** + */ + private function encode_tag( + 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), + ] + ); + } + + + /** + * @param array $parameters { + * record< + * source_name:string, + * calendar_name:string, + * > + * } + */ + private function encode_calendar_identifier( + array $parameters + ) : string + { + return \sprintf( + '%s-%s', + $parameters['source_name'], + $parameters['calendar_name'] + ); + } + + + /** + * @return array { + * record< + * source_name:string, + * calendar_name:string, + * > + * } + */ + private function decode_calendar_identifier( + string $str + ) : array + { + $parts = \explode('-', $str, 2); + return [ + 'source_name' => $parts[0], + 'calendar_name' => $parts[1], + ]; + } + + + /** + */ + private function get_source( + $source_name + ) : \davina\sources\interface_source + { + if ( + ($this->selected_source_name === null) + || + ($this->selected_source_name === $source_name) + ) + { + $this->selected_source_name = $source_name; + if (! \array_key_exists($source_name, $this->sources)) + { + throw (new \Exception(\sprintf('no such source: %s', $source_name))); + } + else + { + return $this->sources[$source_name]; + } + } + else + { + throw (new \Exception('may not change source')); + } + } /** @@ -132,7 +199,10 @@ class class_caldav_backend $principalUri ) { - $calendar = $this->source->get([]); + // $source = $this->source; + $source_name = \explode('/', $principalUri)[1]; + $source = $this->get_source($source_name); + $calendar = $source->read([]); $tags = []; foreach ($calendar->events as $event) { @@ -143,7 +213,12 @@ class class_caldav_backend } $result = \array_map( fn($tag) => [ - 'id' => $this->hash_tag($tag), + 'id' => $this->encode_calendar_identifier( + [ + 'source_name' => $source_name, + 'calendar_name' => $this->encode_tag($tag), + ] + ), 'uri' => $this->encode_tag($tag), 'principaluri' => $principalUri, '{DAV:}displayname' => $tag, @@ -187,8 +262,12 @@ class class_caldav_backend $calendarId ) { - $tag = $calendarId; - $calendar = $this->source->get([]); + $calendar_identifier = $this->decode_calendar_identifier($calendarId); + $source_name = $calendar_identifier['source_name']; + $source = $this->get_source($source_name); + $tag = $calendar_identifier['calendar_name']; + $calendar = $source->read([]); + $result = \array_map( fn($event) => [ 'calendarid' => $calendarId, @@ -207,7 +286,7 @@ class class_caldav_backend fn($event) => \in_array( $tag, \array_map( - fn($x) => $this->hash_tag($x), + fn($x) => $this->encode_tag($x), $event->tags ) ) @@ -227,7 +306,12 @@ class class_caldav_backend ) { $id = $objectUri; - $calendar = $this->source->get([]); + $calendar_identifier = $this->decode_calendar_identifier($calendarId); + $source_name = $calendar_identifier['source_name']; + $source = $this->get_source($source_name); + $tag = $calendar_identifier['calendar_name']; + $calendar = $source->read([]); + $events = \array_values( \array_filter( $calendar->events, diff --git a/source/overwrites/principal_backend.php b/source/overwrites/principal_backend.php index be59c3f..d4194e2 100644 --- a/source/overwrites/principal_backend.php +++ b/source/overwrites/principal_backend.php @@ -3,6 +3,7 @@ namespace davina\overwrites; require_once('vendor/autoload.php'); +require_once(DIR_LOGIC . '/helpers/list.php'); /** @@ -12,17 +13,39 @@ class class_principle_backend { /** + * @var array {map} */ - public function getPrincipalsByPrefix($prefixPath) + private array $sources; + + + /** + */ + public function __construct( + array $sources + ) { - throw (new \Exception('not implemented: getPrincipalsByPrefix')); - /* - return [ - [ - 'uri' => 'principals/dummy', + $this->sources = $sources; + } + + + /** + */ + public function getPrincipalsByPrefix( + $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, ] - ]; - */ + ); } @@ -32,7 +55,6 @@ class class_principle_backend $path ) { - // throw (new \Exception('not implemented: getPrincipalByPath')); $parts = \explode('/', $path); $username = $parts[1]; return [ diff --git a/source/sources/_factory.php b/source/sources/_factory.php index 0721a84..fe04e0f 100644 --- a/source/sources/_factory.php +++ b/source/sources/_factory.php @@ -6,26 +6,21 @@ require_once(DIR_LOGIC . '/sources/ics_feed.php'); /** - * @param array $descriptor { - * record< - * kind:string, - * data:any - * > - * } */ function make( - array $descriptor + string $kind, + $data ) : interface_source { - switch ($descriptor['kind']) + switch ($kind) { case 'ics_feed': { return ( new class_source_ics_feed( - $descriptor['data']['url'], - ($descriptor['data']['lifetime'] ?? (60 * 15)), - ($descriptor['data']['combine'] ?? false) + $data['url'], + ($data['lifetime'] ?? (60 * 15)), + ($data['combine'] ?? false) ) ); } @@ -35,7 +30,7 @@ function make( new \Exception( \sprintf( 'unhandled source kind: %s', - $descriptor['kind'] + $kind ) ) ); @@ -44,4 +39,4 @@ function make( } } - + ?> diff --git a/source/sources/_interface.php b/source/sources/_interface.php index bfb8ab2..bcb414d 100644 --- a/source/sources/_interface.php +++ b/source/sources/_interface.php @@ -16,7 +16,7 @@ interface interface_source * > * } */ - public function get( + public function read( array $parameters ) : \davina\model\struct_calendar ; diff --git a/source/sources/ics_feed.php b/source/sources/ics_feed.php index 2db9513..35df9e6 100644 --- a/source/sources/ics_feed.php +++ b/source/sources/ics_feed.php @@ -121,7 +121,7 @@ class class_source_ics_feed ( $this->combine ? - ['combined'] + ['all'] : $vevent->categories ) @@ -178,7 +178,7 @@ class class_source_ics_feed /** * [implementation] */ - public function get( + public function read( array $parameters ) : \davina\model\struct_calendar {