Passed
Pull Request — master (#1166)
by Osma
02:45
created

Vocabulary::getAlphabet()   A

Complexity

Conditions 6
Paths 16

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 17
nc 16
nop 1
dl 0
loc 25
rs 9.0777
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Vocabulary dataobjects provide access to the vocabularies on the SPARQL endpoint.
5
 */
6
class Vocabulary extends DataObject implements Modifiable
7
{
8
    /** cached value of URI space */
9
    private $urispace = null;
10
    private $config;
11
12
    public function __construct($model, $resource)
13
    {
14
        parent::__construct($model, $resource);
15
        $this->config = new VocabularyConfig($resource, $model->getConfig()->getGlobalPlugins());
16
    }
17
18
    /**
19
     * Returns the VocabularyConfig object
20
     * @return VocabularyConfig
21
     */
22
    public function getConfig()
23
    {
24
      return $this->config;
25
    }
26
27
    /**
28
     * Get the SPARQL endpoint URL for this vocabulary
29
     *
30
     * @return string endpoint URL
31
     */
32
    public function getEndpoint()
33
    {
34
        $endpoint = $this->config->getSparqlEndpoint();
35
        if ($endpoint === null) {
36
            $endpoint = $this->model->getConfig()->getDefaultEndpoint();
37
        }
38
        return $endpoint;
39
    }
40
41
    /**
42
     * Get the SPARQL graph URI for this vocabulary
43
     *
44
     * @return string|null graph URI
45
     */
46
    public function getGraph()
47
    {
48
        return $this->config->getSparqlGraph();
49
    }
50
51
    /**
52
     * Get the SPARQL implementation for this vocabulary
53
     *
54
     * @return GenericSparql SPARQL object
55
     */
56
    public function getSparql()
57
    {
58
        $endpoint = $this->getEndpoint();
59
        $graph = $this->getGraph();
60
        $dialect = $this->config->getSparqlDialect(); ?? $this->model->getConfig()->getDefaultSparqlDialect();
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_COALESCE on line 60 at column 54
Loading history...
61
62
        return $this->model->getSparqlImplementation($dialect, $endpoint, $graph);
63
    }
64
65
    /**
66
     * Get the URI space of concepts in this vocabulary.
67
     *
68
     * @return string full URI of concept
69
     */
70
    public function getUriSpace()
71
    {
72
        if ($this->urispace === null) // initialize cache
73
        {
74
            $this->urispace = $this->resource->getLiteral('void:uriSpace')->getValue();
75
        }
76
77
        return $this->urispace;
78
    }
79
80
    /**
81
     * Return true if the URI is within the URI space of this vocabulary.
82
     *
83
     * @param string full URI of concept
84
     * @return bool true if URI is within URI namespace, false otherwise
85
     */
86
87
    public function containsURI($uri)
88
    {
89
        return (strpos($uri, $this->getUriSpace()) === 0);
90
    }
91
92
    /**
93
     * Get the full URI of a concept in a vocabulary. If the passed local
94
     * name is already a full URI, return it unchanged.
95
     *
96
     * @param $lname string local name of concept
97
     * @return string full URI of concept
98
     */
99
    public function getConceptURI($lname)
100
    {
101
        if (strpos($lname, 'http') === 0) {
102
            return $lname;
103
        }
104
        // already a full URI
105
        return $this->getUriSpace() . $lname;
106
    }
107
108
    /**
109
     * Asks the sparql implementation to make a label query for a uri.
110
     * @param string $uri
111
     * @param string $lang
112
     */
113
    public function getConceptLabel($uri, $lang)
114
    {
115
        return $this->getSparql()->queryLabel($uri, $lang);
116
    }
117
118
    /**
119
     * Asks the sparql implementation to make a label query for a uri.
120
     * @param string $uri
121
     * @param string $lang
122
     * @return array array of altLabels
123
     */
124
    public function getAllConceptLabels($uri, $lang)
125
    {
126
        return $this->getSparql()->queryAllConceptLabels($uri, $lang);
127
    }
128
    /**
129
     * Get the localname of a concept in the vocabulary. If the URI is not
130
     * in the URI space of this vocabulary, return the full URI.
131
     *
132
     * @param $uri string full URI of concept
133
     * @return string local name of concept, or original full URI if the local name cannot be determined
134
     */
135
    public function getLocalName($uri)
136
    {
137
        return str_replace($this->getUriSpace(), "", $uri);
138
    }
139
140
    /**
141
     * Retrieves all the information about the Vocabulary
142
     * from the SPARQL-endpoint.
143
     */
144
    public function getInfo($lang = null)
145
    {
146
        $ret = array();
147
        if (!$lang) {
148
            $lang = $this->getEnvLang();
149
        }
150
151
        // get metadata (literals only e.g. name) from vocabulary configuration file
152
        foreach ($this->resource->properties() as $prop) {
153
            foreach ($this->resource->allLiterals($prop, $lang) as $val) {
154
                $ret[$prop][] = $val->getValue();
155
            }
156
        }
157
158
        // also include ConceptScheme metadata from SPARQL endpoint
159
        $defaultcs = $this->getDefaultConceptScheme();
160
161
        // query everything the endpoint knows about the ConceptScheme
162
        $sparql = $this->getSparql();
163
        $result = $sparql->queryConceptScheme($defaultcs);
164
        $conceptscheme = $result->resource($defaultcs);
165
        $this->order = array("dc:title", "dc11:title", "skos:prefLabel", "rdfs:label", "dc:subject", "dc11:subject", "dc:description", "dc11:description", "dc:publisher", "dc11:publisher", "dc:creator", "dc11:creator", "dc:contributor", "dc:language", "dc11:language", "owl:versionInfo", "dc:source", "dc11:source");
166
167
        foreach ($conceptscheme->properties() as $prop) {
168
            foreach ($conceptscheme->allLiterals($prop, $lang) as $val) {
169
                $prop = (substr($prop, 0, 5) == 'dc11:') ? str_replace('dc11:', 'dc:', $prop) : $prop;
170
                $ret[$prop][$val->getValue()] = $val;
171
            }
172
            if (!isset($ret[$prop]) || sizeof($ret[$prop]) == 0) { // not found with language tag
173
                foreach ($conceptscheme->allLiterals($prop, null) as $val) {
174
                    $prop = (substr($prop, 0, 5) == 'dc11:') ? str_replace('dc11:', 'dc:', $prop) : $prop;
175
                    if ($val->getValue() instanceof DateTime) {
176
                        $val = Punic\Calendar::formatDate($val->getValue(), 'full', $lang) . ' ' . Punic\Calendar::format($val->getValue(), 'HH:mm:ss', $lang);
177
                    }
178
                    $ret[$prop][] = $val;
179
                }
180
            }
181
            foreach ($conceptscheme->allResources($prop) as $val) {
182
                $prop = (substr($prop, 0, 5) == 'dc11:') ? str_replace('dc11:', 'dc:', $prop) : $prop;
183
                $exvocab = $this->model->guessVocabularyFromURI($val->getURI());
184
                $exlabel = $this->getExternalLabel($exvocab, $val->getURI(), $lang);
185
                if (isset($exlabel)) {
186
                    $val->add('skosmos:vocab', $exvocab->getId());
187
                    $val->add('skosmos:label', $exlabel);
188
                }
189
                $label = $val->label($lang) ? $val->label($lang)->getValue() : $val->getUri();
190
                $ret[$prop][$exlabel ? $exlabel->getValue() : $label] = $val;
191
            }
192
            if (isset($ret[$prop])) {
193
                ksort($ret[$prop]);
194
            }
195
        }
196
        if (isset($ret['owl:versionInfo'])) { // if version info available for vocabulary convert it to a more readable format
197
            $ret['owl:versionInfo'][0] = $this->parseVersionInfo($ret['owl:versionInfo'][0]);
198
        }
199
        // remove duplicate values
200
        foreach (array_keys($ret) as $prop) {
201
            $ret[$prop] = array_unique($ret[$prop]);
202
        }
203
204
        $ret = $this->arbitrarySort($ret);
205
206
        // filtering multiple labels
207
        if (isset($ret['dc:title'])) {
208
            unset($ret['dc11:title'], $ret['skos:prefLabel'], $ret['rdfs:label']);
209
        } else if (isset($ret['dc11:title'])) {
210
            unset($ret['skos:prefLabel'], $ret['rdfs:label']);
211
        } else if (isset($ret['skos:prefLabel'])) {
212
            unset($ret['rdfs:label']);
213
        }
214
215
        return $ret;
216
    }
217
218
    /**
219
     * Return all concept schemes in the vocabulary.
220
     * @return array Array with concept scheme URIs (string) as keys and labels (string) as values
221
     */
222
223
    public function getConceptSchemes($lang = '')
224
    {
225
        if ($lang === '') {
226
            $lang = $this->getEnvLang();
227
        }
228
229
        return $this->getSparql()->queryConceptSchemes($lang);
230
    }
231
232
    /**
233
     * Return the URI of the default concept scheme of this vocabulary. If the skosmos:mainConceptScheme property is set in the
234
     * vocabulary configuration, that will be returned. Otherwise an arbitrary concept scheme will be returned.
235
     * @return string concept scheme URI
236
     */
237
238
    public function getDefaultConceptScheme()
239
    {
240
        $conceptScheme = $this->config->getMainConceptSchemeURI();
241
        if ($conceptScheme) {
242
            return $conceptScheme;
243
        }
244
245
        // mainConceptScheme not explicitly set, guess it
246
        $conceptSchemes = $this->getConceptSchemes();
247
        $conceptSchemeURIs = array_keys($conceptSchemes);
248
        // return the URI of the last concept scheme
249
        return array_pop($conceptSchemeURIs);
250
    }
251
252
    /**
253
     * Returns the main Concept Scheme of that Vocabulary, or null if not set.
254
     * @param string $defaultConceptSchemeURI default concept scheme URI
255
     * @return \EasyRdf\Graph|mixed
256
     */
257
    public function getConceptScheme(string $defaultConceptSchemeURI)
258
    {
259
        return $this->getSparql()->queryConceptScheme($defaultConceptSchemeURI);
260
    }
261
262
    /**
263
     * Return the top concepts of a concept scheme in the vocabulary.
264
     * @param string $conceptScheme URI of concept scheme whose top concepts to return. If not set,
265
     *                              the default concept scheme of the vocabulary will be used.
266
     * @param string $lang preferred language for the concept labels,
267
     * @return array Array with concept URIs (string) as keys and labels (string) as values
268
     */
269
270
    public function getTopConcepts($conceptScheme = null, $lang = '')
271
    {
272
        if ($lang === '') {
273
            $lang = $this->getEnvLang();
274
        }
275
        $fallback = $this->config->getDefaultLanguage();
276
277
        if ($conceptScheme === null || $conceptScheme == '') {
278
            $conceptScheme = $this->getDefaultConceptScheme();
279
        }
280
281
        return $this->getSparql()->queryTopConcepts($conceptScheme, $lang, $fallback);
282
    }
283
284
    /**
285
     * Tries to parse version, date and time from sparql version information into a readable format.
286
     * @param string $version
287
     * @return string
288
     */
289
    private function parseVersionInfo($version)
290
    {
291
        $parts = explode(' ', $version);
292
        if ($parts[0] != '$Id:') {
293
            return $version;
294
        }
295
        // don't know how to parse
296
        $rev = $parts[2];
297
        $datestr = $parts[3] . ' ' . $parts[4];
298
299
        return "$datestr (r$rev)";
300
    }
301
302
    /**
303
     * Counts the statistics of the vocabulary.
304
     * @return Array containing the label counts
305
     */
306
    public function getStatistics($lang = '', $array=null, $group=null)
307
    {
308
        $sparql = $this->getSparql();
309
        // find the number of concepts
310
        return $sparql->countConcepts($lang, $array, $group);
311
    }
312
313
    /**
314
     * Counts the statistics of the vocabulary.
315
     * @return array of the concept counts in different languages
316
     */
317
    public function getLabelStatistics()
318
    {
319
        $sparql = $this->getSparql();
320
        $ret = array();
321
        // count the number of different types of concepts in all languages
322
        $ret['terms'] = $sparql->countLangConcepts($this->config->getLanguages(), $this->config->getIndexClasses());
323
324
        return $ret;
325
    }
326
327
    /**
328
     * Gets the parent concepts of a concept and child concepts for all of those.
329
     * @param string $uri
330
     * @param string $lang language identifier.
331
     */
332
    public function getConceptHierarchy($uri, $lang)
333
    {
334
        $lang = $lang ? $lang : $this->getEnvLang();
335
        $fallback = count($this->config->getLanguageOrder($lang)) > 1 ? $this->config->getLanguageOrder($lang)[1] : $this->config->getDefaultLanguage();
336
        $props = $this->config->getHierarchyProperty();
337
        return $this->getSparql()->queryParentList($uri, $lang, $fallback, $props);
338
    }
339
340
    /**
341
     * Gets the child relations of a concept and whether these children have more children.
342
     * @param string $uri
343
     */
344
    public function getConceptChildren($uri, $lang)
345
    {
346
        $lang = $lang ? $lang : $this->getEnvLang();
347
        $fallback = count($this->config->getLanguageOrder($lang)) > 1 ? $this->config->getLanguageOrder($lang)[1] : $this->config->getDefaultLanguage();
348
        $props = $this->config->getHierarchyProperty();
349
        return $this->getSparql()->queryChildren($uri, $lang, $fallback, $props);
350
    }
351
352
    /**
353
     * Gets the skos:narrower relations of a concept.
354
     * @param string $uri
355
     * @param string $lang language identifier.
356
     */
357
    public function getConceptNarrowers($uri, $lang)
358
    {
359
        $lang = $lang ? $lang : $this->getEnvLang();
360
        return $this->getSparql()->queryProperty($uri, 'skos:narrower', $lang);
361
    }
362
363
    /**
364
     * Gets the skos:narrowerTransitive relations of a concept.
365
     * @param string $uri
366
     * @param integer $limit
367
     * @param string $lang language identifier.
368
     */
369
    public function getConceptTransitiveNarrowers($uri, $limit, $lang)
370
    {
371
        $lang = $lang ? $lang : $this->getEnvLang();
372
        return $this->getSparql()->queryTransitiveProperty($uri, array('skos:narrower'), $lang, $limit);
373
    }
374
375
    /**
376
     * Gets the skos:broader relations of a concept.
377
     * @param string $uri
378
     * @param string $lang language identifier.
379
     */
380
    public function getConceptBroaders($uri, $lang)
381
    {
382
        $lang = $lang ? $lang : $this->getEnvLang();
383
        return $this->getSparql()->queryProperty($uri, 'skos:broader', $lang);
384
    }
385
386
    /**
387
     * Gets the skos:broaderTransitive relations of a concept.
388
     * @param string $uri
389
     * @param integer $limit
390
     * @param boolean $any set to true if you want to have a label even in case of a correct language one missing.
391
     * @param string $lang language identifier.
392
     */
393
    public function getConceptTransitiveBroaders($uri, $limit, $any = false, $lang)
394
    {
395
        $lang = $lang ? $lang : $this->getEnvLang();
396
        $fallback = $this->config->getDefaultLanguage();
397
        return $this->getSparql()->queryTransitiveProperty($uri, array('skos:broader'), $lang, $limit, $any, $fallback);
398
    }
399
400
    /**
401
     * Gets all the skos:related concepts of a concept.
402
     * @param string $uri
403
     * @param string $lang language identifier.
404
     */
405
    public function getConceptRelateds($uri, $lang)
406
    {
407
        $lang = $lang ? $lang : $this->getEnvLang();
408
        return $this->getSparql()->queryProperty($uri, 'skos:related', $lang);
409
    }
410
411
    /**
412
     * Makes a query into the sparql endpoint for a concept.
413
     * @param string $uri the full URI of the concept
414
     * @return Concept[]
415
     */
416
    public function getConceptInfo($uri, $clang)
417
    {
418
        $sparql = $this->getSparql();
419
420
        return $sparql->queryConceptInfo($uri, $this->config->getArrayClassURI(), array($this), $clang);
421
    }
422
423
    /**
424
     * Lists the different concept groups available in the vocabulary.
425
     * @param string $clang content language parameter
426
     * @return array
427
     */
428
    public function listConceptGroups($clang = null)
429
    {
430
        if ($clang === null || $clang == '') {
431
            $clang = $this->getEnvLang();
432
        }
433
434
        $ret = array();
435
        $gclass = $this->config->getGroupClassURI();
436
        if ($gclass === null) {
437
            return $ret;
438
        }
439
        // no group class defined, so empty result
440
        $groups = $this->getSparql()->listConceptGroups($gclass, $clang);
441
        foreach ($groups as $uri => $label) {
442
            $ret[$uri] = $label;
443
        }
444
445
        return $ret;
446
    }
447
448
    /**
449
     * Lists the concepts available in the concept group.
450
     * @param $clname
451
     * @return array
452
     */
453
    public function listConceptGroupContents($glname, $clang)
454
    {
455
        if (!$clang) {
456
            $clang = $this->config->getEnvLang();
457
        }
458
459
        $ret = array();
460
        $gclass = $this->config->getGroupClassURI();
461
        if ($gclass === null) {
462
            return $ret;
463
        }
464
        // no group class defined, so empty result
465
        $group = $this->getConceptURI($glname);
466
        $contents = $this->getSparql()->listConceptGroupContents($gclass, $group, $clang, $this->config->getShowDeprecated());
467
        foreach ($contents as $uri => $label) {
468
            $ret[$uri] = $label;
469
        }
470
471
        return $ret;
472
    }
473
474
    /**
475
     * Returns the letters of the alphabet which have been used in this vocabulary.
476
     * The returned letters may also include specials such as '0-9' (digits) and '!*' (special characters).
477
     * @param $clang content language
478
     * @return array array of letters
479
     */
480
    public function getAlphabet($clang)
481
    {
482
        $chars = $this->getSparql()->queryFirstCharacters($clang, $this->config->getIndexClasses());
483
        $letters = array();
484
        $digits = false;
485
        $specials = false;
486
        foreach ($chars as $char) {
487
            if (preg_match('/\p{L}/u', $char)) {
488
                $letters[] = $char;
489
            } elseif (preg_match('/\d/u', $char)) {
490
                $digits = true;
491
            } else {
492
                $specials = true;
493
            }
494
        }
495
        usort($letters, 'strcoll');
496
        if ($specials) {
497
            $letters[] = '!*';
498
        }
499
500
        if ($digits) {
501
            $letters[] = '0-9';
502
        }
503
504
        return $letters;
505
    }
506
507
    /**
508
     * Searches for concepts with a label starting with the specified letter.
509
     * Also the special tokens '0-9' (digits), '!*' (special characters) and '*'
510
     * (everything) are supported.
511
     * @param $letter letter (or special token) to search for
512
     */
513
    public function searchConceptsAlphabetical($letter, $limit = null, $offset = null, $clang = null)
514
    {
515
        return $this->getSparql()->queryConceptsAlphabetical($letter, $clang, $limit, $offset, $this->config->getIndexClasses(), $this->config->getShowDeprecated(), $this->config->getAlphabeticalListQualifier());
516
    }
517
518
    /**
519
     * Makes a query for the transitive broaders of a concept and returns the concepts hierarchy processed for the view.
520
     * @param string $lang
521
     * @param string $uri
522
     */
523
    public function getBreadCrumbs($lang, $uri)
524
    {
525
        $broaders = $this->getConceptTransitiveBroaders($uri, 1000, true, $lang);
526
        $origCrumbs = $this->getCrumbs($broaders, $uri);
527
        return $this->combineCrumbs($origCrumbs);
528
    }
529
530
    /**
531
     * Takes the crumbs as a parameter and combines the crumbs if the path they form is too long.
532
     * @return array
533
     */
534
    private function combineCrumbs($origCrumbs)
535
    {
536
        $combined = array();
537
        foreach ($origCrumbs as $pathKey => $path) {
538
            $firstToCombine = true;
539
            $combinedPath = array();
540
            foreach ($path as $crumbKey => $crumb) {
541
                if ($crumb->getPrefLabel() === '...') {
542
                    array_push($combinedPath, $crumb);
543
                    if ($firstToCombine) {
544
                        $firstToCombine = false;
545
                    } else {
546
                        unset($origCrumbs[$pathKey][$crumbKey]);
547
                    }
548
                }
549
            }
550
            $combined[] = $combinedPath;
551
        }
552
553
        return array('combined' => $combined, 'breadcrumbs' => $origCrumbs);
554
    }
555
556
    /**
557
     * Recursive function for building the breadcrumb paths for the view.
558
     * @param array $bTresult contains the results of the broaderTransitive query.
559
     * @param string $uri
560
     * @param array $path
561
     */
562
    private function getCrumbs($bTresult, $uri, $path = null)
563
    {
564
        $crumbs = array();
565
        if (!isset($path)) {
566
            $path = array();
567
        }
568
569
        // check that there is no cycle (issue #220)
570
        foreach ($path as $childcrumb) {
571
            if ($childcrumb->getUri() == $uri) {
572
                // found a cycle - short-circuit and stop
573
                return $crumbs;
574
            }
575
        }
576
        if (isset($bTresult[$uri]['direct'])) {
577
            foreach ($bTresult[$uri]['direct'] as $broaderUri) {
578
                $newpath = array_merge($path, array(new Breadcrumb($uri, $bTresult[$uri]['label'])));
579
                if ($uri !== $broaderUri) {
580
                    $crumbs = array_merge($crumbs, $this->getCrumbs($bTresult, $broaderUri, $newpath));
581
                }
582
            }
583
        } else { // we have reached the end of a path and we need to start a new row in the 'stack'
584
            if (isset($bTresult[$uri])) {
585
                $path = array_merge($path, array(new Breadcrumb($uri, $bTresult[$uri]['label'])));
586
            }
587
588
            $index = 1;
589
            $length = sizeof($path);
590
            $limit = $length - 5;
591
            foreach ($path as $crumb) {
592
                if ($length > 5 && $index > $length - $limit) { // displays 5 concepts closest to the concept.
593
                    $crumb->hideLabel();
594
                }
595
                $index++;
596
            }
597
            $crumbs[] = array_reverse($path);
598
        }
599
        return $crumbs;
600
    }
601
602
    /**
603
     * Verify that the requested language is supported by the vocabulary. If not, returns
604
     * the default language of the vocabulary.
605
     * @param string $lang language to check
606
     * @return string language tag that is supported by the vocabulary
607
     */
608
609
    public function verifyVocabularyLanguage($lang)
610
    {
611
        return (in_array($lang, $this->config->getLanguages())) ? $lang : $this->config->getDefaultLanguage();
612
    }
613
614
    /**
615
     * Returns a list of recently changed or entirely new concepts.
616
     * @param string $prop the property uri pointing to timestamps, eg. 'dc:modified'
617
     * @param string $clang content language for the labels
618
     * @param int $offset starting index offset
619
     * @param int $limit maximum number of concepts to return
620
     * @return Array
621
     */
622
    public function getChangeList($prop, $clang, $offset, $limit)
623
    {
624
      return $this->getSparql()->queryChangeList($prop, $clang, $offset, $limit);
625
    }
626
627
    public function getTitle($lang=null) {
628
      return $this->config->getTitle($lang);
629
    }
630
631
    public function getShortName() {
632
      return $this->config->getShortName();
633
    }
634
635
    public function getId() {
636
      return $this->config->getId();
637
    }
638
639
    public function getModifiedDate()
640
    {
641
        $modifiedDate = null;
642
643
        $conceptSchemeURI = $this->getDefaultConceptScheme();
644
        if ($conceptSchemeURI) {
645
            $conceptSchemeGraph = $this->getConceptScheme($conceptSchemeURI);
646
            if (!$conceptSchemeGraph->isEmpty()) {
647
                $literal = $conceptSchemeGraph->getLiteral($conceptSchemeURI, "dc:modified");
648
                if ($literal) {
649
                    $modifiedDate = $literal->getValue();
650
                }
651
            }
652
        }
653
654
        return $modifiedDate;
655
    }
656
657
    public function isUseModifiedDate()
658
    {
659
        return $this->getConfig()->isUseModifiedDate();
660
    }
661
}
662