diff --git a/model/Concept.php b/model/Concept.php index 5ee2e54b5..3d7ff40f9 100644 --- a/model/Concept.php +++ b/model/Concept.php @@ -449,7 +449,18 @@ public function getMappingProperties(array $whitelist = null) continue; } } - $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $val, $this->resource, $prop, $this->clang)); + + // check if target vocabularly can be found by scheme + $targetVocab = $this->vocab; + foreach ($val->allResources("skos:inScheme") as $targetScheme) { + $schemeVocab = $this->model->getVocabularyByScheme($targetScheme->getUri()); + if ($schemeVocab) { + $targetVocab = $schemeVocab; + break; + } + } + + $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $targetVocab, $val, $this->resource, $prop, $this->clang)); } } } diff --git a/model/GlobalConfig.php b/model/GlobalConfig.php index 7a1719262..2a88b1454 100644 --- a/model/GlobalConfig.php +++ b/model/GlobalConfig.php @@ -360,4 +360,12 @@ public function getCollationEnabled() { return $this->getBoolean('skosmos:sparqlCollationEnabled', FALSE); } + + /** + * @return boolean + */ + public function getDefaultGraphOnly() + { + return $this->getBoolean('skosmos:defaultGraphOnly', FALSE); + } } diff --git a/model/Model.php b/model/Model.php index e6f58f3a2..d2914db49 100644 --- a/model/Model.php +++ b/model/Model.php @@ -13,6 +13,8 @@ class Model private $vocabsByGraph = null; /** cache for Vocabulary objects */ private $vocabsByUriSpace = null; + /** cache for Vocabulary objects */ + private $vocabsByScheme = null; /** how long to store retrieved URI information in APC cache */ const URI_FETCH_TTL = 86400; // 1 day private $globalConfig; @@ -449,6 +451,29 @@ public function getVocabularyByGraph($graph, $endpoint = null) } + /** + * Return the vocabulary that has the given scheme. + * + * @param $graph string graph URI + * @return Vocabulary vocabulary for this scheme, or null if not found + */ + public function getVocabularyByScheme($scheme) + { + if ($this->vocabsByScheme === null) { // initialize cache + $this->vocabsByScheme = array(); + foreach ($this->getVocabularies() as $voc) { + $key = $voc->getDefaultConceptScheme(); + $this->vocabsByScheme[$key] = $voc; + } + } + + if (array_key_exists($scheme, $this->vocabsByScheme)) { + return $this->vocabsByScheme[$scheme]; + } else { + return null; + } + } + /** * When multiple vocabularies share same URI namespace, return the * vocabulary in which the URI is actually defined (has a label). @@ -585,11 +610,11 @@ public function getResourceFromUri($uri) * @param string $endpoint url address of endpoint * @param string|null $graph uri for the target graph. */ - public function getSparqlImplementation($dialect, $endpoint, $graph) + public function getSparqlImplementation($dialect, $endpoint, $graph, $conceptSchemes=null) { $classname = $dialect . "Sparql"; - return new $classname($endpoint, $graph, $this); + return new $classname($endpoint, $graph, $this, $conceptSchemes); } /** @@ -597,7 +622,8 @@ public function getSparqlImplementation($dialect, $endpoint, $graph) */ public function getDefaultSparql() { - return $this->getSparqlImplementation($this->getConfig()->getDefaultSparqlDialect(), $this->getConfig()->getDefaultEndpoint(), '?graph'); + $graph = !$this->getConfig()->getDefaultGraphOnly() ? '?graph' : null; + return $this->getSparqlImplementation($this->getConfig()->getDefaultSparqlDialect(), $this->getConfig()->getDefaultEndpoint(), $graph); } } diff --git a/model/Vocabulary.php b/model/Vocabulary.php index 44716969e..0d64050e1 100644 --- a/model/Vocabulary.php +++ b/model/Vocabulary.php @@ -58,8 +58,9 @@ public function getSparql() $endpoint = $this->getEndpoint(); $graph = $this->getGraph(); $dialect = $this->config->getSparqlDialect() ?? $this->model->getConfig()->getDefaultSparqlDialect(); + $conceptSchemes = $this->config->getConceptSchemeURIs() ?? null; - return $this->model->getSparqlImplementation($dialect, $endpoint, $graph); + return $this->model->getSparqlImplementation($dialect, $endpoint, $graph, $conceptSchemes); } /** diff --git a/model/VocabularyConfig.php b/model/VocabularyConfig.php index 117533758..9611f930e 100644 --- a/model/VocabularyConfig.php +++ b/model/VocabularyConfig.php @@ -307,6 +307,14 @@ public function getDataURLs() return $ret; } + /** + * Returns an array of concept schemes to restrict to using skos:inScheme. + */ + public function getConceptSchemeURIs() + { + return $this->getResources("skosmos:conceptSchemes"); + } + /** * Returns the main Concept Scheme URI of that Vocabulary, * or null if not set. diff --git a/model/sparql/GenericSparql.php b/model/sparql/GenericSparql.php index 6605102aa..9f4477203 100644 --- a/model/sparql/GenericSparql.php +++ b/model/sparql/GenericSparql.php @@ -25,6 +25,11 @@ class GenericSparql { */ protected $model; + protected $conceptSchemes; + + const SKOS_CONCEPT = 'http://www.w3.org/2004/02/skos/core#Concept'; + const SKOS_IN_SCHEME = 'http://www.w3.org/2004/02/skos/core#inScheme'; + /** * Cache used to avoid expensive shorten() calls * @property array $qnamecache @@ -38,9 +43,10 @@ class GenericSparql { * to use the default graph, or NULL to not use a GRAPH clause. * @param object $model a Model instance. */ - public function __construct($endpoint, $graph, $model) { + public function __construct($endpoint, $graph, $model, $conceptSchemes=null) { $this->graph = $graph; $this->model = $model; + $this->conceptSchemes = $conceptSchemes; // create the EasyRDF SPARQL client instance to use $this->initializeHttpClient(); @@ -178,21 +184,48 @@ private function shortenUri($uri) { */ private function generateCountConceptsQuery($array, $group) { $fcl = $this->generateFromClause(); + $conceptInScheme = ''; + $conc_concept = ''; + $conc_member = ''; + if (!empty($this->conceptSchemes)) { + $conceptInScheme = $this->formatValuesGraphPattern('?concept', self::SKOS_IN_SCHEME, $this->conceptSchemes, 'uri'); + $conc_concept = "bind(?concept as ?conc)"; + $conc_member = "?conc skos:member+ ?concept ."; + } $optional = $array ? "(<$array>) " : ''; $optional .= $group ? "(<$group>)" : ''; $query = <<type->getUri(); $ret[$typeURI]['type'] = $typeURI; + $ret[$typeURI]['count'] = $row->c->getValue(); + $ret[$typeURI]['deprecatedCount'] = $row->deprcount->getValue(); - if (!isset($row->typelabel)) { - $ret[$typeURI]['count'] = $row->c->getValue(); - $ret[$typeURI]['deprecatedCount'] = $row->deprcount->getValue(); - } - - if (isset($row->typelabel) && $row->typelabel->getLang() === $lang) { + if (isset($row->typelabel) && ($row->typelabel->getLang() === $lang || $row->typelabel->getLang() === null)) { $ret[$typeURI]['label'] = $row->typelabel->getValue(); } - } return $ret; } @@ -245,7 +274,7 @@ public function countConcepts($lang = null, $array = null, $group = null) { */ private function generateCountLangConceptsQuery($langs, $classes, $props) { $gcl = $this->graphClause; - $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept'); + $classes = ($classes) ? $classes : array(self::SKOS_CONCEPT); $quote_string = function($val) { return "'$val'"; }; $quoted_values = array_map($quote_string, $langs); @@ -253,6 +282,7 @@ private function generateCountLangConceptsQuery($langs, $classes, $props) { $values = $this->formatValues('?type', $classes, 'uri'); $valuesProp = $this->formatValues('?prop', $props, null); + $conc_inScheme = $this->formatValuesGraphPattern('?conc', self::SKOS_IN_SCHEME, $this->conceptSchemes, 'uri'); $query = << ?v . + * + * If there is only a single value then this is simplified to: + * ?var . + * + * @param string $varname variable name, e.g. "?uri" + * @param string $pred predicate URI + * @param array $values the values + * @param string $type type of values: "uri", "literal" or null (determines quoting style) + */ + protected function formatValuesGraphPattern($varname, $pred, $values, $type = null) { + $gp = ''; + if (!empty($values)) { + if ($pred != 'a') { + $pred = "<$pred>"; + } + + if (sizeof($values) == 1) { + $val = $values[0]; + if ($type == 'uri') { + $val = "<$val>"; + } + + if ($type == 'literal') { + $val = "'$val'"; + } + $gp .= "$varname $pred $val ."; + } else { + $valVar = $varname . '_value'; + $gp .= $this->formatValues($valVar, $values, $type); + $gp .= "\n$varname $pred $valVar ."; + } + } + return $gp; + } + /** * Filters multiple instances of the same vocabulary from the input array. * @param \Vocabulary[]|null $vocabs array of Vocabulary objects @@ -399,6 +469,7 @@ private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) { ?o skos:notation ?on . ?o ?oprop ?oval . ?o ?xlprop ?xlval . + ?o skos:inScheme ?oscheme . ?dt rdfs:label ?dtlabel . ?directgroup skos:member ?uri . ?parent skos:member ?group . @@ -467,6 +538,8 @@ private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) { UNION { ?o a skosxl:Label . ?o ?xlprop ?xlval } + UNION + { ?o skos:inScheme ?oscheme . } } $optional } } @@ -643,9 +716,14 @@ public function queryConceptScheme($conceptscheme) { */ private function generateQueryConceptSchemesQuery($lang) { $fcl = $this->generateFromClause(); + $csValues = ''; + if (!empty($this->conceptSchemes)) { + $csValues .= $this->formatValues('?cs', $this->conceptSchemes, 'uri'); + } $query = <<formatValuesGraphPattern('?s', self::SKOS_IN_SCHEME, $this->conceptSchemes, 'uri'); // extra conditions for label language, if specified $labelcondLabel = ($lang) ? "LANGMATCHES(lang(?label), '$lang')" : "lang(?match) = '' || LANGMATCHES(lang(?label), lang(?match))"; @@ -965,12 +1044,15 @@ protected function generateConceptSearchQueryInner($term, $lang, $searchLang, $p { $valuesProp VALUES (?prop ?pri ?langParam) { (skos:prefLabel 1 $langClause) (skos:altLabel 3 $langClause) (skos:notation 5 '') (skos:hiddenLabel 7 $langClause)} + ?s ?prop ?match . + $s_inScheme $textcond - ?s ?prop ?match } + } OPTIONAL { ?s skos:prefLabel ?label . FILTER ($labelcondLabel) - } $labelcondFallback + } + $labelcondFallback BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri) BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr) OPTIONAL { ?s skos:notation ?notation } @@ -1079,7 +1161,8 @@ protected function generateConceptSearchQuery($fields, $unique, $params, $showDe } $labelpriority $formattedtype - { $pgcond + { + $pgcond ?s a ?type . $extrafields $schemecond } @@ -1254,8 +1337,9 @@ private function formatFilterConditions($letter, $lang) { */ protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated = false, $qualifier = null) { $gcl = $this->graphClause; - $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept'); - $values = $this->formatValues('?type', $classes, 'uri'); + $classes = ($classes) ? $classes : array(self::SKOS_CONCEPT); + $s_type = $this->formatValuesGraphPattern('?s', 'a', $classes, 'uri'); + $s_inScheme = $this->formatValuesGraphPattern('?s', self::SKOS_IN_SCHEME, $this->conceptSchemes, 'uri'); $limitandoffset = $this->formatLimitAndOffset($limit, $offset); $conditions = $this->formatFilterConditions($letter, $lang); $filtercondLabel = $conditions['filterpref']; @@ -1269,6 +1353,8 @@ protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset SELECT DISTINCT ?s ?label ?alabel ?qualifier WHERE { $gcl { + $s_type + $s_inScheme { ?s skos:prefLabel ?label . FILTER ( @@ -1288,10 +1374,8 @@ protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset FILTER (langMatches(lang(?label), '$lang')) } } - ?s a ?type . $qualifierClause $filterDeprecated - $values } } ORDER BY LCASE(STR(COALESCE(?alabel, ?label))) STR(?s) LCASE(STR(?qualifier)) $limitandoffset @@ -1369,15 +1453,16 @@ public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset */ private function generateFirstCharactersQuery($lang, $classes) { $gcl = $this->graphClause; - $classes = (isset($classes) && sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept'); - $values = $this->formatValues('?type', $classes, 'uri'); + $classes = (isset($classes) && sizeof($classes) > 0) ? $classes : array(self::SKOS_CONCEPT); + $c_type = $this->formatValuesGraphPattern('?c', 'a', $classes, 'uri'); + $c_inScheme = $this->formatValuesGraphPattern('?c', self::SKOS_IN_SCHEME, $this->conceptSchemes, 'uri'); $query = <<formatValues('?topuri', $conceptSchemes, 'uri'); + $values = $this->formatValues('?topuri', $topConceptSchemes, 'uri'); + $top_inScheme = $this->formatValuesGraphPattern('?top', self::SKOS_IN_SCHEME, $this->conceptSchemes, 'uri'); $fcl = $this->generateFromClause(); $query = <<query($query); @@ -2255,6 +2342,7 @@ public function listConceptGroupContents($groupClass, $group, $lang,$showDepreca */ private function generateChangeListQuery($prop, $lang, $offset, $limit=200, $showDeprecated=false) { $fcl = $this->generateFromClause(); + $concept_inScheme = $this->formatValuesGraphPattern('?concept', self::SKOS_IN_SCHEME, $this->conceptSchemes, 'uri'); $offset = ($offset) ? 'OFFSET ' . $offset : ''; //Additional clauses when deprecated concepts need to be included in the results @@ -2276,6 +2364,7 @@ private function generateChangeListQuery($prop, $lang, $offset, $limit=200, $sho WHERE { ?concept a skos:Concept ; skos:prefLabel ?label . + $concept_inScheme FILTER (langMatches(lang(?label), '$lang')) { ?concept $prop ?date .