Completed
Push — master ( e821af...b81c51 )
by Osma
41s queued 31s
created

transformTransitivePropertyResults()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 7
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * RestController is responsible for handling all the requests directed to the /rest address.
5
 */
6
class RestController extends Controller
7
{
8
    /* supported MIME types that can be used to return RDF data */
9
    const SUPPORTED_FORMATS = 'application/rdf+xml text/turtle application/ld+json application/json application/marcxml+xml';
10
    /* context array template */
11
    private $context = array(
12
        '@context' => array(
13
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
14
            'uri' => '@id',
15
            'type' => '@type',
16
        ),
17
    );
18
19
    /**
20
     * Handles json encoding, adding the content type headers and optional callback function.
21
     * @param array $data the data to be returned.
22
     */
23
    private function returnJson($data)
24
    {
25
        // wrap with JSONP callback if requested
26
        if (filter_input(INPUT_GET, 'callback', FILTER_SANITIZE_STRING)) {
27
            header("Content-type: application/javascript; charset=utf-8");
28
            echo filter_input(INPUT_GET, 'callback', FILTER_UNSAFE_RAW) . "(" . json_encode($data) . ");";
29
            return;
30
        }
31
32
        // otherwise negotiate suitable format for the response and return that
33
        $negotiator = new \Negotiation\Negotiator();
34
        $priorities = array('application/json', 'application/ld+json');
35
        $best = filter_input(INPUT_SERVER, 'HTTP_ACCEPT', FILTER_SANITIZE_STRING) ? $negotiator->getBest(filter_input(INPUT_SERVER, 'HTTP_ACCEPT', FILTER_SANITIZE_STRING), $priorities) : null;
36
        $format = ($best !== null) ? $best->getValue() : $priorities[0];
37
        header("Content-type: $format; charset=utf-8");
38
        header("Vary: Accept"); // inform caches that we made a choice based on Accept header
39
        echo json_encode($data);
40
    }
41
42
    /**
43
     * Parses and returns the limit parameter. Returns and error if the parameter is missing.
44
     */
45
    private function parseLimit()
46
    {
47
        $limit = filter_input(INPUT_GET, 'limit', FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_GET, 'limit', FILTER_SANITIZE_NUMBER_INT) : $this->model->getConfig()->getDefaultTransitiveLimit();
48
        if ($limit <= 0) {
49
            return $this->returnError(400, "Bad Request", "Invalid limit parameter");
50
        }
51
52
        return $limit;
53
    }
54
55
56
/** Global REST methods **/
57
58
    /**
59
     * Returns all the vocabularies available on the server in a json object.
60
     */
61
    public function vocabularies($request)
62
    {
63
        if (!$request->getLang()) {
64
            return $this->returnError(400, "Bad Request", "lang parameter missing");
65
        }
66
67
        $this->setLanguageProperties($request->getLang());
68
69
        $vocabs = array();
70
        foreach ($this->model->getVocabularies() as $voc) {
71
            $vocabs[$voc->getId()] = $voc->getConfig()->getTitle($request->getLang());
72
        }
73
        ksort($vocabs);
74
        $results = array();
75
        foreach ($vocabs as $id => $title) {
76
            $results[] = array(
77
                'uri' => $id,
78
                'id' => $id,
79
                'title' => $title);
80
        }
81
82
        /* encode the results in a JSON-LD compatible array */
83
        $ret = array(
84
            '@context' => array(
85
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
86
                'onki' => 'http://schema.onki.fi/onki#',
87
                'title' => array('@id' => 'rdfs:label', '@language' => $request->getLang()),
88
                'vocabularies' => 'onki:hasVocabulary',
89
                'id' => 'onki:vocabularyIdentifier',
90
                'uri' => '@id',
91
                '@base' => $this->getBaseHref() . "rest/v1/vocabularies",
92
            ),
93
            'uri' => '',
94
            'vocabularies' => $results,
95
        );
96
97
        return $this->returnJson($ret);
98
    }
99
100
    private function constructSearchParameters($request)
101
    {
102
        $parameters = new ConceptSearchParameters($request, $this->model->getConfig(), true);
103
104
        $vocabs = $request->getQueryParam('vocab'); # optional
105
        // convert to vocids array to support multi-vocabulary search
106
        $vocids = ($vocabs !== null && $vocabs !== '') ? explode(' ', $vocabs) : array();
107
        $vocabObjects = array();
108
        foreach($vocids as $vocid) {
109
            $vocabObjects[] = $this->model->getVocabulary($vocid);
110
        }
111
        $parameters->setVocabularies($vocabObjects);
112
        return $parameters;
113
    }
114
115
    private function transformSearchResults($request, $results, $parameters)
116
    {
117
        // before serializing to JSON, get rid of the Vocabulary object that came with each resource
118
        foreach ($results as &$res) {
119
            unset($res['voc']);
120
        }
121
122
        $context = array(
123
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
124
            'isothes' => 'http://purl.org/iso25964/skos-thes#',
125
            'onki' => 'http://schema.onki.fi/onki#',
126
            'uri' => '@id',
127
            'type' => '@type',
128
            'results' => array(
129
                '@id' => 'onki:results',
130
                '@container' => '@list',
131
            ),
132
            'prefLabel' => 'skos:prefLabel',
133
            'altLabel' => 'skos:altLabel',
134
            'hiddenLabel' => 'skos:hiddenLabel',
135
        );
136
        foreach ($parameters->getAdditionalFields() as $field) {
137
138
            // Quick-and-dirty compactification
139
            $context[$field] = 'skos:' . $field;
140
            foreach ($results as &$result) {
141
                foreach ($result as $k => $v) {
142
                    if ($k == 'skos:' . $field) {
143
                        $result[$field] = $v;
144
                        unset($result['skos:' . $field]);
145
                    }
146
                }
147
            }
148
        }
149
150
        $ret = array(
151
            '@context' => $context,
152
            'uri' => '',
153
            'results' => $results,
154
        );
155
156
        if (isset($results[0]['prefLabels'])) {
157
            $ret['@context']['prefLabels'] = array('@id' => 'skos:prefLabel', '@container' => '@language');
158
        }
159
160
        if ($request->getQueryParam('labellang')) {
161
            $ret['@context']['@language'] = $request->getQueryParam('labellang');
162
        } elseif ($request->getQueryParam('lang')) {
163
            $ret['@context']['@language'] = $request->getQueryParam('lang');;
164
        }
165
        return $ret;
166
    }
167
168
    /**
169
     * Performs the search function calls. And wraps the result in a json-ld object.
170
     * @param Request $request
171
     */
172
    public function search($request)
173
    {
174
        $maxhits = $request->getQueryParam('maxhits');
175
        $offset = $request->getQueryParam('offset');
176
        $term = $request->getQueryParamRaw('query');
177
178
        if (!$term && (!$request->getQueryParam('group') && !$request->getQueryParam('parent'))) {
179
            return $this->returnError(400, "Bad Request", "query parameter missing");
180
        }
181
        if ($maxhits && (!is_numeric($maxhits) || $maxhits <= 0)) {
182
            return $this->returnError(400, "Bad Request", "maxhits parameter is invalid");
183
        }
184
        if ($offset && (!is_numeric($offset) || $offset < 0)) {
185
            return $this->returnError(400, "Bad Request", "offset parameter is invalid");
186
        }
187
188
        $parameters = $this->constructSearchParameters($request);
189
        $results = $this->model->searchConcepts($parameters);
190
        $ret = $this->transformSearchResults($request, $results, $parameters);
191
192
        return $this->returnJson($ret);
193
    }
194
195
/** Vocabulary-specific methods **/
196
197
    /**
198
     * Loads the vocabulary metadata. And wraps the result in a json-ld object.
199
     * @param Request $request
200
     */
201
    public function vocabularyInformation($request)
202
    {
203
        $vocab = $request->getVocab();
204
        if ($this->notModified($vocab)) {
205
            return null;
206
        }
207
208
        /* encode the results in a JSON-LD compatible array */
209
        $conceptschemes = array();
210
        foreach ($vocab->getConceptSchemes($request->getLang()) as $uri => $csdata) {
211
            $csdata['uri'] = $uri;
212
            $csdata['type'] = 'skos:ConceptScheme';
213
            $conceptschemes[] = $csdata;
214
        }
215
216
        $ret = array(
217
            '@context' => array(
218
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
219
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
220
                'onki' => 'http://schema.onki.fi/onki#',
221
                'dct' => 'http://purl.org/dc/terms/',
222
                'uri' => '@id',
223
                'type' => '@type',
224
                'conceptschemes' => 'onki:hasConceptScheme',
225
                'id' => 'onki:vocabularyIdentifier',
226
                'defaultLanguage' => 'onki:defaultLanguage',
227
                'languages' => 'onki:language',
228
                'label' => 'rdfs:label',
229
                'prefLabel' => 'skos:prefLabel',
230
                'title' => 'dct:title',
231
                '@language' => $request->getLang(),
232
                '@base' => $this->getBaseHref() . "rest/v1/" . $vocab->getId() . "/",
233
            ),
234
            'uri' => '',
235
            'id' => $vocab->getId(),
236
            'marcSource' => $vocab->getConfig()->getMarcSourceCode($request->getLang()),
237
            'title' => $vocab->getConfig()->getTitle($request->getLang()),
238
            'defaultLanguage' => $vocab->getConfig()->getDefaultLanguage(),
239
            'languages' => array_values($vocab->getConfig()->getLanguages()),
240
            'conceptschemes' => $conceptschemes,
241
        );
242
243
        if ($vocab->getConfig()->getTypes($request->getLang())) {
244
            $ret['type'] = $vocab->getConfig()->getTypes($request->getLang());
245
        }
246
247
        return $this->returnJson($ret);
248
    }
249
250
    /**
251
     * Loads the vocabulary metadata. And wraps the result in a json-ld object.
252
     * @param Request $request
253
     */
254
    public function vocabularyStatistics($request)
255
    {
256
        if ($this->notModified($request->getVocab())) {
257
            return null;
258
        }
259
        $this->setLanguageProperties($request->getLang());
260
        $arrayClass = $request->getVocab()->getConfig()->getArrayClassURI();
261
        $groupClass = $request->getVocab()->getConfig()->getGroupClassURI();
262
        $vocabStats = $request->getVocab()->getStatistics($request->getQueryParam('lang'), $arrayClass, $groupClass);
263
        $types = array('http://www.w3.org/2004/02/skos/core#Concept', 'http://www.w3.org/2004/02/skos/core#Collection', $arrayClass, $groupClass);
264
        $subTypes = array();
265
        foreach ($vocabStats as $subtype) {
266
            if (!in_array($subtype['type'], $types)) {
267
                $subTypes[] = $subtype;
268
            }
269
        }
270
271
        /* encode the results in a JSON-LD compatible array */
272
        $ret = array(
273
            '@context' => array(
274
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
275
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
276
                'void' => 'http://rdfs.org/ns/void#',
277
                'onki' => 'http://schema.onki.fi/onki#',
278
                'uri' => '@id',
279
                'id' => 'onki:vocabularyIdentifier',
280
                'concepts' => 'void:classPartition',
281
                'label' => 'rdfs:label',
282
                'class' => array('@id' => 'void:class', '@type' => '@id'),
283
                'subTypes' => array('@id' => 'void:class', '@type' => '@id'),
284
                'count' => 'void:entities',
285
                '@language' => $request->getLang(),
286
                '@base' => $this->getBaseHref() . "rest/v1/" . $request->getVocab()->getId() . "/",
287
            ),
288
            'uri' => '',
289
            'id' => $request->getVocab()->getId(),
290
            'title' => $request->getVocab()->getConfig()->getTitle(),
291
            'concepts' => array(
292
                'class' => 'http://www.w3.org/2004/02/skos/core#Concept',
293
                'label' => gettext('skos:Concept'),
294
                'count' => $vocabStats['http://www.w3.org/2004/02/skos/core#Concept']['count'],
295
            ),
296
            'subTypes' => $subTypes,
297
        );
298
299
        if (isset($vocabStats['http://www.w3.org/2004/02/skos/core#Collection'])) {
300
            $ret['conceptGroups'] = array(
301
                'class' => 'http://www.w3.org/2004/02/skos/core#Collection',
302
                'label' => gettext('skos:Collection'),
303
                'count' => $vocabStats['http://www.w3.org/2004/02/skos/core#Collection']['count'],
304
            );
305
        } else if (isset($vocabStats[$groupClass])) {
306
            $ret['conceptGroups'] = array(
307
                'class' => $groupClass,
308
                'label' => isset($vocabStats[$groupClass]['label']) ? $vocabStats[$groupClass]['label'] : gettext(EasyRdf\RdfNamespace::shorten($groupClass)),
309
                'count' => $vocabStats[$groupClass]['count'],
310
            );
311
        } else if (isset($vocabStats[$arrayClass])) {
312
            $ret['arrays'] = array(
313
                'class' => $arrayClass,
314
                'label' => isset($vocabStats[$arrayClass]['label']) ? $vocabStats[$arrayClass]['label'] : gettext(EasyRdf\RdfNamespace::shorten($arrayClass)),
315
                'count' => $vocabStats[$arrayClass]['count'],
316
            );
317
        }
318
319
        return $this->returnJson($ret);
320
    }
321
322
    /**
323
     * Loads the vocabulary metadata. And wraps the result in a json-ld object.
324
     * @param Request $request
325
     */
326
    public function labelStatistics($request)
327
    {
328
        if ($this->notModified($request->getVocab())) {
329
            return null;
330
        }
331
        $lang = $request->getLang();
332
        $this->setLanguageProperties($request->getLang());
333
        $vocabStats = $request->getVocab()->getLabelStatistics();
334
335
        /* encode the results in a JSON-LD compatible array */
336
        $counts = array();
337
        foreach ($vocabStats['terms'] as $proplang => $properties) {
338
            $langdata = array('language' => $proplang);
339
            if ($lang) {
340
                $langdata['literal'] = Punic\Language::getName($proplang, $lang);
341
            }
342
343
            $langdata['properties'] = array();
344
            foreach ($properties as $prop => $value) {
345
                $langdata['properties'][] = array('property' => $prop, 'labels' => $value);
346
            }
347
            $counts[] = $langdata;
348
        }
349
350
        $ret = array(
351
            '@context' => array(
352
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
353
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
354
                'void' => 'http://rdfs.org/ns/void#',
355
                'void-ext' => 'http://ldf.fi/void-ext#',
356
                'onki' => 'http://schema.onki.fi/onki#',
357
                'uri' => '@id',
358
                'id' => 'onki:vocabularyIdentifier',
359
                'languages' => 'void-ext:languagePartition',
360
                'language' => 'void-ext:language',
361
                'properties' => 'void:propertyPartition',
362
                'labels' => 'void:triples',
363
                '@base' => $this->getBaseHref() . "rest/v1/" . $request->getVocab()->getId() . "/",
364
            ),
365
            'uri' => '',
366
            'id' => $request->getVocab()->getId(),
367
            'title' => $request->getVocab()->getConfig()->getTitle($lang),
368
            'languages' => $counts,
369
        );
370
371
        if ($lang) {
372
            $ret['@context']['literal'] = array('@id' => 'rdfs:label', '@language' => $lang);
373
        }
374
375
        return $this->returnJson($ret);
376
    }
377
378
    /**
379
     * Loads the vocabulary type metadata. And wraps the result in a json-ld object.
380
     * @param Request $request
381
     */
382
    public function types($request)
383
    {
384
        $vocid = $request->getVocab() ? $request->getVocab()->getId() : null;
385
        if ($vocid === null && !$request->getLang()) {
386
            return $this->returnError(400, "Bad Request", "lang parameter missing");
387
        }
388
        if ($this->notModified($request->getVocab())) {
389
            return null;
390
        }
391
392
        $this->setLanguageProperties($request->getLang());
393
394
        $queriedtypes = $this->model->getTypes($vocid, $request->getLang());
395
396
        $types = array();
397
398
        /* encode the results in a JSON-LD compatible array */
399
        foreach ($queriedtypes as $uri => $typedata) {
400
            $type = array_merge(array('uri' => $uri), $typedata);
401
            $types[] = $type;
402
        }
403
404
        $base = $request->getVocab() ? $this->getBaseHref() . "rest/v1/" . $request->getVocab()->getId() . "/" : $this->getBaseHref() . "rest/v1/";
405
406
        $ret = array_merge_recursive($this->context, array(
407
            '@context' => array(
408
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
409
                'onki' => 'http://schema.onki.fi/onki#',
410
                'label' => 'rdfs:label',
411
                'superclass' => array('@id' => 'rdfs:subClassOf', '@type' => '@id'),
412
                'types' => 'onki:hasType',
413
                '@language' => $request->getLang(),
414
                '@base' => $base,
415
            ),
416
            'uri' => '',
417
            'types' => $types)
418
        );
419
420
        return $this->returnJson($ret);
421
    }
422
423
    private function findLookupHits($results, $label, $lang)
424
    {
425
        $hits = array();
426
        // case 1: exact match on preferred label
427
        foreach ($results as $res) {
428
            if ($res['prefLabel'] == $label) {
429
                $hits[] = $res;
430
            }
431
        }
432
        if (sizeof($hits) > 0) return $hits;
433
434
        // case 2: case-insensitive match on preferred label
435
        foreach ($results as $res) {
436
            if (strtolower($res['prefLabel']) == strtolower($label)) {
437
                $hits[] = $res;
438
            }
439
        }
440
        if (sizeof($hits) > 0) return $hits;
441
442
        if ($lang === null) {
443
            // case 1A: exact match on preferred label in any language
444 View Code Duplication
            foreach ($results as $res) {
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...
445
                if ($res['matchedPrefLabel'] == $label) {
446
                    $res['prefLabel'] = $res['matchedPrefLabel'];
447
                    unset($res['matchedPrefLabel']);
448
                    $hits[] = $res;
449
                }
450
            }
451
            if (sizeof($hits) > 0) return $hits;
452
453
            // case 2A: case-insensitive match on preferred label in any language
454 View Code Duplication
            foreach ($results as $res) {
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...
455
                if (strtolower($res['matchedPrefLabel']) == strtolower($label)) {
456
                    $res['prefLabel'] = $res['matchedPrefLabel'];
457
                    unset($res['matchedPrefLabel']);
458
                    $hits[] = $res;
459
                }
460
            }
461
            if (sizeof($hits) > 0) return $hits;
462
        }
463
464
        // case 3: exact match on alternate label
465 View Code Duplication
        foreach ($results as $res) {
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...
466
            if (isset($res['altLabel']) && $res['altLabel'] == $label) {
467
                $hits[] = $res;
468
            }
469
        }
470
        if (sizeof($hits) > 0) return $hits;
471
472
473
        // case 4: case-insensitive match on alternate label
474 View Code Duplication
        foreach ($results as $res) {
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...
475
            if (isset($res['altLabel']) && strtolower($res['altLabel']) == strtolower($label)) {
476
                $hits[] = $res;
477
            }
478
        }
479
        if (sizeof($hits) > 0) return $hits;
480
481
        return $hits;
482
    }
483
484
    private function transformLookupResults($lang, $hits)
485
    {
486
        if (sizeof($hits) == 0) {
487
            // no matches found
488
            return;
489
        }
490
491
        // found matches, getting rid of Vocabulary objects
492
        foreach ($hits as &$res) {
493
            unset($res['voc']);
494
        }
495
496
        $ret = array_merge_recursive($this->context, array(
497
            '@context' => array('onki' => 'http://schema.onki.fi/onki#', 'results' => array('@id' => 'onki:results'), 'prefLabel' => 'skos:prefLabel', 'altLabel' => 'skos:altLabel', 'hiddenLabel' => 'skos:hiddenLabel'),
498
            'result' => $hits)
499
        );
500
501
        if ($lang) {
502
            $ret['@context']['@language'] = $lang;
503
        }
504
505
        return $ret;
506
    }
507
508
    /**
509
     * Used for finding terms by their exact prefLabel. Wraps the result in a json-ld object.
510
     * @param Request $request
511
     */
512
    public function lookup($request)
513
    {
514
        $label = $request->getQueryParamRaw('label');
515
        if (!$label) {
516
            return $this->returnError(400, "Bad Request", "label parameter missing");
517
        }
518
519
        $lang = $request->getQueryParam('lang');
520
        $parameters = new ConceptSearchParameters($request, $this->model->getConfig(), true);
521
        $results = $this->model->searchConcepts($parameters);
522
        $hits = $this->findLookupHits($results, $label, $lang);
523
        $ret = $this->transformLookupResults($lang, $hits);
524
        if ($ret === null) {
525
            return $this->returnError(404, 'Not Found', "Could not find label '$label'");
526
        }
527
        return $this->returnJson($ret);
528
    }
529
530
    /**
531
     * Queries the top concepts of a vocabulary and wraps the results in a json-ld object.
532
     * @param Request $request
533
     * @return object json-ld object
534
     */
535
    public function topConcepts($request)
536
    {
537
        $vocab = $request->getVocab();
538
        if ($this->notModified($vocab)) {
539
            return null;
540
        }
541
        $scheme = $request->getQueryParam('scheme');
542
        if (!$scheme) {
543
            $scheme = $vocab->getConfig()->showConceptSchemesInHierarchy() ? array_keys($vocab->getConceptSchemes()) : $vocab->getDefaultConceptScheme();
544
        }
545
546
        /* encode the results in a JSON-LD compatible array */
547
        $topconcepts = $vocab->getTopConcepts($scheme, $request->getLang());
548
549
        $ret = array_merge_recursive($this->context, array(
550
            '@context' => array('onki' => 'http://schema.onki.fi/onki#', 'topconcepts' => 'skos:hasTopConcept', 'notation' => 'skos:notation', 'label' => 'skos:prefLabel', '@language' => $request->getLang()),
551
            'uri' => $scheme,
552
            'topconcepts' => $topconcepts)
553
        );
554
555
        return $this->returnJson($ret);
556
    }
557
558
    private function redirectToVocabData($request) {
559
        $urls = $request->getVocab()->getConfig()->getDataURLs();
560
        if (sizeof($urls) == 0) {
561
            $vocid = $request->getVocab()->getId();
562
            return $this->returnError('404', 'Not Found', "No download source URL known for vocabulary $vocid");
563
        }
564
565
        $format = $this->negotiateFormat(array_keys($urls), $request->getServerConstant('HTTP_ACCEPT'), $request->getQueryParam('format'));
566
        if (!$format) {
567
            return $this->returnError(406, 'Not Acceptable', "Unsupported format. Supported MIME types are: " . implode(' ', array_keys($urls)));
568
        }
569
        if (is_array($urls[$format])) {
570
            $arr = $urls[$format];
571
            $dataLang = $request->getLang();
572
            if (isset($arr[$dataLang])) {
573
                header("Location: " . $arr[$dataLang]);
574
            } else {
575
                $vocid = $request->getVocab()->getId();
576
                return $this->returnError('404', 'Not Found', "No download source URL known for vocabulary $vocid in language $dataLang");
577
            }
578
		} else {
579
            header("Location: " . $urls[$format]);
580
		}
581
    }
582
583
    private function returnDataResults($results, $format) {
584
        if ($format == 'application/ld+json' || $format == 'application/json') {
585
            // further compact JSON-LD document using a context
586
            $context = array(
587
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
588
                'isothes' => 'http://purl.org/iso25964/skos-thes#',
589
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
590
                'owl' => 'http://www.w3.org/2002/07/owl#',
591
                'dct' => 'http://purl.org/dc/terms/',
592
                'dc11' => 'http://purl.org/dc/elements/1.1/',
593
                'uri' => '@id',
594
                'type' => '@type',
595
                'lang' => '@language',
596
                'value' => '@value',
597
                'graph' => '@graph',
598
                'label' => 'rdfs:label',
599
                'prefLabel' => 'skos:prefLabel',
600
                'altLabel' => 'skos:altLabel',
601
                'hiddenLabel' => 'skos:hiddenLabel',
602
                'broader' => 'skos:broader',
603
                'narrower' => 'skos:narrower',
604
                'related' => 'skos:related',
605
                'inScheme' => 'skos:inScheme',
606
                'exactMatch' => 'skos:exactMatch',
607
                'closeMatch' => 'skos:closeMatch',
608
                'broadMatch' => 'skos:broadMatch',
609
                'narrowMatch' => 'skos:narrowMatch',
610
                'relatedMatch' => 'skos:relatedMatch',
611
            );
612
            $compactJsonLD = \ML\JsonLD\JsonLD::compact($results, json_encode($context));
613
            $results = \ML\JsonLD\JsonLD::toString($compactJsonLD);
614
        }
615
616
        header("Content-type: $format; charset=utf-8");
617
        echo $results;
618
    }
619
620
    /**
621
     * Download a concept as json-ld or redirect to download the whole vocabulary.
622
     * @param Request $request
623
     * @return object json-ld formatted concept.
624
     */
625
    public function data($request)
626
    {
627
        $vocab = $request->getVocab();
628
        if ($this->notModified($request->getVocab())) {
629
            return null;
630
        }
631
632
        if ($request->getUri()) {
633
            $uri = $request->getUri();
634
        } else if ($vocab !== null) { // whole vocabulary - redirect to download URL
635
            return $this->redirectToVocabData($request);
636
        } else {
637
            return $this->returnError(400, 'Bad Request', "uri parameter missing");
638
        }
639
640
        $format = $this->negotiateFormat(explode(' ', self::SUPPORTED_FORMATS), $request->getServerConstant('HTTP_ACCEPT'), $request->getQueryParam('format'));
641
        if (!$format) {
642
            return $this->returnError(406, 'Not Acceptable', "Unsupported format. Supported MIME types are: " . self::SUPPORTED_FORMATS);
643
        }
644
645
        $vocid = $vocab ? $vocab->getId() : null;
646
        $results = $this->model->getRDF($vocid, $uri, $format);
647
        if (empty($results)) {
648
            return $this->returnError(404, 'Bad Request', "no concept found with given uri");
649
        }
650
        return $this->returnDataResults($results, $format);
651
    }
652
653
    /**
654
     * Get the mappings associated with a concept, enriched with labels and notations.
655
     * Returns a JSKOS-compatible JSON object.
656
     * @param Request $request
657
     * @throws Exception if the vocabulary ID is not found in configuration
658
     */
659
    public function mappings(Request $request)
660
    {
661
        $this->setLanguageProperties($request->getLang());
662
        $vocab = $request->getVocab();
663
        if ($this->notModified($vocab)) {
664
            return null;
665
        }
666
667
        $uri = $request->getUri();
668
        if (!$uri) {
669
            return $this->returnError(400, 'Bad Request', "uri parameter missing");
670
        }
671
672
        $queryExVocabs = $request->getQueryParamBoolean('external', true);
673
674
        $results = $vocab->getConceptInfo($uri, $request->getContentLang());
675
        if (empty($results)) {
676
            return $this->returnError(404, 'Bad Request', "no concept found with given uri");
677
        }
678
679
        $concept = $results[0];
680
681
        $mappings = [];
682
        foreach ($concept->getMappingProperties() as $mappingProperty) {
683
            foreach ($mappingProperty->getValues() as $mappingPropertyValue) {
684
                $hrefLink = $this->linkUrlFilter($mappingPropertyValue->getUri(), $mappingPropertyValue->getExVocab(), $request->getLang(), 'page', $request->getContentLang());
0 ignored issues
show
Bug introduced by
It seems like $mappingPropertyValue->getExVocab() can be null; however, linkUrlFilter() 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...
685
                $mappings[] = $mappingPropertyValue->asJskos($queryExVocabs, $request->getLang(), $hrefLink);
686
            }
687
        }
688
689
        $ret = array(
690
            'mappings' => $mappings,
691
            'graph' => $concept->dumpJsonLd()
692
        );
693
694
        return $this->returnJson($ret);
695
    }
696
697
    /**
698
     * Used for querying labels for a uri.
699
     * @param Request $request
700
     * @return object json-ld wrapped labels.
701
     */
702
    public function label($request)
703
    {
704
        if (!$request->getUri()) {
705
            return $this->returnError(400, "Bad Request", "uri parameter missing");
706
        }
707
708
        if ($this->notModified($request->getVocab())) {
709
            return null;
710
        }
711
712
        $results = $request->getVocab()->getConceptLabel($request->getUri(), $request->getLang());
713
        if ($results === null) {
714
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
715
        }
716
717
        $ret = array_merge_recursive($this->context, array(
718
            '@context' => array('prefLabel' => 'skos:prefLabel', '@language' => $request->getLang()),
719
            'uri' => $request->getUri())
720
        );
721
722
        if (isset($results[$request->getLang()])) {
723
            $ret['prefLabel'] = $results[$request->getLang()]->getValue();
724
        }
725
726
        return $this->returnJson($ret);
727
    }
728
729
    /**
730
     * Query for the available letters in the alphabetical index.
731
     * @param Request $request
732
     * @return object JSON-LD wrapped list of letters
733
     */
734
735
    public function indexLetters($request)
736
    {
737
        $this->setLanguageProperties($request->getLang());
738
        $letters = $request->getVocab()->getAlphabet($request->getLang());
739
740
        $ret = array_merge_recursive($this->context, array(
741
            '@context' => array(
742
                'indexLetters' => array(
743
                    '@id' => 'skosmos:indexLetters',
744
                    '@container' => '@list',
745
                    '@language' => $request->getLang()
746
                )
747
            ),
748
            'uri' => '',
749
            'indexLetters' => $letters)
750
        );
751
        return $this->returnJson($ret);
752
    }
753
754
    /**
755
     * Query for the concepts with terms starting with a given letter in the
756
     * alphabetical index.
757
     * @param Request $request
758
     * @return object JSON-LD wrapped list of terms/concepts
759
     */
760
761
    public function indexConcepts($letter, $request)
762
    {
763
        $this->setLanguageProperties($request->getLang());
764
765
        $offset_param = $request->getQueryParam('offset');
766
        $offset = (is_numeric($offset_param) && $offset_param >= 0) ? $offset_param : 0;
767
        $limit_param = $request->getQueryParam('limit');
768
        $limit = (is_numeric($limit_param) && $limit_param >= 0) ? $limit_param : 0;
769
770
        $concepts = $request->getVocab()->searchConceptsAlphabetical($letter, $limit, $offset, $request->getLang());
771
772
        $ret = array_merge_recursive($this->context, array(
773
            '@context' => array(
774
                'indexConcepts' => array(
775
                    '@id' => 'skosmos:indexConcepts',
776
                    '@container' => '@list'
777
                )
778
            ),
779
            'uri' => '',
780
            'indexConcepts' => $concepts)
781
        );
782
        return $this->returnJson($ret);
783
    }
784
785
    private function transformPropertyResults($uri, $lang, $objects, $propname, $propuri)
786
    {
787
        $results = array();
788
        foreach ($objects as $objuri => $vals) {
789
            $results[] = array('uri' => $objuri, 'prefLabel' => $vals['label']);
790
        }
791
792
        return array_merge_recursive($this->context, array(
793
            '@context' => array('prefLabel' => 'skos:prefLabel', $propname => $propuri, '@language' => $lang),
794
            'uri' => $uri,
795
            $propname => $results)
796
        );
797
    }
798
799
    private function transformTransitivePropertyResults($uri, $lang, $objects, $tpropname, $tpropuri, $dpropname, $dpropuri)
800
    {
801
        $results = array();
802
        foreach ($objects as $objuri => $vals) {
803
            $result = array('uri' => $objuri, 'prefLabel' => $vals['label']);
804
            if (isset($vals['direct'])) {
805
                $result[$dpropname] = $vals['direct'];
806
            }
807
            $results[$objuri] = $result;
808
        }
809
810
        return array_merge_recursive($this->context, array(
811
            '@context' => array('prefLabel' => 'skos:prefLabel', $dpropname => array('@id' => $dpropuri, '@type' => '@id'), $tpropname => array('@id' => $tpropuri, '@container' => '@index'), '@language' => $lang),
812
            'uri' => $uri,
813
            $tpropname => $results)
814
        );
815
    }
816
817
    /**
818
     * Used for querying broader relations for a concept.
819
     * @param Request $request
820
     * @return object json-ld wrapped broader concept uris and labels.
821
     */
822
    public function broader($request)
823
    {
824
        if ($this->notModified($request->getVocab())) {
825
            return null;
826
        }
827
        $broaders = $request->getVocab()->getConceptBroaders($request->getUri(), $request->getLang());
828
        if ($broaders === null) {
829
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
830
        }
831
        $ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $broaders, "broader", "skos:broader");
832
        return $this->returnJson($ret);
833
    }
