Completed
Push — master ( 9de5b8...ac5bab )
by Osma
02:11
created

Concept::getModifiedDate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Dataobject for a single concept.
5
 */
6
7
class Concept extends VocabularyDataObject
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
20
    /** concept properties that should not be shown to users */
21
    private $DELETED_PROPERTIES = array(
22
        'skosext:broaderGeneric', # these are remnants of bad modeling
23
        'skosext:broaderPartitive', #
24
25
        'skos:hiddenLabel', # because it's supposed to be hidden
26
        'skos:prefLabel', # handled separately by getLabel
27
        'rdfs:label', # handled separately by getLabel
28
29
        'skos:topConceptOf', # because it's too technical, not relevant for users
30
        'skos:inScheme', # should be evident in any case
31
        'skos:member', # this shouldn't be shown on the group page
32
        'dc:created', # handled separately
33
        'dc:modified', # handled separately
34
    );
35
36
    /** related concepts that should be shown to users in the appendix */
37
    private $MAPPING_PROPERTIES = array(
38
        'skos:exactMatch',
39
        'skos:narrowMatch',
40
        'skos:broadMatch',
41
        'skos:closeMatch',
42
        'skos:relatedMatch',
43
        'rdfs:seeAlso',
44
        'owl:sameAs',
45
    );
46
47
    /** default external properties we are interested in saving/displaying from mapped external objects */
48
    private $DEFAULT_EXT_PROPERTIES = array(
49
        "dc11:title",
50
        "dcterms:title",
51
        "skos:prefLabel",
52
        "skos:exactMatch",
53
        "skos:closeMatch",
54
        "skos:inScheme",
55
        "rdfs:label",
56
        "rdfs:isDefinedBy",
57
        "owl:sameAs",
58
        "rdf:type",
59
        "void:inDataset",
60
        "void:sparqlEndpoint",
61
        "void:uriLookupEndpoint",
62
        "schema:about",
63
        "schema:description",
64
        "schema:inLanguage",
65
        "schema:name",
66
        "schema:isPartOf",
67
        "wdt:P31",
68
        "wdt:P625"
69
    );
70
71
    /**
72
     * Initializing the concept object requires the following parameters.
73
     * @param Model $model
74
     * @param Vocabulary $vocab
75
     * @param EasyRdf\Resource $resource
76
     * @param EasyRdf\Graph $graph
77
     */
78
    public function __construct($model, $vocab, $resource, $graph, $clang)
79
    {
80
        parent::__construct($model, $vocab, $resource);
81
        $this->order = array("rdf:type", "dc:isReplacedBy", "skos:definition", "skos:broader", "skos:narrower", "skos:related", "skos:altLabel", "skosmos:memberOf", "skos:note", "skos:scopeNote", "skos:historyNote", "rdfs:comment", "dc11:source", "dc:source", "skos:prefLabel");
82
        $this->graph = $graph;
83
        $this->clang = $clang;
84
        // setting the Punic plugins locale for localized datetime conversions
85
        if ($this->clang && $this->clang !== '') {
86
            Punic\Data::setDefaultLocale($clang);
87
        }
88
89
    }
90
91
    /**
92
     * Returns the concept uri.
93
     * @return string
94
     */
95
    public function getUri()
96
    {
97
        return $this->resource->getUri();
98
    }
99
100
    public function getType()
101
    {
102
        return $this->resource->types();
103
    }
104
105
106
    /**
107
     * Returns a boolean value indicating whether the resource is a group defined in the vocab config as skosmos:groupClass.
108
     * @return boolean
109
     */
110
    public function isGroup() {
111
        $groupClass = $this->getVocab()->getConfig()->getGroupClassURI();
112
        if ($groupClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $groupClass of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
113
            $groupClass = EasyRdf\RdfNamespace::shorten($groupClass) !== null ? EasyRdf\RdfNamespace::shorten($groupClass) : $groupClass;
114
            return in_array($groupClass, $this->getType());
115
        }
116
        return false;
117
    }
118
119
    /**
120
     * Returns a boolean value indicating if the concept has been deprecated.
121
     * @return boolean
122
     */
123
    public function getDeprecated()
124
    {
125
        $deprecatedValue = $this->resource->getLiteral('owl:deprecated');
126
        return ($deprecatedValue !== null && filter_var($deprecatedValue->getValue(), FILTER_VALIDATE_BOOLEAN));
127
    }
128
129
    /**
130
     * Returns a label for the concept in the content language or if not possible in any language.
131
     * @return string
132
     */
133
    public function getLabel()
