Passed
Push — set-user-agent-version ( ede3e4 )
by Osma
07:35
created

Model   F

Complexity

Total Complexity 119

Size/Duplication

Total Lines 654
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 119
eloc 263
c 0
b 0
f 0
dl 0
loc 654
rs 2

29 Methods

Rating   Name   Duplication   Size   Complexity  
A getConfig() 0 3 1
A getVersion() 0 8 2
A initializeLogging() 0 19 4
A __construct() 0 14 3
A getLogger() 0 3 1
A getVocabularyByGraph() 0 18 5
A getDefaultSparql() 0 3 1
C disambiguateVocabulary() 0 45 12
A getLocale() 0 3 1
B getTypes() 0 20 7
A setLocale() 0 3 1
A getTranslator() 0 3 1
B getVocabularyList() 0 27 8
A getLanguages() 0 12 3
C searchConceptsAndInfo() 0 49 12
A getVocabularyCategories() 0 8 2
A getUserAgent() 0 4 1
A getClassificationLabel() 0 4 2
A getVocabularies() 0 20 6
A createDataObjects() 0 8 2
B guessVocabularyFromURI() 0 29 8
C searchConcepts() 0 56 17
A getResourceFromUri() 0 16 4
A getVocabulariesInCategory() 0 4 1
A getText() 0 3 1
A getVocabulary() 0 9 3
A getSparqlImplementation() 0 5 1
A getResourceLabel() 0 11 3
B getRDF() 0 30 6

How to fix   Complexity   

Complex Class

Complex classes like Model often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Model, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Importing the dependencies.
5
 */
6
use Symfony\Component\Translation\Translator;
7
use Symfony\Component\Translation\Loader\JsonFileLoader;
8
9
/**
10
 * Model provides access to the data.
11
 * @property EasyRdf\Graph $graph
12
 * @property GlobalConfig $globalConfig
13
 */
