Passed
Push — master ( b8aa78...aab3aa )
by
unknown
03:35 queued 17s
created

Concept::getConceptModifiedDate()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
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
    );
36
37
    /** related concepts that should be shown to users in the appendix */
38
    private $MAPPING_PROPERTIES = array(
39
        'skos:exactMatch',
40
        'skos:narrowMatch',
41
        'skos:broadMatch',
42
        'skos:closeMatch',
43
        'skos:relatedMatch',
44
        'rdfs:seeAlso',
45
        'owl:sameAs',
46
    );
47
48
    /** default external properties we are interested in saving/displaying from mapped external objects */
49
    private $DEFAULT_EXT_PROPERTIES = array(
50
        "dc11:title",
51
        "dcterms:title",
52
        "skos:prefLabel",
53
        "skos:exactMatch",
54
        "skos:closeMatch",
55
        "skos:inScheme",
56
        "rdfs:label",
57
        "rdfs:isDefinedBy",
58
        "owl:sameAs",
59
        "rdf:type",
60
        "void:inDataset",
61
        "void:sparqlEndpoint",
62
        "void:uriLookupEndpoint",
63
        "schema:about",
64
        "schema:description",
65
        "schema:inLanguage",
66
        "schema:name",
67
        "schema:isPartOf",
68
        "wdt:P31",
69
        "wdt:P625"
70
    );
71
72
    /**
73
     * Initializing the concept object requires the following parameters.
74
     * @param Model $model
75
     * @param Vocabulary $vocab
76
     * @param EasyRdf\Resource $resource
77
     * @param EasyRdf\Graph $graph
78
     */
79
    public function __construct($model, $vocab, $resource, $graph, $clang)
80
    {
81
        parent::__construct($model, $vocab, $resource);
82
        $this->deleted = $this->DELETED_PROPERTIES;
83
        if ($vocab !== null) {
84
            $this->order = $vocab->getConfig()->getPropertyOrder();
85
            // delete notations unless configured to show them
86
            if (!$vocab->getConfig()->getShowNotationAsProperty()) {
87
                $this->deleted[] = 'skos:notation';
88
            }
89
        } else {
90
            $this->order = VocabularyConfig::DEFAULT_PROPERTY_ORDER;
91
        }
92
        $this->graph = $graph;
93
        $this->clang = $clang;
94
    }
95
96
    /**
97
     * Returns the concept uri.
98
     * @return string
99
     */
100
    public function getUri()
101
    {
102
        return $this->resource->getUri();
103
    }
104
105
    public function getType()
106
    {
107
        return $this->resource->types();
108
    }
109
110
111
    /**
112
     * Returns a boolean value indicating whether the resource is a group defined in the vocab config as skosmos:groupClass.
113
     * @return boolean
114
     */
115
    public function isGroup() {
116
        $groupClass = $this->getVocab()->getConfig()->getGroupClassURI();
117
        if ($groupClass) {
118
            $groupClass = EasyRdf\RdfNamespace::shorten($groupClass) !== null ? EasyRdf\RdfNamespace::shorten($groupClass) : $groupClass;
0 ignored issues
show
introduced by
The condition EasyRdf\RdfNamespace::sh...n($groupClass) !== null is always true.
Loading history...
119
            return in_array($groupClass, $this->getType());
120
        }
121
        return false;
122
    }
123
124
    /**
125
     * Returns a boolean value indicating if the concept has been deprecated.
126
     * @return boolean
127
     */
128
    public function getDeprecated()
129
    {
130
        $deprecatedValue = $this->resource->getLiteral('owl:deprecated');
131
        return ($deprecatedValue !== null && filter_var($deprecatedValue->getValue(), FILTER_VALIDATE_BOOLEAN));
132
    }
133
134
    /**
135
     * Returns a label for the concept in the content language or if not possible in any language.
136
     * @return string
137
     */
138
    public function getLabel()