134
    {
135 View Code Duplication
        foreach ($this->vocab->getConfig()->getLanguageOrder($this->clang) as $fallback) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
136
            if ($this->resource->label($fallback) !== null) {
137
                return $this->resource->label($fallback);
138
            }
139
            // We need to check all the labels in case one of them matches a subtag of the current language
140
            foreach($this->resource->allLiterals('skos:prefLabel') as $label) {
141
                // the label lang code is a subtag of the UI lang eg. en-GB - create a new literal with the main language
142
                if ($label !== null && strpos($label->getLang(), $fallback . '-') === 0) {
143
                    return EasyRdf\Literal::create($label, $fallback);
144
                }
145
            }
146
        }
147
148
        // Last resort: label in any language, including literal with empty language tag
149
        $label = $this->resource->label();
150
        if ($label !== null) {
151
            if (!$label->getLang()) {
152
                return $label->getValue();
153
            }
154
            return EasyRdf\Literal::create($label->getValue(), $label->getLang());
155
        }
156
157
        // empty
158
        return "";
159
    }
160
161
    public function hasXlLabel($prop = 'prefLabel')
162
    {
163
        if ($this->resource->hasProperty('skosxl:' . $prop)) {
164
            return true;
165
        }
166
        return false;
167
    }
168
169
    public function getXlLabel()
170
    {
171
        $labels = $this->resource->allResources('skosxl:prefLabel');
172
        foreach($labels as $labres) {
173
            $label = $labres->getLiteral('skosxl:literalForm');
174
            if ($label !== null && $label->getLang() == $this->clang) {
175
                return new LabelSkosXL($this->model, $labres);
176
            }
177
        }
178
        return null;
179
    }
180
181
    /**
182
     * Returns a notation for the concept or null if it has not been defined.
183
     * @return string eg. '999'
184
     */
185
    public function getNotation()
186
    {
187
        $notation = $this->resource->get('skos:notation');
188
        if ($this->vocab->getConfig()->showNotation() && $notation !== null) {
189
            return $notation->getValue();
190
        }
191
192
        return null;
193
    }
194
195
    /**
196
     * Returns the Vocabulary object or undefined if that is not available.
197
     * @return Vocabulary
198
     */
199
    public function getVocab()
200
    {
201
        return $this->vocab;
202
    }
203
204
    /**
205
     * Returns the vocabulary shortname string or id if that is not available.
206
     * @return string
207
     */
208
    public function getShortName()
209
    {
210
        return $this->vocab ? $this->vocab->getShortName() : null;
211
    }
212
213
    /**
214
     * Returns the vocabulary shortname string or id if that is not available.
215
     * @return string
216
     */
217
    public function getVocabTitle()
218
    {
219
        return $this->vocab ? $this->vocab->getTitle() : null;
220
    }
221
222
    /**
223
     * Setter for the $clang property.
224
     * @param string $clang language code eg. 'en'
225
     */
226
    public function setContentLang($clang)
227
    {
228
        $this->clang = $clang;
229
    }
230
231
    public function getContentLang()
232
    {
233
        return $this->clang;
234
    }
235
236
    /**
237
     * Setter for the $foundby property.
238
     * @param string $label label that was matched
239
     * @param string $type type of match: 'alt', 'hidden', or 'lang'
240
     */
241
    public function setFoundBy($label, $type)
242
    {
243
        $this->foundby = $label;
244
        $this->foundbytype = $type;
245
    }
246
247
    /**
248
     * Getter for the $foundby property.
249
     * @return string
250
     */
251
    public function getFoundBy()
252
    {
253
        return $this->foundby;
254
    }
255
256
    /**
257
     * Getter for the $foundbytype property.
258
     * @return string
259
     */
260
    public function getFoundByType()
261
    {
262
        return $this->foundbytype;
263
    }
264
265
    /**
266
     * Processes a single external resource i.e., adds the properties from
267
     * 1) $this->$DEFAULT_EXT_PROPERTIES
268
     * 2) VocabConfig external properties
269
     * 3) Possible plugin defined external properties
270
     * to $this->graph
271
     * @param EasyRdf\Resource $res
272
     */
273
    public function processExternalResource($res)
