Completed
Push — master ( a9877c...af7729 )
by
unknown
02:02 queued 11s
created

Concept::removeDuplicatePropertyValues()   B

Complexity

Conditions 9
Paths 40

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 40
nop 2
dl 0
loc 29
rs 8.0555
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
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", "isothes:broaderGeneric", "isothes:broaderPartitive", "isothes:broaderInstantial", "skos:narrower", "isothes:narrowerGeneric", "isothes:narrowerPartitive", "isothes:narrowerInstantial", "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
    /**
395
     * @return ConceptProperty[]
396
     */
397
    public function getMappingProperties(array $whitelist = null)
398
    {
399
        $ret = array();
400
401
        $longUris = $this->resource->propertyUris();
402
        foreach ($longUris as &$prop) {
403 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...
404
                // shortening property labels if possible
405
                $prop = $sprop = EasyRdf\RdfNamespace::shorten($prop);
406
            } else {
407
                // EasyRdf requires full URIs to be in angle brackets
408
                $sprop = "<$prop>";
409
            }
410
            if ($whitelist && !in_array($prop, $whitelist)) {
411
                // whitelist in use and this is not a whitelisted property, skipping
412
                continue;
413
            }
414
415
            if (in_array($prop, $this->MAPPING_PROPERTIES) && !in_array($prop, $this->DELETED_PROPERTIES)) {
416
                $propres = new EasyRdf\Resource($prop, $this->graph);
417
                $proplabel = $propres->label($this->getEnvLang()) ? $propres->label($this->getEnvLang()) : $propres->label(); // current language
418
                $propobj = new ConceptProperty($prop, $proplabel);
419
                if ($propobj->getLabel() !== null) {
420
                    // only display properties for which we have a label
421
                    $ret[$prop] = $propobj;
422
                }
423
424
                // Iterating through every resource and adding these to the data object.
425
                foreach ($this->resource->allResources($sprop) as $val) {
426
                    if (isset($ret[$prop])) {
427
                        // checking if the target vocabulary can be found at the skosmos endpoint
428
                        $exuri = $val->getUri();
429
                        // if multiple vocabularies are found, the following method will return in priority the current vocabulary of the concept
430
                        $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...
431
                        // if not querying the uri itself
432
                        if (!$exvoc) {
433
                            $response = null;
434
                            // if told to do so in the vocabulary configuration
435
                            if ($this->vocab->getConfig()->getExternalResourcesLoading()) {
436
                                $response = $this->model->getResourceFromUri($exuri);
437
                            }
438
439
                            if ($response) {
440
                                $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $response, $this->resource, $prop), $this->clang);
441
442
                                $this->processExternalResource($response);
443
444
                                continue;
445
                            }
446
                        }
447
                        $ret[$prop]->addValue(new ConceptMappingPropertyValue($this->model, $this->vocab, $val, $this->resource, $prop, $this->clang), $this->clang);
448
                    }
449
                }
450
            }
451
        }
452
453
        // sorting the properties to a order preferred in the Skosmos concept page.
454
        return $this->arbitrarySort($ret);
455
    }
456
457
    /**
458
     * Iterates over all the properties of the concept and returns those in an array.
459
     * @return array
460
     */
461
    public function getProperties()
