Passed
Pull Request — main (#1918)
by Osma
33:55 queued 27:58
created

Concept::getXlLabel()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 3
nop 0
dl 0
loc 10
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 an array of dct:isReplacedBy values
138
     * @return ConceptPropertyValue[]
139
     */
140
    public function getIsReplacedBy()
141
    {
142
        $values = [];
143
        foreach ($this->resource->allResources('dc:isReplacedBy') as $res) {
144
            $values[] = new ConceptPropertyValue($this->model, $this->vocab, $res, 'dc:isReplacedBy', $this->clang);
145
        }
146
        return $values;
147
    }
148
149
    /**
150
     * Returns a label for the concept in the content language or if not possible in any language.
151
     * @return string
152
     */
153
    public function getLabel()
154
    {
155
        foreach ($this->vocab->getConfig()->getLanguageOrder($this->clang) as $fallback) {
156
            if ($this->resource->label($fallback) !== null) {
157
                return $this->resource->label($fallback);
158
            }
159
            // We need to check all the labels in case one of them matches a subtag of the current language
160
            foreach ($this->resource->allLiterals('skos:prefLabel') as $label) {
161
                // the label lang code is a subtag of the UI lang eg. en-GB - create a new literal with the main language
162
                if ($label !== null && strpos($label->getLang(), $fallback . '-') === 0) {
163
                    return EasyRdf\Literal::create($label, $fallback);
164
                }
165
            }
166
        }
167
168
        // Last resort: label in any language, including literal with empty language tag
169
        $label = $this->resource->label();
170
        if ($label !== null) {
171
            if (!$label->getLang()) {
172
                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...
173
            }
174
            return EasyRdf\Literal::create($label->getValue(), $label->getLang());
175
        }
176
177
        // empty
178
        return "";
179
    }
180
181
    public function hasXlLabel()
182
    {
183
        $xlLabel = $this->getXlLabel();
184
        return ($xlLabel !== null && !empty($xlLabel->getProperties()));
185
    }
186
187
    public function getXlLabel()
188
    {
189
        $labels = $this->resource->allResources('skosxl:prefLabel');
190
        foreach ($labels as $labres) {
191
            $label = $labres->getLiteral('skosxl:literalForm');
192
            if ($label !== null && $label->getLang() == $this->clang) {
193
                return new LabelSkosXL($this->model, $labres);
194
            }
195
        }
196
        return null;
197
    }
198
199
    /**
200
     * Returns a notation for the concept or null if it has not been defined.
201
     * @return string eg. '999'
202
     */
203
    public function getNotation()
204
    {
205
        $notation = $this->resource->get('skos:notation');
206
        if ($this->vocab->getConfig()->showNotation() && $notation !== null) {
207
            return $notation->getValue();
208
        }
209
210
        return null;
211
    }
212
213
    /**
214
     * Returns the Vocabulary object or undefined if that is not available.
215
     * @return Vocabulary
216
     */
217
    public function getVocab()
218
    {
219
        return $this->vocab;
220
    }
221
222
    /**
223
     * Returns the vocabulary shortname string or id if that is not available.
224
     * @return string
225
     */
226
    public function getShortName()
227
    {
228
        return $this->vocab ? $this->vocab->getShortName() : null;
229
    }
230
231
    /**
232
     * Returns the vocabulary shortname string or id if that is not available.
233
     * @return string
234
     */
235
    public function getVocabTitle()
236
    {
237
        return $this->vocab ? $this->vocab->getTitle() : null;
238
    }
239
240
    /**
241
     * Setter for the $clang property.
242
     * @param string $clang language code eg. 'en'
243
     */
244
    public function setContentLang($clang)
245
    {
246
        $this->clang = $clang;
247
    }
248
249
    public function getContentLang()
250
    {
251
        return $this->clang;
252
    }
253
254
    /**
255
     * Setter for the $foundby property.
256
     * @param string $label label that was matched
257
     * @param string $type type of match: 'alt', 'hidden', or 'lang'
258
     */
259
    public function setFoundBy($label, $type)
260
    {
261
        $this->foundby = $label;
262
        $this->foundbytype = $type;
263
    }
264
265
    /**
266
     * Getter for the $foundby property.
267
     * @return string
268
     */
269
    public function getFoundBy()
270
    {
271
        return $this->foundby;
272
    }
273
274
    /**
275
     * Getter for the $foundbytype property.
276
     * @return string
277
     */
278
    public function getFoundByType()
279
    {
280
        return $this->foundbytype;
281
    }
282
283
    /**
284
     * Processes a single external resource i.e., adds the properties from
285
     * 1) $this->$DEFAULT_EXT_PROPERTIES
286
     * 2) VocabConfig external properties
287
     * 3) Possible plugin defined external properties
288
     * to $this->graph
289
     * @param EasyRdf\Resource $res
290
     */
291
    public function processExternalResource($res)
292
    {
293
        $exGraph = $res->getGraph();
294
        // catch external subjects that have $res as object
295
        $extSubjects = $exGraph->resourcesMatching("schema:about", $res);
296
297
        $propList =  array_unique(array_merge(
298
            $this->DEFAULT_EXT_PROPERTIES,
299
            $this->getVocab()->getConfig()->getExtProperties(),
300
            $this->getVocab()->getConfig()->getPluginRegister()->getExtProperties()
301
        ));
302
303
        $seen = array();
304
        $this->addExternalTriplesToGraph($res, $seen, $propList);
305
        foreach ($extSubjects as $extSubject) {
306
            if ($extSubject->isBNode() && array_key_exists($extSubject->getUri(), $seen)) {
307
                // already processed, skip
308
                continue;
309
            }
310
            $seen[$extSubject->getUri()] = 1;
311
            $this->addExternalTriplesToGraph($extSubject, $seen, $propList);
312
        }
313
314
    }
315
316
    /**
317
     * Adds resource properties to $this->graph
318
     * @param EasyRdf\Resource $res
319
     * @param string[] $seen Processed resources so far
320
     * @param string[] $props (optional) limit to these property URIs
321
     */
322
    private function addExternalTriplesToGraph($res, &$seen, $props = null)
323
    {
324
        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...
325
            return;
326
        }
327
        $seen[$res->getUri()] = 0;
328
329
        if ($res->isBNode() || is_null($props)) {
330
            foreach ($res->propertyUris() as $prop) {
331
                $this->addPropertyValues($res, $prop, $seen);
332
            }
333
        } else {
334
            foreach ($props as $prop) {
335
                if ($res->hasProperty($prop)) {
336
                    $this->addPropertyValues($res, $prop, $seen);
337
                }
338
            }
339
        }
340
    }