274
    {
275
        $exGraph = $res->getGraph();
276
        // catch external subjects that have $res as object
277
        $extSubjects = $exGraph->resourcesMatching("schema:about", $res);
278
279
        $propList =  array_unique(array_merge(
280
            $this->DEFAULT_EXT_PROPERTIES,
281
            $this->getVocab()->getConfig()->getExtProperties(),
282
            $this->getVocab()->getConfig()->getPlugins()->getExtProperties()
283
        ));
284
285
        $seen = array();
286
        $this->addExternalTriplesToGraph($res, $seen, $propList);
287
        foreach ($extSubjects as $extSubject) {
288
            if ($extSubject->isBNode() && array_key_exists($extSubject->getUri(), $seen)) {
0 ignored issues
show
Bug introduced by
The method isBNode cannot be called on $extSubject (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
Bug introduced by
The method getUri cannot be called on $extSubject (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
289
                // already processed, skip
290
                continue;
291
            }
292
            $seen[$extSubject->getUri()] = 1;
0 ignored issues
show
Bug introduced by
The method getUri cannot be called on $extSubject (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
293
            $this->addExternalTriplesToGraph($extSubject, $seen, $propList);
0 ignored issues
show
Documentation introduced by
$extSubject is of type resource, but the function expects a object<EasyRdf\Resource>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
294
        }
295
296
    }
297
298
    /**
299
     * Adds resource properties to $this->graph
300
     * @param EasyRdf\Resource $res
301
     * @param string[] $seen Processed resources so far
302
     * @param string[] $props (optional) limit to these property URIs
303
     */
304
    private function addExternalTriplesToGraph($res, &$seen, $props=null)
305
    {
306
        if (array_key_exists($res->getUri(), $seen) && $seen[$res->getUri()] === 0) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $seen[$res->getUri()] (string) and 0 (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
307
            return;
308
        }
309
        $seen[$res->getUri()] = 0;
310
311
        if ($res->isBNode() || is_null($props)) {
312
            foreach ($res->propertyUris() as $prop) {
313
                $this->addPropertyValues($res, $prop, $seen);
314
            }
315
        }
316
        else {
317
            foreach ($props as $prop) {
318
                if ($res->hasProperty($prop)) {
319
                    $this->addPropertyValues($res, $prop, $seen);
320
                }
321
            }
322
        }
323
    }
324
325
    /**
326
     * Adds values of a single single property of a resource to $this->graph
327
     * implements Concise Bounded Description definition
328
     * @param EasyRdf\Resource $res
329
     * @param string $prop
330
     * @param string[] $seen Processed resources so far
331
     */
332
    private function addPropertyValues($res, $prop, &$seen)
333
    {
334
        $resList = $res->allResources('<' . $prop . '>');
335
336
        foreach ($resList as $res2) {
337
            if ($res2->isBNode()) {
338
                $this->addExternalTriplesToGraph($res2, $seen);
339
            }
340
            $this->graph->addResource($res, $prop, $res2);
341
            $this->addResourceReifications($res, $prop, $res2, $seen);
342
        }
343
344
        $litList = $res->allLiterals('<' . $prop . '>');
345
346
        foreach ($litList as $lit) {
347
            $this->graph->addLiteral($res, $prop, $lit);
348
            $this->addLiteralReifications($res, $prop, $lit, $seen);
349
        }
350
    }
351
352
    /**
353
     * Adds reifications of a triple having a literal object to $this->graph
354
     * @param EasyRdf\Resource $sub
355
     * @param string $pred
356
     * @param EasyRdf\Literal $obj
357
     * @param string[] $seen Processed resources so far
358
     */
359
    private function addLiteralReifications($sub, $pred, $obj, &$seen)
360
    {
361
        $pos_reifs = $sub->getGraph()->resourcesMatching("rdf:subject", $sub);
362
        foreach ($pos_reifs as $pos_reif) {
363
            $lit = $pos_reif->getLiteral("rdf:object", $obj->getLang());
0 ignored issues
show
Bug introduced by
The method getLiteral cannot be called on $pos_reif (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
364
365 View Code Duplication
            if (!is_null($lit) && $lit->getValue() === $obj->getValue() &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
366
                $pos_reif->isA("rdf:Statement") &&
0 ignored issues
show
Bug introduced by
The method isA cannot be called on $pos_reif (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
367
                $pos_reif->hasProperty("rdf:predicate", new EasyRdf\Resource($pred, $sub->getGraph())))
0 ignored issues
show
Bug introduced by
The method hasProperty cannot be called on $pos_reif (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
368
            {
369
                $this->addExternalTriplesToGraph($pos_reif, $seen);
0 ignored issues
show
Documentation introduced by
$pos_reif is of type resource, but the function expects a object<EasyRdf\Resource>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
370
            }
371
        }
372
    }
373
374
    /**
375
     * Adds reifications of a triple having a resource object to $this->graph
376
     * @param EasyRdf\Resource $sub
377
     * @param string $pred
378
     * @param EasyRdf\Resource $obj
379
     * @param string[] $seen Processed resources so far
380
     */
381
    private function addResourceReifications($sub, $pred, $obj, &$seen)
382
    {
383
        $pos_reifs = $sub->getGraph()->resourcesMatching("rdf:subject", $sub);
384
        foreach ($pos_reifs as $pos_reif) {
385 View Code Duplication
            if ($pos_reif->isA("rdf:Statement") &&
0 ignored issues
show
Bug introduced by
The method isA cannot be called on $pos_reif (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
386
                $pos_reif->hasProperty("rdf:object", $obj) &&
0 ignored issues
show
Bug introduced by
The method hasProperty cannot be called on $pos_reif (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
387
                $pos_reif->hasProperty("rdf:predicate", new EasyRdf\Resource($pred, $sub->getGraph())))
0 ignored issues
show
Bug introduced by
The method hasProperty cannot be called on $pos_reif (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
388
            {
389
                $this->addExternalTriplesToGraph($pos_reif, $seen);
0 ignored issues
show
Documentation introduced by
$pos_reif is of type resource, but the function expects a object<EasyRdf\Resource>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
390
            }
391
        }
392
    }
393
394
    public function getMappingProperties()
395
    {
396
        $ret = array();
397
398
        $longUris = $this->resource->propertyUris();
399
        foreach ($longUris as &$prop) {
400 View Code Duplication
            if (EasyRdf\RdfNamespace::shorten($prop) !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
401
                // shortening property labels if possible
402
                $prop = $sprop = EasyRdf\RdfNamespace::shorten($prop);
403
            } else {
404
                $sprop = "<$prop>";
405
            }
406
            // EasyRdf requires full URIs to be in angle brackets
407
408
            if (in_array($prop, $this->MAPPING_PROPERTIES) && !in_array($prop, $this->DELETED_PROPERTIES)) {
409
                $propres = new EasyRdf\Resource($prop, $this->graph);
410
                $proplabel = $propres->label($this->getEnvLang()) ? $propres->label($this->getEnvLang()) : $propres->label(); // current language
411
                $propobj = new ConceptProperty($prop, $proplabel);
412
                if ($propobj->getLabel() !== null) {
413
                    // only display properties for which we have a label
414
                    $ret[$prop] = $propobj;
415
                }
416
417
                // Iterating through every resource and adding these to the data object.
418
                foreach ($this->resource->allResources($sprop) as $val) {
419
                    if (isset($ret[$prop])) {
420
                        // checking if the target vocabulary can be found at the skosmos endpoint
421
                        $exuri = $val->getUri();
422
                        // if multiple vocabularies are found, the following method will return in priority the current vocabulary of the concept
423
                        $exvoc = $this->model->guessVocabularyFromURI($exuri, $this->vocab->getId());
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $exvoc is correct as $this->model->guessVocab... $this->vocab->getId()) (which targets Model::guessVocabularyFromURI()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
424
                        // if not querying the uri itself
425
                        if (!$exvoc) {
426
                            $response = null;
427
                            // if told to do so in the vocabulary configuration
428
                            if ($this->vocab->getConfig()->getExternalResourcesLoading()) {
429
                                $response = $this->model->getResourceFromUri($exuri);
430
                            }
431
432 View Code Duplication
                            if ($response) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
433
                                $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $response, $prop), $this->clang);
434
435
                                $this->processExternalResource($response);
436
437
                                continue;
438
                            }
439
                        }
440
                        $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $val, $prop, $this->clang), $this->clang);
441
                    }
442
                }
443
            }
444
        }
445
446
        // sorting the properties to a order preferred in the Skosmos concept page.
447
        $ret = $this->arbitrarySort($ret);
448
449
        return $ret;
450
    }
451
452
    /**
453
     * Iterates over all the properties of the concept and returns those in an array.
454
     * @return array
455
     */
456
    public function getProperties()
457
    {
458
        $properties = array();
459
        $narrowersByUri = array();
460
        $inCollection = array();
461
        $membersArray = array();
462
        $longUris = $this->resource->propertyUris();
463
        $duplicates = array();
464
        $ret = array();
465
466
        // looking for collections and linking those with their narrower concepts
467
        if ($this->vocab->getConfig()->getArrayClassURI() !== null) {
468
            $collections = $this->graph->allOfType($this->vocab->getConfig()->getArrayClassURI());
469
            if (sizeof($collections) > 0) {
470
                // indexing the narrowers once to avoid iterating all of them with every collection
471
                foreach ($this->resource->allResources('skos:narrower') as $narrower) {
472
                    $narrowersByUri[$narrower->getUri()] = $narrower;
473
                }
474
475
                foreach ($collections as $coll) {
476
                    $currCollMembers = $this->getCollectionMembers($coll, $narrowersByUri);
477
                    foreach ($currCollMembers as $collection) {
478
                        if ($collection->getSubMembers()) {
479
                            $submembers = $collection->getSubMembers();
480
                            foreach ($submembers as $member) {
481
                                $inCollection[$member->getUri()] = true;
482
                            }
483
484
                        }
485
                    }
486
487
                    if (isset($collection) && $collection->getSubMembers()) {
488
                        $membersArray = array_merge($currCollMembers, $membersArray);
489
                    }
490
491
                }
492
                $properties['skos:narrower'] = $membersArray;
493
            }
494
        }
495
496
        foreach ($longUris as &$prop) {
497
            // storing full URI without brackets in a separate variable
498
            $longUri = $prop;
499 View Code Duplication
            if (EasyRdf\RdfNamespace::shorten($prop) !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
500
                // shortening property labels if possible
501
                $prop = $sprop = EasyRdf\RdfNamespace::shorten($prop);
502
            } else {
503
                $sprop = "<$prop>";
504
            }
505
            // EasyRdf requires full URIs to be in angle brackets
506
507
            if (!in_array($prop, $this->DELETED_PROPERTIES) || ($this->isGroup() === false && $prop === 'skos:member')) {
508
                // retrieve property label and super properties from the current vocabulary first
509
                $propres = new EasyRdf\Resource($prop, $this->graph);
510
                $proplabel = $propres->label($this->getEnvLang()) ? $propres->label($this->getEnvLang()) : $propres->label();
511
512
                // check if the property is one of the well-known properties for which we have a gettext translation
513
                // if it is then we can skip the additional lookups in the default graph
514
                $propkey = (substr($prop, 0, 5) == 'dc11:') ?
515
                    str_replace('dc11:', 'dc:', $prop) : $prop;
516
                $is_well_known = (gettext($propkey) != $propkey);
517
518
                // if not found in current vocabulary, look up in the default graph to be able
519
                // to read an ontology loaded in a separate graph
520
                // note that this imply that the property has an rdf:type declared for the query to work
521
                if(!$is_well_known && !$proplabel) {
522
                    $envLangLabels = $this->model->getDefaultSparql()->queryLabel($longUri, $this->getEnvLang());
523
                    
524
                    $defaultPropLabel = $this->model->getDefaultSparql()->queryLabel($longUri, '');
525
526
					if($envLangLabels) {
527
						$proplabel = $envLangLabels[$this->getEnvLang()];
528
                    } else {
529
						if($defaultPropLabel) {
530
							$proplabel = $defaultPropLabel[''];
531
						}
532
					}
533
                }
534
535
                // look for superproperties in the current graph
536
                $superprops = array();
537
                foreach ($this->graph->allResources($prop, 'rdfs:subPropertyOf') as $subi) {
538
                    $superprops[] = $subi->getUri();
539
                }
540
541
                // also look up superprops in the default graph if not found in current vocabulary
542
                if(!$is_well_known && (!$superprops || empty($superprops))) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $superprops of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
543
                    $superprops = $this->model->getDefaultSparql()->querySuperProperties($longUri);
544
                }
545
546
                // we're reading only one super property, even if there are multiple ones
547
                $superprop = ($superprops)?$superprops[0]:null;
548
                if ($superprop) {
549
                    $superprop = EasyRdf\RdfNamespace::shorten($superprop) ? EasyRdf\RdfNamespace::shorten($superprop) : $superprop;
550
                }
551
                $sort_by_notation = $this->vocab->getConfig()->sortByNotation();
552
                $propobj = new ConceptProperty($prop, $proplabel, $superprop, $sort_by_notation);
553
554
                if ($propobj->getLabel() !== null) {
555
                    // only display properties for which we have a label
556
                    $ret[$prop] = $propobj;
557
                }
558
559
                // searching for subproperties of literals too
560
                if($superprops) {
561
                    foreach ($superprops as $subi) {
562
                        $suburi = EasyRdf\RdfNamespace::shorten($subi) ? EasyRdf\RdfNamespace::shorten($subi) : $subi;
563
                        $duplicates[$suburi] = $prop;
564
                    }
565
                }
566
567
                // Iterating through every literal and adding these to the data object.
568
                foreach ($this->resource->allLiterals($sprop) as $val) {
569
                    $literal = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $val, $prop);
570
                    // only add literals when they match the content/hit language or have no language defined
571
                    if (isset($ret[$prop]) && ($literal->getLang() === $this->clang || $literal->getLang() === null)) {
572
                        $ret[$prop]->addValue($literal);
573
                    }
574
575
                }
576
577
                // Iterating through every resource and adding these to the data object.
578
                foreach ($this->resource->allResources($sprop) as $val) {
579
                    // skipping narrower concepts which are already shown in a collection
580
                    if ($sprop === 'skos:narrower' && array_key_exists($val->getUri(), $inCollection)) {
581
                        continue;
582
                    }
583
584
                    // hiding rdf:type property if it's just skos:Concept
585
                    if ($sprop === 'rdf:type' && $val->shorten() === 'skos:Concept') {
586
                        continue;
587
                    }
588
589
                    // handled by getMappingProperties()
590
                    if (in_array($sprop, $this->MAPPING_PROPERTIES)) {
591
                        continue;
592
                    }
593
594
                    if (isset($ret[$prop])) {
595
                        // checking if the property value is not in the current vocabulary
596
                        $exvoc = $this->model->guessVocabularyFromURI($val->getUri());
597 View Code Duplication
                        if ($exvoc && $exvoc->getId() !== $this->vocab->getId()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
598
                            $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $val, $prop, $this->clang), $this->clang);
599
                            continue;
600
                        }
601
                        $ret[$prop]->addValue(new ConceptPropertyValue($this->model, $this->vocab, $val, $prop, $this->clang), $this->clang);
602
                    }
603
604
                }
605
            }
606
        }
607
        // adding narrowers part of a collection
608
        foreach ($properties as $prop => $values) {
609
            foreach ($values as $value) {
610
                $ret[$prop]->addValue($value, $this->clang);
611
            }
612
        }
613
614
        foreach ($ret as $key => $prop) {
615
            if (sizeof($prop->getValues()) === 0) {
616
                unset($ret[$key]);
617
            }
618
        }
619
620
        $ret = $this->removeDuplicatePropertyValues($ret, $duplicates);
621
        // sorting the properties to the order preferred in the Skosmos concept page.
622
        $ret = $this->arbitrarySort($ret);
623
        return $ret;
624
    }