462
    {
463
        $properties = array();
464
        $narrowersByUri = array();
465
        $inCollection = array();
466
        $membersArray = array();
467
        $longUris = $this->resource->propertyUris();
468
        $duplicates = array();
469
        $ret = array();
470
471
        // looking for collections and linking those with their narrower concepts
472
        if ($this->vocab->getConfig()->getArrayClassURI() !== null) {
473
            $collections = $this->graph->allOfType($this->vocab->getConfig()->getArrayClassURI());
474
            if (sizeof($collections) > 0) {
475
                // indexing the narrowers once to avoid iterating all of them with every collection
476
                foreach ($this->resource->allResources('skos:narrower') as $narrower) {
477
                    $narrowersByUri[$narrower->getUri()] = $narrower;
478
                }
479
480
                foreach ($collections as $coll) {
481
                    $currCollMembers = $this->getCollectionMembers($coll, $narrowersByUri);
482
                    foreach ($currCollMembers as $collection) {
483
                        if ($collection->getSubMembers()) {
484
                            $submembers = $collection->getSubMembers();
485
                            foreach ($submembers as $member) {
486
                                $inCollection[$member->getUri()] = true;
487
                            }
488
489
                        }
490
                    }
491
492
                    if (isset($collection) && $collection->getSubMembers()) {
493
                        $membersArray = array_merge($currCollMembers, $membersArray);
494
                    }
495
496
                }
497
                $properties['skos:narrower'] = $membersArray;
498
            }
499
        }
500
501
        foreach ($longUris as &$prop) {
502
            // storing full URI without brackets in a separate variable
503
            $longUri = $prop;
504 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...
505
                // shortening property labels if possible
506
                $prop = $sprop = EasyRdf\RdfNamespace::shorten($prop);
507
            } else {
508
                // EasyRdf requires full URIs to be in angle brackets
509
                $sprop = "<$prop>";
510
            }
511
512
            if (!in_array($prop, $this->DELETED_PROPERTIES) || ($this->isGroup() === false && $prop === 'skos:member')) {
513
                // retrieve property label and super properties from the current vocabulary first
514
                $propres = new EasyRdf\Resource($prop, $this->graph);
515
                $proplabel = $propres->label($this->getEnvLang()) ? $propres->label($this->getEnvLang()) : $propres->label();
516
517
                // check if the property is one of the well-known properties for which we have a gettext translation
518
                // if it is then we can skip the additional lookups in the default graph
519
                $propkey = (substr($prop, 0, 5) == 'dc11:') ?
520
                    str_replace('dc11:', 'dc:', $prop) : $prop;
521
                $is_well_known = (gettext($propkey) != $propkey);
522
523
                // if not found in current vocabulary, look up in the default graph to be able
524
                // to read an ontology loaded in a separate graph
525
                // note that this imply that the property has an rdf:type declared for the query to work
526
                if(!$is_well_known && !$proplabel) {
527
                    $envLangLabels = $this->model->getDefaultSparql()->queryLabel($longUri, $this->getEnvLang());
528
                    
529
                    $defaultPropLabel = $this->model->getDefaultSparql()->queryLabel($longUri, '');
530
531
					if($envLangLabels) {
532
						$proplabel = $envLangLabels[$this->getEnvLang()];
533
                    } else {
534
						if($defaultPropLabel) {
535
							$proplabel = $defaultPropLabel[''];
536
						}
537
					}
538
                }
539
540
                // look for superproperties in the current graph
541
                $superprops = array();
542
                foreach ($this->graph->allResources($prop, 'rdfs:subPropertyOf') as $subi) {
543
                    $superprops[] = $subi->getUri();
544
                }
545
546
                // also look up superprops in the default graph if not found in current vocabulary
547
                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...
548
                    $superprops = $this->model->getDefaultSparql()->querySuperProperties($longUri);
549
                }
550
551
                // we're reading only one super property, even if there are multiple ones
552
                $superprop = ($superprops)?$superprops[0]:null;
553
                if ($superprop) {
554
                    $superprop = EasyRdf\RdfNamespace::shorten($superprop) ? EasyRdf\RdfNamespace::shorten($superprop) : $superprop;
555
                }
556
                $sort_by_notation = $this->vocab->getConfig()->sortByNotation();
557
                $propobj = new ConceptProperty($prop, $proplabel, $superprop, $sort_by_notation);
558
559
                if ($propobj->getLabel() !== null) {
560
                    // only display properties for which we have a label
561
                    $ret[$prop] = $propobj;
562
                }
563
564
                // searching for subproperties of literals too
565
                if($superprops) {
566
                    foreach ($superprops as $subi) {
567
                        $suburi = EasyRdf\RdfNamespace::shorten($subi) ? EasyRdf\RdfNamespace::shorten($subi) : $subi;
568
                        $duplicates[$suburi] = $prop;
569
                    }
570
                }
571
572
                // Iterating through every literal and adding these to the data object.
573
                foreach ($this->resource->allLiterals($sprop) as $val) {
574
                    $literal = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $val, $prop);
575
                    // only add literals when they match the content/hit language or have no language defined
576
                    if (isset($ret[$prop]) && ($literal->getLang() === $this->clang || $literal->getLang() === null)) {
577
                        $ret[$prop]->addValue($literal);
578
                    }
579
580
                }
581
582
                // Iterating through every resource and adding these to the data object.
583
                foreach ($this->resource->allResources($sprop) as $val) {
584
                    // skipping narrower concepts which are already shown in a collection
585
                    if ($sprop === 'skos:narrower' && array_key_exists($val->getUri(), $inCollection)) {
586
                        continue;
587
                    }
588
589
                    // hiding rdf:type property if it's just skos:Concept
590
                    if ($sprop === 'rdf:type' && $val->shorten() === 'skos:Concept') {
591
                        continue;
592
                    }
593
594
                    // handled by getMappingProperties()
595
                    if (in_array($sprop, $this->MAPPING_PROPERTIES)) {
596
                        continue;
597
                    }
598
599 View Code Duplication
                    if (isset($ret[$prop])) {
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...
600
                        // create a ConceptPropertyValue first, assuming the resource exists in current vocabulary
601
                        $value = new ConceptPropertyValue($this->model, $this->vocab, $val, $prop, $this->clang);
602
                        $ret[$prop]->addValue($value, $this->clang);
603
                    }
604
605
                }
