Issues (177)

model/Concept.php (10 issues)

1
<?php
2
3
/**
4
 * Dataobject for a single concept.
5
 */
6
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
        $groupClass = $this->getVocab()->getConfig()->getGroupClassURI();
118
        if ($groupClass) {
119
            $groupClass = EasyRdf\RdfNamespace::shorten($groupClass) !== null ? EasyRdf\RdfNamespace::shorten($groupClass) : $groupClass;
0 ignored issues
show
The condition EasyRdf\RdfNamespace::sh...n($groupClass) !== null is always true.
Loading history...
120
            return in_array($groupClass, $this->getType());
121
        }
122
        return false;
123
    }
124
125
    /**
126
     * Returns a boolean value indicating if the concept has been deprecated.
127
     * @return boolean
128
     */
129
    public function getDeprecated()
130
    {
131
        $deprecatedValue = $this->resource->getLiteral('owl:deprecated');
132
        return ($deprecatedValue !== null && filter_var($deprecatedValue->getValue(), FILTER_VALIDATE_BOOLEAN));
133
    }
134
135
    /**
136
     * Returns a label for the concept in the content language or if not possible in any language.
137
     * @return string
138
     */
139
    public function getLabel()
140
    {
141
        foreach ($this->vocab->getConfig()->getLanguageOrder($this->clang) as $fallback) {
142
            if ($this->resource->label($fallback) !== null) {
143
                return $this->resource->label($fallback);
144
            }
145
            // We need to check all the labels in case one of them matches a subtag of the current language
146
            foreach($this->resource->allLiterals('skos:prefLabel') as $label) {
147
                // the label lang code is a subtag of the UI lang eg. en-GB - create a new literal with the main language
148
                if ($label !== null && strpos($label->getLang(), $fallback . '-') === 0) {
149
                    return EasyRdf\Literal::create($label, $fallback);
150
                }
151
            }
152
        }
153
154
        // Last resort: label in any language, including literal with empty language tag
155
        $label = $this->resource->label();
156
        if ($label !== null) {
157
            if (!$label->getLang()) {
158
                return $label->getValue();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $label->getValue() also could return the type DateTime|boolean which is incompatible with the documented return type string.
Loading history...
159
            }
160
            return EasyRdf\Literal::create($label->getValue(), $label->getLang());
161
        }
162
163
        // empty
164
        return "";
165
    }
166
167
    public function hasXlLabel()
168
    {
169
        $xlLabel = $this->getXlLabel();
170
        return ($xlLabel !== null && !empty($xlLabel->getProperties()));
171
    }
172
173
    public function getXlLabel()
174
    {
175
        $labels = $this->resource->allResources('skosxl:prefLabel');
176
        foreach($labels as $labres) {
177
            $label = $labres->getLiteral('skosxl:literalForm');
178
            if ($label !== null && $label->getLang() == $this->clang) {
179
                return new LabelSkosXL($this->model, $labres);
180
            }
181
        }
182
        return null;
183
    }
184
185
    /**
186
     * Returns a notation for the concept or null if it has not been defined.
187
     * @return string eg. '999'
188
     */
189
    public function getNotation()
190
    {
191
        $notation = $this->resource->get('skos:notation');
192
        if ($this->vocab->getConfig()->showNotation() && $notation !== null) {
193
            return $notation->getValue();
194
        }
195
196
        return null;
197
    }
198
199
    /**
200
     * Returns the Vocabulary object or undefined if that is not available.
201
     * @return Vocabulary
202
     */
203
    public function getVocab()
204
    {
205
        return $this->vocab;
206
    }
207
208
    /**
209
     * Returns the vocabulary shortname string or id if that is not available.
210
     * @return string
211
     */
212
    public function getShortName()
213
    {
214
        return $this->vocab ? $this->vocab->getShortName() : null;
215
    }
216
217
    /**
218
     * Returns the vocabulary shortname string or id if that is not available.
219
     * @return string
220
     */
221
    public function getVocabTitle()
222
    {
223
        return $this->vocab ? $this->vocab->getTitle() : null;
224
    }