625
626
    /**
627
     * Removes properties that have duplicate values.
628
     * @param $ret the array of properties generated by getProperties
629
     * @param $duplicates array of properties found are a subProperty of a another property
630
     * @return array of ConceptProperties
631
     */
632
    public function removeDuplicatePropertyValues($ret, $duplicates)
633
    {
634
        $propertyValues = array();
635
636
        foreach ($ret as $prop) {
637
            foreach ($prop->getValues() as $value) {
638
                $label = $value->getLabel();
639
                $propertyValues[(method_exists($label, 'getValue')) ? $label->getValue() : $label][] = $value->getType();
640
            }
641
        }
642
643
        foreach ($propertyValues as $value => $propnames) {
644
            // if there are multiple properties with the same string value.
645
            if (count($propnames) > 1) {
646
                foreach ($propnames as $property) {
647
                    // if there is a more accurate property delete the more generic one.
648
                    if (isset($duplicates[$property])) {
649
                        unset($ret[$property]);
650
                    }
651
                }
652
653
            }
654
        }
655
        return $ret;
656
    }
657
658
    /**
659
     * @return DateTime|null the modified date, or null if not available
660
     */
661
    public function getModifiedDate()
662
    {
663
        $modified = null;
664
        // finding the modified properties
665
        /** @var \EasyRdf\Resource $modifiedResource */
666
        $modifiedResource = $this->resource->get('dc:modified');
667
        if ($modifiedResource) {
668
            $modified = $modifiedResource->getValue();
0 ignored issues
show
Bug introduced by
The method getValue() does not seem to exist on object<EasyRdf\Resource>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
669
        }
670
        return $modified;
671
    }