606
            }
607
        }
608
        // adding narrowers part of a collection
609
        foreach ($properties as $prop => $values) {
610
            foreach ($values as $value) {
611
                $ret[$prop]->addValue($value, $this->clang);
612
            }
613
        }
614
615
        foreach ($ret as $key => $prop) {
616
            if (sizeof($prop->getValues()) === 0) {
617
                unset($ret[$key]);
618
            }
619
        }
620
621
        $ret = $this->removeDuplicatePropertyValues($ret, $duplicates);
622
        // sorting the properties to the order preferred in the Skosmos concept page.
623
        return $this->arbitrarySort($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
        // handled separately: remove duplicate skos:prefLabel value (#854)
656
        if (isset($duplicates["skos:prefLabel"])) {
657
            unset($ret[$duplicates["skos:prefLabel"]]);
658
        }
659
        return $ret;
660
    }
661
662
    /**
663
     * @param $lang UI language
664
     * @return String|null the translated label of skos:prefLabel subproperty, or null if not available
665
     */
666
    public function getPreferredSubpropertyLabelTranslation($lang) {
667
        $prefLabelProp = $this->graph->resource("skos:prefLabel");
668
        $subPrefLabelProps = $this->graph->resourcesMatching('rdfs:subPropertyOf', $prefLabelProp);
669
        foreach ($subPrefLabelProps as $subPrefLabelProp) {
670
            // return the first available translation
671
            if ($subPrefLabelProp->label($lang)) return $subPrefLabelProp->label($lang);
0 ignored issues
show
Bug introduced by
The method label cannot be called on $subPrefLabelProp (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...
672
        }
673
        return null;
674
    }
675
676
    /**
677
     * @return DateTime|null the modified date, or null if not available
678
     */
679
    public function getModifiedDate()
680
    {
681
        // finding the modified properties
682
        /** @var \EasyRdf\Resource|\EasyRdf\Literal|null $modifiedResource */
683
        $modifiedResource = $this->resource->get('dc:modified');
684
        if ($modifiedResource && $modifiedResource instanceof \EasyRdf\Literal\Date) {
685
            return $modifiedResource->getValue();
686
        }
687
688
        // if the concept does not have a modified date, we look for it in its
689
        // vocabulary
690
        return $this->getVocab()->getModifiedDate();
691
    }
692
693
    /**
694
     * Gets the creation date and modification date if available.
695
     * @return String containing the date information in a human readable format.
696
     */
697
    public function getDate()
698
    {
699
        $ret = '';
700
        $created = '';
701
        try {
702
            // finding the created properties
703
            if ($this->resource->get('dc:created')) {
704
                $created = $this->resource->get('dc:created')->getValue();
705
            }
706
707
            $modified = $this->getModifiedDate();
708
709
            // making a human readable string from the timestamps
710
            if ($created != '') {
711
                $ret = gettext('skosmos:created') . ' ' . (Punic\Calendar::formatDate($created, 'short'));
712
            }
713
714
            if ($modified != '') {
715
                if ($created != '') {
716
                    $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 707 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...
717
                } else {
718
                    $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 707 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...
719
                }
720
721
            }
722
        } catch (Exception $e) {
723
            trigger_error($e->getMessage(), E_USER_WARNING);
724
            $ret = '';
725
            if ($this->resource->get('dc:modified')) {
726
                $modified = (string) $this->resource->get('dc:modified');
727
                $ret = gettext('skosmos:modified') . ' ' . $modified;
728
            }
729
            if ($this->resource->get('dc:created')) {
730
                $created .= (string) $this->resource->get('dc:created');
731
                $ret .= ' ' . gettext('skosmos:created') . ' ' . $created;
732
            }
733
        }
734
        return $ret;
735
    }
736
737
    /**
738
     * Gets the members of a specific collection.
739
     * @param $coll
740
     * @param array containing all narrowers as EasyRdf\Resource
741
     * @return array containing ConceptPropertyValue objects
742
     */
743
    private function getCollectionMembers($coll, $narrowers)
744
    {
745
        $membersArray = array();
746
        if ($coll->label()) {
747
            $collLabel = $coll->label()->getValue($this->clang) ? $coll->label($this->clang) : $coll->label();
748
            if ($collLabel) {
749
                $collLabel = $collLabel->getValue();
750
            }
751
        } else {
752
            $collLabel = $coll->getUri();
753
        }
754
        $membersArray[$collLabel] = new ConceptPropertyValue($this->model, $this->vocab, $coll, 'skos:narrower', $this->clang);
755
        foreach ($coll->allResources('skos:member') as $member) {
756 View Code Duplication
            if (array_key_exists($member->getUri(), $narrowers)) {
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...
757
                $narrower = $narrowers[$member->getUri()];
758
                if (isset($narrower)) {
759
                    $membersArray[$collLabel]->addSubMember(new ConceptPropertyValue($this->model, $this->vocab, $narrower, 'skos:member', $this->clang), $this->clang);
760
                }
761
762
            }
763
        }
764
765
        return $membersArray;
766
    }
767
768
    /**
769
     * Gets the groups the concept belongs to.
770
     */
771
    public function getGroupProperties()
772
    {
773
        return $this->getCollections(false);
774
    }
775
776
    /**
777
     * Gets the groups/arrays the concept belongs to.
778
     */
779
    private function getCollections($includeArrays) {
780
        $groups = array();
781
        $collections = $this->graph->resourcesMatching('skos:member', $this->resource);
782
        if (isset($collections)) {
783
            $arrayClassURI = $this->vocab !== null ? $this->vocab->getConfig()->getArrayClassURI() : null;
784
            $arrayClass = $arrayClassURI !== null ? EasyRdf\RdfNamespace::shorten($arrayClassURI) : null;
785
            $superGroups = $this->resource->all('isothes:superGroup');
786
            $superGroupUris = array_map(function($obj) { return $obj->getUri(); }, $superGroups);
787
            foreach ($collections as $collection) {
788
                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...
789
                    // not adding the memberOf if the reverse resource is already covered by isothes:superGroup see issue #433
790
                    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...
791
                        continue;
792
                    }
793
                    $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...
794
                    $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...
795
                    if ($collLabel) {
796
                        $collLabel = $collLabel->getValue();
797
                    }
798
799
                    $group = new ConceptPropertyValue($this->model, $this->vocab, $collection, $property, $this->clang);
800
                    $groups[$collLabel] = array($group);
801
802
                    $res = $collection;
803
                    while($super = $this->graph->resourcesMatching('skos:member', $res)) {
804
                        foreach ($super as $res) {
805
                            $superprop = new ConceptPropertyValue($this->model, $this->vocab, $res, 'skosmos:memberOfSuper', $this->clang);
806
                            array_unshift($groups[$collLabel], $superprop);
807
                        }
808
                    }
809
                }
810
            }
811
        }
