Skip to content

Commit a039d07

Browse files
committed
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.
1 parent a22b389 commit a039d07

File tree

7 files changed

+135
-27
lines changed

7 files changed

+135
-27
lines changed

model/GlobalConfig.php

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
EasyRdf\RdfNamespace::set('wdt', 'http://www.wikidata.org/prop/direct/');
1414

1515
/**
16-
* GlobalConfig provides access to the Skosmos configuration in config.ttl.
16+
* GlobalConfig provides access to the Skosmos configuration.
1717
*/
1818
class GlobalConfig extends BaseConfig {
1919

@@ -59,7 +59,7 @@ public function getConfigModifiedTime()
5959
}
6060

6161
/**
62-
* Initialize configuration, reading the configuration file from the disk,
62+
* Initialize configuration, reading the configuration file from disk or URL,
6363
* and creating the graph and resources objects. Uses a cache if available,
6464
* in order to avoid re-loading the complete configuration on each request.
6565
*/
@@ -78,7 +78,9 @@ private function initializeConfig()
7878
$nskey = "namespaces of " . $key;
7979
$this->graph = $this->cache->fetch($key);
8080
$this->namespaces = $this->cache->fetch($nskey);
81-
if ($this->graph === false || $this->namespaces === false) { // was not found in cache
81+
if ($this->graph && $this->namespaces) { // found in cache
82+
$this->resource = $this->configResource($this->graph, "cache");
83+
} else {
8284
$this->parseConfig($this->filePath);
8385
$this->cache->store($key, $this->graph);
8486
$this->cache->store($nskey, $this->namespaces);
@@ -88,31 +90,89 @@ private function initializeConfig()
8890
$this->parseConfig($this->filePath);
8991
}
9092

91-
$configResources = $this->graph->allOfType("skosmos:Configuration");
92-
if (is_null($configResources) || !is_array($configResources) || count($configResources) !== 1) {
93-
throw new Exception("config.ttl must have exactly one skosmos:Configuration");
94-
}
95-
96-
$this->resource = $configResources[0];
9793
$this->initializeNamespaces();
9894
} catch (Exception $e) {
9995
echo "Error: " . $e->getMessage();
10096
}
10197
}
10298

10399
/**
104-
* Parses configuration from the config.ttl file
105-
* @param string $filename path to config.ttl file
100+
* Ensure there is exactely one skosmos:Configuration and return it.
101+
*/
102+
private function configResource($graph, $source) {
103+
$configResources = $graph->allOfType("skosmos:Configuration");
104+
if (is_null($configResources) || !is_array($configResources) || count($configResources) !== 1) {
105+
throw new Exception("$source must have exactly one skosmos:Configuration");
106+
}
107+
return $configResources[0];
108+
}
109+
110+
/**
111+
* Retrieves and parses configuration with optional inclusion.
112+
* @param string file or URL of configuration in Turtle syntax.
113+
* @param int inclusion depth (0 for root configuration)
106114
* @throws \EasyRdf\Exception
107115
*/
108-
private function parseConfig($filename)
109-
{
110-
$this->graph = new EasyRdf\Graph();
116+
private function parseConfig($location, $depth=0) {
117+
if (str_starts_with($location, "http://") || str_starts_with($location, "https://")) {
118+
$ch = curl_init($location);
119+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
120+
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: text/turtle'));
121+
$turtle = curl_exec($ch);
122+
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
123+
if ($httpCode != 200 && $httpCode != 303) {
124+
throw new Exception("Failed to include configuration from $location");
125+
}
126+
curl_close($ch);
127+
} else {
128+
if (file_exists($location)) {
129+
$turtle = file_get_contents($location);
130+
} else {
131+
throw new Exception("Config file $location does not exist!");
132+
}
133+
}
134+
111135
$parser = new SkosmosTurtleParser();
112-
$parser->parse($this->graph, file_get_contents($filename), 'turtle', $filename);
113-
$this->namespaces = $parser->getNamespaces();
114-
}
136+
try {
137+
$graph = $parser->parseGraph($turtle, $location);
138+
$namespaces = $parser->getNamespaces();
139+
} catch (Exception $e) {
140+
throw new Exception("Failed to parse $location: " . $e->getMessage());
141+
}
142+
143+
$configResource = $this->configResource($graph, $location);
144+
145+
if ($depth === 0) {
146+
$this->graph = $graph;
147+
$this->namespaces = $namespaces;
148+
$this->resource = $configResource;
149+
} else {
150+
// Add triples to existing configuration
151+
foreach($graph->resources() as $resource) {
152+
$subject = $resource == $configResource ? $this->resource : $resource;
153+
foreach($graph->properties($resource) as $property) {
154+
foreach($resource->all($property) as $value) {
155+
$this->graph->add($subject, $property, $value);
156+
}
157+
}
158+
}
159+
// Add namespaces unless prefix has been defined
160+
foreach ($namespaces as $prefix => $fullUri) {
161+
if (!isset($this->namespaces[$prefix])) {
162+
$this->namespaces[$prefix] = $fullUri;
163+
}
164+
}
165+
}
115166

167+
// only include up to four levels
168+
if ($depth < 4) {
169+
$includes = $this->graph->allResources($this->resource, "skosmos:includeConfig");
170+
foreach($includes as $location) {
171+
$this->parseConfig($location->getUri(), $depth+1);
172+
}
173+
}
174+
}
175+
116176
/**
117177
* Returns the graph created after parsing the configuration file.
118178
* @return \EasyRdf\Graph

model/SkosmosTurtleParser.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,14 @@ public function getNamespaces()
1111
return $this->namespaces;
1212
}
1313

14+
/**
15+
* Parse Turtle into a new Graph and return it.
16+
* @return EasyRdf\Graph
17+
*/
18+
public function parseGraph($data, $baseUri)
19+
{
20+
$graph = new EasyRdf\Graph();
21+
$this->parse($graph, $data, 'turtle', $baseUri);
22+
return $graph;
23+
}
1424
}