341
342
    /**
343
     * Adds values of a single single property of a resource to $this->graph
344
     * implements Concise Bounded Description definition
345
     * @param EasyRdf\Resource $res
346
     * @param string $prop
347
     * @param string[] $seen Processed resources so far
348
     */
349
    private function addPropertyValues($res, $prop, &$seen)
350
    {
351
        $resList = $res->allResources('<' . $prop . '>');
352
353
        foreach ($resList as $res2) {
354
            if ($res2->isBNode()) {
355
                $this->addExternalTriplesToGraph($res2, $seen);
356
            }
357
            $this->graph->addResource($res, $prop, $res2);
358
            $this->addResourceReifications($res, $prop, $res2, $seen);
359
        }
360
361
        $litList = $res->allLiterals('<' . $prop . '>');
362
363
        foreach ($litList as $lit) {
364
            $this->graph->addLiteral($res, $prop, $lit);
365
            $this->addLiteralReifications($res, $prop, $lit, $seen);
366
        }
367
    }
368
369
    /**
370
     * Adds reifications of a triple having a literal object to $this->graph
371
     * @param EasyRdf\Resource $sub
372
     * @param string $pred
373
     * @param EasyRdf\Literal $obj
374
     * @param string[] $seen Processed resources so far
375
     */
376
    private function addLiteralReifications($sub, $pred, $obj, &$seen)
377
    {
378
        $pos_reifs = $sub->getGraph()->resourcesMatching("rdf:subject", $sub);
379
        foreach ($pos_reifs as $pos_reif) {
380
            $lit = $pos_reif->getLiteral("rdf:object", $obj->getLang());
381
382
            if (!is_null($lit) && $lit->getValue() === $obj->getValue() &&
383
                $pos_reif->isA("rdf:Statement") &&
384
                $pos_reif->hasProperty("rdf:predicate", new EasyRdf\Resource($pred, $sub->getGraph()))) {
385
                $this->addExternalTriplesToGraph($pos_reif, $seen);
386
            }
387
        }
388
    }
389
390
    /**
391
     * Adds reifications of a triple having a resource object to $this->graph
392
     * @param EasyRdf\Resource $sub
393
     * @param string $pred
394
     * @param EasyRdf\Resource $obj
395
     * @param string[] $seen Processed resources so far
396
     */