225
226
    /**
227
     * Setter for the $clang property.
228
     * @param string $clang language code eg. 'en'
229
     */
230
    public function setContentLang($clang)
231
    {
232
        $this->clang = $clang;
233
    }
234
235
    public function getContentLang()
236
    {
237
        return $this->clang;
238
    }
239
240
    /**
241
     * Setter for the $foundby property.
242
     * @param string $label label that was matched
243
     * @param string $type type of match: 'alt', 'hidden', or 'lang'
244
     */
245
    public function setFoundBy($label, $type)
246
    {
247
        $this->foundby = $label;
248
        $this->foundbytype = $type;
249
    }
250
251
    /**
252
     * Getter for the $foundby property.
253
     * @return string
254
     */
255
    public function getFoundBy()
256
    {
257
        return $this->foundby;
258
    }
259
260
    /**
261
     * Getter for the $foundbytype property.
262
     * @return string
263
     */
264
    public function getFoundByType()
265
    {
266
        return $this->foundbytype;
267
    }
268
269
    /**
270
     * Processes a single external resource i.e., adds the properties from
271
     * 1) $this->$DEFAULT_EXT_PROPERTIES
272
     * 2) VocabConfig external properties
273
     * 3) Possible plugin defined external properties
274
     * to $this->graph
275
     * @param EasyRdf\Resource $res
276
     */
277
    public function processExternalResource($res)
278
    {
279
        $exGraph = $res->getGraph();
280
        // catch external subjects that have $res as object
281
        $extSubjects = $exGraph->resourcesMatching("schema:about", $res);
282
283
        $propList =  array_unique(array_merge(
284
            $this->DEFAULT_EXT_PROPERTIES,
285
            $this->getVocab()->getConfig()->getExtProperties(),
286
            $this->getVocab()->getConfig()->getPluginRegister()->getExtProperties()
287
        ));
288
289
        $seen = array();
290
        $this->addExternalTriplesToGraph($res, $seen, $propList);
291
        foreach ($extSubjects as $extSubject) {
292
            if ($extSubject->isBNode() && array_key_exists($extSubject->getUri(), $seen)) {
293
                // already processed, skip
294
                continue;
295
            }
296
            $seen[$extSubject->getUri()] = 1;
297
            $this->addExternalTriplesToGraph($extSubject, $seen, $propList);
298
        }
299
300
    }
301
302
    /**
303
     * Adds resource properties to $this->graph
304
     * @param EasyRdf\Resource $res
305
     * @param string[] $seen Processed resources so far
306
     * @param string[] $props (optional) limit to these property URIs
307
     */
308
    private function addExternalTriplesToGraph($res, &$seen, $props=null)
309
    {
310
        if (array_key_exists($res->getUri(), $seen) && $seen[$res->getUri()] === 0) {
0 ignored issues
show
The condition $seen[$res->getUri()] === 0 is always false.
Loading history...
311
            return;
312
        }
313
        $seen[$res->getUri()] = 0;
314
315
        if ($res->isBNode() || is_null($props)) {
316
            foreach ($res->propertyUris() as $prop) {
317
                $this->addPropertyValues($res, $prop, $seen);
318
            }
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
            {
373
                $this->addExternalTriplesToGraph($pos_reif, $seen);
374
            }
375
        }
376
    }
377
378
    /**
379
     * Adds reifications of a triple having a resource object to $this->graph
380
     * @param EasyRdf\Resource $sub
381
     * @param string $pred
382
     * @param EasyRdf\Resource $obj
383
     * @param string[] $seen Processed resources so far
384
     */
385
    private function addResourceReifications($sub, $pred, $obj, &$seen)
386
    {
387
        $pos_reifs = $sub->getGraph()->resourcesMatching("rdf:subject", $sub);
388
        foreach ($pos_reifs as $pos_reif) {
389
            if ($pos_reif->isA("rdf:Statement") &&
390
                $pos_reif->hasProperty("rdf:object", $obj) &&
391
                $pos_reif->hasProperty("rdf:predicate", new EasyRdf\Resource($pred, $sub->getGraph())))
392
            {
393
                $this->addExternalTriplesToGraph($pos_reif, $seen);
394
            }
395
        }
396
    }