834
835
    /**
836
     * Used for querying broader transitive relations for a concept.
837
     * @param Request $request
838
     * @return object json-ld wrapped broader transitive concept uris and labels.
839
     */
840 View Code Duplication
    public function broaderTransitive($request)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
841
    {
842
        if ($this->notModified($request->getVocab())) {
843
            return null;
844
        }
845
        $broaders = $request->getVocab()->getConceptTransitiveBroaders($request->getUri(), $this->parseLimit(), false, $request->getLang());
846
        if (empty($broaders)) {
847
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
848
        }
849
        $ret = $this->transformTransitivePropertyResults($request->getUri(), $request->getLang(), $broaders, "broaderTransitive", "skos:broaderTransitive", "broader", "skos:broader");
850
        return $this->returnJson($ret);
851
    }
852
853
    /**
854
     * Used for querying narrower relations for a concept.
855
     * @param Request $request
856
     * @return object json-ld wrapped narrower concept uris and labels.
857
     */
858
    public function narrower($request)
859
    {
860
        if ($this->notModified($request->getVocab())) {
861
            return null;
862
        }
863
        $narrowers = $request->getVocab()->getConceptNarrowers($request->getUri(), $request->getLang());
864
        if ($narrowers === null) {
865
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
866
        }
867
        $ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $narrowers, "narrower", "skos:narrower");