397
    private function addResourceReifications($sub, $pred, $obj, &$seen)
398
    {
399
        $pos_reifs = $sub->getGraph()->resourcesMatching("rdf:subject", $sub);
400
        foreach ($pos_reifs as $pos_reif) {
401
            if ($pos_reif->isA("rdf:Statement") &&
402
                $pos_reif->hasProperty("rdf:object", $obj) &&
403
                $pos_reif->hasProperty("rdf:predicate", new EasyRdf\Resource($pred, $sub->getGraph()))) {
404
                $this->addExternalTriplesToGraph($pos_reif, $seen);
405
            }
406
        }
407
    }
408
409
    /**
410
     * @param string[]|null $allowedProperties List of properties to include, or null to include all.
411
     * @return ConceptProperty[]
412
     */
413
    public function getMappingProperties(array $allowedProperties = null)
414
    {
415
        $ret = array();
416
417
        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...
418
            $prop = $this->shortenOrKeep($longUri);
419
            // EasyRdf requires full URIs to be in angle brackets
420
            $sprop = ($prop == $longUri) ? "<$longUri>" : $prop;
421
            if ($allowedProperties && !in_array($prop, $allowedProperties)) {
422
                // allowedProperties in use and this is not an allowed property, skipping
423
                continue;
424
            }
425
426
            if (in_array($prop, $this->MAPPING_PROPERTIES) && !in_array($prop, $this->DELETED_PROPERTIES)) {
427
                $propres = new EasyRdf\Resource($prop, $this->graph);
428
                $proplabel = $propres->label($this->getLang()) ? $propres->label($this->getLang()) : $propres->label(); // current language
429
                $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

429
                $propobj = new ConceptProperty(/** @scrutinizer ignore-type */ $this->model, $prop, $proplabel);
Loading history...
430
                if ($propobj->getLabel() !== null) {
431
                    // only display properties for which we have a label
432
                    $ret[$prop] = $propobj;
433
                }
434
435
                // Iterating through every resource and adding these to the data object.
436
                foreach ($this->resource->allResources($sprop) as $resourceValue) {
437
                    if (isset($ret[$prop])) {
438
                        // checking if the target vocabulary can be found at the skosmos endpoint
439
                        $exuri = $resourceValue->getUri();
440
                        // if multiple vocabularies are found, the following method will return in priority the current vocabulary of the concept
441
                        $exvoc = $this->model->guessVocabularyFromURI($exuri, $this->vocab->getId());
442
                        // if not querying the uri itself
443
                        if (!$exvoc) {
444
                            $response = null;
445
                            // if told to do so in the vocabulary configuration
446
                            if ($this->vocab->getConfig()->getExternalResourcesLoading()) {
447
                                $response = $this->model->getResourceFromUri($exuri);
448
                            }
449
450
                            if ($response) {
451
                                $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $response, $this->resource, $prop));
452
453
                                $this->processExternalResource($response);
454
455
                                continue;
456
                            }
457
                        }
458
                        $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $resourceValue, $this->resource, $prop, $this->clang));
459
                    }
460
                }
461
            }
462
        }
463
464
        // sorting the properties to a order preferred in the Skosmos concept page.
465
        return $this->arbitrarySort($ret);
466
    }
467
468
    private function shortenOrKeep($uri)
469
    {
470
        return EasyRdf\RdfNamespace::shorten($uri) ?? $uri;
471
    }
472
473
    private function getCollectionMembersInUse(): array
474
    {
475
        $inCollection = [];
476
        $membersArray = [];
477
478
        $arrayClassUri = $this->vocab->getConfig()->getArrayClassURI();
479
        if ($arrayClassUri === null) {
0 ignored issues
show
introduced by
The condition $arrayClassUri === null is always false.
Loading history...
480
            return ['inCollection' => $inCollection, 'membersArray' => $membersArray];
481
        }
482
483
        $collections = $this->graph->allOfType($arrayClassUri);
484
        if (count($collections) === 0) {
485
            return ['inCollection' => $inCollection, 'membersArray' => $membersArray];
486
        }
487
488
        // Index narrowers by URI
489
        $narrowersByUri = [];
490
        foreach ($this->resource->allResources('skos:narrower') as $narrower) {
491
            $narrowersByUri[$narrower->getUri()] = $narrower;
492
        }
493
494
        foreach ($collections as $coll) {
495
            $currCollMembers = $this->getCollectionMembers($coll, $narrowersByUri);
496
497
            foreach ($currCollMembers as $collection) {
498
                if ($collection->getSubMembers()) {
499
                    foreach ($collection->getSubMembers() as $member) {
500
                        $inCollection[$member->getUri()] = true;
501
                    }
502
                }
503
            }
504
505
            if (isset($collection) && $collection->getSubMembers()) {
506
                $membersArray = array_merge($membersArray, $currCollMembers);
507
            }
508
        }
509
510
        return ['inCollection' => $inCollection, 'membersArray' => $membersArray];
511
    }
