Concept::getContentLang()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
    {
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();
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...
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()
223
    {
224
        return $this->vocab ? $this->vocab->getTitle() : null;
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()
266
    {
267
        return $this->foundbytype;
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) {
0 ignored issues
show
introduced by
The condition $seen[$res->getUri()] === 0 is always false.
Loading history...
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)
385
    {
386
        $pos_reifs = $sub->getGraph()->resourcesMatching("rdf:subject", $sub);
387
        foreach ($pos_reifs as $pos_reif) {
388
            if ($pos_reif->isA("rdf:Statement") &&
389
                $pos_reif->hasProperty("rdf:object", $obj) &&
390
                $pos_reif->hasProperty("rdf:predicate", new EasyRdf\Resource($pred, $sub->getGraph()))) {
391
                $this->addExternalTriplesToGraph($pos_reif, $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) {
0 ignored issues
show
Bug introduced by
The expression $this->resource->propertyUris() cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
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);
0 ignored issues
show
Bug introduced by
$this->model of type Model is incompatible with the type string expected by parameter $model of ConceptProperty::__construct(). ( Ignorable by Annotation )

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

416
                $propobj = new ConceptProperty(/** @scrutinizer ignore-type */ $this->model, $prop, $proplabel);
Loading history...
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) {
0 ignored issues
show
introduced by
The condition $arrayClassUri === null is always false.
Loading history...
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) {
0 ignored issues
show
Bug introduced by
The expression $this->resource->propertyUris() cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
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')) {
0 ignored issues
show
Bug introduced by
$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

530
            if (!in_array($prop, /** @scrutinizer ignore-type */ $this->deleted) || ($this->isGroup() === false && $prop === 'skos:member')) {
Loading history...
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);
0 ignored issues
show
Bug introduced by
$this->model of type Model is incompatible with the type string expected by parameter $model of ConceptProperty::__construct(). ( Ignorable by Annotation )

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

578
                $propobj = new ConceptProperty(/** @scrutinizer ignore-type */ $this->model, $prop, $proplabel, $prophelp, $superprop, $sort_by_notation);
Loading history...
Bug introduced by
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

578
                $propobj = new ConceptProperty($this->model, $prop, $proplabel, $prophelp, $superprop, /** @scrutinizer ignore-type */ $sort_by_notation);
Loading history...
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)) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (IssetNode && $literal->...iLingualProperty($prop), Probably Intended Meaning: IssetNode && ($literal->...LingualProperty($prop))
Loading history...
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
0 ignored issues
show
Bug introduced by
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...
699
     * @return String|null the translated label of skos:prefLabel subproperty, or null if not available
700
     */
701
    public function getPreferredSubpropertyLabelTranslation($lang)
702
    {
703
        $prefLabelProp = $this->graph->resource("skos:prefLabel");
704
        $subPrefLabelProps = $this->graph->resourcesMatching('rdfs:subPropertyOf', $prefLabelProp);
705
        foreach ($subPrefLabelProps as $subPrefLabelProp) {
706
            // return the first available translation
707
            if ($subPrefLabelProp->label($lang)) {
708
                return $subPrefLabelProp->label($lang);
709
            }
710
        }
711
        return null;
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
0 ignored issues
show
Bug introduced by
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...
779
     * @return array containing ConceptPropertyValue objects
780
     */
781
    private function getCollectionMembers($coll, $narrowers)
782
    {
783
        $membersArray = array();
784
        if ($coll->label()) {
785
            $collLabel = $coll->label()->getValue($this->clang) ? $coll->label($this->clang) : $coll->label();
786
            if ($collLabel) {
787
                $collLabel = $collLabel->getValue();
788
            }
789
        } else {
790
            $collLabel = $coll->getUri();
791
        }
792
        $membersArray[$collLabel] = new ConceptPropertyValue($this->model, $this->vocab, $coll, 'skos:narrower', $this->clang);
793
        foreach ($coll->allResources('skos:member') as $member) {
794
            if (array_key_exists($member->getUri(), $narrowers)) {
795
                $narrower = $narrowers[$member->getUri()];
796
                if (isset($narrower)) {
797
                    $membersArray[$collLabel]->addSubMember(new ConceptPropertyValue($this->model, $this->vocab, $narrower, 'skos:member', $this->clang), $this->clang);
798
                }
799
800
            }
801
        }
802
803
        return $membersArray;
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>";
0 ignored issues
show
introduced by
The condition EasyRdf\RdfNamespace::shorten($property) !== null is always true.
Loading history...
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()
991
    {
992
        return $this->getVocab()->isUseModifiedDate();
993
    }
994
}
995