672
673
    /**
674
     * Gets the creation date and modification date if available.
675
     * @return String containing the date information in a human readable format.
676
     */
677
    public function getDate()
678
    {
679
        $ret = '';
680
        $created = '';
681
        $modified = '';
682
        try {
683
            // finding the created properties
684
            if ($this->resource->get('dc:created')) {
685
                $created = $this->resource->get('dc:created')->getValue();
686
            }
687
688
            $modified = $this->getModifiedDate();
689
690
            // making a human readable string from the timestamps
691
            if ($created != '') {
692
                $ret = gettext('skosmos:created') . ' ' . (Punic\Calendar::formatDate($created, 'short'));
693
            }
694
695
            if ($modified != '') {
696
                if ($created != '') {
697
                    $ret .= ', ' . gettext('skosmos:modified') . ' ' . (Punic\Calendar::formatDate($modified, 'short'));
0 ignored issues
show
Bug introduced by
It seems like $modified defined by $this->getModifiedDate() on line 688 can be null; however, Punic\Calendar::formatDate() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
698
                } else {
699
                    $ret .= ' ' . ucfirst(gettext('skosmos:modified')) . ' ' . (Punic\Calendar::formatDate($modified, 'short'));
0 ignored issues
show
Bug introduced by
It seems like $modified defined by $this->getModifiedDate() on line 688 can be null; however, Punic\Calendar::formatDate() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
700
                }
701
702
            }
703
        } catch (Exception $e) {
704
            trigger_error($e->getMessage(), E_USER_WARNING);
705
            $ret = '';
706
            if ($this->resource->get('dc:modified')) {
707
                $modified = (string) $this->resource->get('dc:modified');
708
                $ret = gettext('skosmos:modified') . ' ' . $modified;
709
            }
710
            if ($this->resource->get('dc:created')) {
711
                $created .= (string) $this->resource->get('dc:created');
712
                $ret .= ' ' . gettext('skosmos:created') . ' ' . $created;
713
            }
714
        }
715
        return $ret;
716
    }