512
513
514
    /**
515
     * Iterates over all the properties of the concept and returns those in an array.
516
     * @param string[]|null $allowedProperties List of properties to include, or null to include all.
517
     * @return array
518
     */
519
    public function getProperties($allowedProperties = null)
520
    {
521
        $properties = array();
522
        $duplicates = array();
523
        $propertiesWithValues = array();
524
525
        $collectionData = $this->getCollectionMembersInUse();
526
        $inCollection = $collectionData['inCollection'];
527
        $membersArray = $collectionData['membersArray'];
528
529
        if (!empty($membersArray)) {
530
            $properties['skos:narrower'] = $membersArray;
531
        }
532
533
        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...
534
            // storing full URI without brackets in a separate variable
535
            $prop = $this->shortenOrKeep($longUri);
536
            // EasyRdf requires full URIs to be in angle brackets
537
            $sprop = ($prop == $longUri) ? "<$longUri>" : $prop;
538
539
            if ($allowedProperties !== null && !in_array($prop, $allowedProperties)) {
540
                continue;
541
            }
542
543
            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

543
            if (!in_array($prop, /** @scrutinizer ignore-type */ $this->deleted) || ($this->isGroup() === false && $prop === 'skos:member')) {
Loading history...
544
                // retrieve property label and super properties from the current vocabulary first
545
                $propertyResource = new EasyRdf\Resource($prop, $this->graph);
546
                $proplabel = $propertyResource->label($this->getLang()) ? $propertyResource->label($this->getLang()) : $propertyResource->label();
547
548
                $prophelp = $propertyResource->getLiteral('rdfs:comment|skos:definition', $this->getLang());
549
                if ($prophelp === null) {
550
                    // try again without language restriction
551
                    $prophelp = $propertyResource->getLiteral('rdfs:comment|skos:definition');
552
                }
553
554
                // check if the property is one of the well-known properties for which we have a translation
555
                // if it is then we can skip the additional lookups in the default graph
556
                $propkey = (substr($prop, 0, 5) == 'dc11:') ?
557
                    str_replace('dc11:', 'dc:', $prop) : $prop;
558
                $is_well_known = ($this->model->getText($propkey) != $propkey);
559
560
                // if not found in current vocabulary, look up in the default graph to be able
561
                // to read an ontology loaded in a separate graph
562
                // note that this imply that the property has an rdf:type declared for the query to work
563
                if (!$is_well_known && !$proplabel) {
564
                    $envLangLabels = $this->model->getDefaultSparql()->queryLabel($longUri, $this->getLang());
565
566
                    $defaultPropLabel = $this->model->getDefaultSparql()->queryLabel($longUri, '');
567
568
                    if ($envLangLabels) {
569
                        $proplabel = $envLangLabels[$this->getLang()];
570
                    } else {
571
                        if ($defaultPropLabel) {
572
                            $proplabel = $defaultPropLabel[''];
573
                        }
574
                    }
575
                }
576
577
                // look for superproperties in the current graph
578
                $superprops = array();
579
                foreach ($this->graph->allResources($prop, 'rdfs:subPropertyOf') as $superProperty) {
580
                    $superprops[] = $superProperty->getUri();
581
                }
582
583
                // also look up superprops in the default graph if not found in current vocabulary
584
                if (!$is_well_known && (!$superprops || empty($superprops))) {
585
                    $superprops = $this->model->getDefaultSparql()->querySuperProperties($longUri);
586
                }
587
588
                // we're reading only one super property, even if there are multiple ones
589
                $superprop = ($superprops) ? $this->shortenOrKeep($superprops[0]) : null;
590
                $sort_by_notation = $this->vocab->getConfig()->getSortByNotation();
591
                $propobj = new ConceptProperty($this->model, $prop, $proplabel, $prophelp, $superprop, $sort_by_notation);
0 ignored issues
show
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

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

591
                $propobj = new ConceptProperty(/** @scrutinizer ignore-type */ $this->model, $prop, $proplabel, $prophelp, $superprop, $sort_by_notation);
Loading history...
592
593
                if ($propobj->getLabel() !== null) {
594
                    // only display properties for which we have a label
595
                    $propertiesWithValues[$prop] = $propobj;
596
                }
597
598
                // searching for subproperties of literals too
599
                if ($superprops) {
600
                    foreach ($superprops as $superProperty) {
601
                        $duplicates[$this->shortenOrKeep($superProperty)] = $prop;
602
                    }
603
                }
604
605
                // Iterating through every literal and adding these to the data object.
606
                foreach ($this->resource->allLiterals($sprop) as $literalValue) {
607
                    $literal = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $literalValue, $prop);
608
                    // only add literals when they match the content/hit language or have no language defined OR when they are literals of a multilingual property
609
                    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...
610
                        $propertiesWithValues[$prop]->addValue($literal);
611
                    }
612
                }
