Skip to content

Commit

Permalink
Support inclusion of configuration
Browse files Browse the repository at this point in the history
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 NatLibFi#1403 for discussion.
  • Loading branch information
nichtich committed Mar 28, 2023
1 parent a22b389 commit 2c708ea
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 19 deletions.
75 changes: 66 additions & 9 deletions model/GlobalConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -88,29 +90,84 @@ 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('Accept: 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());
}

// Add triples from included configuration, adjust :config resource
$configResource = $this->configResource($graph, $location);
foreach($graph->resources() as $resource) {
$subject = $resource == $configResource ? $this->resource : $resource;
foreach($graph->properties($resource) as $property) {
foreach($resource->all($property) as $value) {
$this->graph->add($subject, $property, $value);
}
}
}
}

/**
* Parses configuration from the config.ttl file
* @param string $filename path to config.ttl file
* @throws \EasyRdf\Exception
*/
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);
}
}

/**
Expand Down
10 changes: 10 additions & 0 deletions model/SkosmosTurtleParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
25 changes: 20 additions & 5 deletions tests/GlobalConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -126,17 +133,25 @@ public function testGetHoneypotTime()
$this->assertEquals(2, $this->config->getHoneypotTime());
}

public function testGetGlobalPlugins()
{
$this->assertEquals(["alpha", "Bravo", "charlie"], $this->config->getGlobalPlugins());
public function testVocabularyExists() {
$this->assertEquals(4, count($this->config->getGraph()->allOfType('skos:Concept')));
}

// --- 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);
}

Expand Down
2 changes: 2 additions & 0 deletions tests/init_fuseki.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 ..

8 changes: 8 additions & 0 deletions tests/testconfig-include.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@prefix skosmos: <http://purl.org/net/skosmos#> .
@prefix : <http://base/#> .

:config a skosmos:Configuration ;

# include configuration from from URL
skosmos:includeConfig <http://localhost:13030/skosmos-test/data?graph=http://skosmos.config/> .

14 changes: 14 additions & 0 deletions tests/testconfig-included.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@prefix skosmos: <http://purl.org/net/skosmos#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .

# This configuration is being included from file

<http://example.org/> 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 .

<whatever> a skos:Concept .
9 changes: 4 additions & 5 deletions tests/testconfig.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -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 <testconfig-included.ttl> .

# Skosmos vocabularies

Expand Down

0 comments on commit 2c708ea

Please sign in to comment.