From 588cdafc76467beee27c045044fbc26a7fb753bd Mon Sep 17 00:00:00 2001 From: Jakob Voss Date: Wed, 22 Feb 2023 20:12:45 +0100 Subject: [PATCH] Support inclusion of configuration This adds repeatable configuration property `skosmos:includeConfig` to include configuration from file or URL. The configuration is cached as usual, based on modification time of `config.ttl` only. See #1403 for discussion. --- model/GlobalConfig.php | 71 ++++++++++++++++++++++++++++++----- model/SkosmosTurtleParser.php | 10 +++++ tests/GlobalConfigTest.php | 21 ++++++++--- tests/init_fuseki.sh | 2 + tests/testconfig-include.ttl | 8 ++++ tests/testconfig-included.ttl | 12 ++++++ tests/testconfig.ttl | 9 ++--- 7 files changed, 114 insertions(+), 19 deletions(-) create mode 100644 tests/testconfig-include.ttl create mode 100644 tests/testconfig-included.ttl diff --git a/model/GlobalConfig.php b/model/GlobalConfig.php index 7a1719262..de04787be 100644 --- a/model/GlobalConfig.php +++ b/model/GlobalConfig.php @@ -78,7 +78,9 @@ private function initializeConfig() $nskey = "namespaces of " . $key; $this->graph = $this->cache->fetch($key); $this->namespaces = $this->cache->fetch($nskey); - if ($this->graph === false || $this->namespaces === false) { // was not found in cache + if ($this->graph && $this->namespaces) { // found in cache + $this->resource = $this->configResource($this->graph, "cache"); + } else { $this->parseConfig($this->filePath); $this->cache->store($key, $this->graph); $this->cache->store($nskey, $this->namespaces); @@ -88,18 +90,63 @@ private function initializeConfig() $this->parseConfig($this->filePath); } - $configResources = $this->graph->allOfType("skosmos:Configuration"); - if (is_null($configResources) || !is_array($configResources) || count($configResources) !== 1) { - throw new Exception("config.ttl must have exactly one skosmos:Configuration"); - } - - $this->resource = $configResources[0]; $this->initializeNamespaces(); } catch (Exception $e) { echo "Error: " . $e->getMessage(); } } + /** + * Ensure there is exactely one skosmos:Configuration and return it. + */ + private function configResource($graph, $source) { + $configResources = $graph->allOfType("skosmos:Configuration"); + if (is_null($configResources) || !is_array($configResources) || count($configResources) !== 1) { + throw new Exception("$source must have exactly one skosmos:Configuration"); + } + return $configResources[0]; + } + + /** + * Retrieves, parses and includes configuration in existing configuration. + * @param \EasyRdf\Resource URL or file of configuration in Turtle syntax. + */ + private function includeConfig($location) { + $location = $location->getUri(); + + if (str_starts_with($location, "http://") || str_starts_with($location, "https://")) { + $ch = curl_init($location); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: text/turtle')); + $turtle = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if ($httpCode != 200 && $httpCode != 303) { + throw new Exception("Failed to include configuration from $location"); + } + curl_close($ch); + } else { + if (file_exists($location)) { + $turtle = file_get_contents($location); + } else { + throw new Exception("Included config file $location does not exist!"); + } + } + + $parser = new SkosmosTurtleParser(); + try { + $graph = $parser->parseGraph($turtle, $location); + } catch (Exception $e) { + throw new Exception("Failed to parse $location: " . $e->getMessage()); + } + + $configResource = $this->configResource($graph, $location); + foreach($graph->properties($configResource) as $property) { + foreach($configResource->all($property) as $value) { + $this->graph->add($this->resource, $property, $value); + } + } + } + /** * Parses configuration from the config.ttl file * @param string $filename path to config.ttl file @@ -107,10 +154,16 @@ private function initializeConfig() */ private function parseConfig($filename) { - $this->graph = new EasyRdf\Graph(); $parser = new SkosmosTurtleParser(); - $parser->parse($this->graph, file_get_contents($filename), 'turtle', $filename); + $this->graph = $parser->parseGraph(file_get_contents($filename), $filename); $this->namespaces = $parser->getNamespaces(); + + $this->resource = $this->configResource($this->graph, $filename); + + $includes = $this->graph->allResources($this->resource, "skosmos:includeConfig"); + foreach($includes as $location) { + $this->includeConfig($location); + } } /** diff --git a/model/SkosmosTurtleParser.php b/model/SkosmosTurtleParser.php index 4f9e395de..7ad24710e 100644 --- a/model/SkosmosTurtleParser.php +++ b/model/SkosmosTurtleParser.php @@ -11,4 +11,14 @@ public function getNamespaces() return $this->namespaces; } + /** + * Parse Turtle into a new Graph and return it. + * @return EasyRdf\Graph + */ + public function parseGraph($data, $baseUri) + { + $graph = new EasyRdf\Graph(); + $this->parse($graph, $data, 'turtle', $baseUri); + return $graph; + } } diff --git a/tests/GlobalConfigTest.php b/tests/GlobalConfigTest.php index 7715a3952..cabf285eb 100644 --- a/tests/GlobalConfigTest.php +++ b/tests/GlobalConfigTest.php @@ -116,6 +116,13 @@ public function testGetUiLanguageDropdown() $this->assertEquals(true, $this->config->getUiLanguageDropdown()); } + public function testGetGlobalPlugins() + { + $this->assertEquals(["alpha", "Bravo", "charlie"], $this->config->getGlobalPlugins()); + } + + // included from testconfig-included.ttl + public function testGetHoneypotEnabled() { $this->assertEquals(false, $this->config->getHoneypotEnabled()); @@ -126,17 +133,21 @@ public function testGetHoneypotTime() $this->assertEquals(2, $this->config->getHoneypotTime()); } - public function testGetGlobalPlugins() - { - $this->assertEquals(["alpha", "Bravo", "charlie"], $this->config->getGlobalPlugins()); + // --- test inclusion from URL + + public function testInclusionFromURL() { + $conf = new GlobalConfig("/../tests/testconfig-include.ttl"); + $this->assertEquals(2, $conf->getHoneypotTime()); } // --- tests for the exception paths public function testInitializeConfigWithoutGraph() { - $this->expectOutputString('Error: config.ttl must have exactly one skosmos:Configuration'); - $conf = new GlobalConfig('/../tests/testconfig-nograph.ttl'); + $file = '/../tests/testconfig-nograph.ttl'; + $filepath = realpath( dirname(__FILE__) . $file ); + $this->expectOutputString("Error: $filepath must have exactly one skosmos:Configuration"); + $conf = new GlobalConfig($file); $this->assertNotNull($conf); } diff --git a/tests/init_fuseki.sh b/tests/init_fuseki.sh index e4d5ca59b..2fb04eda7 100755 --- a/tests/init_fuseki.sh +++ b/tests/init_fuseki.sh @@ -33,5 +33,7 @@ for fn in ../test-vocab-data/*.ttl; do $(./bin/s-put http://localhost:13030/skosmos-test/data "http://www.skosmos.skos/$name/" "$fn") done +$(./bin/s-put http://localhost:13030/skosmos-test/data "http://skosmos.config/" "../testconfig-included.ttl") + cd .. diff --git a/tests/testconfig-include.ttl b/tests/testconfig-include.ttl new file mode 100644 index 000000000..fb87ca52d --- /dev/null +++ b/tests/testconfig-include.ttl @@ -0,0 +1,8 @@ +@prefix skosmos: . +@prefix : . + +:config a skosmos:Configuration ; + + # include configuration from from URL + skosmos:includeConfig . + diff --git a/tests/testconfig-included.ttl b/tests/testconfig-included.ttl new file mode 100644 index 000000000..53b5e055c --- /dev/null +++ b/tests/testconfig-included.ttl @@ -0,0 +1,12 @@ +@prefix skosmos: . + +# This configuration is being included from file + + a skosmos:Configuration ; + + # whether to enable the spam honey pot or not, enabled by default + skosmos:uiHoneypotEnabled false ; + + # default time a user must wait before submitting a form + skosmos:uiHoneypotTime 2 . + diff --git a/tests/testconfig.ttl b/tests/testconfig.ttl index 924da08db..15404a84f 100644 --- a/tests/testconfig.ttl +++ b/tests/testconfig.ttl @@ -59,12 +59,11 @@ skosmos:feedbackEnvelopeSender "skosmos tests" ; # whether to display the ui language selection as a dropdown (useful for cases where there are more than 3 languages) skosmos:uiLanguageDropdown true ; - # whether to enable the spam honey pot or not, enabled by default - skosmos:uiHoneypotEnabled false ; - # default time a user must wait before submitting a form - skosmos:uiHoneypotTime 2 ; # plugins to activate for the whole installation (including all vocabularies) - skosmos:globalPlugins ("alpha" "Bravo" "charlie") . + skosmos:globalPlugins ("alpha" "Bravo" "charlie") ; + + # include another config file + skosmos:includeConfig . # Skosmos vocabularies