613
614
                // Iterating through every resource and adding these to the data object.
615
                foreach ($this->resource->allResources($sprop) as $resourceValue) {
616
                    // skipping narrower concepts which are already shown in a collection
617
                    if ($sprop === 'skos:narrower' && array_key_exists($resourceValue->getUri(), $inCollection)) {
618
                        continue;
619
                    }
620
621
                    // hiding rdf:type property if it's just skos:Concept
622
                    if ($sprop === 'rdf:type' && $resourceValue->shorten() === 'skos:Concept') {
623
                        continue;
624
                    }
625
626
                    // handled by getMappingProperties()
627
                    if (in_array($sprop, $this->MAPPING_PROPERTIES)) {
628
                        continue;
629
                    }
630
631
                    if (isset($propertiesWithValues[$prop])) {
632
                        // create a ConceptPropertyValue first, assuming the resource exists in current vocabulary
633
                        $value = new ConceptPropertyValue($this->model, $this->vocab, $resourceValue, $prop, $this->clang);
634
                        $propertiesWithValues[$prop]->addValue($value);
635
                    }
636
637
                }
638
            }
639
        }
640
        // adding narrowers part of a collection
641
        foreach ($properties as $prop => $values) {
642
            foreach ($values as $value) {
643
                $propertiesWithValues[$prop]->addValue($value);
644
            }
645
        }
646
647
        $groupPropObj = new ConceptProperty($this->model, 'skosmos:memberOf', null);
648
        foreach ($this->getGroupProperties() as $propVals) {
649
            foreach ($propVals as $propVal) {
650
                $groupPropObj->addValue($propVal);
651
            }
652
        }
653
        $propertiesWithValues['skosmos:memberOf'] = $groupPropObj;
654
655
        $arrayPropObj = new ConceptProperty($this->model, 'skosmos:memberOfArray', null);
656
        foreach ($this->getArrayProperties() as $propVals) {
657
            foreach ($propVals as $propVal) {
658
                $arrayPropObj->addValue($propVal);
659
            }
660
        }
661
        $propertiesWithValues['skosmos:memberOfArray'] = $arrayPropObj;
662
663
        foreach ($propertiesWithValues as $key => $prop) {
664
            if (sizeof($prop->getValues()) === 0) {
665
                unset($propertiesWithValues[$key]);
666
            }
667
        }
668
669
        $propertiesWithValues = $this->removeDuplicatePropertyValues($propertiesWithValues, $duplicates);
670
        // sorting the properties to the order preferred in the Skosmos concept page.
671
        return $this->arbitrarySort($propertiesWithValues);
672
    }
673
674
    /**
675
     * Removes properties that have duplicate values.
676
     * @param array $propertiesWithValues the array of properties generated by getProperties
677
     * @param array $duplicates array of properties found are a subProperty of a another property
678
     * @return array of ConceptProperties
679
     */
680
    public function removeDuplicatePropertyValues($propertiesWithValues, $duplicates)