139
    {
140
        foreach ($this->vocab->getConfig()->getLanguageOrder($this->clang) as $fallback) {
141
            if ($this->resource->label($fallback) !== null) {
142
                return $this->resource->label($fallback);
143
            }
144
            // We need to check all the labels in case one of them matches a subtag of the current language
145
            foreach($this->resource->allLiterals('skos:prefLabel') as $label) {
146
                // the label lang code is a subtag of the UI lang eg. en-GB - create a new literal with the main language
147
                if ($label !== null && strpos($label->getLang(), $fallback . '-') === 0) {
148
                    return EasyRdf\Literal::create($label, $fallback);
149
                }
150
            }
151
        }
152
153
        // Last resort: label in any language, including literal with empty language tag
154
        $label = $this->resource->label();
155
        if ($label !== null) {
156
            if (!$label->getLang()) {
157
                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...
158
            }
159
            return EasyRdf\Literal::create($label->getValue(), $label->getLang());
160
        }
161
162
        // empty
163
        return "";
164
    }
165
166
    public function hasXlLabel()
167
    {
168
        $xlLabel = $this->getXlLabel();
169
        return ($xlLabel !== null && !empty($xlLabel->getProperties()));
170
    }
171
172
    public function getXlLabel()
173
    {
174
        $labels = $this->resource->allResources('skosxl:prefLabel');
175
        foreach($labels as $labres) {
176
            $label = $labres->getLiteral('skosxl:literalForm');
177
            if ($label !== null && $label->getLang() == $this->clang) {
178
                return new LabelSkosXL($this->model, $labres);
179
            }
180
        }
181
        return null;
182
    }
183
184
    /**
185
     * Returns a notation for the concept or null if it has not been defined.
186
     * @return string eg. '999'
187
     */
188
    public function getNotation()
189
    {
190
        $notation = $this->resource->get('skos:notation');
191
        if ($this->vocab->getConfig()->showNotation() && $notation !== null) {
192
            return $notation->getValue();
193
        }
194
195
        return null;
196
    }
197
198
    /**
199
     * Returns the Vocabulary object or undefined if that is not available.
200
     * @return Vocabulary
201
     */
202
    public function getVocab()
203
    {
204
        return $this->vocab;
205
    }
206
207
    /**
208
     * Returns the vocabulary shortname string or id if that is not available.
209
     * @return string
210
     */
211
    public function getShortName()
212
    {
213
        return $this->vocab ? $this->vocab->getShortName() : null;
214
    }
215
216
    /**
217
     * Returns the vocabulary shortname string or id if that is not available.
218
     * @return string
219
     */
220
    public function getVocabTitle()
221
    {
222
        return $this->vocab ? $this->vocab->getTitle() : null;
223
    }
224
225
    /**
226
     * Setter for the $clang property.
227
     * @param string $clang language code eg. 'en'
228
     */
229
    public function setContentLang($clang)
230
    {
231
        $this->clang = $clang;
232
    }
233
234
    public function getContentLang()
235
    {
236
        return $this->clang;
237
    }
238
239
    /**
240
     * Setter for the $foundby property.
241
     * @param string $label label that was matched
242
     * @param string $type type of match: 'alt', 'hidden', or 'lang'
243
     */
244
    public function setFoundBy($label, $type)
245
    {
246
        $this->foundby = $label;
247
        $this->foundbytype = $type;
248
    }
249
250
    /**
251
     * Getter for the $foundby property.
252
     * @return string
253
     */
254
    public function getFoundBy()
255
    {
256
        return $this->foundby;
257
    }
258
259
    /**
260
     * Getter for the $foundbytype property.
261
     * @return string
262
     */
263
    public function getFoundByType()
264
    {
265
        return $this->foundbytype;
266
    }
267
268
    /**
269
     * Processes a single external resource i.e., adds the properties from
270
     * 1) $this->$DEFAULT_EXT_PROPERTIES
271
     * 2) VocabConfig external properties
272
     * 3) Possible plugin defined external properties
273
     * to $this->graph
274
     * @param EasyRdf\Resource $res
275
     */
276
    public function processExternalResource($res)
