Commit ef12178b authored by Matthias Tietz's avatar Matthias Tietz
Browse files

Merge branch 'live-tisaf-de'

parents a713cd10 8f902c88
......@@ -3,6 +3,7 @@ hs_err*.log
build
vendor/*
!vendor/fastselect
!vendor/gdpr-cookie-notice
report
components
plugins/*
......
......@@ -5,7 +5,7 @@ RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^entity$ entity.php [L]
RewriteRule ^rest/v1/swagger.json$ swagger.json [L]
RewriteRule ^rest/v1/openapi.json$ openapi.json [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^rest/v1/(.*)$ rest.php/$1 [L]
......
......@@ -4,7 +4,7 @@
### Installation/Setup
0. install apache2, clone this repo into /var/www/html
1. change [config.ttl](config.ttl), [resource/js/tags.js](./resource/js/tags.js) constants to your productive host name etc.
1. change [config.ttl](config.ttl), [resource/js/tisaf.js](resource/js/tisaf.js) constants to your productive host name etc.
2. follow https://github.com/NatLibFi/Skosmos/wiki/InstallTutorial
(hints: on debian 10, you need to install openjdk-11 instead of 8 seems not to be available out-of-the-box, also `apt install php libapache2-mod-php` and `apt install php7.3-xsl php7.3-intl php7.3-mbstring` need to be used - adapt 7.2 with your php version)
......@@ -14,8 +14,8 @@ To make that double sure, you can start Fuseki with `./fuseki-server --localhost
An even better solution would be to "hide" the host port 3030 completely to the outside world, e.g. with proxying or something similar.
### Import TISAF-specific terminologies
- optional: import [tisaf terminology](./terminologies/tisaf.ttl)
- !!! [tisaf tags terminology](./terminologies/tisaf-tags-v4-skosified.ttl) -> needed for tagging of terminologies (and optionally also concepts - TODO)
- optional: import [tisaf terminology](resource/terminologies/tisaf.ttl)
- !!! [tisaf tags terminology](resource/terminologies/tisaf-tags-v4-skosified.ttl) -> needed for tagging of terminologies (and optionally also concepts - TODO)
The **tags terminology** is a hard depenecy of the TISAF service, and **should** be imported with terminology shortName (aka Vocabulary-ID) = `tags`.
You need to use the cmd line tools for a new TISAF installation (e.g. `/opt/fuseki/bin/s-put http://localhost:3030/skosmos/data http://www.tisaf.de/tags/ tisaf-tags-v6-skosified.ttl`) or you can use the own TISAF importer (see below).
......@@ -80,7 +80,7 @@ E.g.:
upload_max_filesize = 25M # original: 2M
post_max_size = 30M # post_max should be a bit bigger; original: 8M
```
Sync the values between the frontend part in [tags.js](./resource/js/tags.js) and your php.ini.
Sync the values between the frontend part in [tisaf.js](resource/js/tisaf.js) and your php.ini.
And do not forget to: `sudo service apache2 restart`.
......
......@@ -12,7 +12,7 @@
@prefix isothes: <http://purl.org/iso25964/skos-thes#> .
@prefix mdrtype: <http://publications.europa.eu/resource/authority/dataset-type/> .
@prefix : <#> .
@prefix tisaf: <http://vsrstud01.informatik.tu-chemnitz.de/tisaf/> .
@prefix tisaf: <http://www.tisaf.de/> .
# Skosmos main configuration
......@@ -37,11 +37,12 @@
# customize the base element. Set this if the automatic base url detection doesn't work. For example setups behind a proxy.
# skosmos:baseHref "http://localhost/Skosmos/" ;
# interface languages available, and the corresponding system locales
## this also sets the priority of the ui language, in case no lang is set in cookie (e.g. when a client visits the site for the 1st time)
skosmos:languages (
[ rdfs:label "fi" ; rdf:value "fi_FI.utf8" ]
[ rdfs:label "sv" ; rdf:value "sv_SE.utf8" ]
[ rdfs:label "en" ; rdf:value "en_GB.utf8" ]
[ rdfs:label "de" ; rdf:value "de_DE.utf8" ]
# [ rdfs:label "fi" ; rdf:value "fi_FI.utf8" ]
# [ rdfs:label "sv" ; rdf:value "sv_SE.utf8" ]
[ rdfs:label "de" ; rdf:value "de_DE.utf8" ]
[ rdfs:label "en" ; rdf:value "en_GB.utf8" ]
) ;
# how many results (maximum) to load at a time on the search results page
skosmos:searchResultsSize 20 ;
......@@ -74,14 +75,16 @@
####################################################################################################################
####################################################################################################################
# tisaf specific configuration:
tisaf:fullSystemHost "http://localhost/tisaf/";
tisaf:fullSystemHost "http://www.tisaf.de/";
## graph name of the terminology used to tag data in TISAF
tisaf:tagsGraphName "http://example.com/tisaf/tags/6" ;
tisaf:tagsGraphName "http://www.tisaf.de/tags/" ;
## rdf @prefix tisaf, tt (tisaf tags)
tisaf:tisafPrefix "http://vsrstud01.informatik.tu-chemnitz.de/tisaf/" ;
tisaf:tisafTTPrefix "http://vsrstud01.informatik.tu-chemnitz.de/tisaf/tags/" ;
tisaf:tisafPrefix "http://www.tisaf.de/" ;
tisaf:tisafTTPrefix "http://www.tisaf.de/tags/" ;
## tisaf:uri property name
tisaf:uri "tisaf:uri"
tisaf:uri "tisaf:uri" ;
## special graph to store missing terms for evaulation 2
tisaf:missingTermsTwo "http://tisaf.de/missing/terms/graph/"
.
####################################################################################################################
####################################################################################################################
......@@ -101,9 +104,21 @@
# skosmos:loadExternalResources "true" .
# added manually
:tags a skosmos:Vocabulary, void:Dataset ;
dc:title "TISAF tags"@en ;
skosmos:shortName "tisaftags";
dc:subject :cat_meta ;
void:uriSpace "http://www.tisaf.de/tags/";
skosmos:language "en", "de";
skosmos:defaultLanguage "en";
void:sparqlEndpoint <http://localhost:3030/skosmos/sparql> ;
skosmos:sparqlGraph <http://www.tisaf.de/tags/> .
:stw a skosmos:Vocabulary, void:Dataset ;
dc:title "STW Thesaurus for Economics"@en ;
skosmos:shortName "STW";
skosmos:shortName "stw";
dc:subject :cat_general ;
void:uriSpace "http://zbw.eu/stw/";
skosmos:language "en", "de";
......@@ -130,8 +145,109 @@
:cat_meta a skos:Concept ;
skos:topConceptOf :categories ;
skos:inScheme :categories ;
skos:prefLabel "Meta"@en
skos:prefLabel "Meta"@en, "Meta"@de
.
# added by /import
:unescothes2 a skosmos:Vocabulary, void:Dataset ;
dc:title "UNESCO Thesaurus"@en ;
skosmos:shortName "unescothes2";
dc:subject :cat_general ;
void:uriSpace "https://skos.um.es/unescothes/2";
skosmos:language "en";
skosmos:defaultLanguage "en";
void:sparqlEndpoint <http://localhost:3030/skosmos/sparql> ;
skosmos:groupClass isothes:ConceptGroup ;
skosmos:sparqlGraph <https://skos.um.es/unescothes/2> .
:missingterms2 a skosmos:Vocabulary, void:Dataset ;
dc:title "Missing terms (Evaluation #2)"@en ;
skosmos:shortName "missingterms2";
dc:subject :cat_meta ;
void:uriSpace "http://tisaf.de/missing/terms/graph/";
skosmos:language "en";
skosmos:defaultLanguage "en";
void:sparqlEndpoint <http://localhost:3030/skosmos/sparql> ;
skosmos:sparqlGraph <http://tisaf.de/missing/terms/graph/> .
:IDOCOVID19 a skosmos:Vocabulary, void:Dataset ;
dc:title "The COVID-19 Infectious Disease Ontology"@en ;
skosmos:shortName "IDOCOVID19";
dc:subject :cat_general ;
void:uriSpace "http://bioportal.bioontology.org/ontologies/IDO-COVID-19";
skosmos:language "en";
skosmos:defaultLanguage "en";
void:sparqlEndpoint <http://localhost:3030/skosmos/sparql> ;
skosmos:sparqlGraph <http://bioportal.bioontology.org/ontologies/IDO-COVID-19> .
:ERO a skosmos:Vocabulary, void:Dataset ;
dc:title "Eagle-I Research Resource Ontology"@en ;
skosmos:shortName "ERO";
dc:subject :cat_general ;
void:uriSpace "http://bioportal.bioontology.org/ontologies/ERO";
skosmos:language "en";
skosmos:defaultLanguage "en";
void:sparqlEndpoint <http://localhost:3030/skosmos/sparql> ;
skosmos:sparqlGraph <http://bioportal.bioontology.org/ontologies/ERO> .
:rdflicense a skosmos:Vocabulary, void:Dataset ;
dc:title "RDFLicense dataset"@en ;
skosmos:shortName "rdflicense";
dc:subject :cat_general ;
void:uriSpace "http://rdflicense.appspot.com/";
skosmos:language "en";
skosmos:defaultLanguage "en";
void:sparqlEndpoint <http://localhost:3030/skosmos/sparql> ;
skosmos:sparqlGraph <http://rdflicense.appspot.com/> .
:ebuff2 a skosmos:Vocabulary, void:Dataset ;
dc:title "EBU SKOS Classification Scheme for File Format Type Codes"@en ;
skosmos:shortName "ebuff2";
dc:subject :cat_general ;
void:uriSpace "https://www.ebu.ch/metadata/ontologies/skos/ff2";
skosmos:language "en";
skosmos:defaultLanguage "en";
void:sparqlEndpoint <http://localhost:3030/skosmos/sparql> ;
skosmos:sparqlGraph <https://www.ebu.ch/metadata/ontologies/skos/ff2> .
:bibo1 a skosmos:Vocabulary, void:Dataset ;
dc:title "The Bibliographic Ontology"@en ;
skosmos:shortName "bibo1";
dc:subject :cat_general ;
void:uriSpace "https://github.com/structureddynamics/Bibliographic-Ontology-BIBO/1";
skosmos:language "en";
skosmos:defaultLanguage "en";
void:sparqlEndpoint <http://localhost:3030/skosmos/sparql> ;
skosmos:sparqlGraph <https://github.com/structureddynamics/Bibliographic-Ontology-BIBO/1> .
:esco a skosmos:Vocabulary, void:Dataset ;
dc:title "The ESCO ontology"@en ;
skosmos:shortName "esco";
dc:subject :cat_general ;
void:uriSpace "http://data.europa.eu/esco/model";
skosmos:language "en";
skosmos:defaultLanguage "en";
void:sparqlEndpoint <http://localhost:3030/skosmos/sparql> ;
skosmos:sparqlGraph <http://data.europa.eu/esco/model> .
:juso a skosmos:Vocabulary, void:Dataset ;
dc:title "Juso Ontology"@en ;
skosmos:shortName "juso";
dc:subject :cat_general ;
void:uriSpace "http://rdfs.co/juso/";
skosmos:language "en";
skosmos:defaultLanguage "en";
void:sparqlEndpoint <http://localhost:3030/skosmos/sparql> ;
skosmos:sparqlGraph <http://rdfs.co/juso/> .
:obi a skosmos:Vocabulary, void:Dataset ;
dc:title "Ontology for Biomedical Investigations"@en ;
skosmos:shortName "obi";
dc:subject :cat_general ;
void:uriSpace "https://bioportal.bioontology.org/ontologies/OBI";
skosmos:language "en";
skosmos:defaultLanguage "en";
void:sparqlEndpoint <http://localhost:3030/skosmos/sparql> ;
skosmos:sparqlGraph <https://bioportal.bioontology.org/ontologies/OBI> .
......@@ -95,6 +95,20 @@ EOD;
return $result;
}
public function getTisafUri($graphName, $subjectUri)
{
$tisafUriProperty = $this->model->getConfig()->getTisafPrefix() . "uri";
$queryString = <<<EOD
SELECT DISTINCT ?tisafUri
FROM <$graphName>
WHERE {
<$subjectUri> <$tisafUriProperty> ?tisafUri .
}
EOD;
$result = $this->model->getDefaultSparql()->execute($queryString);
return $result;
}
public function getAllTags() {
$TAGS_GRAPH = $this->model->getConfig()->getTisafTagsGraphName();
$queryString = <<<EOD
......
......@@ -3,9 +3,24 @@
use EasyRdf\GraphStore;
use EasyRDF\RdfNamespace;
use EasyRdf\Resource;
use EasyRdf\Literal;
use EasyRdf\Sparql\Result;
require_once 'controller/TisafUriHelper.php';
/**
* @OA\Schema(
* schema="format",
* type="array",
* description="Format to serialize a RDF resource",
* default="application/rdf+xml",
* @OA\Items(
* type="string",
* enum = {"application/rdf+xml", "text/turtle", "application/ld+json", "application/json"},
* )
* )
*/
/**
* RestController is responsible for handling all the requests directed to the /rest address.
*/
......@@ -77,6 +92,12 @@ class RestController extends Controller
* @param Request $request
* @param bool $useTisafUris use custom TISAF uris, e.g. for /terminologies rest api call.
* @return void
*
* @OA\Get(
* path="/terminologies",
* @OA\Response(response="200", description="List of all terminologies")
* )
*
*/
public function vocabularies(Request $request, bool $useTisafUris = false)
{
......@@ -115,13 +136,13 @@ class RestController extends Controller
'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
'onki' => 'http://schema.onki.fi/onki#',
'title' => array('@id' => 'rdfs:label', '@language' => $request->getLang()),
'vocabularies' => 'onki:hasVocabulary',
'terminologies' => 'onki:hasVocabulary',
'id' => 'onki:vocabularyIdentifier',
'uri' => '@id',
'@base' => $basePath,
),
'uri' => '',
'vocabularies' => $results,
'terminologies' => $results,
);
return $this->returnJson($ret);
......@@ -168,7 +189,7 @@ class RestController extends Controller
// build usable redirect uri
$res['vocabRef'] = $fullSystemHost . $vocab->getId();
$outProps = ['skos:altLabel', 'skos:narrower', 'skos:related', 'skos:broader'];
$outProps = ['skos:altLabel', 'skos:narrower', 'skos:related', 'skos:broader', 'skos:definition'];
foreach ($outProps as $prop) {
if (key_exists($prop, $props)) {
$res[$prop] = self::getValueFromConceptProp($props[$prop], $res);
......@@ -228,7 +249,7 @@ class RestController extends Controller
$ret = array(
'@context' => $context,
'uri' => '',
'results' => $results,
'results' => $results
);
if (isset($results[0]['prefLabels'])) {
......@@ -256,7 +277,14 @@ class RestController extends Controller
$tisafUri = $concept->getTisafUri();
}
// add data for property
$array = ['label' => $value->getLabel()->getValue(), 'uri' =>
$label = '';
if ($value->getLabel() instanceof Literal) {
$label = $value->getLabel()->getValue();
} elseif (is_string($value->getLabel())) {
$label = $value->getLabel();
error_log($label);
}
$array = ['label' => $label, 'uri' =>
// TODO: still legacy link!
$this->model->getConfig()->getFullSystemHost() . $res['vocab'] . '/' . $res['lang']
. '/page/?uri=' . urlencode($value->getUri())];
......@@ -272,6 +300,56 @@ class RestController extends Controller
/**
* Performs the search function calls. And wraps the result in a json-ld object.
* @param Request $request
*
* @OA\Get(
* path="/search",
* @OA\Response(response="200", description="Search results list"),
* @OA\Parameter(
* name="query",
* in="query",
* required=true,
* description="Search term. It's recommended to use wildcards like query* or *query*",
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="tags",
* in="query",
* description="Comma separated list of tags (conceptIds)",
* @OA\Schema(
* type="string",
* example="Biology,Economics,device"
* )
* ),
* @OA\Parameter(
* name="tagsjoin",
* in="query",
* description="Tags combination style",
* @OA\Schema(
* type="array",
* default="any",
* @OA\Items(
* type="string",
* enum = {"any", "exact"}
* )
* )
* ),
* @OA\Parameter(
* name="terminologies",
* in="query",
* description="Comma separated list of terminology IDs to limit search request (filter)",
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="maxhits",
* in="query",
* @OA\Schema(type="integer")
* ),
* @OA\Parameter(
* name="offset",
* in="query",
* @OA\Schema(type="integer")
* )
* )
*/
public function search($request)
{
......@@ -308,8 +386,7 @@ class RestController extends Controller
$parameters = $this->constructSearchParameters($request);
// override vocabs here, if specified by tag input
// TODO: what if vocab & tags is set??????!?!?!?!?!??!?!?!?!?!?!?!?!?!?!?
// intersection?!?!?!?!?
// if vocab & tags is set: intersection OR prioritization
if (!is_null($vocabsFromTags)) {
$parameters->setVocabularies($vocabsFromTags);
}
......@@ -335,7 +412,7 @@ class RestController extends Controller
$tagsForVocab = self::getAllTagsForVocabulary($vocab, true);
// any/exact match
if ($tagsJoin == self::TAGSJOIN_ANY && array_intersect($tagsForVocab, $fullFragments)) {
// first match found, can add vocab and skip loop iteration
// match found, can add vocab and skip loop iteration
$vocabsToSearch[] = $vocab;
continue;
} elseif ($tagsJoin == self::TAGSJOIN_EXACT && sizeof($tagsForVocab) == sizeof($fullFragments) &&
......@@ -356,6 +433,19 @@ class RestController extends Controller
/**
* Loads the vocabulary metadata. And wraps the result in a json-ld object.
* @param Request $request
*
* @OA\Get(
* path="/{terminologyId}",
* @OA\Parameter(
* name="terminologyId",
* in="path",
* required=true,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Response(response="200", description="Terminology metadata")
* )
*/
public function vocabularyInformation($request)
{
......@@ -369,6 +459,8 @@ class RestController extends Controller
foreach ($vocab->getConceptSchemes($request->getLang()) as $uri => $csdata) {
$csdata['uri'] = $uri;
$csdata['type'] = 'skos:ConceptScheme';
$fullSystemHost = $this->model->getConfig()->getFullSystemHost();
$csdata["tisafUri"] = $fullSystemHost . $vocab->getId();
$conceptschemes[] = $csdata;
}
......@@ -691,6 +783,17 @@ class RestController extends Controller
* Queries the top concepts of a vocabulary and wraps the results in a json-ld object.
* @param Request $request
* @return object json-ld object
*
* @OA\Get(
* path="/{terminologyId}/topConcepts",
* @OA\Response(response="200", description="Terminology top concepts as JSON-LD"),
* @OA\Parameter(
* name="terminologyId",
* in="path",
* required=true,
* @OA\Schema(type="string")
* )
* )
*/
public function topConcepts($request)
{
......@@ -706,6 +809,15 @@ class RestController extends Controller
/* encode the results in a JSON-LD compatible array */
$topconcepts = $vocab->getTopConcepts($scheme, $request->getLang());
// tisaf:uri
$graphName = $request->getVocab()->getGraph();
foreach ($topconcepts as &$topconcept) {
$tisafUri = $this->getTisafUri($graphName, $topconcept["uri"]);
if (!empty($tisafUri) && $tisafUri[0]->tisafUri instanceof Resource) {
$topconcept["tisafUri"] = $tisafUri[0]->tisafUri->getUri();
}
}
$ret = array_merge_recursive($this->context, array(
'@context' => array('onki' => 'http://schema.onki.fi/onki#', 'topconcepts' => 'skos:hasTopConcept', 'notation' => 'skos:notation', 'label' => 'skos:prefLabel', '@language' => $request->getLang()),
'uri' => $scheme,
......@@ -811,9 +923,37 @@ class RestController extends Controller
return $this->returnDataResults($results, $format);
}
// TODO: potentially heavy load operation - optimization possible?
/**
* Download the terminology as RDF file
*
* @param Request $request
*
* @OA\Get(
* path="/{terminologyId}/export",
* @OA\Response(response="200", description="Terminology as RDF file"),
* @OA\Parameter(
* name="terminologyId",
* in="path",
* required=true,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="format",
* in="query",
* @OA\Schema(ref="#/components/schemas/format")
* ),
* @OA\Parameter(
* name="Accept",
* in="header",
* @OA\Schema(ref="#/components/schemas/format")
* )
* )
*/
public function dataTerminologyDownload(Request $request)
{
// TODO: potentially heavy load operation - optimization possible?
$vocab = $request->getVocab();
// '+' sign in format parameter must be encoded: application/ld+json, application/rdf+xml => application/rdf%2Bxml
$format = $this->negotiateFormat(explode(' ', self::TISAF_SUPPORTED_FORMATS), $request->getServerConstant('HTTP_ACCEPT'), $request->getQueryParam('format'));
......@@ -1055,10 +1195,54 @@ class RestController extends Controller
return null;
}
$narrowers = $request->getVocab()->getConceptNarrowers($request->getUri(), $request->getLang());
if ($narrowers === null) {
// if uri owl:sameAs object -> get info from object too in result!
$uri = $request->getUri();
$vocab = $request->getVocab();
$concepts = $vocab->getConceptInfo($uri, $request->getContentLang());
$narrowersTwin = [];
if (!empty($concepts) && $concepts[0] && $concepts[0]->getSameAs() instanceof Resource) {
$tisafUriValue = $concepts[0]->getSameAs()->getUri();
$tisaf = $this->model->getConfig()->getTisafPrefix();
$tisafUri = $tisaf . "uri";
// search for concept with ?s tisaf:uri tisafUriValue
foreach ($this->graphs(false) as $graphName) {
$queryString = <<<EOD
SELECT DISTINCT ?s
FROM <$graphName>
WHERE{
?s <$tisafUri> <$tisafUriValue>
}
EOD;
$result = $request->getVocab()->getSparql()->execute($queryString);
if ($result instanceof Result && $result->count() == 1) {
// TODO: additional check: check if graphName -> vocabId -> tisafUriValue.contains(vocabId)
$twinUri = $result[0]->s->getUri();
$twinVocab = $this->model->getVocabularyByGraph($graphName);
$narrowersTwin = $twinVocab->getConceptNarrowers($twinUri, $request->getLang());
break;
}
}
}
// add to narrowers array.
$narrowers = array_merge($narrowers, $narrowersTwin);
if ($narrowers === null) {
return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
}
$ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $narrowers, "narrower", "skos:narrower");
// add 2 "parents" uris
$parentUris[] = ["terminologyId" => $request->getVocab()->getId(), "conceptUri" => $request->getURI()];
if(!empty($twinUri)) {
$parentUris[] = ["terminologyId" => $twinVocab->getId(), "conceptUri" => $twinUri];
}
$ret["parentUris"] = $parentUris;
return $this->returnJson($ret);
}
......@@ -1306,8 +1490,70 @@ class RestController extends Controller
/**
* Imports a new terminology including meta data and tags.
*
* @OA\Post(
* path="/import",
* summary="Import a new terminology with form data",
* @OA\Response(
* response=201,
* description="Terminology imported"
* ),
* @OA\Response(
* response=400,
* description="Invalid input"
* ),
* @OA\RequestBody(
* description="Terminology Form Data upload",
* @OA\MediaType(
* mediaType="multipart/form-data",
* @OA\Schema(
* type="object",
@OA\Property(
* property="terminologyID",
* description="Terminology shortname (identifier)",
* type="string",
* ),
* @OA\Property(
* property="source",
* description="Terminology source (URL or other unique identifier)",
* type="string",
* ),
* @OA\Property(
* property="title",
* description="Terminology title",
* type="string",
* ),
* @OA\Property(
* property="authors",
* description="Comma separated list of author names",
* type="string",
* ),