681
    {
682
        $propertyValues = array();
683
684
        foreach ($propertiesWithValues as $prop) {
685
            foreach ($prop->getValues() as $value) {
686
                $label = $value->getLabel(allowExternal: false);
687
                $propertyValues[(is_object($label) && method_exists($label, 'getValue')) ? $label->getValue() : $label][] = $value->getType();
688
            }
689
        }
690
691
        foreach ($propertyValues as $value => $propnames) {
692
            // if there are multiple properties with the same string value.
693
            if (count($propnames) > 1) {
694
                foreach ($propnames as $property) {
695
                    // if there is a more accurate property delete the more generic one.
696
                    if (isset($duplicates[$property])) {
697
                        unset($propertiesWithValues[$property]);
698
                    }
699
                }
700
701
            }
702
        }
703
        // handled separately: remove duplicate skos:prefLabel value (#854)
704
        if (isset($duplicates["skos:prefLabel"])) {
705
            unset($propertiesWithValues[$duplicates["skos:prefLabel"]]);
706
        }
707
        return $propertiesWithValues;
708
    }
709
710
    /**
711
     * @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...
712
     * @return String|null the translated label of skos:prefLabel subproperty, or null if not available
713
     */
714
    public function getPreferredSubpropertyLabelTranslation($lang)
715
    {
716
        $prefLabelProp = $this->graph->resource("skos:prefLabel");
717
        $subPrefLabelProps = $this->graph->resourcesMatching('rdfs:subPropertyOf', $prefLabelProp);
718
        foreach ($subPrefLabelProps as $subPrefLabelProp) {
719
            // return the first available translation
720
            if ($subPrefLabelProp->label($lang)) {
721
                return $subPrefLabelProp->label($lang);
722
            }
723
        }
724
        return null;
725
    }
726
727
    /**
728
     * @return DateTime|null the modified date, or null if not available
729
     */
730
    public function getModifiedDate()
731
    {
732
        // finding the modified properties
733
        /** @var \EasyRdf\Resource|\EasyRdf\Literal|null $modifiedResource */
734
        $modifiedResource = $this->resource->get('dc:modified');
735
        if ($modifiedResource && $modifiedResource instanceof \EasyRdf\Literal\Date) {
736
            return $modifiedResource->getValue();
737
        }
738
739
        // if the concept does not have a modified date, we look for it in its
740
        // vocabulary
741
        return $this->getVocab()->getModifiedDate();
742
    }
743
744
    /**
745
     * Gets the creation date and modification date if available.
746
     * @return String containing the date information in a human readable format.
747
     */
748
    public function getDate()
749
    {
750
        $ret = '';
751
        $created = '';
752
        try {
753
            // finding the created properties
754
            if ($this->resource->get('dc:created')) {
755
                $created = $this->resource->get('dc:created')->getValue();
756
            }
757
758
            $modified = $this->getModifiedDate();
759
760
            // making a human readable string from the timestamps
761
            if ($created != '') {
762
                $ret = $this->model->getText('skosmos:created') . ' ' . (Punic\Calendar::formatDate($created, 'short', $this->getLang()));
763
            }
764
765
            if ($modified != '') {
766
                if ($created != '') {
767
                    $ret .= ', ' . $this->model->getText('skosmos:modified') . ' ' . (Punic\Calendar::formatDate($modified, 'short', $this->getLang()));
768
                } else {
769
                    $ret .= ' ' . ucfirst($this->model->getText('skosmos:modified')) . ' ' . (Punic\Calendar::formatDate($modified, 'short', $this->getLang()));
770
                }
771
772
            }
773
        } catch (Exception $e) {
774
            trigger_error($e->getMessage(), E_USER_WARNING);
775
            $ret = '';
776
            if ($this->resource->get('dc:modified')) {
777
                $modified = (string) $this->resource->get('dc:modified');
778
                $ret = $this->model->getText('skosmos:modified') . ' ' . $modified;
779
            }
780
            if ($this->resource->get('dc:created')) {
781
                $created .= (string) $this->resource->get('dc:created');
782
                $ret .= ' ' . $this->model->getText('skosmos:created') . ' ' . $created;
783
            }
784
        }
785
        return $ret;
786
    }
787
788
    /**
789
     * Gets the members of a specific collection.
790
     * @param $coll
791
     * @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...
792
     * @return array containing ConceptPropertyValue objects
793
     */
794
    private function getCollectionMembers($coll, $narrowers)
795
    {
796
        $membersArray = array();
797
        if ($coll->label()) {
798
            $collLabel = $coll->label()->getValue($this->clang) ? $coll->label($this->clang) : $coll->label();
799
            if ($collLabel) {
800
                $collLabel = $collLabel->getValue();
801
            }
802
        } else {
803
            $collLabel = $coll->getUri();
804
        }
805
        $membersArray[$collLabel] = new ConceptPropertyValue($this->model, $this->vocab, $coll, 'skos:narrower', $this->clang);
806
        foreach ($coll->allResources('skos:member') as $member) {
807
            if (array_key_exists($member->getUri(), $narrowers)) {
808
                $narrower = $narrowers[$member->getUri()];
809
                if (isset($narrower)) {
810
                    $membersArray[$collLabel]->addSubMember(new ConceptPropertyValue($this->model, $this->vocab, $narrower, 'skos:member', $this->clang), $this->clang);
811
                }
812
813
            }
814
        }
815
816
        return $membersArray;
817
    }