812
        uksort($groups, 'strcoll');
813
        return $groups;
814
    }
815
816
    public function getArrayProperties() {
817
        return $this->getCollections(true);
818
    }
819
820
    /**
821
     * Given a language code, gets its name in UI language via Punic or alternatively
822
     * tries to search for a gettext translation.
823
     * @param string $langCode
824
     * @return string e.g. 'English'
825
     */
826
    private function langToString($langCode) {
827
        // using empty string as the language name when there is no langcode set
828
        $langName = '';
829
        if (!empty($langCode)) {
830
            $langName = Punic\Language::getName($langCode, $this->getEnvLang()) !== $langCode ? Punic\Language::getName($langCode, $this->getEnvLang()) : gettext($langCode);
831
        }
832
        return $langName;
833
    }
834
835
    /**
836
     * Gets the values of a property in all other languages than in the current language
837
     * and places them in a [langCode][userDefinedKey] array.
838
     * @param string $prop The property for which the values are looked upon to
839
     * @param string $key User-defined key for accessing the values
840
     * @return array LangCode-based multidimensional array ([string][string][ConceptPropertyValueLiteral]) or empty array if no values
841
     */
842
    private function getForeignLabelList($prop, $key) {
843
        $ret = array();
844
        $labels = $this->resource->allLiterals($prop);
845
846
        foreach ($labels as $lit) {
847
            // filtering away subsets of the current language eg. en vs en-GB
848
            if ($lit->getLang() != $this->clang && strpos($lit->getLang(), $this->getEnvLang() . '-') !== 0) {
849
                $langCode = $lit->getLang() ? $lit->getLang() : '';
850
                $ret[$langCode][$key][] = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $lit, $prop);
851
            }
852
        }
853
        return $ret;
854
    }