397
398
    /**
399
     * @return ConceptProperty[]
400
     */
401
    public function getMappingProperties(array $whitelist = null)
402
    {
403
        $ret = array();
404
405
        $longUris = $this->resource->propertyUris();
406
        foreach ($longUris as &$prop) {
407
            if (EasyRdf\RdfNamespace::shorten($prop) !== null) {
408
                // shortening property labels if possible
409
                $prop = $sprop = EasyRdf\RdfNamespace::shorten($prop);
410
            } else {
411
                // EasyRdf requires full URIs to be in angle brackets
412
                $sprop = "<$prop>";
413
            }
414
            if ($whitelist && !in_array($prop, $whitelist)) {
415
                // whitelist in use and this is not a whitelisted property, skipping
416
                continue;
417
            }
418
419
            if (in_array($prop, $this->MAPPING_PROPERTIES) && !in_array($prop, $this->DELETED_PROPERTIES)) {
420
                $propres = new EasyRdf\Resource($prop, $this->graph);
421
                $proplabel = $propres->label($this->getEnvLang()) ? $propres->label($this->getEnvLang()) : $propres->label(); // current language
422
                $propobj = new ConceptProperty($prop, $proplabel);
423
                if ($propobj->getLabel() !== null) {
424
                    // only display properties for which we have a label
425
                    $ret[$prop] = $propobj;
426
                }
427
428
                // Iterating through every resource and adding these to the data object.
429
                foreach ($this->resource->allResources($sprop) as $val) {
430
                    if (isset($ret[$prop])) {
431
                        // checking if the target vocabulary can be found at the skosmos endpoint
432
                        $exuri = $val->getUri();
433
                        // if multiple vocabularies are found, the following method will return in priority the current vocabulary of the concept
434
                        $exvoc = $this->model->guessVocabularyFromURI($exuri, $this->vocab->getId());
435
                        // if not querying the uri itself
436
                        if (!$exvoc) {
437
                            $response = null;
438
                            // if told to do so in the vocabulary configuration
439
                            if ($this->vocab->getConfig()->getExternalResourcesLoading()) {
440
                                $response = $this->model->getResourceFromUri($exuri);
441
                            }
442
443
                            if ($response) {
444
                                $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $response, $this->resource, $prop));
445
446
                                $this->processExternalResource($response);
447
448
                                continue;
449
                            }
450
                        }
451
                        $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $val, $this->resource, $prop, $this->clang));
452
                    }
453
                }
454
            }
455
        }
456
457
        // sorting the properties to a order preferred in the Skosmos concept page.
458
        return $this->arbitrarySort($ret);
459
    }
460
461
    /**
462
     * Iterates over all the properties of the concept and returns those in an array.
463
     * @return array
464
     */
465
    public function getProperties()