717
718
    /**
719
     * Gets the members of a specific collection.
720
     * @param $coll
721
     * @param array containing all narrowers as EasyRdf\Resource
722
     * @return array containing ConceptPropertyValue objects
723
     */
724
    private function getCollectionMembers($coll, $narrowers)
725
    {
726
        $membersArray = array();
727
        if ($coll->label()) {
728
            $collLabel = $coll->label()->getValue($this->clang) ? $coll->label($this->clang) : $coll->label();
729
            if ($collLabel) {
730
                $collLabel = $collLabel->getValue();
731
            }
732
        } else {
733
            $collLabel = $coll->getUri();
734
        }
735
        $membersArray[$collLabel] = new ConceptPropertyValue($this->model, $this->vocab, $coll, 'skos:narrower', $this->clang);
736
        foreach ($coll->allResources('skos:member') as $member) {
737
            if (array_key_exists($member->getUri(), $narrowers)) {
738
                $narrower = $narrowers[$member->getUri()];
739
                if (isset($narrower)) {
740
                    $membersArray[$collLabel]->addSubMember(new ConceptPropertyValue($this->model, $this->vocab, $narrower, 'skos:member', $this->clang), $this->clang);
741
                }
742
743
            }
744
        }
745
746
        return $membersArray;
747
    }