818
819
    /**
820
     * Gets the groups the concept belongs to.
821
     */
822
    public function getGroupProperties()
823
    {
824
        return $this->getCollections(false);
825
    }
826
827
    /**
828
     * Gets the groups/arrays the concept belongs to.
829
     */
830
    private function getCollections($includeArrays)
831
    {
832
        $groups = array();
833
        $collections = $this->graph->resourcesMatching('skos:member', $this->resource);
834
        if (isset($collections)) {
835
            $arrayClassURI = $this->vocab !== null ? $this->vocab->getConfig()->getArrayClassURI() : null;
836
            $arrayClass = $arrayClassURI !== null ? EasyRdf\RdfNamespace::shorten($arrayClassURI) : null;
837
            $superGroups = $this->resource->all('isothes:superGroup');
838
            $superGroupUris = array_map(function ($obj) { return $obj->getUri(); }, $superGroups);
839
            foreach ($collections as $collection) {
840
                if (in_array($arrayClass, $collection->types()) === $includeArrays) {
841
                    // not adding the memberOf if the reverse resource is already covered by isothes:superGroup see issue #433
842
                    if (in_array($collection->getUri(), $superGroupUris)) {
843
                        continue;
844
                    }
845
                    $property = in_array($arrayClass, $collection->types()) ? "skosmos:memberOfArray" : "skosmos:memberOf";
846
                    $collLabel = $collection->label($this->clang) ? $collection->label($this->clang) : $collection->label();
847
                    if ($collLabel) {
848
                        $collLabel = $collLabel->getValue();
849
                    }
850
851
                    $group = new ConceptPropertyValue($this->model, $this->vocab, $collection, $property, $this->clang);
852
                    $groups[$collLabel] = array($group);
853
854
                    $res = $collection;
855
                    while ($super = $this->graph->resourcesMatching('skos:member', $res)) {
856
                        foreach ($super as $res) {
857
                            $superprop = new ConceptPropertyValue($this->model, $this->vocab, $res, 'skosmos:memberOfSuper', $this->clang);
858
                            array_unshift($groups[$collLabel], $superprop);
859
                        }
860
                    }
861
                }
862
            }
863
        }
864
        uksort($groups, 'strcoll');
865
        return $groups;
866
    }
867
868
    public function getArrayProperties()
869
    {
870
        return $this->getCollections(true);
871
    }
872
873
    /**
874
     * Given a language code, gets its name in UI language via Punic or alternatively
875
     * tries to search for getText translation from Model.
876
     * @param string $langCode
877
     * @return string e.g. 'English'
878
     */
879
    private function langToString($langCode)
880
    {
881
        // using empty string as the language name when there is no langcode set
882
        $langName = '';
883
        if (!empty($langCode)) {
884
            $langName = Punic\Language::getName($langCode, $this->getLang()) !== $langCode ? Punic\Language::getName($langCode, $this->getLang()) : $this->model->getText($langCode);
885
        }
886
        return $langName;
887
    }
888
889
    /**
890
     * Gets the values of a property in all other languages than in the current language
891
     * and places them in a [langCode][userDefinedKey] array.
892
     * @param string $prop The property for which the values are looked upon to
893
     * @param string $key User-defined key for accessing the values
894
     * @return array LangCode-based multidimensional array ([string][string][ConceptPropertyValueLiteral]) or empty array if no values
895
     */
896
    private function getForeignLabelList($prop, $key)
897
    {
898
        $ret = array();
899
        $labels = $this->resource->allLiterals($prop);
900
901
        foreach ($labels as $lit) {
902
            // filtering away subsets of the current language eg. en vs en-GB
903
            $langCode = strval($lit->getLang());
904
            if ($langCode != $this->clang && strpos($langCode, $this->getLang() . '-') !== 0) {
905
                $ret[$langCode][$key][] = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $lit, $prop);
906
            }
907
        }
908
        return $ret;
909
    }