tests/GlobalConfigTest.php

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ public function testGetUiLanguageDropdown()
116116
$this->assertEquals(true, $this->config->getUiLanguageDropdown());
117117
}
118118

119+
public function testGetGlobalPlugins()
120+
{
121+
$this->assertEquals(["alpha", "Bravo", "charlie"], $this->config->getGlobalPlugins());
122+
}
123+
124+
// included from testconfig-included.ttl
125+
119126
public function testGetHoneypotEnabled()
120127
{
121128
$this->assertEquals(false, $this->config->getHoneypotEnabled());
@@ -126,17 +133,25 @@ public function testGetHoneypotTime()
126133
$this->assertEquals(2, $this->config->getHoneypotTime());
127134
}
128135

129-
public function testGetGlobalPlugins()
130-
{
131-
$this->assertEquals(["alpha", "Bravo", "charlie"], $this->config->getGlobalPlugins());
136+
public function testVocabularyExists() {
137+
$this->assertEquals(4, count($this->config->getGraph()->allOfType('skos:Concept')));
138+
}
139+
140+
// --- test inclusion from URL
141+
142+
public function testInclusionFromURL() {
143+
$conf = new GlobalConfig("/../tests/testconfig-include.ttl");
144+
$this->assertEquals(2, $conf->getHoneypotTime());
132145
}
133146

134147
// --- tests for the exception paths
135148

136149
public function testInitializeConfigWithoutGraph()
137150
{
138-
$this->expectOutputString('Error: config.ttl must have exactly one skosmos:Configuration');
139-
$conf = new GlobalConfig('/../tests/testconfig-nograph.ttl');
151+
$file = '/../tests/testconfig-nograph.ttl';
152+
$filepath = realpath( dirname(__FILE__) . $file );
153+
$this->expectOutputString("Error: $filepath must have exactly one skosmos:Configuration");
154+
$conf = new GlobalConfig($file);
140155
$this->assertNotNull($conf);
141156
}
142157

tests/init_fuseki.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,7 @@ for fn in ../test-vocab-data/*.ttl; do
3333
$(./bin/s-put http://localhost:13030/skosmos-test/data "http://www.skosmos.skos/$name/" "$fn")
3434
done
3535

36+
$(./bin/s-put http://localhost:13030/skosmos-test/data "http://skosmos.config/" "../testconfig-included.ttl")
37+
3638
cd ..
3739

tests/testconfig-include.ttl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@prefix skosmos: <http://purl.org/net/skosmos#> .
2+
@prefix : <http://base/#> .
3+
4+
:config a skosmos:Configuration ;
5+
6+
# include configuration from from URL
7+
skosmos:includeConfig <http://localhost:13030/skosmos-test/data?graph=http://skosmos.config/> .
8+

tests/testconfig-included.ttl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@prefix skosmos: <http://purl.org/net/skosmos#> .
2+
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
3+
4+
# This configuration is being included from file
5+
6+
<http://example.org/> a skosmos:Configuration ;
7+
8+
# whether to enable the spam honey pot or not, enabled by default
9+
skosmos:uiHoneypotEnabled false ;
10+
11+
# default time a user must wait before submitting a form
12+
skosmos:uiHoneypotTime 2 .
13+
14+
<whatever> a skos:Concept .

tests/testconfig.ttl

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,11 @@
5959
skosmos:feedbackEnvelopeSender "skosmos tests" ;
6060
# whether to display the ui language selection as a dropdown (useful for cases where there are more than 3 languages)
6161
skosmos:uiLanguageDropdown true ;
62-
# whether to enable the spam honey pot or not, enabled by default
63-
skosmos:uiHoneypotEnabled false ;
64-
# default time a user must wait before submitting a form
65-
skosmos:uiHoneypotTime 2 ;
6662
# plugins to activate for the whole installation (including all vocabularies)
67-
skosmos:globalPlugins ("alpha" "Bravo" "charlie") .
63+
skosmos:globalPlugins ("alpha" "Bravo" "charlie") ;
64+
65+
# include another config file
66+
skosmos:includeConfig <testconfig-included.ttl> .
6867

6968
# Skosmos vocabularies
7069

0 commit comments

Comments
 (0)