868
        return $this->returnJson($ret);
869
    }
870
871
    /**
872
     * Used for querying narrower transitive relations for a concept.
873
     * @param Request $request
874
     * @return object json-ld wrapped narrower transitive concept uris and labels.
875
     */
876 View Code Duplication
    public function narrowerTransitive($request)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
877
    {
878
        if ($this->notModified($request->getVocab())) {
879
            return null;
880
        }
881
        $narrowers = $request->getVocab()->getConceptTransitiveNarrowers($request->getUri(), $this->parseLimit(), $request->getLang());
882
        if (empty($narrowers)) {
883
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
884
        }
885
        $ret = $this->transformTransitivePropertyResults($request->getUri(), $request->getLang(), $narrowers, "narrowerTransitive", "skos:narrowerTransitive", "narrower", "skos:narrower");
886
        return $this->returnJson($ret);
887
    }
888
889
    /**
890
     * Used for querying broader transitive relations
891
     * and some narrowers for a concept in the hierarchy view.
892
     * @param Request $request
893
     * @return object json-ld wrapped hierarchical concept uris and labels.
894
     */
895
    public function hierarchy($request)
896
    {
897
        if ($this->notModified($request->getVocab())) {
898
            return null;
899
        }
900
        $results = $request->getVocab()->getConceptHierarchy($request->getUri(), $request->getLang());
901
        if (empty($results)) {
902
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
903
        }
904
905
        // set the "top" key from the "tops" key
906
        foreach ($results as $value) {
907
            $uri = $value['uri'];
908
            if (isset($value['tops'])) {
909
                if ($request->getVocab()->getConfig()->getMainConceptSchemeURI() != null) {
910
                    foreach ($results[$uri]['tops'] as $top) {
911
                        // if a value in 'tops' matches the main concept scheme of the vocabulary, take it
912
                        if ($top == $request->getVocab()->getConfig()->getMainConceptSchemeURI()) {
913
                            $results[$uri]['top'] = $top;
914
                            break;
915
                        }
916
                    }
917
                    // if the main concept scheme was not found, set 'top' to the first 'tops' (sorted alphabetically on the URIs)
918 View Code Duplication
                    if (! isset($results[$uri]['top'])) {
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...
919
                        $results[$uri]['top'] = $results[$uri]['tops'][0];
920
                    }
921
                } else {
922
                    // no main concept scheme set on the vocab, take the first value of 'tops' (sorted alphabetically)
923
                    $results[$uri]['top'] = $results[$uri]['tops'][0];
924
                }
925
            }
926
        }
927
928
        if ($request->getVocab()->getConfig()->getShowHierarchy()) {
929
            $schemes = $request->getVocab()->getConceptSchemes($request->getLang());
930
            foreach ($schemes as $scheme) {
931
                if (!isset($scheme['title']) && !isset($scheme['label']) && !isset($scheme['prefLabel'])) {
932
                    unset($schemes[array_search($scheme, $schemes)]);
933
                }
934
935
            }
936
937
            /* encode the results in a JSON-LD compatible array */
938
            $topconcepts = $request->getVocab()->getTopConcepts(array_keys($schemes), $request->getLang());
0 ignored issues
show
Documentation introduced by
array_keys($schemes) is of type array<integer,integer|string>, but the function expects a string|null.

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...
939
            foreach ($topconcepts as $top) {
940
                if (!isset($results[$top['uri']])) {
941
                    $results[$top['uri']] = array('uri' => $top['uri'], 'top'=>$top['topConceptOf'], 'tops'=>array($top['topConceptOf']), 'prefLabel' => $top['label'], 'hasChildren' => $top['hasChildren']);
942 View Code Duplication
                    if (isset($top['notation'])) {
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...
943
                        $results[$top['uri']]['notation'] = $top['notation'];
944
                    }
945
946
                }
947
            }
948
        }
949
950
        $ret = array_merge_recursive($this->context, array(
951
            '@context' => array(
952
                'onki' => 'http://schema.onki.fi/onki#',
953
                'prefLabel' => 'skos:prefLabel',
954
                'notation' => 'skos:notation',
955
                'narrower' => array('@id' => 'skos:narrower', '@type' => '@id'),
956
                'broader' => array('@id' => 'skos:broader', '@type' => '@id'),
957
                'broaderTransitive' => array('@id' => 'skos:broaderTransitive', '@container' => '@index'),
958
                'top' => array('@id' => 'skos:topConceptOf', '@type' => '@id'),
959
                // the tops key will contain all the concept scheme values, while top (singular) contains a single value
960
                'tops' => array('@id' => 'skos:topConceptOf', '@type' => '@id'),
961
                'hasChildren' => 'onki:hasChildren',
962
                '@language' => $request->getLang()
963
            ),
964
            'uri' => $request->getUri(),
965
            'broaderTransitive' => $results)
966
        );
967
968
        return $this->returnJson($ret);
969
    }