466
    {
467
        $properties = array();
468
        $narrowersByUri = array();
469
        $inCollection = array();
470
        $membersArray = array();
471
        $longUris = $this->resource->propertyUris();
472
        $duplicates = array();
473
        $ret = array();
474
475
        // looking for collections and linking those with their narrower concepts
476
        if ($this->vocab->getConfig()->getArrayClassURI() !== null) {
0 ignored issues
show
The condition $this->vocab->getConfig(...rrayClassURI() !== null is always true.
Loading history...
477
            $collections = $this->graph->allOfType($this->vocab->getConfig()->getArrayClassURI());
478
            if (sizeof($collections) > 0) {
479
                // indexing the narrowers once to avoid iterating all of them with every collection
480
                foreach ($this->resource->allResources('skos:narrower') as $narrower) {
481
                    $narrowersByUri[$narrower->getUri()] = $narrower;
482
                }
483
484
                foreach ($collections as $coll) {
485
                    $currCollMembers = $this->getCollectionMembers($coll, $narrowersByUri);
486
                    foreach ($currCollMembers as $collection) {
487
                        if ($collection->getSubMembers()) {
488
                            $submembers = $collection->getSubMembers();
489
                            foreach ($submembers as $member) {
490
                                $inCollection[$member->getUri()] = true;
491
                            }
492
493
                        }
494
                    }
495
496
                    if (isset($collection) && $collection->getSubMembers()) {
497
                        $membersArray = array_merge($currCollMembers, $membersArray);
498
                    }
499
500
                }
501
                $properties['skos:narrower'] = $membersArray;
502
            }
503
        }
504
505
        foreach ($longUris as &$prop) {
506
            // storing full URI without brackets in a separate variable
507
            $longUri = $prop;
508
            if (EasyRdf\RdfNamespace::shorten($prop) !== null) {
509
                // shortening property labels if possible
510
                $prop = $sprop = EasyRdf\RdfNamespace::shorten($prop);
511
            } else {
512
                // EasyRdf requires full URIs to be in angle brackets
513
                $sprop = "<$prop>";
514
            }
515
516
            if (!in_array($prop, $this->deleted) || ($this->isGroup() === false && $prop === 'skos:member')) {
0 ignored issues
show
$this->deleted of type mixed is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

516
            if (!in_array($prop, /** @scrutinizer ignore-type */ $this->deleted) || ($this->isGroup() === false && $prop === 'skos:member')) {
Loading history...
517
                // retrieve property label and super properties from the current vocabulary first
518
                $propres = new EasyRdf\Resource($prop, $this->graph);
519
                $proplabel = $propres->label($this->getEnvLang()) ? $propres->label($this->getEnvLang()) : $propres->label();
520
521
                $prophelp = $propres->getLiteral('rdfs:comment|skos:definition', $this->getEnvLang());
522
                if ($prophelp === null) {
523
                    // try again without language restriction
524
                    $prophelp = $propres->getLiteral('rdfs:comment|skos:definition');
525
                }
526
527
                // check if the property is one of the well-known properties for which we have a gettext translation
528
                // if it is then we can skip the additional lookups in the default graph
529
                $propkey = (substr($prop, 0, 5) == 'dc11:') ?
530
                    str_replace('dc11:', 'dc:', $prop) : $prop;
531
                $is_well_known = (gettext($propkey) != $propkey);
532
533
                // if not found in current vocabulary, look up in the default graph to be able
534
                // to read an ontology loaded in a separate graph
535
                // note that this imply that the property has an rdf:type declared for the query to work
536
                if(!$is_well_known && !$proplabel) {
537
                    $envLangLabels = $this->model->getDefaultSparql()->queryLabel($longUri, $this->getEnvLang());
538
                    
539
                    $defaultPropLabel = $this->model->getDefaultSparql()->queryLabel($longUri, '');
540
541
					if($envLangLabels) {
542
						$proplabel = $envLangLabels[$this->getEnvLang()];
543
                    } else {
544
						if($defaultPropLabel) {
545
							$proplabel = $defaultPropLabel[''];
546
						}
547
					}
548
                }
549
550
                // look for superproperties in the current graph
551
                $superprops = array();
552
                foreach ($this->graph->allResources($prop, 'rdfs:subPropertyOf') as $subi) {
553
                    $superprops[] = $subi->getUri();
554
                }
555
556
                // also look up superprops in the default graph if not found in current vocabulary
557
                if(!$is_well_known && (!$superprops || empty($superprops))) {
558
                    $superprops = $this->model->getDefaultSparql()->querySuperProperties($longUri);
559
                }
560
561
                // we're reading only one super property, even if there are multiple ones
562
                $superprop = ($superprops)?$superprops[0]:null;
563
                if ($superprop) {
564
                    $superprop = EasyRdf\RdfNamespace::shorten($superprop) ? EasyRdf\RdfNamespace::shorten($superprop) : $superprop;
565
                }
566
                $sort_by_notation = $this->vocab->getConfig()->getSortByNotation();
567
                $propobj = new ConceptProperty($prop, $proplabel, $prophelp, $superprop, $sort_by_notation);
0 ignored issues
show
It seems like $sort_by_notation can also be of type string; however, parameter $sort_by_notation of ConceptProperty::__construct() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

567
                $propobj = new ConceptProperty($prop, $proplabel, $prophelp, $superprop, /** @scrutinizer ignore-type */ $sort_by_notation);
Loading history...
568
569
                if ($propobj->getLabel() !== null) {
570
                    // only display properties for which we have a label
571
                    $ret[$prop] = $propobj;
572
                }
573
574
                // searching for subproperties of literals too
575
                if($superprops) {
576
                    foreach ($superprops as $subi) {
577
                        $suburi = EasyRdf\RdfNamespace::shorten($subi) ? EasyRdf\RdfNamespace::shorten($subi) : $subi;
578
                        $duplicates[$suburi] = $prop;
579
                    }
580
                }
581
582
                // Iterating through every literal and adding these to the data object.
583
                foreach ($this->resource->allLiterals($sprop) as $val) {
584
                    $literal = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $val, $prop);
585
                    // only add literals when they match the content/hit language or have no language defined OR when they are literals of a multilingual property
586
                    if (isset($ret[$prop]) && ($literal->getLang() === $this->clang || $literal->getLang() === null) || $this->vocab->getConfig()->hasMultiLingualProperty($prop)) {
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: (IssetNode && $literal->...iLingualProperty($prop), Probably Intended Meaning: IssetNode && ($literal->...LingualProperty($prop))
Loading history...
587
                        $ret[$prop]->addValue($literal);
588
                    }
589
                }
590
591
                // Iterating through every resource and adding these to the data object.
592
                foreach ($this->resource->allResources($sprop) as $val) {
593
                    // skipping narrower concepts which are already shown in a collection
594
                    if ($sprop === 'skos:narrower' && array_key_exists($val->getUri(), $inCollection)) {
595
                        continue;
596
                    }
597
598
                    // hiding rdf:type property if it's just skos:Concept
599
                    if ($sprop === 'rdf:type' && $val->shorten() === 'skos:Concept') {
600
                        continue;
601
                    }
602
603
                    // handled by getMappingProperties()
604
                    if (in_array($sprop, $this->MAPPING_PROPERTIES)) {
605
                        continue;
606
                    }
607
608
                    if (isset($ret[$prop])) {
609
                        // create a ConceptPropertyValue first, assuming the resource exists in current vocabulary
610
                        $value = new ConceptPropertyValue($this->model, $this->vocab, $val, $prop, $this->clang);
611
                        $ret[$prop]->addValue($value);
612
                    }
613
614
                }
615
            }
616
        }
617
        // adding narrowers part of a collection
618
        foreach ($properties as $prop => $values) {
619
            foreach ($values as $value) {
620
                $ret[$prop]->addValue($value);
621
            }
622
        }
623
624
        $groupPropObj = new ConceptProperty('skosmos:memberOf', null);
625
        foreach ($this->getGroupProperties() as $propVals) {
626
            foreach ($propVals as $propVal) {
627
                $groupPropObj->addValue($propVal);
628
            }
629
        }
630
        $ret['skosmos:memberOf'] = $groupPropObj;
631
632
        $arrayPropObj = new ConceptProperty('skosmos:memberOfArray', null);
633
        foreach ($this->getArrayProperties() as $propVals) {
634
            foreach ($propVals as $propVal) {
635
                $arrayPropObj->addValue($propVal);
636
            }
637
        }
638
        $ret['skosmos:memberOfArray'] = $arrayPropObj;
639
640
        foreach ($ret as $key => $prop) {
641
            if (sizeof($prop->getValues()) === 0) {
642
                unset($ret[$key]);
643
            }
644
        }
645
646
        $ret = $this->removeDuplicatePropertyValues($ret, $duplicates);
647
        // sorting the properties to the order preferred in the Skosmos concept page.
648
        return $this->arbitrarySort($ret);
649
    }
650
651
    /**
652
     * Removes properties that have duplicate values.
653
     * @param array $ret the array of properties generated by getProperties
654
     * @param array $duplicates array of properties found are a subProperty of a another property
655
     * @return array of ConceptProperties
656
     */
657
    public function removeDuplicatePropertyValues($ret, $duplicates)
658
    {
659
        $propertyValues = array();
660
661
        foreach ($ret as $prop) {
662
            foreach ($prop->getValues() as $value) {
663
                $label = $value->getLabel();
664
                $propertyValues[(is_object($label) && method_exists($label, 'getValue')) ? $label->getValue() : $label][] = $value->getType();
665
            }
666
        }
667
668
        foreach ($propertyValues as $value => $propnames) {
669
            // if there are multiple properties with the same string value.
670
            if (count($propnames) > 1) {
671
                foreach ($propnames as $property) {
672
                    // if there is a more accurate property delete the more generic one.
673
                    if (isset($duplicates[$property])) {
674
                        unset($ret[$property]);
675
                    }
676
                }
677
678
            }
679
        }
680
        // handled separately: remove duplicate skos:prefLabel value (#854)
681
        if (isset($duplicates["skos:prefLabel"])) {
682
            unset($ret[$duplicates["skos:prefLabel"]]);
683
        }
684
        return $ret;
685
    }
686
687
    /**
688
     * @param $lang UI language
0 ignored issues
show
The type UI was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
689
     * @return String|null the translated label of skos:prefLabel subproperty, or null if not available
690
     */
691
    public function getPreferredSubpropertyLabelTranslation($lang) {
692
        $prefLabelProp = $this->graph->resource("skos:prefLabel");
693
        $subPrefLabelProps = $this->graph->resourcesMatching('rdfs:subPropertyOf', $prefLabelProp);
694
        foreach ($subPrefLabelProps as $subPrefLabelProp) {
695
            // return the first available translation
696
            if ($subPrefLabelProp->label($lang)) return $subPrefLabelProp->label($lang);
697
        }
698
        return null;
699
    }
700
701
    /**
702
     * @return DateTime|null the modified date of this concept, or null if not available
703
     */
704
    public function getConceptModifiedDate()
705
    {
706
        // finding the modified properties
707
        /** @var \EasyRdf\Resource|\EasyRdf\Literal|null $modifiedResource */
708
        $modifiedResource = $this->resource->get('dc:modified');
709
        if ($modifiedResource && $modifiedResource instanceof \EasyRdf\Literal\Date) {
710
            return $modifiedResource->getValue();
711
        }
712
713
        return null;
714
    }
715
716
717
    /**
718
     * @return DateTime|null the modified date, or null if not available
719
     */
720
    public function getModifiedDate()
721
    {
722
        // check if this concept has a specific modified date
723
        $conceptModified = $this->getConceptModifiedDate();
724
725
        if ($conceptModified !== null) {
726
            return $conceptModified;
727
        } else {
728
            // if the concept does not have a modified date, return the
729
            // modified date of the vocabulary instead
730
            return $this->getVocab()->getModifiedDate();
731
        }
732
    }
733
734
    /**
735
     * Gets the creation date and modification date if available.
736
     * @return String containing the date information in a human readable format.
737
     */
738
    public function getDate()
739
    {
740
        $ret = '';
741
        $created = '';
742
        try {
743
            // finding the created properties
744
            if ($this->resource->get('dc:created')) {
745
                $created = $this->resource->get('dc:created')->getValue();
746
            }
747
748
            $modified = $this->getConceptModifiedDate();
749
750
            // making a human readable string from the timestamps
751
            if ($created != '') {
752
                $ret = gettext('skosmos:created') . ' ' . (Punic\Calendar::formatDate($created, 'short', $this->getEnvLang()));
753
            }
754
755
            if ($modified != '') {
756
                if ($created != '') {
757
                    $ret .= ', ' . gettext('skosmos:modified') . ' ' . (Punic\Calendar::formatDate($modified, 'short', $this->getEnvLang()));
758
                } else {
759
                    $ret .= ' ' . ucfirst(gettext('skosmos:modified')) . ' ' . (Punic\Calendar::formatDate($modified, 'short', $this->getEnvLang()));
760
                }
761
762
            }
763
        } catch (Exception $e) {
764
            trigger_error($e->getMessage(), E_USER_WARNING);
765
            $ret = '';
766
            if ($this->resource->get('dc:modified')) {
767
                $modified = (string) $this->resource->get('dc:modified');
768
                $ret = gettext('skosmos:modified') . ' ' . $modified;
769
            }
770
            if ($this->resource->get('dc:created')) {
771
                $created .= (string) $this->resource->get('dc:created');
772
                $ret .= ' ' . gettext('skosmos:created') . ' ' . $created;
773
            }
774
        }
775
        return $ret;
776
    }
777
778
    /**
779
     * Gets the members of a specific collection.
780
     * @param $coll
781
     * @param array containing all narrowers as EasyRdf\Resource
0 ignored issues
show
The type containing was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
782
     * @return array containing ConceptPropertyValue objects
783
     */
784
    private function getCollectionMembers($coll, $narrowers)
785
    {
786
        $membersArray = array();
787
        if ($coll->label()) {
788
            $collLabel = $coll->label()->getValue($this->clang) ? $coll->label($this->clang) : $coll->label();
789
            if ($collLabel) {
790
                $collLabel = $collLabel->getValue();
791
            }
792
        } else {
793
            $collLabel = $coll->getUri();
794
        }
795
        $membersArray[$collLabel] = new ConceptPropertyValue($this->model, $this->vocab, $coll, 'skos:narrower', $this->clang);
796
        foreach ($coll->allResources('skos:member') as $member) {
797
            if (array_key_exists($member->getUri(), $narrowers)) {
798
                $narrower = $narrowers[$member->getUri()];
799
                if (isset($narrower)) {
800
                    $membersArray[$collLabel]->addSubMember(new ConceptPropertyValue($this->model, $this->vocab, $narrower, 'skos:member', $this->clang), $this->clang);
801
                }
802
803
            }
804
        }
805
806
        return $membersArray;
807
    }
808
809
    /**
810
     * Gets the groups the concept belongs to.
811
     */
812
    public function getGroupProperties()
813
    {
814
        return $this->getCollections(false);
815
    }
816
817
    /**
818
     * Gets the groups/arrays the concept belongs to.
819
     */
820
    private function getCollections($includeArrays) {
821
        $groups = array();
822
        $collections = $this->graph->resourcesMatching('skos:member', $this->resource);
823
        if (isset($collections)) {
824
            $arrayClassURI = $this->vocab !== null ? $this->vocab->getConfig()->getArrayClassURI() : null;
825
            $arrayClass = $arrayClassURI !== null ? EasyRdf\RdfNamespace::shorten($arrayClassURI) : null;
826
            $superGroups = $this->resource->all('isothes:superGroup');
827
            $superGroupUris = array_map(function($obj) { return $obj->getUri(); }, $superGroups);
828
            foreach ($collections as $collection) {
829
                if (in_array($arrayClass, $collection->types()) === $includeArrays) {
830
                    // not adding the memberOf if the reverse resource is already covered by isothes:superGroup see issue #433
831
                    if (in_array($collection->getUri(), $superGroupUris)) {
832
                        continue;
833
                    }
834
                    $property = in_array($arrayClass, $collection->types()) ? "skosmos:memberOfArray" : "skosmos:memberOf";
835
                    $collLabel = $collection->label($this->clang) ? $collection->label($this->clang) : $collection->label();
836
                    if ($collLabel) {
837
                        $collLabel = $collLabel->getValue();
838
                    }
839
840
                    $group = new ConceptPropertyValue($this->model, $this->vocab, $collection, $property, $this->clang);
841
                    $groups[$collLabel] = array($group);
842
843
                    $res = $collection;
844
                    while($super = $this->graph->resourcesMatching('skos:member', $res)) {
845
                        foreach ($super as $res) {
846
                            $superprop = new ConceptPropertyValue($this->model, $this->vocab, $res, 'skosmos:memberOfSuper', $this->clang);
847
                            array_unshift($groups[$collLabel], $superprop);
848
                        }
849
                    }
850
                }
851
            }
852
        }
853
        uksort($groups, 'strcoll');
854
        return $groups;
855
    }
856
857
    public function getArrayProperties() {
858
        return $this->getCollections(true);
859
    }
860
861
    /**
862
     * Given a language code, gets its name in UI language via Punic or alternatively
863
     * tries to search for a gettext translation.
864
     * @param string $langCode
865
     * @return string e.g. 'English'
866
     */
867
    private function langToString($langCode) {
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->getEnvLang()) !== $langCode ? Punic\Language::getName($langCode, $this->getEnvLang()) : 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
        $ret = array();
885
        $labels = $this->resource->allLiterals($prop);
886
887
        foreach ($labels as $lit) {
888
            // filtering away subsets of the current language eg. en vs en-GB
889
            $langCode = strval($lit->getLang());
890
            if ($langCode != $this->clang && strpos($langCode, $this->getEnvLang() . '-') !== 0) {
891
                $ret[$langCode][$key][] = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $lit, $prop);
892
            }
893
        }
894
        return $ret;
895
    }