748
749
    /**
750
     * Gets the groups the concept belongs to.
751
     */
752
    public function getGroupProperties()
753
    {
754
        return $this->getCollections(false);
755
    }
756
757
    /**
758
     * Gets the groups/arrays the concept belongs to.
759
     */
760
    private function getCollections($includeArrays) {
761
        $groups = array();
762
        $collections = $this->graph->resourcesMatching('skos:member', $this->resource);
763
        if (isset($collections)) {
764
            $arrayClassURI = $this->vocab !== null ? $this->vocab->getConfig()->getArrayClassURI() : null;
765
            $arrayClass = $arrayClassURI !== null ? EasyRdf\RdfNamespace::shorten($arrayClassURI) : null;
766
            $superGroups = $this->resource->all('isothes:superGroup');
767
            $superGroupUris = array_map(function($obj) { return $obj->getUri(); }, $superGroups);
768
            foreach ($collections as $collection) {
769
                if (in_array($arrayClass, $collection->types()) === $includeArrays) {
0 ignored issues
show
Bug introduced by
The method types cannot be called on $collection (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
770
                    // not adding the memberOf if the reverse resource is already covered by isothes:superGroup see issue #433
771
                    if (in_array($collection->getUri(), $superGroupUris)) {
0 ignored issues
show
Bug introduced by
The method getUri cannot be called on $collection (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
772
                        continue;
773
                    }
774
                    $property = in_array($arrayClass, $collection->types()) ? "skosmos:memberOfArray" : "skosmos:memberOf";
0 ignored issues
show
Bug introduced by
The method types cannot be called on $collection (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
775
                    $collLabel = $collection->label($this->clang) ? $collection->label($this->clang) : $collection->label();
0 ignored issues
show
Bug introduced by
The method label cannot be called on $collection (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
776
                    if ($collLabel) {
777
                        $collLabel = $collLabel->getValue();
778
                    }
779
780
                    $group = new ConceptPropertyValue($this->model, $this->vocab, $collection, $property, $this->clang);
781
                    $groups[$collLabel] = array($group);
782
783
                    $res = $collection;
784
                    while($super = $this->graph->resourcesMatching('skos:member', $res)) {
785
                        foreach ($super as $res) {
786
                            $superprop = new ConceptPropertyValue($this->model, $this->vocab, $res, 'skosmos:memberOfSuper', $this->clang);
787
                            array_unshift($groups[$collLabel], $superprop);
788
                        }
789
                    }
790
                }
791
            }
792
        }
793
        uksort($groups, 'strcoll');
794
        return $groups;
795
    }
796
797
    public function getArrayProperties() {
798
        return $this->getCollections(true);
799
    }
800
801
    /**
802
     * Reads the literal language code and gets a name for it from Punic or alternatively
803
     * tries to search for a gettext translation.
804
     * @param EasyRdf\Literal $lit
805
     * @return string e.g. 'English'
806
     */
807
    private function literalLanguageToString($lit) {
808
        // using empty string as the language literal when there is no langcode set
809
        $langName = '';
810
        if ($lit->getLang() !== null) {
811
            $langName = Punic\Language::getName($lit->getLang(), $this->getEnvLang()) !== $lit->getLang() ? Punic\Language::getName($lit->getLang(), $this->getEnvLang()) : gettext($lit->getLang());
812
        }
813
        return $langName;
814
    }
815
816
    /**
817
     * Gets the values for the property in question in all other languages than the ui language.
818
     */
819
    public function getForeignLabels()
820
    {
821
        $prefLabels = $this->resource->allLiterals('skos:prefLabel');
822
        $labels = array_merge($prefLabels, $this->resource->allLiterals('skos:altLabel'));
823
        $ret = array();
824
        foreach ($labels as $lit) {
825
            // filtering away subsets of the current language eg. en vs en-GB
826
            if ($lit->getLang() != $this->clang && strpos($lit->getLang(), $this->getEnvLang() . '-') !== 0) {
827
                $prop = in_array($lit, $prefLabels) ? 'skos:prefLabel' : 'skos:altLabel';
828
                $ret[$this->literalLanguageToString($lit)][] = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $lit, $prop);
829
            }
830
        }
831
        ksort($ret);
832
        return $ret;
833
    }
834
835
    /**
836
     * Gets the values for the property in question in all other languages than the ui language.
837
     * @param string $property
838
     */
839
    public function getAllLabels($property)
840
    {
841
        $labels = array();
842
        // shortening property labels if possible, EasyRdf requires full URIs to be in angle brackets
843
        $property = (EasyRdf\RdfNamespace::shorten($property) !== null) ? EasyRdf\RdfNamespace::shorten($property) : "<$property>";
844
        foreach ($this->resource->allLiterals($property) as $lit) {
845
            $labels[Punic\Language::getName($lit->getLang(), $this->getEnvLang())][] = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $lit, $property);
846
        }
847
        ksort($labels);
848
        return $labels;
849
    }
850
851
    /**
852
     * Dump concept graph as JSON-LD.
853
     */
854
    public function dumpJsonLd() {
855
        $context = array(
856
            'skos' => EasyRdf\RdfNamespace::get("skos"),
857
            'isothes' => EasyRdf\RdfNamespace::get("isothes"),
858
            'rdfs' => EasyRdf\RdfNamespace::get("rdfs"),
859
            'owl' =>EasyRdf\RdfNamespace::get("owl"),
860
            'dct' => EasyRdf\RdfNamespace::get("dcterms"),
861
            'dc11' => EasyRdf\RdfNamespace::get("dc11"),
862
            'uri' => '@id',
863
            'type' => '@type',
864
            'lang' => '@language',
865
            'value' => '@value',
866
            'graph' => '@graph',
867
            'label' => 'rdfs:label',
868
            'prefLabel' => 'skos:prefLabel',
869
            'altLabel' => 'skos:altLabel',
870
            'hiddenLabel' => 'skos:hiddenLabel',
871
            'broader' => 'skos:broader',
872
            'narrower' => 'skos:narrower',
873
            'related' => 'skos:related',
874
            'inScheme' => 'skos:inScheme',
875
            'schema' => EasyRdf\RdfNamespace::get("schema"),
876
            'wd' => EasyRdf\RdfNamespace::get("wd"),
877
            'wdt' => EasyRdf\RdfNamespace::get("wdt"),
878
        );
879
        $vocabPrefix = preg_replace('/\W+/', '', $this->vocab->getId()); // strip non-word characters
880
        $vocabUriSpace = $this->vocab->getUriSpace();
881
882
        if (!in_array($vocabUriSpace, $context, true)) {
883
            if (!isset($context[$vocabPrefix])) {
884
                $context[$vocabPrefix] = $vocabUriSpace;
885
            }
886
            else if ($context[$vocabPrefix] !== $vocabUriSpace) {
887
                $i = 2;
888
                while (isset($context[$vocabPrefix . $i]) && $context[$vocabPrefix . $i] !== $vocabUriSpace) {
889
                    $i += 1;
890
                }
891
                $context[$vocabPrefix . $i] = $vocabUriSpace;
892
            }
893
        }
894
        $compactJsonLD = \ML\JsonLD\JsonLD::compact($this->graph->serialise('jsonld'), json_encode($context));
895
        $results = \ML\JsonLD\JsonLD::toString($compactJsonLD);
896
897
        return $results;
898
    }
899
}
900