970
971
    /**
972
     * Used for querying group hierarchy for the sidebar group view.
973
     * @param Request $request
974
     * @return object json-ld wrapped hierarchical concept uris and labels.
975
     */
976
    public function groups($request)
977
    {
978
        if ($this->notModified($request->getVocab())) {
979
            return null;
980
        }
981
        $results = $request->getVocab()->listConceptGroups($request->getLang());
982
983
        $ret = array_merge_recursive($this->context, array(
984
            '@context' => array('onki' => 'http://schema.onki.fi/onki#', 'prefLabel' => 'skos:prefLabel', 'groups' => 'onki:hasGroup', 'childGroups' => array('@id' => 'skos:member', '@type' => '@id'), 'hasMembers' => 'onki:hasMembers', '@language' => $request->getLang()),
985
            'uri' => '',
986
            'groups' => $results)
987
        );
988
989
        return $this->returnJson($ret);
990
    }
991
992
    /**
993
     * Used for querying member relations for a group.
994
     * @param Request $request
995
     * @return object json-ld wrapped narrower concept uris and labels.
996
     */
997
    public function groupMembers($request)
998
    {
999
        if ($this->notModified($request->getVocab())) {
1000
            return null;
1001
        }
1002
        $children = $request->getVocab()->listConceptGroupContents($request->getUri(), $request->getLang());
1003
        if (empty($children)) {
1004
            return $this->returnError('404', 'Not Found', "Could not find group <{$request->getUri()}>");
1005
        }
1006
1007
        $ret = array_merge_recursive($this->context, array(
1008
            '@context' => array('prefLabel' => 'skos:prefLabel', 'members' => 'skos:member', '@language' => $request->getLang()),
1009
            'uri' => $request->getUri(),
1010
            'members' => $children)
1011
        );
1012
1013
        return $this->returnJson($ret);
1014
    }