896
897
    /**
898
     * Gets the values of skos:prefLabel and skos:altLabel in all other languages than in the current language.
899
     * @return array Language-based multidimensional sorted array ([string][string][ConceptPropertyValueLiteral]) or empty array if no values
900
     */
901
    public function getForeignLabels()
902
    {
903
        $prefLabels = $this->getForeignLabelList('skos:prefLabel', 'prefLabel');
904
        $altLabels = $this->getForeignLabelList('skos:altLabel', 'altLabel');
905
        $ret = array_merge_recursive($prefLabels, $altLabels);
906
907
        $langArray = array_keys($ret);
908
        foreach ($langArray as $lang) {
909
            $coll = collator_create($lang);
910
            if (isset($ret[$lang]['prefLabel']))
911
            {
912
                $coll->sort($ret[$lang]['prefLabel'], Collator::SORT_STRING);
913
            }
914
            if (isset($ret[$lang]['altLabel']))
915
            {
916
                $coll->sort($ret[$lang]['altLabel'], Collator::SORT_STRING);
917
            }
918
            if ($lang !== '') {
919
                $ret[$this->langToString($lang)] = $ret[$lang];
920
                unset($ret[$lang]);
921
            }
922
        }
923
        uksort($ret, 'strcoll');
924
        return $ret;
925
    }