14
class Model
15
{
16
    /** cache for Vocabulary objects */
17
    private $allVocabularies = null;
18
    /** cache for Vocabulary objects */
19
    private $vocabsByGraph = null;
20
    /** cache for Vocabulary objects */
21
    private $vocabsByUriSpace = null;
22
    /** how long to store retrieved URI information in APC cache */
23
    public const URI_FETCH_TTL = 86400; // 1 day
24
    private $globalConfig;
25
    private $logger;
26
    private $resolver;
27
    private $translator;
28
29
    /**
30
     * Initializes the Model object
31
     */
32
    public function __construct(string $config_filename = "../../config.ttl")
33
    {
34
        $this->resolver = new Resolver($this);
35
        $this->globalConfig = new GlobalConfig($this, $config_filename);
36
        $this->translator = null;
37
        foreach ($this->globalConfig->getLanguages() as $langcode => $locale) {
38
            if (is_null($this->translator)) {
39
                // use the first configured language as default language
40
                $this->translator = new Translator($langcode);
41
            }
42
            $this->translator->addLoader('json', new JsonFileLoader());
43
            $this->translator->addResource('json', __DIR__.'/../../resource/translations/messages.' . $langcode . '.json', $langcode);
44
        }
45
        $this->initializeLogging();
46
    }
47
48
    /**
49
     * Returns the GlobalConfig object given to the Model as a constructor parameter.
50
     * @return GlobalConfig
51
     */
52
    public function getConfig()
53
    {
54
        return $this->globalConfig;
55
    }
56
57
    /**
58
     * Configures the logging facility
59
     */
60
    private function initializeLogging()
61
    {
62
        $this->logger = new \Monolog\Logger('general');
63
        $formatter = new \Monolog\Formatter\LineFormatter("[%datetime%] %level_name% %message%\n");
64
        $formatter->allowInlineLineBreaks(true);
65
        if ($this->getConfig()->getLoggingBrowserConsole()) {
66
            $browserHandler = new \Monolog\Handler\BrowserConsoleHandler(\Monolog\Logger::INFO);
0 ignored issues
show
Deprecated Code introduced by
The constant Monolog\Logger::INFO has been deprecated: Use \Monolog\Level::Info ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

66
            $browserHandler = new \Monolog\Handler\BrowserConsoleHandler(/** @scrutinizer ignore-deprecated */ \Monolog\Logger::INFO);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
67
            $browserHandler->setFormatter($formatter);
68
            $this->logger->pushHandler($browserHandler);
69
        }
70
        if ($this->getConfig()->getLoggingFilename() !== null) {
0 ignored issues
show
introduced by
The condition $this->getConfig()->getLoggingFilename() !== null is always true.
Loading history...
71
            $streamHandler = new \Monolog\Handler\StreamHandler($this->getConfig()->getLoggingFilename(), \Monolog\Logger::INFO);
0 ignored issues
show
Deprecated Code introduced by
The constant Monolog\Logger::INFO has been deprecated: Use \Monolog\Level::Info ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

71
            $streamHandler = new \Monolog\Handler\StreamHandler($this->getConfig()->getLoggingFilename(), /** @scrutinizer ignore-deprecated */ \Monolog\Logger::INFO);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
72
            $streamHandler->setFormatter($formatter);
73
            $this->logger->pushHandler($streamHandler);
74
        }
75
        if (!$this->logger->getHandlers()) {
76
            // add a NullHandler to suppress the default Monolog logging to stderr
77
            $nullHandler = new \Monolog\Handler\NullHandler();
78
            $this->logger->pushHandler($nullHandler);
79
        }
80
    }
81
82
    /**
83
     * Return the logging facility
84
     * @return object logger
85
     */
86
    public function getLogger()
87
    {
88
        return $this->logger;
89
    }
90
91
    /**
92
     * Return the version of this Skosmos installation, or "unknown" if
93
     * it cannot be determined. The version information is based on Git tags.
94
     * @return string version
95
     */
96
    public function getVersion(): string
97
    {
98
        $ver = \Composer\InstalledVersions::getRootPackage()['pretty_version'];
99
        if ($ver === null) {
100
            return "unknown";
101
        }
102
103
        return $ver;
104
    }
105
106
    /**
107
     * Return a User-Agent header for this Skosmos installation, including the
108
     * name of the software (Skosmos), the version number and URL.
109
     * @return string userAgent
110
     */
111
    public function getUserAgent(): string
112
    {
113
        $version = $this->getVersion();
114
        return "Skosmos/$version (https://skosmos.org/)";
115
    }
116
117
    /**
118
     * Return all the vocabularies available.
119
     * @param boolean $categories whether you want everything included in a subarray of
120
     * a category.
121
     * @param boolean $shortname set to true if you want the vocabularies sorted by
122
     * their shortnames instead of their titles.
123
     */
124
    public function getVocabularyList($categories = true, $shortname = false)
125
    {
126
        $cats = $this->getVocabularyCategories();
127
        $ret = array();
128
        foreach ($cats as $cat) {
129
            $catlabel = $cat->getTitle();
130
131
            // find all the vocabs in this category
132
            $vocs = array();
133
            foreach ($cat->getVocabularies() as $voc) {
134
                $vocs[$shortname ? $voc->getConfig()->getShortname() : $voc->getConfig()->getTitle()] = $voc;
135
            }
136
            uksort($vocs, 'strcoll');
137
138
            if (sizeof($vocs) > 0 && $categories) {
139
                $ret[$catlabel] = $vocs;
140
            } elseif (sizeof($vocs) > 0) {
141
                $ret = array_merge($ret, $vocs);
142
            }
143
144
        }
145
146
        if (!$categories) {
147
            uksort($ret, 'strcoll');
148
        }
149
150
        return $ret;
151
    }
152
153
    /**
154
     * Return all types (RDFS/OWL classes) present in the specified vocabulary or all vocabularies.
155
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
156
     */
157
    public function getTypes($vocid = null, $lang = null)
158
    {
159
        $sparql = (isset($vocid)) ? $this->getVocabulary($vocid)->getSparql() : $this->getDefaultSparql();
160
        $result = $sparql->queryTypes($lang);
0 ignored issues
show
Bug introduced by
The method queryTypes() does not exist on GenericSparql. Did you maybe mean query()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

160
        /** @scrutinizer ignore-call */ 
161
        $result = $sparql->queryTypes($lang);

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...
161
        foreach ($result as $uri => $values) {
162
            if (empty($values)) {
163
                $shorteneduri = EasyRdf\RdfNamespace::shorten($uri);
164
                if ($shorteneduri !== null) {
165
                    if (isset($lang)) {
166
                        $this->translator->setlocale($lang);
0 ignored issues
show
Bug introduced by
The method setlocale() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

166
                        $this->translator->/** @scrutinizer ignore-call */ 
167
                                           setlocale($lang);

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...
167
                    }
168
                    $trans = $this->getText($shorteneduri);
169
                    if ($trans) {
170
                        $result[$uri] = array('label' => $trans);
171
                    }
172
                }
173
            }
174
        }
175
176
        return $result;
177
    }
178
179
    /**
180
     * Return the languages present in the configured vocabularies.
181
     * @return array Array with lang codes (string)
182
     */
183
    public function getLanguages($lang)
184
    {
185
        $vocabs = $this->getVocabularyList(false);
186
        $ret = array();
187
        foreach ($vocabs as $vocab) {
188
            foreach ($vocab->getConfig()->getLanguages() as $langcode) {
189
                $langlit = Punic\Language::getName($langcode, $lang);
190
                $ret[$langlit] = $langcode;
191
            }
192
        }
193
        ksort($ret);
194
        return array_unique($ret);
195
    }
196
197
    /**
198
     * Return Symfony Translator object for translations in given language.
199
     * @return Translator Translator object from Symfony package
200
     */
201
    public function getTranslator()
202
    {
203
        return $this->translator;
204
    }
205
206
    /**
207
     * Sets translation language for Symfony Translator objext
208
     * @param string $lang two character language code 'fi' or compound language (locale) name such as 'fi_FI'
209
     */
210
    public function setLocale($locale)
211
    {
212
        $this->translator->setlocale($locale);
213
    }
214
215
    /**
216
     * Gets translation language from Symfony Translator objext
217
     * @return string $lang two character language code 'fi' or compound language (locale) name such as 'fi_FI'
218
     */
219
    public function getLocale()
220
    {
221
        return $this->translator->getlocale();
222
    }
223
224
    /**
225
     * Get text translated in language set by SetLocale function
226
     *
227
     * @param string $text text to be translated
228
     */
229
    public function getText($text)
230
    {
231
        return $this->translator->trans($text);
232
    }
233
234
    /**
235
     * returns a concept's RDF data in downloadable format
236
     * @param string $vocid vocabulary id, or null for global data (from all vocabularies)
237
     * @param string $uri concept URI
238
     * @param string $format the format in which you want to get the result, currently this function supports
239
     * text/turtle, application/rdf+xml and application/json
240
     * @return string RDF data in the requested serialization
241
     */
242
    public function getRDF($vocid, $uri, $format)
243
    {
244
245
        if ($format == 'text/turtle') {
246
            $retform = 'turtle';
247
            $serialiser = new EasyRdf\Serialiser\Turtle();
248
        } elseif ($format == 'application/ld+json' || $format == 'application/json') {
249
            $retform = 'jsonld'; // serve JSON-LD for both JSON-LD and plain JSON requests
250
            $serialiser = new EasyRdf\Serialiser\JsonLd();
251
        } else {
252
            $retform = 'rdfxml';
253
            $serialiser = new EasyRdf\Serialiser\RdfXml();
254
        }
255
256
        if ($vocid !== null) {
0 ignored issues
show
introduced by
The condition $vocid !== null is always true.
Loading history...
257
            $vocab = $this->getVocabulary($vocid);
258
            $sparql = $vocab->getSparql();
259
            $arrayClass = $vocab->getConfig()->getArrayClassURI();
260
            $vocabs = array($vocab);
261
        } else {
262
            $sparql = $this->getDefaultSparql();
263
            $arrayClass = null;
264
            $vocabs = null;
265
        }
266
        $result = $sparql->queryConceptInfoGraph($uri, $arrayClass, $vocabs);
0 ignored issues
show
Bug introduced by
The method queryConceptInfoGraph() does not exist on GenericSparql. Did you maybe mean queryConcepts()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

266
        /** @scrutinizer ignore-call */ 
267
        $result = $sparql->queryConceptInfoGraph($uri, $arrayClass, $vocabs);

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...
267
268
        if (!$result->isEmpty()) {
269
            return $serialiser->serialise($result, $retform);
270
        }
271
        return "";
272
    }
273
274
    /**
275
     * Makes a SPARQL-query to the endpoint that retrieves concept
276
     * references as it's search results.
277
     * @param ConceptSearchParameters $params an object that contains all the parameters needed for the search
278
     * @return array search results
279
     */
280
    public function searchConcepts($params)
281
    {
282
        // don't even try to search for empty prefix if no other search criteria (group or parent concept) has been set
283
        if (($params->getSearchTerm() === "" || !preg_match('/[^*]/', $params->getSearchTerm())) && !$params->getGroupLimit() && !$params->getParentLimit()) {
284
            return array();
285
        }
286
287
        $vocabs = $params->getVocabs();
288
        $showDeprecated = false;
289
        if (sizeof($vocabs) === 1) { // search within vocabulary
290
            $voc = $vocabs[0];
291
            $sparql = $voc->getSparql();
292
            $showDeprecated = $voc->getConfig()->getShowDeprecated();
293
        } else { // multi-vocabulary or global search
294
            $voc = null;
295
            $sparql = $this->getDefaultSparql();
296
            // @TODO : in a global search showDeprecated will always be false and cannot be set globally
297
        }
298
299
        $results = $sparql->queryConcepts($vocabs, $params->getAdditionalFields(), $params->getUnique(), $params, $showDeprecated);
300
        if ($params->getRest() && $results && $params->getSearchLimit() !== 0) {
301
            $results = array_slice($results, $params->getOffset(), $params->getSearchLimit());
302
        }
303
        $ret = array();
304
305
        foreach ($results as $hit) {
306
            if (sizeof($vocabs) == 1) {
307
                $hitvoc = $voc;
308
                $hit['vocab'] = $vocabs[0]->getId();
309
            } else {
310
                try {
311
                    $hitvoc = $this->getVocabularyByGraph($hit['graph']);
312
                    $hit['vocab'] = $hitvoc->getId();
313
                } catch (ValueError $e) {
314
                    trigger_error($e->getMessage(), E_USER_WARNING);
315
                    $hitvoc = null;
316
                    $hit['vocab'] = "???";
317
                }
318
            }
319
            unset($hit['graph']);
320
321
            $hit['voc'] = $hitvoc;
322
323
            if ($hitvoc === null || !$hitvoc->containsURI($hit['uri'])) {
324
                // if uri is a external vocab uri that is included in the current vocab
325
                $realvoc = $this->guessVocabularyFromURI($hit['uri'], $voc !== null ? $voc->getId() : null);
326
                if ($realvoc !== $hitvoc) {
327
                    unset($hit['localname']);
328
                    $hit['exvocab'] = ($realvoc !== null) ? $realvoc->getId() : "???";
329
                }
330
            }
331
332
            $ret[] = $hit;
333
        }
334
335
        return $ret;
336
    }
337
338
    /**
339
     * Function for performing a search for concepts and their data fields.
340
     * @param ConceptSearchParameters $params an object that contains all the parameters needed for the search
341
     * @return array array with keys 'count' and 'results'
342
     */
343
    public function searchConceptsAndInfo($params)
344
    {
345
        $params->setUnique(true);
346
        $allhits = $this->searchConcepts($params);
347
        $count = sizeof($allhits);
348
        $hits = array_slice($allhits, intval($params->getOffset()), $params->getSearchLimit());
349
350
        $ret = array();
351
        $uris = array();
352
        $vocabs = array();
353
        $uniqueVocabs = array();
354
        foreach ($hits as $hit) {
355
            $uniqueVocabs[$hit['voc']->getId()] = $hit['voc']->getId();
356
            $vocabs[] = $hit['voc'];
357
            $uris[] = $hit['uri'];
358
        }
359
        if (sizeof($uniqueVocabs) == 1) {
360
            $voc = $vocabs[0];
361
            $sparql = $voc->getSparql();
362
            $arrayClass = $voc->getConfig()->getArrayClassURI();
363
        } else {
364
            $arrayClass = null;
365
            $sparql = $this->getDefaultSparql();
366
        }
367
        if (sizeof($uris) > 0) {
368
            $ret = $sparql->queryConceptInfo($uris, $arrayClass, $vocabs, $params->getSearchLang());
369
        }
370
371
        // For marking that the concept has been found through an alternative label, hidden
372
        // label or a label in another language
373
        foreach ($hits as $idx => $hit) {
374
            if (isset($hit['altLabel']) && isset($ret[$idx])) {
375
                $ret[$idx]->setFoundBy($hit['altLabel'], 'alt');
376
            }
377
378
            if (isset($hit['hiddenLabel']) && isset($ret[$idx])) {
379
                $ret[$idx]->setFoundBy($hit['hiddenLabel'], 'hidden');
380
            }
381
382
            if (isset($hit['matchedPrefLabel'])) {
383
                $ret[$idx]->setFoundBy($hit['matchedPrefLabel'], 'lang');
384
            }
385
386
            if ($ret[$idx] && isset($hit['lang'])) {
387
                $ret[$idx]->setContentLang($hit['lang']);
388
            }
389
        }
390
391
        return array('count' => $count, 'results' => $ret);
392
    }
393
394
    /**
395
     * Creates dataobjects from an input array.
396
     * @param string $class the type of class eg. 'Vocabulary'.
397
     * @param array $resarr contains the EasyRdf\Resources.
398
     */
399
    private function createDataObjects($class, $resarr)
400
    {
401
        $ret = array();
402
        foreach ($resarr as $res) {
403
            $ret[] = new $class($this, $res);
404
        }
405
406
        return $ret;
407
    }
408
409
    /**
410
     * Returns the cached vocabularies.
411
     * @return array of Vocabulary dataobjects
412
     */
413
    public function getVocabularies()
414
    {
415
        if ($this->allVocabularies === null) { // initialize cache
416
            $vocs = $this->globalConfig->getGraph()->allOfType('skosmos:Vocabulary');
417
            $this->allVocabularies = $this->createDataObjects("Vocabulary", $vocs);
418
            foreach ($this->allVocabularies as $voc) {
419
                // register vocabulary ids as RDF namespace prefixes
420
                $prefix = preg_replace('/\W+/', '', $voc->getId()); // strip non-word characters
421
                try {
422
                    if ($prefix != '' && EasyRdf\RdfNamespace::get($prefix) === null) { // if not already defined
423
                        EasyRdf\RdfNamespace::set($prefix, $voc->getUriSpace());
424
                    }
425
426
                } catch (Exception $e) {
427
                    // not valid as namespace identifier, ignore
428
                }
429
            }
430
        }
431
432
        return $this->allVocabularies;
433
    }
434
435
    /**
436
     * Returns the cached vocabularies from a category.
437
     * @param EasyRdf\Resource $cat the category in question
438
     * @return array of vocabulary dataobjects
439
     */
440
    public function getVocabulariesInCategory($cat)
441
    {
442
        $vocs = $this->globalConfig->getGraph()->resourcesMatching('dc:subject', $cat);
443
        return $this->createDataObjects("Vocabulary", $vocs);
444
    }
445
446
    /**
447
     * Creates dataobjects of all the different vocabulary categories (Health etc.).
448
     * @return array of Dataobjects of the type VocabularyCategory.
449
     */
450
    public function getVocabularyCategories()
451
    {
452
        $cats = $this->globalConfig->getGraph()->allOfType('skos:Concept');
453
        if (empty($cats)) {
454
            return array(new VocabularyCategory($this, null));
455
        }
456
457
        return $this->createDataObjects("VocabularyCategory", $cats);
458
    }
459
460
    /**
461
     * Returns the label defined in config.ttl with the appropriate language.
462
     * @param string $lang language code of returned labels, eg. 'fi'
463
     * @return string the label for vocabulary categories.
464
     */
465
    public function getClassificationLabel($lang)
466
    {
467
        $cats = $this->globalConfig->getGraph()->allOfType('skos:ConceptScheme');
468
        return $cats ? $cats[0]->label($lang) : null;
0 ignored issues
show
Bug introduced by
The method label() does not exist on EasyRdf\Literal. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

468
        return $cats ? $cats[0]->/** @scrutinizer ignore-call */ label($lang) : null;

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...
Bug introduced by
The method label() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

468
        return $cats ? $cats[0]->/** @scrutinizer ignore-call */ label($lang) : null;

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...
469
    }
470
471
    /**
472
     * Returns a single cached vocabulary.
473
     * @param string $vocid the vocabulary id eg. 'mesh'.
474
     * @return Vocabulary dataobject
475
     */
476
    public function getVocabulary($vocid): Vocabulary
477
    {
478
        $vocs = $this->getVocabularies();
479
        foreach ($vocs as $voc) {
480
            if ($voc->getId() == $vocid) {
481
                return $voc;
482
            }
483
        }
484
        throw new ValueError("Vocabulary id '$vocid' not found in configuration.");
485
    }
486
487
    /**
488
     * Return the vocabulary that is stored in the given graph on the given endpoint.
489
     *
490
     * @param $graph string graph URI
491
     * @param $endpoint string endpoint URL (default SPARQL endpoint if omitted)
492
     * @return Vocabulary vocabulary of this URI, or null if not found
493
     */
494
    public function getVocabularyByGraph($graph, $endpoint = null)
495
    {
496
        if ($endpoint === null) {
497
            $endpoint = $this->getConfig()->getDefaultEndpoint();
498
        }
499
        if ($this->vocabsByGraph === null) { // initialize cache
500
            $this->vocabsByGraph = array();
501
            foreach ($this->getVocabularies() as $voc) {
502
                $key = json_encode(array($voc->getGraph(), $voc->getEndpoint()));
503
                $this->vocabsByGraph[$key] = $voc;
504
            }
505
        }
506
507
        $key = json_encode(array($graph, $endpoint));
508
        if (array_key_exists($key, $this->vocabsByGraph)) {
509
            return $this->vocabsByGraph[$key];
510
        } else {
511
            throw new ValueError("no vocabulary found for graph $graph and endpoint $endpoint");
512
        }
513
514
    }
515
516
    /**
517
     * When multiple vocabularies share same URI namespace, return the
518
     * vocabulary in which the URI is actually defined (has a label).
519
     *
520
     * @param Vocabulary[] $vocabs vocabularies to search
521
     * @param string $uri URI to look for
522
     * @param $preferredVocabId string ID of the preferred vocabulary to return if more than one is found
523
     * @return Vocabulary the vocabulary with the URI
524
     */
525
526
    private function disambiguateVocabulary($vocabs, $uri, $preferredVocabId = null)
527
    {
528
        // if there is only one candidate vocabulary, return it
529
        if (sizeof($vocabs) == 1) {
530
            return $vocabs[0];
531
        }
532
533
        // if there are multiple vocabularies and one is the preferred vocabulary, return it
534
        if ($preferredVocabId != null) {
535
            foreach ($vocabs as $vocab) {
536
                if ($vocab->getId() == $preferredVocabId) {
537
                    try {
538
                        // double check that a label exists in the preferred vocabulary
539
                        if ($vocab->getConceptLabel($uri, null) !== null) {
540
                            return $vocab;
541
                        } else {
542
                            // not found in preferred vocabulary, fall back to next method
543
                            break;
544
                        }
545
                    } catch (EasyRdf\Http\Exception | EasyRdf\Exception | Throwable $e) {
546
                        if ($this->getConfig()->getLogCaughtExceptions()) {
547
                            error_log('Caught exception: ' . $e->getMessage());
548
                        }
549
                        break;
550
                    }
551
                }
552
            }
553
        }
554
555
        // no preferred vocabulary, or it was not found, search in which vocabulary the concept has a label
556
        foreach ($vocabs as $vocab) {
557
            try {
558
                if ($vocab->getConceptLabel($uri, null) !== null) {
559
                    return $vocab;
560
                }
561
            } catch (EasyRdf\Http\Exception | EasyRdf\Exception | Throwable $e) {
562
                if ($this->getConfig()->getLogCaughtExceptions()) {
563
                    error_log('Caught exception: ' . $e->getMessage());
564
                }
565
                break;
566
            }
567
        }
568
569
        // if the URI couldn't be found, fall back to the first vocabulary
570
        return $vocabs[0];
571
    }
572
573
    /**
574
     * Guess which vocabulary a URI originates from, based on the declared
575
     * vocabulary URI spaces.
576
     *
577
     * @param $uri string URI to search
578
     * @param $preferredVocabId string ID of the preferred vocabulary to return if more than one is found
579
     * @return Vocabulary vocabulary of this URI, or null if not found
580
     */
581
    public function guessVocabularyFromURI($uri, $preferredVocabId = null)
582
    {
583
        if ($this->vocabsByUriSpace === null) { // initialize cache
584
            $this->vocabsByUriSpace = array();
585
            foreach ($this->getVocabularies() as $voc) {
586
                $uriSpace = $voc->getUriSpace();
587
                if ($uriSpace) {
588
                    $this->vocabsByUriSpace[$uriSpace][] = $voc;
589
                }
590
            }
591
        }
592
593
        // try to guess the URI space and look it up in the cache
594
        $res = new EasyRdf\Resource($uri);
595
        $namespace = substr(strval($uri), 0, -strlen(strval($res->localName())));
596
        if ($namespace && array_key_exists($namespace, $this->vocabsByUriSpace)) {
597
            $vocabs = $this->vocabsByUriSpace[$namespace];
598
            return $this->disambiguateVocabulary($vocabs, $uri, $preferredVocabId);
599
        }
600
601
        // didn't work, try to match with each URI space separately
602
        foreach ($this->vocabsByUriSpace as $urispace => $vocabs) {
603
            if (strpos($uri, $urispace) === 0) {
604
                return $this->disambiguateVocabulary($vocabs, $uri, $preferredVocabId);
605
            }
606
        }
607
608
        // not found
609
        return null;
610
    }
611
612
    /**
613
     * Get the label for a resource, preferring 1. the given language 2. configured languages 3. any language.
614
     * @param EasyRdf\Resource $res resource whose label to return
615
     * @param string $lang preferred language
616
     * @return EasyRdf\Literal label as an EasyRdf\Literal object, or null if not found
617
     */
618
    public function getResourceLabel($res, $lang)
619
    {
620
        $langs = array_merge(array($lang), array_keys($this->getConfig()->getLanguages()));
621
        foreach ($langs as $l) {
622
            $label = $res->label($l);
623
            if ($label !== null) {
624
                return $label;
625
            }
626
627
        }
628
        return $res->label(); // desperate check for label in any language; will return null if even this fails
629
    }
630
631
    public function getResourceFromUri($uri)
632
    {
633
        // using apc cache for the resource if available
634
        if ($this->globalConfig->getCache()->isAvailable()) {
635
            // @codeCoverageIgnoreStart
636
            $key = 'fetch: ' . $uri;
637
            $resource = $this->globalConfig->getCache()->fetch($key);
638
            if ($resource === null || $resource === false) { // was not found in cache, or previous request failed
639
                $resource = $this->resolver->resolve($uri, $this->getConfig()->getHttpTimeout());
640
                $this->globalConfig->getCache()->store($key, $resource, self::URI_FETCH_TTL);
641
            }
642
            // @codeCoverageIgnoreEnd
643
        } else { // APC not available, parse on every request
644
            $resource = $this->resolver->resolve($uri, $this->getConfig()->getHttpTimeout());
645
        }
646
        return $resource;
647
    }
648
649
    /**
650
     * Returns a SPARQL endpoint object.
651
     * @param string $dialect eg. 'JenaText'.
652
     * @param string $endpoint url address of endpoint
653
     * @param string|null $graph uri for the target graph.
654
     */
655
    public function getSparqlImplementation($dialect, $endpoint, $graph)
656
    {
657
        $classname = $dialect . "Sparql";
658
659
        return new $classname($endpoint, $graph, $this);
660
    }
661
662
    /**
663
     * Returns a SPARQL endpoint object using the default implementation set in the config.ttl.
664
     */
665
    public function getDefaultSparql()
666
    {
667
        return $this->getSparqlImplementation($this->getConfig()->getDefaultSparqlDialect(), $this->getConfig()->getDefaultEndpoint(), '?graph');
668
    }
669
670
}
671