277
    {
278
        $exGraph = $res->getGraph();
279
        // catch external subjects that have $res as object
280
        $extSubjects = $exGraph->resourcesMatching("schema:about", $res);
281
282
        $propList =  array_unique(array_merge(
283
            $this->DEFAULT_EXT_PROPERTIES,
284
            $this->getVocab()->getConfig()->getExtProperties(),
285
            $this->getVocab()->getConfig()->getPluginRegister()->getExtProperties()
286
        ));
287
288
        $seen = array();
289
        $this->addExternalTriplesToGraph($res, $seen, $propList);
290
        foreach ($extSubjects as $extSubject) {
291
            if ($extSubject->isBNode() && array_key_exists($extSubject->getUri(), $seen)) {
292
                // already processed, skip
293
                continue;
294
            }
295
            $seen[$extSubject->getUri()] = 1;
296
            $this->addExternalTriplesToGraph($extSubject, $seen, $propList);
297
        }
298
299
    }
300
301
    /**
302
     * Adds resource properties to $this->graph
303
     * @param EasyRdf\Resource $res
304
     * @param string[] $seen Processed resources so far
305
     * @param string[] $props (optional) limit to these property URIs
306
     */
307
    private function addExternalTriplesToGraph($res, &$seen, $props=null)
308
    {
309
        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...
310
            return;
311
        }
312
        $seen[$res->getUri()] = 0;
313
314
        if ($res->isBNode() || is_null($props)) {
315
            foreach ($res->propertyUris() as $prop) {
316
                $this->addPropertyValues($res, $prop, $seen);
317
            }
318
        }
319
        else {
320
            foreach ($props as $prop) {
321
                if ($res->hasProperty($prop)) {
322
                    $this->addPropertyValues($res, $prop, $seen);
323
                }
324
            }
325
        }
326
    }
327
328
    /**
329
     * Adds values of a single single property of a resource to $this->graph
330
     * implements Concise Bounded Description definition
331
     * @param EasyRdf\Resource $res
332
     * @param string $prop
333
     * @param string[] $seen Processed resources so far
334
     */
335
    private function addPropertyValues($res, $prop, &$seen)
336
    {
337
        $resList = $res->allResources('<' . $prop . '>');
338
339
        foreach ($resList as $res2) {
340
            if ($res2->isBNode()) {
341
                $this->addExternalTriplesToGraph($res2, $seen);
342
            }
343
            $this->graph->addResource($res, $prop, $res2);
344
            $this->addResourceReifications($res, $prop, $res2, $seen);
345
        }
346
347
        $litList = $res->allLiterals('<' . $prop . '>');
348
349
        foreach ($litList as $lit) {
350
            $this->graph->addLiteral($res, $prop, $lit);
351
            $this->addLiteralReifications($res, $prop, $lit, $seen);
352
        }
353
    }
354
355
    /**
356
     * Adds reifications of a triple having a literal object to $this->graph
357
     * @param EasyRdf\Resource $sub
358
     * @param string $pred
359
     * @param EasyRdf\Literal $obj
360
     * @param string[] $seen Processed resources so far
361
     */
362
    private function addLiteralReifications($sub, $pred, $obj, &$seen)