910
911
    /**
912
     * Gets the values of skos:prefLabel and skos:altLabel in all other languages than in the current language.
913
     * @return array Language-based multidimensional sorted array ([string][string][ConceptPropertyValueLiteral]) or empty array if no values
914
     */
915
    public function getForeignLabels()
916
    {
917
        $prefLabels = $this->getForeignLabelList('skos:prefLabel', 'prefLabel');
918
        $altLabels = $this->getForeignLabelList('skos:altLabel', 'altLabel');
919
        $ret = array_merge_recursive($prefLabels, $altLabels);
920
921
        $langArray = array_keys($ret);
922
        foreach ($langArray as $lang) {
923
            $coll = collator_create($lang);
924
            if (isset($ret[$lang]['prefLabel'])) {
925
                $coll->sort($ret[$lang]['prefLabel'], Collator::SORT_STRING);
926
            }
927
            if (isset($ret[$lang]['altLabel'])) {
928
                $coll->sort($ret[$lang]['altLabel'], Collator::SORT_STRING);
929
            }
930
            if ($lang !== '') {
931
                $ret[$this->langToString($lang)] = $ret[$lang];
932
                unset($ret[$lang]);
933
            }
934
        }
935
        uksort($ret, 'strcoll');
936
        return $ret;
937
    }
938
939
    /**
940
     * Gets the values for the property in question in all other languages than the ui language.
941
     * @param string $property
942
     * @return array array of labels for the values of the given property
943
     */
944
    public function getAllLabels($property)
945
    {
946
        $labels = array();
947
        // shortening property labels if possible, EasyRdf requires full URIs to be in angle brackets
948
        $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...
949
        foreach ($this->resource->allLiterals($property) as $lit) {
950
            $labels[Punic\Language::getName($lit->getLang(), $this->getLang())][] = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $lit, $property);
951
        }
952
        ksort($labels);
953
        return $labels;
954
    }
955
956
    /**
957
     * Dump concept graph as JSON-LD.
958
     */
959
    public function dumpJsonLd()
960
    {
961
        $context = array(
962
            'skos' => EasyRdf\RdfNamespace::get("skos"),
963
            'isothes' => EasyRdf\RdfNamespace::get("isothes"),
964
            'rdfs' => EasyRdf\RdfNamespace::get("rdfs"),
965
            'owl' => EasyRdf\RdfNamespace::get("owl"),
966
            'dct' => EasyRdf\RdfNamespace::get("dcterms"),
967
            'dc11' => EasyRdf\RdfNamespace::get("dc11"),
968
            'uri' => '@id',
969
            'type' => '@type',
970
            'lang' => '@language',
971
            'value' => '@value',
972
            'graph' => '@graph',
973
            'label' => 'rdfs:label',
974
            'prefLabel' => 'skos:prefLabel',
975
            'altLabel' => 'skos:altLabel',
976
            'hiddenLabel' => 'skos:hiddenLabel',
977
            'broader' => 'skos:broader',
978
            'narrower' => 'skos:narrower',
979
            'related' => 'skos:related',
980
            'inScheme' => 'skos:inScheme',
981
            'schema' => EasyRdf\RdfNamespace::get("schema"),
982
            'wd' => EasyRdf\RdfNamespace::get("wd"),
983
            'wdt' => EasyRdf\RdfNamespace::get("wdt"),
984
        );
985
        $vocabPrefix = preg_replace('/\W+/', '', $this->vocab->getId()); // strip non-word characters
986
        $vocabUriSpace = $this->vocab->getUriSpace();
987
988
        if (!in_array($vocabUriSpace, $context, true)) {
989
            if (!isset($context[$vocabPrefix])) {
990
                $context[$vocabPrefix] = $vocabUriSpace;
991
            } elseif ($context[$vocabPrefix] !== $vocabUriSpace) {
992
                $i = 2;
993
                while (isset($context[$vocabPrefix . $i]) && $context[$vocabPrefix . $i] !== $vocabUriSpace) {
994
                    $i += 1;
995
                }
996
                $context[$vocabPrefix . $i] = $vocabUriSpace;
997
            }
998
        }
999
        $compactJsonLD = \ML\JsonLD\JsonLD::compact($this->graph->serialise('jsonld'), json_encode($context));
1000
        return \ML\JsonLD\JsonLD::toString($compactJsonLD);
1001
    }
1002
1003
    public function isUseModifiedDate()
1004
    {
1005
        return $this->getVocab()->isUseModifiedDate();
1006
    }
1007
}
1008