1015
1016
    /**
1017
     * Used for querying narrower relations for a concept in the hierarchy view.
1018
     * @param Request $request
1019
     * @return object json-ld wrapped narrower concept uris and labels.
1020
     */
1021
    public function children($request)
1022
    {
1023
        if ($this->notModified($request->getVocab())) {
1024
            return null;
1025
        }
1026
        $children = $request->getVocab()->getConceptChildren($request->getUri(), $request->getLang());
1027
        if ($children === null) {
1028
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
1029
        }
1030
1031
        $ret = array_merge_recursive($this->context, array(
1032
            '@context' => array('prefLabel' => 'skos:prefLabel', 'narrower' => 'skos:narrower', 'notation' => 'skos:notation', 'hasChildren' => 'onki:hasChildren', '@language' => $request->getLang()),
1033
            'uri' => $request->getUri(),
1034
            'narrower' => $children)
1035
        );
1036
1037
        return $this->returnJson($ret);
1038
    }
1039
1040
    /**
1041
     * Used for querying narrower relations for a concept in the hierarchy view.
1042
     * @param Request $request
1043
     * @return object json-ld wrapped hierarchical concept uris and labels.
1044
     */
1045
    public function related($request)
1046
    {
1047
        if ($this->notModified($request->getVocab())) {
1048
            return null;
1049
        }
1050
        $related = $request->getVocab()->getConceptRelateds($request->getUri(), $request->getLang());
1051
        if ($related === null) {
1052
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
1053
        }
1054
        $ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $related, "related", "skos:related");
1055
        return $this->returnJson($ret);
1056
    }
1057
}
1058