363
    {
364
        $pos_reifs = $sub->getGraph()->resourcesMatching("rdf:subject", $sub);
365
        foreach ($pos_reifs as $pos_reif) {
366
            $lit = $pos_reif->getLiteral("rdf:object", $obj->getLang());
367
368
            if (!is_null($lit) && $lit->getValue() === $obj->getValue() &&
369
                $pos_reif->isA("rdf:Statement") &&
370
                $pos_reif->hasProperty("rdf:predicate", new EasyRdf\Resource($pred, $sub->getGraph())))
371
            {
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
            {
392
                $this->addExternalTriplesToGraph($pos_reif, $seen);
393
            }
394
        }
395
    }
396
397
    /**
398
     * @return ConceptProperty[]
399
     */
400
    public function getMappingProperties(array $whitelist = null)
401
    {
402
        $ret = array();
403
404
        $longUris = $this->resource->propertyUris();
405
        foreach ($longUris as &$prop) {
406
            if (EasyRdf\RdfNamespace::shorten($prop) !== null) {
407
                // shortening property labels if possible
408
                $prop = $sprop = EasyRdf\RdfNamespace::shorten($prop);
409
            } else {
410
                // EasyRdf requires full URIs to be in angle brackets
411
                $sprop = "<$prop>";
412
            }
413
            if ($whitelist && !in_array($prop, $whitelist)) {
414
                // whitelist in use and this is not a whitelisted property, skipping
415
                continue;
416
            }
417
418
            if (in_array($prop, $this->MAPPING_PROPERTIES) && !in_array($prop, $this->DELETED_PROPERTIES)) {
419
                $propres = new EasyRdf\Resource($prop, $this->graph);
420
                $proplabel = $propres->label($this->getEnvLang()) ? $propres->label($this->getEnvLang()) : $propres->label(); // current language
421
                $propobj = new ConceptProperty($prop, $proplabel);
422
                if ($propobj->getLabel() !== null) {
423
                    // only display properties for which we have a label
424
                    $ret[$prop] = $propobj;
425
                }
426
427
                // Iterating through every resource and adding these to the data object.
428
                foreach ($this->resource->allResources($sprop) as $val) {
429
                    if (isset($ret[$prop])) {
430
                        // checking if the target vocabulary can be found at the skosmos endpoint
431
                        $exuri = $val->getUri();
432
                        // if multiple vocabularies are found, the following method will return in priority the current vocabulary of the concept
433
                        $exvoc = $this->model->guessVocabularyFromURI($exuri, $this->vocab->getId());
434
                        // if not querying the uri itself
435
                        if (!$exvoc) {
436
                            $response = null;
437
                            // if told to do so in the vocabulary configuration
438
                            if ($this->vocab->getConfig()->getExternalResourcesLoading()) {
439
                                $response = $this->model->getResourceFromUri($exuri);
440
                            }
441
442
                            if ($response) {
443
                                $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $response, $this->resource, $prop));
444
445
                                $this->processExternalResource($response);
446
447
                                continue;
448
                            }
449
                        }
450
                        $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $val, $this->resource, $prop, $this->clang));
451
                    }
452
                }
453
            }
454
        }
455
456
        // sorting the properties to a order preferred in the Skosmos concept page.
457
        return $this->arbitrarySort($ret);
458
    }
459
460
    /**
461
     * Iterates over all the properties of the concept and returns those in an array.
462
     * @return array
463
     */
464
    public function getProperties()
465
    {
466
        $properties = array();
467
        $narrowersByUri = array();
468
        $inCollection = array();
469
        $membersArray = array();
470
        $longUris = $this->resource->propertyUris();
471
        $duplicates = array();
472
        $ret = array();
473
474
        // looking for collections and linking those with their narrower concepts
475
        if ($this->vocab->getConfig()->getArrayClassURI() !== null) {
0 ignored issues
show
introduced by
The condition $this->vocab->getConfig(...rrayClassURI() !== null is always true.
Loading history...
476
            $collections = $this->graph->allOfType($this->vocab->getConfig()->getArrayClassURI());
477
            if (sizeof($collections) > 0) {
478
                // indexing the narrowers once to avoid iterating all of them with every collection
479
                foreach ($this->resource->allResources('skos:narrower') as $narrower) {
480
                    $narrowersByUri[$narrower->getUri()] = $narrower;
481
                }
482
483
                foreach ($collections as $coll) {
484
                    $currCollMembers = $this->getCollectionMembers($coll, $narrowersByUri);
485
                    foreach ($currCollMembers as $collection) {
486
                        if ($collection->getSubMembers()) {
487
                            $submembers = $collection->getSubMembers();
488
                            foreach ($submembers as $member) {
489
                                $inCollection[$member->getUri()] = true;
490
                            }
491
492
                        }
493
                    }
494
495
                    if (isset($collection) && $collection->getSubMembers()) {
496
                        $membersArray = array_merge($currCollMembers, $membersArray);
497
                    }
498
499
                }
500
                $properties['skos:narrower'] = $membersArray;
501
            }
502
        }
503
504
        foreach ($longUris as &$prop) {
505
            // storing full URI without brackets in a separate variable
506
            $longUri = $prop;
507
            if (EasyRdf\RdfNamespace::shorten($prop) !== null) {
508
                // shortening property labels if possible
509
                $prop = $sprop = EasyRdf\RdfNamespace::shorten($prop);
510
            } else {
511
                // EasyRdf requires full URIs to be in angle brackets
512
                $sprop = "<$prop>";
513
            }
514
515
            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

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

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