| Total Complexity | 199 | 
| Total Lines | 986 | 
| Duplicated Lines | 0 % | 
| Changes | 7 | ||
| Bugs | 1 | Features | 0 | 
Complex classes like Concept often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Concept, and based on these observations, apply Extract Interface, too.
| 1 | <?php | ||
| 7 | class Concept extends VocabularyDataObject implements Modifiable | ||
| 8 | { | ||
| 9 | /** | ||
| 10 | * Stores a label string if the concept has been found through | ||
| 11 | * a altLabel/label in a another language than the ui. | ||
| 12 | */ | ||
| 13 | private $foundby; | ||
| 14 | /** Type of foundby match: 'alt', 'hidden' or 'lang' */ | ||
| 15 | private $foundbytype; | ||
| 16 | /** the EasyRdf\Graph object of the concept */ | ||
| 17 | private $graph; | ||
| 18 | private $clang; | ||
| 19 | private $deleted; | ||
| 20 | |||
| 21 | /** concept properties that should not be shown to users */ | ||
| 22 | private $DELETED_PROPERTIES = array( | ||
| 23 | 'skosext:broaderGeneric', # these are remnants of bad modeling | ||
| 24 | 'skosext:broaderPartitive', # | ||
| 25 | |||
| 26 | 'skos:hiddenLabel', # because it's supposed to be hidden | ||
| 27 | 'skos:prefLabel', # handled separately by getLabel | ||
| 28 | 'rdfs:label', # handled separately by getLabel | ||
| 29 | |||
| 30 | 'skos:topConceptOf', # because it's too technical, not relevant for users | ||
| 31 | 'skos:inScheme', # should be evident in any case | ||
| 32 | 'skos:member', # this shouldn't be shown on the group page | ||
| 33 | 'dc:created', # handled separately | ||
| 34 | 'dc:modified', # handled separately | ||
| 35 | 'owl:deprecated', # indicated visually | ||
| 36 | ); | ||
| 37 | |||
| 38 | /** related concepts that should be shown to users in the appendix */ | ||
| 39 | private $MAPPING_PROPERTIES = array( | ||
| 40 | 'skos:exactMatch', | ||
| 41 | 'skos:narrowMatch', | ||
| 42 | 'skos:broadMatch', | ||
| 43 | 'skos:closeMatch', | ||
| 44 | 'skos:relatedMatch', | ||
| 45 | 'rdfs:seeAlso', | ||
| 46 | 'owl:sameAs', | ||
| 47 | ); | ||
| 48 | |||
| 49 | /** default external properties we are interested in saving/displaying from mapped external objects */ | ||
| 50 | private $DEFAULT_EXT_PROPERTIES = array( | ||
| 51 | "dc11:title", | ||
| 52 | "dcterms:title", | ||
| 53 | "skos:prefLabel", | ||
| 54 | "skos:exactMatch", | ||
| 55 | "skos:closeMatch", | ||
| 56 | "skos:inScheme", | ||
| 57 | "rdfs:label", | ||
| 58 | "rdfs:isDefinedBy", | ||
| 59 | "owl:sameAs", | ||
| 60 | "rdf:type", | ||
| 61 | "void:inDataset", | ||
| 62 | "void:sparqlEndpoint", | ||
| 63 | "void:uriLookupEndpoint", | ||
| 64 | "schema:about", | ||
| 65 | "schema:description", | ||
| 66 | "schema:inLanguage", | ||
| 67 | "schema:name", | ||
| 68 | "schema:isPartOf", | ||
| 69 | "wdt:P31", | ||
| 70 | "wdt:P625" | ||
| 71 | ); | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Initializing the concept object requires the following parameters. | ||
| 75 | * @param Model $model | ||
| 76 | * @param Vocabulary $vocab | ||
| 77 | * @param EasyRdf\Resource $resource | ||
| 78 | * @param EasyRdf\Graph $graph | ||
| 79 | */ | ||
| 80 | public function __construct($model, $vocab, $resource, $graph, $clang) | ||
| 81 |     { | ||
| 82 | parent::__construct($model, $vocab, $resource); | ||
| 83 | $this->deleted = $this->DELETED_PROPERTIES; | ||
| 84 |         if ($vocab !== null) { | ||
| 85 | $this->order = $vocab->getConfig()->getPropertyOrder(); | ||
| 86 | // delete notations unless configured to show them | ||
| 87 |             if (!$vocab->getConfig()->getShowNotationAsProperty()) { | ||
| 88 | $this->deleted[] = 'skos:notation'; | ||
| 89 | } | ||
| 90 |         } else { | ||
| 91 | $this->order = VocabularyConfig::DEFAULT_PROPERTY_ORDER; | ||
| 92 | } | ||
| 93 | $this->graph = $graph; | ||
| 94 | $this->clang = $clang; | ||
| 95 | } | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Returns the concept uri. | ||
| 99 | * @return string | ||
| 100 | */ | ||
| 101 | public function getUri() | ||
| 102 |     { | ||
| 103 | return $this->resource->getUri(); | ||
| 104 | } | ||
| 105 | |||
| 106 | public function getType() | ||
| 107 |     { | ||
| 108 | return $this->resource->types(); | ||
| 109 | } | ||
| 110 | |||
| 111 | |||
| 112 | /** | ||
| 113 | * Returns a boolean value indicating whether the resource is a group defined in the vocab config as skosmos:groupClass. | ||
| 114 | * @return boolean | ||
| 115 | */ | ||
| 116 | public function isGroup() | ||
| 117 |     { | ||
| 118 | $groupClass = $this->getVocab()->getConfig()->getGroupClassURI(); | ||
| 119 |         if ($groupClass) { | ||
| 120 | $groupClass = $this->shortenOrKeep($groupClass); | ||
| 121 | return in_array($groupClass, $this->getType()); | ||
| 122 | } | ||
| 123 | return false; | ||
| 124 | } | ||
| 125 | |||
| 126 | /** | ||
| 127 | * Returns a boolean value indicating if the concept has been deprecated. | ||
| 128 | * @return boolean | ||
| 129 | */ | ||
| 130 | public function getDeprecated() | ||
| 131 |     { | ||
| 132 |         $deprecatedValue = $this->resource->getLiteral('owl:deprecated'); | ||
| 133 | return ($deprecatedValue !== null && filter_var($deprecatedValue->getValue(), FILTER_VALIDATE_BOOLEAN)); | ||
| 134 | } | ||
| 135 | |||
| 136 | /** | ||
| 137 | * Returns a label for the concept in the content language or if not possible in any language. | ||
| 138 | * @return string | ||
| 139 | */ | ||
| 140 | public function getLabel() | ||
| 141 |     { | ||
| 142 |         foreach ($this->vocab->getConfig()->getLanguageOrder($this->clang) as $fallback) { | ||
| 143 |             if ($this->resource->label($fallback) !== null) { | ||
| 144 | return $this->resource->label($fallback); | ||
| 145 | } | ||
| 146 | // We need to check all the labels in case one of them matches a subtag of the current language | ||
| 147 |             foreach ($this->resource->allLiterals('skos:prefLabel') as $label) { | ||
| 148 | // the label lang code is a subtag of the UI lang eg. en-GB - create a new literal with the main language | ||
| 149 |                 if ($label !== null && strpos($label->getLang(), $fallback . '-') === 0) { | ||
| 150 | return EasyRdf\Literal::create($label, $fallback); | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | // Last resort: label in any language, including literal with empty language tag | ||
| 156 | $label = $this->resource->label(); | ||
| 157 |         if ($label !== null) { | ||
| 158 |             if (!$label->getLang()) { | ||
| 159 | return $label->getValue(); | ||
|  | |||
| 160 | } | ||
| 161 | return EasyRdf\Literal::create($label->getValue(), $label->getLang()); | ||
| 162 | } | ||
| 163 | |||
| 164 | // empty | ||
| 165 | return ""; | ||
| 166 | } | ||
| 167 | |||
| 168 | public function hasXlLabel() | ||
| 169 |     { | ||
| 170 | $xlLabel = $this->getXlLabel(); | ||
| 171 | return ($xlLabel !== null && !empty($xlLabel->getProperties())); | ||
| 172 | } | ||
| 173 | |||
| 174 | public function getXlLabel() | ||
| 175 |     { | ||
| 176 |         $labels = $this->resource->allResources('skosxl:prefLabel'); | ||
| 177 |         foreach ($labels as $labres) { | ||
| 178 |             $label = $labres->getLiteral('skosxl:literalForm'); | ||
| 179 |             if ($label !== null && $label->getLang() == $this->clang) { | ||
| 180 | return new LabelSkosXL($this->model, $labres); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | return null; | ||
| 184 | } | ||
| 185 | |||
| 186 | /** | ||
| 187 | * Returns a notation for the concept or null if it has not been defined. | ||
| 188 | * @return string eg. '999' | ||
| 189 | */ | ||
| 190 | public function getNotation() | ||
| 191 |     { | ||
| 192 |         $notation = $this->resource->get('skos:notation'); | ||
| 193 |         if ($this->vocab->getConfig()->showNotation() && $notation !== null) { | ||
| 194 | return $notation->getValue(); | ||
| 195 | } | ||
| 196 | |||
| 197 | return null; | ||
| 198 | } | ||
| 199 | |||
| 200 | /** | ||
| 201 | * Returns the Vocabulary object or undefined if that is not available. | ||
| 202 | * @return Vocabulary | ||
| 203 | */ | ||
| 204 | public function getVocab() | ||
| 205 |     { | ||
| 206 | return $this->vocab; | ||
| 207 | } | ||
| 208 | |||
| 209 | /** | ||
| 210 | * Returns the vocabulary shortname string or id if that is not available. | ||
| 211 | * @return string | ||
| 212 | */ | ||
| 213 | public function getShortName() | ||
| 214 |     { | ||
| 215 | return $this->vocab ? $this->vocab->getShortName() : null; | ||
| 216 | } | ||
| 217 | |||
| 218 | /** | ||
| 219 | * Returns the vocabulary shortname string or id if that is not available. | ||
| 220 | * @return string | ||
| 221 | */ | ||
| 222 | public function getVocabTitle() | ||
| 225 | } | ||
| 226 | |||
| 227 | /** | ||
| 228 | * Setter for the $clang property. | ||
| 229 | * @param string $clang language code eg. 'en' | ||
| 230 | */ | ||
| 231 | public function setContentLang($clang) | ||
| 232 |     { | ||
| 233 | $this->clang = $clang; | ||
| 234 | } | ||
| 235 | |||
| 236 | public function getContentLang() | ||
| 237 |     { | ||
| 238 | return $this->clang; | ||
| 239 | } | ||
| 240 | |||
| 241 | /** | ||
| 242 | * Setter for the $foundby property. | ||
| 243 | * @param string $label label that was matched | ||
| 244 | * @param string $type type of match: 'alt', 'hidden', or 'lang' | ||
| 245 | */ | ||
| 246 | public function setFoundBy($label, $type) | ||
| 247 |     { | ||
| 248 | $this->foundby = $label; | ||
| 249 | $this->foundbytype = $type; | ||
| 250 | } | ||
| 251 | |||
| 252 | /** | ||
| 253 | * Getter for the $foundby property. | ||
| 254 | * @return string | ||
| 255 | */ | ||
| 256 | public function getFoundBy() | ||
| 257 |     { | ||
| 258 | return $this->foundby; | ||
| 259 | } | ||
| 260 | |||
| 261 | /** | ||
| 262 | * Getter for the $foundbytype property. | ||
| 263 | * @return string | ||
| 264 | */ | ||
| 265 | public function getFoundByType() | ||
| 268 | } | ||
| 269 | |||
| 270 | /** | ||
| 271 | * Processes a single external resource i.e., adds the properties from | ||
| 272 | * 1) $this->$DEFAULT_EXT_PROPERTIES | ||
| 273 | * 2) VocabConfig external properties | ||
| 274 | * 3) Possible plugin defined external properties | ||
| 275 | * to $this->graph | ||
| 276 | * @param EasyRdf\Resource $res | ||
| 277 | */ | ||
| 278 | public function processExternalResource($res) | ||
| 279 |     { | ||
| 280 | $exGraph = $res->getGraph(); | ||
| 281 | // catch external subjects that have $res as object | ||
| 282 |         $extSubjects = $exGraph->resourcesMatching("schema:about", $res); | ||
| 283 | |||
| 284 | $propList = array_unique(array_merge( | ||
| 285 | $this->DEFAULT_EXT_PROPERTIES, | ||
| 286 | $this->getVocab()->getConfig()->getExtProperties(), | ||
| 287 | $this->getVocab()->getConfig()->getPluginRegister()->getExtProperties() | ||
| 288 | )); | ||
| 289 | |||
| 290 | $seen = array(); | ||
| 291 | $this->addExternalTriplesToGraph($res, $seen, $propList); | ||
| 292 |         foreach ($extSubjects as $extSubject) { | ||
| 293 |             if ($extSubject->isBNode() && array_key_exists($extSubject->getUri(), $seen)) { | ||
| 294 | // already processed, skip | ||
| 295 | continue; | ||
| 296 | } | ||
| 297 | $seen[$extSubject->getUri()] = 1; | ||
| 298 | $this->addExternalTriplesToGraph($extSubject, $seen, $propList); | ||
| 299 | } | ||
| 300 | |||
| 301 | } | ||
| 302 | |||
| 303 | /** | ||
| 304 | * Adds resource properties to $this->graph | ||
| 305 | * @param EasyRdf\Resource $res | ||
| 306 | * @param string[] $seen Processed resources so far | ||
| 307 | * @param string[] $props (optional) limit to these property URIs | ||
| 308 | */ | ||
| 309 | private function addExternalTriplesToGraph($res, &$seen, $props = null) | ||
| 310 |     { | ||
| 311 |         if (array_key_exists($res->getUri(), $seen) && $seen[$res->getUri()] === 0) { | ||
| 312 | return; | ||
| 313 | } | ||
| 314 | $seen[$res->getUri()] = 0; | ||
| 315 | |||
| 316 |         if ($res->isBNode() || is_null($props)) { | ||
| 317 |             foreach ($res->propertyUris() as $prop) { | ||
| 318 | $this->addPropertyValues($res, $prop, $seen); | ||
| 319 | } | ||
| 320 |         } else { | ||
| 321 |             foreach ($props as $prop) { | ||
| 322 |                 if ($res->hasProperty($prop)) { | ||
| 323 | $this->addPropertyValues($res, $prop, $seen); | ||
| 324 | } | ||
| 325 | } | ||
| 326 | } | ||
| 327 | } | ||
| 328 | |||
| 329 | /** | ||
| 330 | * Adds values of a single single property of a resource to $this->graph | ||
| 331 | * implements Concise Bounded Description definition | ||
| 332 | * @param EasyRdf\Resource $res | ||
| 333 | * @param string $prop | ||
| 334 | * @param string[] $seen Processed resources so far | ||
| 335 | */ | ||
| 336 | private function addPropertyValues($res, $prop, &$seen) | ||
| 337 |     { | ||
| 338 |         $resList = $res->allResources('<' . $prop . '>'); | ||
| 339 | |||
| 340 |         foreach ($resList as $res2) { | ||
| 341 |             if ($res2->isBNode()) { | ||
| 342 | $this->addExternalTriplesToGraph($res2, $seen); | ||
| 343 | } | ||
| 344 | $this->graph->addResource($res, $prop, $res2); | ||
| 345 | $this->addResourceReifications($res, $prop, $res2, $seen); | ||
| 346 | } | ||
| 347 | |||
| 348 |         $litList = $res->allLiterals('<' . $prop . '>'); | ||
| 349 | |||
| 350 |         foreach ($litList as $lit) { | ||
| 351 | $this->graph->addLiteral($res, $prop, $lit); | ||
| 352 | $this->addLiteralReifications($res, $prop, $lit, $seen); | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | /** | ||
| 357 | * Adds reifications of a triple having a literal object to $this->graph | ||
| 358 | * @param EasyRdf\Resource $sub | ||
| 359 | * @param string $pred | ||
| 360 | * @param EasyRdf\Literal $obj | ||
| 361 | * @param string[] $seen Processed resources so far | ||
| 362 | */ | ||
| 363 | private function addLiteralReifications($sub, $pred, $obj, &$seen) | ||
| 364 |     { | ||
| 365 |         $pos_reifs = $sub->getGraph()->resourcesMatching("rdf:subject", $sub); | ||
| 366 |         foreach ($pos_reifs as $pos_reif) { | ||
| 367 |             $lit = $pos_reif->getLiteral("rdf:object", $obj->getLang()); | ||
| 368 | |||
| 369 | if (!is_null($lit) && $lit->getValue() === $obj->getValue() && | ||
| 370 |                 $pos_reif->isA("rdf:Statement") && | ||
| 371 |                 $pos_reif->hasProperty("rdf:predicate", new EasyRdf\Resource($pred, $sub->getGraph()))) { | ||
| 372 | $this->addExternalTriplesToGraph($pos_reif, $seen); | ||
| 373 | } | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | /** | ||
| 378 | * Adds reifications of a triple having a resource object to $this->graph | ||
| 379 | * @param EasyRdf\Resource $sub | ||
| 380 | * @param string $pred | ||
| 381 | * @param EasyRdf\Resource $obj | ||
| 382 | * @param string[] $seen Processed resources so far | ||
| 383 | */ | ||
| 384 | private function addResourceReifications($sub, $pred, $obj, &$seen) | ||
| 392 | } | ||
| 393 | } | ||
| 394 | } | ||
| 395 | |||
| 396 | /** | ||
| 397 | * @param string[]|null $allowedProperties List of properties to include, or null to include all. | ||
| 398 | * @return ConceptProperty[] | ||
| 399 | */ | ||
| 400 | public function getMappingProperties(array $allowedProperties = null) | ||
| 401 |     { | ||
| 402 | $ret = array(); | ||
| 403 | |||
| 404 |         foreach ($this->resource->propertyUris() as &$longUri) { | ||
| 405 | $prop = $this->shortenOrKeep($longUri); | ||
| 406 | // EasyRdf requires full URIs to be in angle brackets | ||
| 407 | $sprop = ($prop == $longUri) ? "<$longUri>" : $prop; | ||
| 408 |             if ($allowedProperties && !in_array($prop, $allowedProperties)) { | ||
| 409 | // allowedProperties in use and this is not an allowed property, skipping | ||
| 410 | continue; | ||
| 411 | } | ||
| 412 | |||
| 413 |             if (in_array($prop, $this->MAPPING_PROPERTIES) && !in_array($prop, $this->DELETED_PROPERTIES)) { | ||
| 414 | $propres = new EasyRdf\Resource($prop, $this->graph); | ||
| 415 | $proplabel = $propres->label($this->getLang()) ? $propres->label($this->getLang()) : $propres->label(); // current language | ||
| 416 | $propobj = new ConceptProperty($this->model, $prop, $proplabel); | ||
| 417 |                 if ($propobj->getLabel() !== null) { | ||
| 418 | // only display properties for which we have a label | ||
| 419 | $ret[$prop] = $propobj; | ||
| 420 | } | ||
| 421 | |||
| 422 | // Iterating through every resource and adding these to the data object. | ||
| 423 |                 foreach ($this->resource->allResources($sprop) as $resourceValue) { | ||
| 424 |                     if (isset($ret[$prop])) { | ||
| 425 | // checking if the target vocabulary can be found at the skosmos endpoint | ||
| 426 | $exuri = $resourceValue->getUri(); | ||
| 427 | // if multiple vocabularies are found, the following method will return in priority the current vocabulary of the concept | ||
| 428 | $exvoc = $this->model->guessVocabularyFromURI($exuri, $this->vocab->getId()); | ||
| 429 | // if not querying the uri itself | ||
| 430 |                         if (!$exvoc) { | ||
| 431 | $response = null; | ||
| 432 | // if told to do so in the vocabulary configuration | ||
| 433 |                             if ($this->vocab->getConfig()->getExternalResourcesLoading()) { | ||
| 434 | $response = $this->model->getResourceFromUri($exuri); | ||
| 435 | } | ||
| 436 | |||
| 437 |                             if ($response) { | ||
| 438 | $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $response, $this->resource, $prop)); | ||
| 439 | |||
| 440 | $this->processExternalResource($response); | ||
| 441 | |||
| 442 | continue; | ||
| 443 | } | ||
| 444 | } | ||
| 445 | $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $resourceValue, $this->resource, $prop, $this->clang)); | ||
| 446 | } | ||
| 447 | } | ||
| 448 | } | ||
| 449 | } | ||
| 450 | |||
| 451 | // sorting the properties to a order preferred in the Skosmos concept page. | ||
| 452 | return $this->arbitrarySort($ret); | ||
| 453 | } | ||
| 454 | |||
| 455 | private function shortenOrKeep($uri) | ||
| 456 |     { | ||
| 457 | return EasyRdf\RdfNamespace::shorten($uri) ?? $uri; | ||
| 458 | } | ||
| 459 | |||
| 460 | private function getCollectionMembersInUse(): array | ||
| 461 |     { | ||
| 462 | $inCollection = []; | ||
| 463 | $membersArray = []; | ||
| 464 | |||
| 465 | $arrayClassUri = $this->vocab->getConfig()->getArrayClassURI(); | ||
| 466 |         if ($arrayClassUri === null) { | ||
| 467 | return ['inCollection' => $inCollection, 'membersArray' => $membersArray]; | ||
| 468 | } | ||
| 469 | |||
| 470 | $collections = $this->graph->allOfType($arrayClassUri); | ||
| 471 |         if (count($collections) === 0) { | ||
| 472 | return ['inCollection' => $inCollection, 'membersArray' => $membersArray]; | ||
| 473 | } | ||
| 474 | |||
| 475 | // Index narrowers by URI | ||
| 476 | $narrowersByUri = []; | ||
| 477 |         foreach ($this->resource->allResources('skos:narrower') as $narrower) { | ||
| 478 | $narrowersByUri[$narrower->getUri()] = $narrower; | ||
| 479 | } | ||
| 480 | |||
| 481 |         foreach ($collections as $coll) { | ||
| 482 | $currCollMembers = $this->getCollectionMembers($coll, $narrowersByUri); | ||
| 483 | |||
| 484 |             foreach ($currCollMembers as $collection) { | ||
| 485 |                 if ($collection->getSubMembers()) { | ||
| 486 |                     foreach ($collection->getSubMembers() as $member) { | ||
| 487 | $inCollection[$member->getUri()] = true; | ||
| 488 | } | ||
| 489 | } | ||
| 490 | } | ||
| 491 | |||
| 492 |             if (isset($collection) && $collection->getSubMembers()) { | ||
| 493 | $membersArray = array_merge($membersArray, $currCollMembers); | ||
| 494 | } | ||
| 495 | } | ||
| 496 | |||
| 497 | return ['inCollection' => $inCollection, 'membersArray' => $membersArray]; | ||
| 498 | } | ||
| 499 | |||
| 500 | |||
| 501 | /** | ||
| 502 | * Iterates over all the properties of the concept and returns those in an array. | ||
| 503 | * @param string[]|null $allowedProperties List of properties to include, or null to include all. | ||
| 504 | * @return array | ||
| 505 | */ | ||
| 506 | public function getProperties($allowedProperties = null) | ||
| 507 |     { | ||
| 508 | $properties = array(); | ||
| 509 | $duplicates = array(); | ||
| 510 | $propertiesWithValues = array(); | ||
| 511 | |||
| 512 | $collectionData = $this->getCollectionMembersInUse(); | ||
| 513 | $inCollection = $collectionData['inCollection']; | ||
| 514 | $membersArray = $collectionData['membersArray']; | ||
| 515 | |||
| 516 |         if (!empty($membersArray)) { | ||
| 517 | $properties['skos:narrower'] = $membersArray; | ||
| 518 | } | ||
| 519 | |||
| 520 |         foreach ($this->resource->propertyUris() as &$longUri) { | ||
| 521 | // storing full URI without brackets in a separate variable | ||
| 522 | $prop = $this->shortenOrKeep($longUri); | ||
| 523 | // EasyRdf requires full URIs to be in angle brackets | ||
| 524 | $sprop = ($prop == $longUri) ? "<$longUri>" : $prop; | ||
| 525 | |||
| 526 |             if ($allowedProperties !== null && !in_array($prop, $allowedProperties)) { | ||
| 527 | continue; | ||
| 528 | } | ||
| 529 | |||
| 530 |             if (!in_array($prop, $this->deleted) || ($this->isGroup() === false && $prop === 'skos:member')) { | ||
| 531 | // retrieve property label and super properties from the current vocabulary first | ||
| 532 | $propertyResource = new EasyRdf\Resource($prop, $this->graph); | ||
| 533 | $proplabel = $propertyResource->label($this->getLang()) ? $propertyResource->label($this->getLang()) : $propertyResource->label(); | ||
| 534 | |||
| 535 |                 $prophelp = $propertyResource->getLiteral('rdfs:comment|skos:definition', $this->getLang()); | ||
| 536 |                 if ($prophelp === null) { | ||
| 537 | // try again without language restriction | ||
| 538 |                     $prophelp = $propertyResource->getLiteral('rdfs:comment|skos:definition'); | ||
| 539 | } | ||
| 540 | |||
| 541 | // check if the property is one of the well-known properties for which we have a translation | ||
| 542 | // if it is then we can skip the additional lookups in the default graph | ||
| 543 | $propkey = (substr($prop, 0, 5) == 'dc11:') ? | ||
| 544 |                     str_replace('dc11:', 'dc:', $prop) : $prop; | ||
| 545 | $is_well_known = ($this->model->getText($propkey) != $propkey); | ||
| 546 | |||
| 547 | // if not found in current vocabulary, look up in the default graph to be able | ||
| 548 | // to read an ontology loaded in a separate graph | ||
| 549 | // note that this imply that the property has an rdf:type declared for the query to work | ||
| 550 |                 if (!$is_well_known && !$proplabel) { | ||
| 551 | $envLangLabels = $this->model->getDefaultSparql()->queryLabel($longUri, $this->getLang()); | ||
| 552 | |||
| 553 | $defaultPropLabel = $this->model->getDefaultSparql()->queryLabel($longUri, ''); | ||
| 554 | |||
| 555 |                     if ($envLangLabels) { | ||
| 556 | $proplabel = $envLangLabels[$this->getLang()]; | ||
| 557 |                     } else { | ||
| 558 |                         if ($defaultPropLabel) { | ||
| 559 | $proplabel = $defaultPropLabel['']; | ||
| 560 | } | ||
| 561 | } | ||
| 562 | } | ||
| 563 | |||
| 564 | // look for superproperties in the current graph | ||
| 565 | $superprops = array(); | ||
| 566 |                 foreach ($this->graph->allResources($prop, 'rdfs:subPropertyOf') as $superProperty) { | ||
| 567 | $superprops[] = $superProperty->getUri(); | ||
| 568 | } | ||
| 569 | |||
| 570 | // also look up superprops in the default graph if not found in current vocabulary | ||
| 571 |                 if (!$is_well_known && (!$superprops || empty($superprops))) { | ||
| 572 | $superprops = $this->model->getDefaultSparql()->querySuperProperties($longUri); | ||
| 573 | } | ||
| 574 | |||
| 575 | // we're reading only one super property, even if there are multiple ones | ||
| 576 | $superprop = ($superprops) ? $this->shortenOrKeep($superprops[0]) : null; | ||
| 577 | $sort_by_notation = $this->vocab->getConfig()->getSortByNotation(); | ||
| 578 | $propobj = new ConceptProperty($this->model, $prop, $proplabel, $prophelp, $superprop, $sort_by_notation); | ||
| 579 | |||
| 580 |                 if ($propobj->getLabel() !== null) { | ||
| 581 | // only display properties for which we have a label | ||
| 582 | $propertiesWithValues[$prop] = $propobj; | ||
| 583 | } | ||
| 584 | |||
| 585 | // searching for subproperties of literals too | ||
| 586 |                 if ($superprops) { | ||
| 587 |                     foreach ($superprops as $superProperty) { | ||
| 588 | $duplicates[$this->shortenOrKeep($superProperty)] = $prop; | ||
| 589 | } | ||
| 590 | } | ||
| 591 | |||
| 592 | // Iterating through every literal and adding these to the data object. | ||
| 593 |                 foreach ($this->resource->allLiterals($sprop) as $literalValue) { | ||
| 594 | $literal = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $literalValue, $prop); | ||
| 595 | // only add literals when they match the content/hit language or have no language defined OR when they are literals of a multilingual property | ||
| 596 |                     if (isset($propertiesWithValues[$prop]) && ($literal->getLang() === $this->clang || $literal->getLang() === null) || $this->vocab->getConfig()->hasMultiLingualProperty($prop)) { | ||
| 597 | $propertiesWithValues[$prop]->addValue($literal); | ||
| 598 | } | ||
| 599 | } | ||
| 600 | |||
| 601 | // Iterating through every resource and adding these to the data object. | ||
| 602 |                 foreach ($this->resource->allResources($sprop) as $resourceValue) { | ||
| 603 | // skipping narrower concepts which are already shown in a collection | ||
| 604 |                     if ($sprop === 'skos:narrower' && array_key_exists($resourceValue->getUri(), $inCollection)) { | ||
| 605 | continue; | ||
| 606 | } | ||
| 607 | |||
| 608 | // hiding rdf:type property if it's just skos:Concept | ||
| 609 |                     if ($sprop === 'rdf:type' && $resourceValue->shorten() === 'skos:Concept') { | ||
| 610 | continue; | ||
| 611 | } | ||
| 612 | |||
| 613 | // handled by getMappingProperties() | ||
| 614 |                     if (in_array($sprop, $this->MAPPING_PROPERTIES)) { | ||
| 615 | continue; | ||
| 616 | } | ||
| 617 | |||
| 618 |                     if (isset($propertiesWithValues[$prop])) { | ||
| 619 | // create a ConceptPropertyValue first, assuming the resource exists in current vocabulary | ||
| 620 | $value = new ConceptPropertyValue($this->model, $this->vocab, $resourceValue, $prop, $this->clang); | ||
| 621 | $propertiesWithValues[$prop]->addValue($value); | ||
| 622 | } | ||
| 623 | |||
| 624 | } | ||
| 625 | } | ||
| 626 | } | ||
| 627 | // adding narrowers part of a collection | ||
| 628 |         foreach ($properties as $prop => $values) { | ||
| 629 |             foreach ($values as $value) { | ||
| 630 | $propertiesWithValues[$prop]->addValue($value); | ||
| 631 | } | ||
| 632 | } | ||
| 633 | |||
| 634 | $groupPropObj = new ConceptProperty($this->model, 'skosmos:memberOf', null); | ||
| 635 |         foreach ($this->getGroupProperties() as $propVals) { | ||
| 636 |             foreach ($propVals as $propVal) { | ||
| 637 | $groupPropObj->addValue($propVal); | ||
| 638 | } | ||
| 639 | } | ||
| 640 | $propertiesWithValues['skosmos:memberOf'] = $groupPropObj; | ||
| 641 | |||
| 642 | $arrayPropObj = new ConceptProperty($this->model, 'skosmos:memberOfArray', null); | ||
| 643 |         foreach ($this->getArrayProperties() as $propVals) { | ||
| 644 |             foreach ($propVals as $propVal) { | ||
| 645 | $arrayPropObj->addValue($propVal); | ||
| 646 | } | ||
| 647 | } | ||
| 648 | $propertiesWithValues['skosmos:memberOfArray'] = $arrayPropObj; | ||
| 649 | |||
| 650 |         foreach ($propertiesWithValues as $key => $prop) { | ||
| 651 |             if (sizeof($prop->getValues()) === 0) { | ||
| 652 | unset($propertiesWithValues[$key]); | ||
| 653 | } | ||
| 654 | } | ||
| 655 | |||
| 656 | $propertiesWithValues = $this->removeDuplicatePropertyValues($propertiesWithValues, $duplicates); | ||
| 657 | // sorting the properties to the order preferred in the Skosmos concept page. | ||
| 658 | return $this->arbitrarySort($propertiesWithValues); | ||
| 659 | } | ||
| 660 | |||
| 661 | /** | ||
| 662 | * Removes properties that have duplicate values. | ||
| 663 | * @param array $propertiesWithValues the array of properties generated by getProperties | ||
| 664 | * @param array $duplicates array of properties found are a subProperty of a another property | ||
| 665 | * @return array of ConceptProperties | ||
| 666 | */ | ||
| 667 | public function removeDuplicatePropertyValues($propertiesWithValues, $duplicates) | ||
| 668 |     { | ||
| 669 | $propertyValues = array(); | ||
| 670 | |||
| 671 |         foreach ($propertiesWithValues as $prop) { | ||
| 672 |             foreach ($prop->getValues() as $value) { | ||
| 673 | $label = $value->getLabel(allowExternal: false); | ||
| 674 | $propertyValues[(is_object($label) && method_exists($label, 'getValue')) ? $label->getValue() : $label][] = $value->getType(); | ||
| 675 | } | ||
| 676 | } | ||
| 677 | |||
| 678 |         foreach ($propertyValues as $value => $propnames) { | ||
| 679 | // if there are multiple properties with the same string value. | ||
| 680 |             if (count($propnames) > 1) { | ||
| 681 |                 foreach ($propnames as $property) { | ||
| 682 | // if there is a more accurate property delete the more generic one. | ||
| 683 |                     if (isset($duplicates[$property])) { | ||
| 684 | unset($propertiesWithValues[$property]); | ||
| 685 | } | ||
| 686 | } | ||
| 687 | |||
| 688 | } | ||
| 689 | } | ||
| 690 | // handled separately: remove duplicate skos:prefLabel value (#854) | ||
| 691 |         if (isset($duplicates["skos:prefLabel"])) { | ||
| 692 | unset($propertiesWithValues[$duplicates["skos:prefLabel"]]); | ||
| 693 | } | ||
| 694 | return $propertiesWithValues; | ||
| 695 | } | ||
| 696 | |||
| 697 | /** | ||
| 698 | * @param $lang UI language | ||
| 699 | * @return String|null the translated label of skos:prefLabel subproperty, or null if not available | ||
| 700 | */ | ||
| 701 | public function getPreferredSubpropertyLabelTranslation($lang) | ||
| 712 | } | ||
| 713 | |||
| 714 | /** | ||
| 715 | * @return DateTime|null the modified date, or null if not available | ||
| 716 | */ | ||
| 717 | public function getModifiedDate() | ||
| 718 |     { | ||
| 719 | // finding the modified properties | ||
| 720 | /** @var \EasyRdf\Resource|\EasyRdf\Literal|null $modifiedResource */ | ||
| 721 |         $modifiedResource = $this->resource->get('dc:modified'); | ||
| 722 |         if ($modifiedResource && $modifiedResource instanceof \EasyRdf\Literal\Date) { | ||
| 723 | return $modifiedResource->getValue(); | ||
| 724 | } | ||
| 725 | |||
| 726 | // if the concept does not have a modified date, we look for it in its | ||
| 727 | // vocabulary | ||
| 728 | return $this->getVocab()->getModifiedDate(); | ||
| 729 | } | ||
| 730 | |||
| 731 | /** | ||
| 732 | * Gets the creation date and modification date if available. | ||
| 733 | * @return String containing the date information in a human readable format. | ||
| 734 | */ | ||
| 735 | public function getDate() | ||
| 736 |     { | ||
| 737 | $ret = ''; | ||
| 738 | $created = ''; | ||
| 739 |         try { | ||
| 740 | // finding the created properties | ||
| 741 |             if ($this->resource->get('dc:created')) { | ||
| 742 |                 $created = $this->resource->get('dc:created')->getValue(); | ||
| 743 | } | ||
| 744 | |||
| 745 | $modified = $this->getModifiedDate(); | ||
| 746 | |||
| 747 | // making a human readable string from the timestamps | ||
| 748 |             if ($created != '') { | ||
| 749 |                 $ret = $this->model->getText('skosmos:created') . ' ' . (Punic\Calendar::formatDate($created, 'short', $this->getLang())); | ||
| 750 | } | ||
| 751 | |||
| 752 |             if ($modified != '') { | ||
| 753 |                 if ($created != '') { | ||
| 754 |                     $ret .= ', ' . $this->model->getText('skosmos:modified') . ' ' . (Punic\Calendar::formatDate($modified, 'short', $this->getLang())); | ||
| 755 |                 } else { | ||
| 756 |                     $ret .= ' ' . ucfirst($this->model->getText('skosmos:modified')) . ' ' . (Punic\Calendar::formatDate($modified, 'short', $this->getLang())); | ||
| 757 | } | ||
| 758 | |||
| 759 | } | ||
| 760 |         } catch (Exception $e) { | ||
| 761 | trigger_error($e->getMessage(), E_USER_WARNING); | ||
| 762 | $ret = ''; | ||
| 763 |             if ($this->resource->get('dc:modified')) { | ||
| 764 |                 $modified = (string) $this->resource->get('dc:modified'); | ||
| 765 |                 $ret = $this->model->getText('skosmos:modified') . ' ' . $modified; | ||
| 766 | } | ||
| 767 |             if ($this->resource->get('dc:created')) { | ||
| 768 |                 $created .= (string) $this->resource->get('dc:created'); | ||
| 769 |                 $ret .= ' ' . $this->model->getText('skosmos:created') . ' ' . $created; | ||
| 770 | } | ||
| 771 | } | ||
| 772 | return $ret; | ||
| 773 | } | ||
| 774 | |||
| 775 | /** | ||
| 776 | * Gets the members of a specific collection. | ||
| 777 | * @param $coll | ||
| 778 | * @param array containing all narrowers as EasyRdf\Resource | ||
| 779 | * @return array containing ConceptPropertyValue objects | ||
| 780 | */ | ||
| 781 | private function getCollectionMembers($coll, $narrowers) | ||
| 804 | } | ||
| 805 | |||
| 806 | /** | ||
| 807 | * Gets the groups the concept belongs to. | ||
| 808 | */ | ||
| 809 | public function getGroupProperties() | ||
| 810 |     { | ||
| 811 | return $this->getCollections(false); | ||
| 812 | } | ||
| 813 | |||
| 814 | /** | ||
| 815 | * Gets the groups/arrays the concept belongs to. | ||
| 816 | */ | ||
| 817 | private function getCollections($includeArrays) | ||
| 818 |     { | ||
| 819 | $groups = array(); | ||
| 820 |         $collections = $this->graph->resourcesMatching('skos:member', $this->resource); | ||
| 821 |         if (isset($collections)) { | ||
| 822 | $arrayClassURI = $this->vocab !== null ? $this->vocab->getConfig()->getArrayClassURI() : null; | ||
| 823 | $arrayClass = $arrayClassURI !== null ? EasyRdf\RdfNamespace::shorten($arrayClassURI) : null; | ||
| 824 |             $superGroups = $this->resource->all('isothes:superGroup'); | ||
| 825 |             $superGroupUris = array_map(function ($obj) { return $obj->getUri(); }, $superGroups); | ||
| 826 |             foreach ($collections as $collection) { | ||
| 827 |                 if (in_array($arrayClass, $collection->types()) === $includeArrays) { | ||
| 828 | // not adding the memberOf if the reverse resource is already covered by isothes:superGroup see issue #433 | ||
| 829 |                     if (in_array($collection->getUri(), $superGroupUris)) { | ||
| 830 | continue; | ||
| 831 | } | ||
| 832 | $property = in_array($arrayClass, $collection->types()) ? "skosmos:memberOfArray" : "skosmos:memberOf"; | ||
| 833 | $collLabel = $collection->label($this->clang) ? $collection->label($this->clang) : $collection->label(); | ||
| 834 |                     if ($collLabel) { | ||
| 835 | $collLabel = $collLabel->getValue(); | ||
| 836 | } | ||
| 837 | |||
| 838 | $group = new ConceptPropertyValue($this->model, $this->vocab, $collection, $property, $this->clang); | ||
| 839 | $groups[$collLabel] = array($group); | ||
| 840 | |||
| 841 | $res = $collection; | ||
| 842 |                     while ($super = $this->graph->resourcesMatching('skos:member', $res)) { | ||
| 843 |                         foreach ($super as $res) { | ||
| 844 | $superprop = new ConceptPropertyValue($this->model, $this->vocab, $res, 'skosmos:memberOfSuper', $this->clang); | ||
| 845 | array_unshift($groups[$collLabel], $superprop); | ||
| 846 | } | ||
| 847 | } | ||
| 848 | } | ||
| 849 | } | ||
| 850 | } | ||
| 851 | uksort($groups, 'strcoll'); | ||
| 852 | return $groups; | ||
| 853 | } | ||
| 854 | |||
| 855 | public function getArrayProperties() | ||
| 856 |     { | ||
| 857 | return $this->getCollections(true); | ||
| 858 | } | ||
| 859 | |||
| 860 | /** | ||
| 861 | * Given a language code, gets its name in UI language via Punic or alternatively | ||
| 862 | * tries to search for getText translation from Model. | ||
| 863 | * @param string $langCode | ||
| 864 | * @return string e.g. 'English' | ||
| 865 | */ | ||
| 866 | private function langToString($langCode) | ||
| 867 |     { | ||
| 868 | // using empty string as the language name when there is no langcode set | ||
| 869 | $langName = ''; | ||
| 870 |         if (!empty($langCode)) { | ||
| 871 | $langName = Punic\Language::getName($langCode, $this->getLang()) !== $langCode ? Punic\Language::getName($langCode, $this->getLang()) : $this->model->getText($langCode); | ||
| 872 | } | ||
| 873 | return $langName; | ||
| 874 | } | ||
| 875 | |||
| 876 | /** | ||
| 877 | * Gets the values of a property in all other languages than in the current language | ||
| 878 | * and places them in a [langCode][userDefinedKey] array. | ||
| 879 | * @param string $prop The property for which the values are looked upon to | ||
| 880 | * @param string $key User-defined key for accessing the values | ||
| 881 | * @return array LangCode-based multidimensional array ([string][string][ConceptPropertyValueLiteral]) or empty array if no values | ||
| 882 | */ | ||
| 883 | private function getForeignLabelList($prop, $key) | ||
| 884 |     { | ||
| 885 | $ret = array(); | ||
| 886 | $labels = $this->resource->allLiterals($prop); | ||
| 887 | |||
| 888 |         foreach ($labels as $lit) { | ||
| 889 | // filtering away subsets of the current language eg. en vs en-GB | ||
| 890 | $langCode = strval($lit->getLang()); | ||
| 891 |             if ($langCode != $this->clang && strpos($langCode, $this->getLang() . '-') !== 0) { | ||
| 892 | $ret[$langCode][$key][] = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $lit, $prop); | ||
| 893 | } | ||
| 894 | } | ||
| 895 | return $ret; | ||
| 896 | } | ||
| 897 | |||
| 898 | /** | ||
| 899 | * Gets the values of skos:prefLabel and skos:altLabel in all other languages than in the current language. | ||
| 900 | * @return array Language-based multidimensional sorted array ([string][string][ConceptPropertyValueLiteral]) or empty array if no values | ||
| 901 | */ | ||
| 902 | public function getForeignLabels() | ||
| 903 |     { | ||
| 904 |         $prefLabels = $this->getForeignLabelList('skos:prefLabel', 'prefLabel'); | ||
| 905 |         $altLabels = $this->getForeignLabelList('skos:altLabel', 'altLabel'); | ||
| 906 | $ret = array_merge_recursive($prefLabels, $altLabels); | ||
| 907 | |||
| 908 | $langArray = array_keys($ret); | ||
| 909 |         foreach ($langArray as $lang) { | ||
| 910 | $coll = collator_create($lang); | ||
| 911 |             if (isset($ret[$lang]['prefLabel'])) { | ||
| 912 | $coll->sort($ret[$lang]['prefLabel'], Collator::SORT_STRING); | ||
| 913 | } | ||
| 914 |             if (isset($ret[$lang]['altLabel'])) { | ||
| 915 | $coll->sort($ret[$lang]['altLabel'], Collator::SORT_STRING); | ||
| 916 | } | ||
| 917 |             if ($lang !== '') { | ||
| 918 | $ret[$this->langToString($lang)] = $ret[$lang]; | ||
| 919 | unset($ret[$lang]); | ||
| 920 | } | ||
| 921 | } | ||
| 922 | uksort($ret, 'strcoll'); | ||
| 923 | return $ret; | ||
| 924 | } | ||
| 925 | |||
| 926 | /** | ||
| 927 | * Gets the values for the property in question in all other languages than the ui language. | ||
| 928 | * @param string $property | ||
| 929 | * @return array array of labels for the values of the given property | ||
| 930 | */ | ||
| 931 | public function getAllLabels($property) | ||
| 932 |     { | ||
| 933 | $labels = array(); | ||
| 934 | // shortening property labels if possible, EasyRdf requires full URIs to be in angle brackets | ||
| 935 | $property = (EasyRdf\RdfNamespace::shorten($property) !== null) ? EasyRdf\RdfNamespace::shorten($property) : "<$property>"; | ||
| 936 |         foreach ($this->resource->allLiterals($property) as $lit) { | ||
| 937 | $labels[Punic\Language::getName($lit->getLang(), $this->getLang())][] = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $lit, $property); | ||
| 938 | } | ||
| 939 | ksort($labels); | ||
| 940 | return $labels; | ||
| 941 | } | ||
| 942 | |||
| 943 | /** | ||
| 944 | * Dump concept graph as JSON-LD. | ||
| 945 | */ | ||
| 946 | public function dumpJsonLd() | ||
| 947 |     { | ||
| 948 | $context = array( | ||
| 949 |             'skos' => EasyRdf\RdfNamespace::get("skos"), | ||
| 950 |             'isothes' => EasyRdf\RdfNamespace::get("isothes"), | ||
| 951 |             'rdfs' => EasyRdf\RdfNamespace::get("rdfs"), | ||
| 952 |             'owl' => EasyRdf\RdfNamespace::get("owl"), | ||
| 953 |             'dct' => EasyRdf\RdfNamespace::get("dcterms"), | ||
| 954 |             'dc11' => EasyRdf\RdfNamespace::get("dc11"), | ||
| 955 | 'uri' => '@id', | ||
| 956 | 'type' => '@type', | ||
| 957 | 'lang' => '@language', | ||
| 958 | 'value' => '@value', | ||
| 959 | 'graph' => '@graph', | ||
| 960 | 'label' => 'rdfs:label', | ||
| 961 | 'prefLabel' => 'skos:prefLabel', | ||
| 962 | 'altLabel' => 'skos:altLabel', | ||
| 963 | 'hiddenLabel' => 'skos:hiddenLabel', | ||
| 964 | 'broader' => 'skos:broader', | ||
| 965 | 'narrower' => 'skos:narrower', | ||
| 966 | 'related' => 'skos:related', | ||
| 967 | 'inScheme' => 'skos:inScheme', | ||
| 968 |             'schema' => EasyRdf\RdfNamespace::get("schema"), | ||
| 969 |             'wd' => EasyRdf\RdfNamespace::get("wd"), | ||
| 970 |             'wdt' => EasyRdf\RdfNamespace::get("wdt"), | ||
| 971 | ); | ||
| 972 |         $vocabPrefix = preg_replace('/\W+/', '', $this->vocab->getId()); // strip non-word characters | ||
| 973 | $vocabUriSpace = $this->vocab->getUriSpace(); | ||
| 974 | |||
| 975 |         if (!in_array($vocabUriSpace, $context, true)) { | ||
| 976 |             if (!isset($context[$vocabPrefix])) { | ||
| 977 | $context[$vocabPrefix] = $vocabUriSpace; | ||
| 978 |             } elseif ($context[$vocabPrefix] !== $vocabUriSpace) { | ||
| 979 | $i = 2; | ||
| 980 |                 while (isset($context[$vocabPrefix . $i]) && $context[$vocabPrefix . $i] !== $vocabUriSpace) { | ||
| 981 | $i += 1; | ||
| 982 | } | ||
| 983 | $context[$vocabPrefix . $i] = $vocabUriSpace; | ||
| 984 | } | ||
| 985 | } | ||
| 986 |         $compactJsonLD = \ML\JsonLD\JsonLD::compact($this->graph->serialise('jsonld'), json_encode($context)); | ||
| 987 | return \ML\JsonLD\JsonLD::toString($compactJsonLD); | ||
| 988 | } | ||
| 989 | |||
| 990 | public function isUseModifiedDate() | ||
| 993 | } | ||
| 994 | } | ||
| 995 |