diff --git a/controller/Controller.php b/controller/Controller.php index e33514812..39a5c8f74 100644 --- a/controller/Controller.php +++ b/controller/Controller.php @@ -115,6 +115,56 @@ public function getBaseHref() return ($this->model->getConfig()->getBaseHref() !== null) ? $this->model->getConfig()->getBaseHref() : $this->guessBaseHref(); } + /** + * Creates Skosmos links from uris. + * @param string $uri + * @param Vocabulary $vocab + * @param string $lang + * @param string $type + * @param string $clang content + * @param string $term + * @throws Exception if the vocabulary ID is not found in configuration + * @return string containing the Skosmos link + */ + public function linkUrlFilter($uri, $vocab, $lang, $type = 'page', $clang = null, $term = null) { + // $vocab can either be null, a vocabulary id (string) or a Vocabulary object + if ($vocab === null) { + // target vocabulary is unknown, best bet is to link to the plain URI + return $uri; + } elseif (is_string($vocab)) { + $vocid = $vocab; + $vocab = $this->model->getVocabulary($vocid); + } else { + $vocid = $vocab->getId(); + } + + $params = array(); + if (isset($clang) && $clang !== $lang) { + $params['clang'] = $clang; + } + + if (isset($term)) { + $params['q'] = $term; + } + + // case 1: URI within vocabulary namespace: use only local name + $localname = $vocab->getLocalName($uri); + if ($localname !== $uri && $localname === urlencode($localname)) { + // check that the prefix stripping worked, and there are no problematic chars in localname + $paramstr = count($params) > 0 ? '?' . http_build_query($params) : ''; + if ($type && $type !== '' && $type !== 'vocab' && !($localname === '' && $type === 'page')) { + return "$vocid/$lang/$type/$localname" . $paramstr; + } + + return "$vocid/$lang/$localname" . $paramstr; + } + + // case 2: URI outside vocabulary namespace, or has problematic chars + // pass the full URI as parameter instead + $params['uri'] = $uri; + return "$vocid/$lang/$type/?" . http_build_query($params); + } + /** * Echos an error message when the request can't be fulfilled. * @param string $code diff --git a/controller/RestController.php b/controller/RestController.php index 63b130537..a0acb01c5 100644 --- a/controller/RestController.php +++ b/controller/RestController.php @@ -635,14 +635,15 @@ public function data($request) * Get the mappings associated with a concept, enriched with labels and notations. * Returns a JSKOS-compatible JSON object. * @param Request $request + * @throws Exception if the vocabulary ID is not found in configuration */ public function mappings(Request $request) { + $this->setLanguageProperties($request->getLang()); $vocab = $request->getVocab(); - if ($request->getUri()) { - $uri = $request->getUri(); - } else { + $uri = $request->getUri(); + if (!$uri) { return $this->returnError(400, 'Bad Request', "uri parameter missing"); } @@ -655,13 +656,19 @@ public function mappings(Request $request) $concept = $results[0]; - $ret = []; + $mappings = []; foreach ($concept->getMappingProperties() as $mappingProperty) { foreach ($mappingProperty->getValues() as $mappingPropertyValue) { - $ret[] = $mappingPropertyValue->asJskos($queryExVocabs); + $hrefLink = $this->linkUrlFilter($mappingPropertyValue->getUri(), $mappingPropertyValue->getExVocab(), $request->getLang(), 'page', $request->getContentLang()); + $mappings[] = $mappingPropertyValue->asJskos($queryExVocabs, $request->getLang(), $hrefLink); } } + $ret = array( + 'mappings' => $mappings, + 'graph' => $concept->dumpJsonLd() + ); + return $this->returnJson($ret); } diff --git a/controller/WebController.php b/controller/WebController.php index 73f8182e9..15a577ef2 100644 --- a/controller/WebController.php +++ b/controller/WebController.php @@ -78,54 +78,6 @@ public function __construct($model) $this->twig->addGlobal('honeypot', $this->honeypot); } - /** - * Creates Skosmos links from uris. - * @param string $uri - * @param Vocabulary $vocab - * @param string $lang - * @param string $type - * @param string $clang content - * @param string $term - */ - public function linkUrlFilter($uri, $vocab, $lang, $type = 'page', $clang = null, $term = null) { - // $vocab can either be null, a vocabulary id (string) or a Vocabulary object - if ($vocab === null) { - // target vocabulary is unknown, best bet is to link to the plain URI - return $uri; - } elseif (is_string($vocab)) { - $vocid = $vocab; - $vocab = $this->model->getVocabulary($vocid); - } else { - $vocid = $vocab->getId(); - } - - $params = array(); - if (isset($clang) && $clang !== $lang) { - $params['clang'] = $clang; - } - - if (isset($term)) { - $params['q'] = $term; - } - - // case 1: URI within vocabulary namespace: use only local name - $localname = $vocab->getLocalName($uri); - if ($localname !== $uri && $localname === urlencode($localname)) { - // check that the prefix stripping worked, and there are no problematic chars in localname - $paramstr = sizeof($params) > 0 ? '?' . http_build_query($params) : ''; - if ($type && $type !== '' && $type !== 'vocab' && !($localname === '' && $type === 'page')) { - return "$vocid/$lang/$type/$localname" . $paramstr; - } - - return "$vocid/$lang/$localname" . $paramstr; - } - - // case 2: URI outside vocabulary namespace, or has problematic chars - // pass the full URI as parameter instead - $params['uri'] = $uri; - return "$vocid/$lang/$type/?" . http_build_query($params); - } - /** * Guess the language of the user. Return a language string that is one * of the supported languages defined in the $LANGUAGES setting, e.g. "fi". diff --git a/model/Concept.php b/model/Concept.php index db4e045be..ea91738f3 100644 --- a/model/Concept.php +++ b/model/Concept.php @@ -391,6 +391,9 @@ private function addResourceReifications($sub, $pred, $obj, &$seen) } } + /** + * @return ConceptProperty[] + */ public function getMappingProperties(array $whitelist = null) { $ret = array(); diff --git a/model/ConceptMappingPropertyValue.php b/model/ConceptMappingPropertyValue.php index 1386331f2..3dbc11488 100644 --- a/model/ConceptMappingPropertyValue.php +++ b/model/ConceptMappingPropertyValue.php @@ -170,10 +170,20 @@ public function getNotation() * Return the mapping as a JSKOS-compatible array. * @return array */ - public function asJskos($queryExVocabs = true) + public function asJskos($queryExVocabs = true, $lang = null, $hrefLink = null) { + $propertyLabel = $this->getLabel($lang, $queryExVocabs); + $propertyLang = $lang; + if (!is_string($propertyLabel)) { + $propertyLang = $propertyLabel->getLang(); + $propertyLabel = $propertyLabel->getValue(); + } $ret = [ + // JSKOS + 'uri' => $this->source->getUri(), + 'notation' => $this->getNotation(), 'type' => [$this->type], + 'prefLabel' => $propertyLabel, 'from' => [ 'memberSet' => [ [ @@ -187,7 +197,13 @@ public function asJskos($queryExVocabs = true) 'uri' => (string) $this->getUri() ] ] - ] + ], + // EXTRA + 'description' => gettext($this->type . "_help"), // pop-up text + 'hrefLink' => $hrefLink, // link to resource as displayed in the UI + 'lang' => $propertyLang, // TBD: could it be part of the prefLabel? + 'vocabName' => (string) $this->getVocabName(), // vocabulary as displayed in the UI + 'typeLabel' => gettext($this->type), // a text used in the UI instead of, for example, skos:closeMatch ]; $fromScheme = $this->vocab->getDefaultConceptScheme(); diff --git a/resource/js/docready.js b/resource/js/docready.js index 58d2f46b6..5fa660ef3 100644 --- a/resource/js/docready.js +++ b/resource/js/docready.js @@ -251,6 +251,22 @@ $(function() { // DOCUMENT READY loading = setTimeout(function() { $('.concept-spinner').show() }, 500); } + function ajaxConceptMapping(data) { + // ajaxing the concept mapping properties on the concept page + var $conceptAppendix = $('.concept-appendix'); + if ($conceptAppendix.length) { + var concept = { + uri: $conceptAppendix.data('concept-uri'), + type: $conceptAppendix.data('concept-type') + }; + + // Defined in scripts.js. Will load the mapping properties via Ajax request to JSKOS REST service, and render them. + loadMappingProperties(concept, lang, clang, $conceptAppendix, data); + } else { + makeCallbacks(data); + } + } + // event handler for clicking the hierarchy concepts $(document).on('click', '.concept-hierarchy a', function(event) { @@ -275,7 +291,7 @@ $(function() { // DOCUMENT READY updateJsonLD(data); updateTitle(data); updateTopbarLang(data); - makeCallbacks(data); + ajaxConceptMapping(data); // take the content language buttons from the response $('.header-float .dropdown-menu').empty().append($('.header-float .dropdown-menu', data).html()); } @@ -304,7 +320,7 @@ $(function() { // DOCUMENT READY updateJsonLD(data); updateTitle(data); updateTopbarLang(data); - makeCallbacks(data); + ajaxConceptMapping(data); // take the content language buttons from the response $('.header-float .dropdown-menu').empty().append($('.header-float .dropdown-menu', data).html()); } @@ -432,7 +448,7 @@ $(function() { // DOCUMENT READY $('.nav').scrollTop(0); if (window.history.pushState) { window.history.pushState({}, null, event.target.href); } updateTitle(data); - makeCallbacks(data); + ajaxConceptMapping(data); // take the content language buttons from the response $('.header-float .dropdown-menu').empty().append($('.header-float .dropdown-menu', data).html()); } @@ -1098,6 +1114,18 @@ $(function() { // DOCUMENT READY })}); } - makeCallbacks(); + // ajaxing the concept mapping properties on the concept page + var $conceptAppendix = $('.concept-appendix'); + if ($conceptAppendix.length) { + var concept = { + uri: $conceptAppendix.data('concept-uri'), + type: $conceptAppendix.data('concept-type') + }; + + // Defined in scripts.js. Will load the mapping properties via Ajax request to JSKOS REST service, and render them. + loadMappingProperties(concept, lang, clang, $conceptAppendix, null); + } else { + makeCallbacks(); + } }); diff --git a/resource/js/scripts.js b/resource/js/scripts.js index 2ebc1c790..8477cd85a 100644 --- a/resource/js/scripts.js +++ b/resource/js/scripts.js @@ -298,3 +298,141 @@ function escapeHtml(string) { }); } +function renderPropertyMappingValues(groupedByType) { + var propertyMappingValues = []; + var source = document.getElementById("property-mapping-values-template").innerHTML; + var template = Handlebars.compile(source); + var context = { + property: { + uri: conceptMappingPropertyValue.uri, + label: conceptMappingPropertyValue.prefLabel, + } + }; + propertyMappingValues.push({'body': template(context)}); + return propertyMappingValues; +} + +function renderPropertyMappings(concept, contentLang, properties) { + var source = document.getElementById("property-mappings-template").innerHTML; + // handlebarjs helper functions + Handlebars.registerHelper('ifDeprecated', function(conceptType, value, opts) { + if(conceptType == value) { + return opts.fn(this); + } + return opts.inverse(this); + }); + Handlebars.registerHelper('toUpperCase', function(str) { + if (str === undefined) { + return ''; + } + return str.toUpperCase(); + }); + Handlebars.registerHelper('ifNotInDescription', function(type, description, opts) { + if (type === undefined) { + return opts.inverse(this); + } + if (description === undefined) { + return opts.inverse(this); + } + if (description.indexOf(type) > 0 && description.indexOf('_help') > 0) { + return opts.inverse(this); + } + return opts.fn(this); + }); + Handlebars.registerHelper('ifDifferentLabelLang', function(labelLang, explicitLangCodes, opts) { + if (labelLang !== undefined && labelLang !== '' && labelLang !== null) { + if (explicitLangCodes !== undefined && typeof explicitLangCodes === "boolean") { + return opts.fn(explicitLangCodes); + } + if (labelLang !== contentLang) { + return opts.fn(this); + } + } + return opts.inverse(this); + }); + + var template = Handlebars.compile(source); + + var context = { + concept: concept, + properties: properties + }; + + return template(context); +} + +/** + * Load mapping properties, via the JSKOS REST endpoint. Then, render the concept mapping properties template. This + * template is comprised of another template, for concept mapping property values. + * + * @param concept dictionary/object populated with data from the Concept object + * @param lang language used in the UI + * @param contentLang the content language + * @param $htmlElement HTML (a div) parent object (initially hidden) + * @param conceptData concept page data returned via ajax, passed to makeCallback only + */ +function loadMappingProperties(concept, lang, contentLang, $htmlElement, conceptData) { + // display with the spinner + $htmlElement + .removeClass('hidden') + .append('
'); + $.ajax({ + url: rest_base_url + vocab + '/mappings', + data: $.param({'uri': concept.uri, lang: lang, clang: contentLang}), + success: function(data) { + + // The JSKOS REST mapping properties call will have added more resources into the graph. The graph + // is returned alongside the mapping properties, so now we just need to replace it on the UI. + $('script[type="application/ld+json"]')[0].innerHTML = data.graph; + + var conceptProperties = []; + for (var i = 0; i < data.mappings.length; i++) { + /** + * @var conceptMappingPropertyValue JSKOS transformed ConceptMappingPropertyValue + */ + var conceptMappingPropertyValue = data.mappings[i]; + var found = false; + var conceptProperty = null; + for (var j = 0; j < conceptProperties.length; j++) { + conceptProperty = conceptProperties[j]; + if (conceptProperty.type === conceptMappingPropertyValue.type[0]) { + conceptProperty.values.push(conceptMappingPropertyValue); + found = true; + break; + } + } + + if (!found) { + conceptProperty = { + 'type': conceptMappingPropertyValue.type[0], + 'label': conceptMappingPropertyValue.typeLabel, + 'notation': conceptMappingPropertyValue.notation, + 'description': conceptMappingPropertyValue.description, + 'values': [] + }; + conceptProperty.values.push(conceptMappingPropertyValue); + conceptProperties.push(conceptProperty); + } + } + + if (conceptProperties.length > 0) { + var template = renderPropertyMappings(concept, contentLang, conceptProperties); + + $htmlElement.empty(); + $htmlElement.append(template); + } else { + // No concept properties found + $htmlElement.empty(); + $htmlElement.addClass("hidden"); + } + + + }, + error: function(data) { + console.log("Error retrieving mapping properties for [" + $htmlElement.data('concept-uri') + "]: " + data.responseText); + }, + complete: function() { + makeCallbacks(conceptData); + } + }); +} diff --git a/rest.php b/rest.php index 296ba8dd9..6b02dcbe2 100644 --- a/rest.php +++ b/rest.php @@ -19,7 +19,9 @@ if ($request->getQueryParam('vocab')) { $request->setVocab($request->getQueryParam('vocab')); } - if ($request->getQueryParam('lang')) { + if ($request->getQueryParam('clang')) { + $request->setContentLang($request->getQueryParam('clang')); + } elseif ($request->getQueryParam('lang')) { $request->setContentLang($request->getQueryParam('lang')); } diff --git a/tests/ConceptMappingPropertyValueTest.php b/tests/ConceptMappingPropertyValueTest.php index 5a62bc494..69a715921 100644 --- a/tests/ConceptMappingPropertyValueTest.php +++ b/tests/ConceptMappingPropertyValueTest.php @@ -200,6 +200,14 @@ public function testAsJskos() { ] ] ], + 'uri' => 'http://www.skosmos.skos/mapping/m1', + 'notation' => null, + 'prefLabel' => 'Eel', + 'description' => 'Exactly matching concepts in another vocabulary.', + 'hrefLink' => null, + 'lang' => 'en', + 'vocabName' => 'Test ontology', + 'typeLabel' => 'Exactly matching concepts', ], $propvals['Eelhttp://www.skosmos.skos/test/ta115']->asJskos()); } diff --git a/view/concept-shared.twig b/view/concept-shared.twig index a66509f60..5fa2a7111 100644 --- a/view/concept-shared.twig +++ b/view/concept-shared.twig @@ -175,37 +175,13 @@ - {% set appendixProperties = concept.mappingProperties %} - {% if appendixProperties %} -