855
856
    /**
857
     * Gets the values of skos:prefLabel and skos:altLabel in all other languages than in the current language.
858
     * @return array Language-based multidimensional sorted array ([string][string][ConceptPropertyValueLiteral]) or empty array if no values
859
     */
860
    public function getForeignLabels()
861
    {
862
        $prefLabels = $this->getForeignLabelList('skos:prefLabel', 'prefLabel');
863
        $altLabels = $this->getForeignLabelList('skos:altLabel', 'altLabel');
864
        $ret = array_merge_recursive($prefLabels, $altLabels);
865
866
        $langArray = array_keys($ret);
867
        foreach ($langArray as $lang) {
868
            $coll = collator_create($lang);
869
            if (isset($ret[$lang]['prefLabel']))
870
            {
871
                $coll->sort($ret[$lang]['prefLabel'], Collator::SORT_STRING);
872
            }
873
            if (isset($ret[$lang]['altLabel']))
874
            {
875
                $coll->sort($ret[$lang]['altLabel'], Collator::SORT_STRING);
876
            }
877
            if ($lang !== '') {
878
                $ret[$this->langToString($lang)] = $ret[$lang];
879
                unset($ret[$lang]);
880
            }
881
        }
882
        uksort($ret, 'strcoll');
883
        return $ret;
884
    }
885
886
    /**
887
     * Gets the values for the property in question in all other languages than the ui language.
888
     * @param string $property
889
     */
890
    public function getAllLabels($property)
891
    {
892
        $labels = array();
893
        // shortening property labels if possible, EasyRdf requires full URIs to be in angle brackets
894
        $property = (EasyRdf\RdfNamespace::shorten($property) !== null) ? EasyRdf\RdfNamespace::shorten($property) : "<$property>";
895
        foreach ($this->resource->allLiterals($property) as $lit) {
896
            $labels[Punic\Language::getName($lit->getLang(), $this->getEnvLang())][] = new ConceptPropertyValueLiteral($this->model, $this->vocab, $this->resource, $lit, $property);
897
        }
898
        ksort($labels);
899
        return $labels;
900
    }
901
902
    /**
903
     * Dump concept graph as JSON-LD.
904
     */
905
    public function dumpJsonLd() {
906
        $context = array(
907
            'skos' => EasyRdf\RdfNamespace::get("skos"),
908
            'isothes' => EasyRdf\RdfNamespace::get("isothes"),
909
            'rdfs' => EasyRdf\RdfNamespace::get("rdfs"),
910
            'owl' =>EasyRdf\RdfNamespace::get("owl"),
911
            'dct' => EasyRdf\RdfNamespace::get("dcterms"),
912
            'dc11' => EasyRdf\RdfNamespace::get("dc11"),
913
            'uri' => '@id',
914
            'type' => '@type',
915
            'lang' => '@language',
916
            'value' => '@value',
917
            'graph' => '@graph',
918
            'label' => 'rdfs:label',
919
            'prefLabel' => 'skos:prefLabel',
920
            'altLabel' => 'skos:altLabel',
921
            'hiddenLabel' => 'skos:hiddenLabel',
922
            'broader' => 'skos:broader',
923
            'narrower' => 'skos:narrower',
924
            'related' => 'skos:related',
925
            'inScheme' => 'skos:inScheme',
926
            'schema' => EasyRdf\RdfNamespace::get("schema"),
927
            'wd' => EasyRdf\RdfNamespace::get("wd"),
928
            'wdt' => EasyRdf\RdfNamespace::get("wdt"),
929
        );
930
        $vocabPrefix = preg_replace('/\W+/', '', $this->vocab->getId()); // strip non-word characters
931
        $vocabUriSpace = $this->vocab->getUriSpace();
932
933
        if (!in_array($vocabUriSpace, $context, true)) {
934
            if (!isset($context[$vocabPrefix])) {
935
                $context[$vocabPrefix] = $vocabUriSpace;
936
            }
937
            else if ($context[$vocabPrefix] !== $vocabUriSpace) {
938
                $i = 2;
939
                while (isset($context[$vocabPrefix . $i]) && $context[$vocabPrefix . $i] !== $vocabUriSpace) {
940
                    $i += 1;
941
                }
942
                $context[$vocabPrefix . $i] = $vocabUriSpace;
943
            }
944
        }
945
        $compactJsonLD = \ML\JsonLD\JsonLD::compact($this->graph->serialise('jsonld'), json_encode($context));
946
        return \ML\JsonLD\JsonLD::toString($compactJsonLD);
947
    }
948
949
    public function isUseModifiedDate()
950
    {
951
        return $this->getVocab()->isUseModifiedDate();
952
    }
953
}
954