926
927
    /**
928
     * Gets the values for the property in question in all other languages than the ui language.
929
     * @param string $property
930
     * @return array array of labels for the values of the given property
931
     */
932
    public function getAllLabels($property)
933
    {
934
        $labels = array();
935
        // shortening property labels if possible, EasyRdf requires full URIs to be in angle brackets
936
        $property = (EasyRdf\RdfNamespace::shorten($property) !== null) ? EasyRdf\RdfNamespace::shorten($property) : "<$property>";
0 ignored issues
show
The condition EasyRdf\RdfNamespace::shorten($property) !== null is always true.
Loading history...
937
        foreach ($this->resource->allLiterals($property) as $lit) {
938
            $labels[Punic\Language::getName($lit->getLang(), $this->getEnvLang())][] = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $lit, $property);
939
        }
940
        ksort($labels);
941
        return $labels;
942
    }
943
944
    /**
945
     * Dump concept graph as JSON-LD.
946
     */
947
    public function dumpJsonLd() {
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
            }
979
            else if ($context[$vocabPrefix] !== $vocabUriSpace) {
980
                $i = 2;
981
                while (isset($context[$vocabPrefix . $i]) && $context[$vocabPrefix . $i] !== $vocabUriSpace) {
982
                    $i += 1;
983
                }
984
                $context[$vocabPrefix . $i] = $vocabUriSpace;
985
            }
986
        }
987
        $compactJsonLD = \ML\JsonLD\JsonLD::compact($this->graph->serialise('jsonld'), json_encode($context));
988
        return \ML\JsonLD\JsonLD::toString($compactJsonLD);
989
    }
990
991
    public function isUseModifiedDate()
992
    {
993
        return $this->getVocab()->isUseModifiedDate();
994
    }
995
}
996