Completed
Push — master ( bc9513...38f056 )
by Osma
02:06
created

RestController::redirectToVocabData()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 14
rs 9.7998
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';
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
205
        /* encode the results in a JSON-LD compatible array */
206
        $conceptschemes = array();
207
        foreach ($vocab->getConceptSchemes($request->getLang()) as $uri => $csdata) {
208
            $csdata['uri'] = $uri;
209
            $csdata['type'] = 'skos:ConceptScheme';
210
            $conceptschemes[] = $csdata;
211
        }
212
213
        $ret = array(
214
            '@context' => array(
215
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
216
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
217
                'onki' => 'http://schema.onki.fi/onki#',
218
                'dct' => 'http://purl.org/dc/terms/',
219
                'uri' => '@id',
220
                'type' => '@type',
221
                'conceptschemes' => 'onki:hasConceptScheme',
222
                'id' => 'onki:vocabularyIdentifier',
223
                'defaultLanguage' => 'onki:defaultLanguage',
224
                'languages' => 'onki:language',
225
                'label' => 'rdfs:label',
226
                'prefLabel' => 'skos:prefLabel',
227
                'title' => 'dct:title',
228
                '@language' => $request->getLang(),
229
                '@base' => $this->getBaseHref() . "rest/v1/" . $vocab->getId() . "/",
230
            ),
231
            'uri' => '',
232
            'id' => $vocab->getId(),
233
            'marcSource' => $vocab->getConfig()->getMarcSourceCode($request->getLang()),
234
            'title' => $vocab->getConfig()->getTitle($request->getLang()),
235
            'defaultLanguage' => $vocab->getConfig()->getDefaultLanguage(),
236
            'languages' => array_values($vocab->getConfig()->getLanguages()),
237
            'conceptschemes' => $conceptschemes,
238
        );
239
240
        if ($vocab->getConfig()->getTypes($request->getLang())) {
241
            $ret['type'] = $vocab->getConfig()->getTypes($request->getLang());
242
        }
243
244
        return $this->returnJson($ret);
245
    }
246
247
    /**
248
     * Loads the vocabulary metadata. And wraps the result in a json-ld object.
249
     * @param Request $request
250
     */
251
    public function vocabularyStatistics($request)
252
    {
253
        $this->setLanguageProperties($request->getLang());
254
        $arrayClass = $request->getVocab()->getConfig()->getArrayClassURI();
255
        $groupClass = $request->getVocab()->getConfig()->getGroupClassURI();
256
        $vocabStats = $request->getVocab()->getStatistics($request->getQueryParam('lang'), $arrayClass, $groupClass);
257
        $types = array('http://www.w3.org/2004/02/skos/core#Concept', 'http://www.w3.org/2004/02/skos/core#Collection', $arrayClass, $groupClass);
258
        $subTypes = array();
259
        foreach ($vocabStats as $subtype) {
260
            if (!in_array($subtype['type'], $types)) {
261
                $subTypes[] = $subtype;
262
            }
263
        }
264
265
        /* encode the results in a JSON-LD compatible array */
266
        $ret = array(
267
            '@context' => array(
268
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
269
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
270
                'void' => 'http://rdfs.org/ns/void#',
271
                'onki' => 'http://schema.onki.fi/onki#',
272
                'uri' => '@id',
273
                'id' => 'onki:vocabularyIdentifier',
274
                'concepts' => 'void:classPartition',
275
                'label' => 'rdfs:label',
276
                'class' => array('@id' => 'void:class', '@type' => '@id'),
277
                'subTypes' => array('@id' => 'void:class', '@type' => '@id'),
278
                'count' => 'void:entities',
279
                '@language' => $request->getLang(),
280
                '@base' => $this->getBaseHref() . "rest/v1/" . $request->getVocab()->getId() . "/",
281
            ),
282
            'uri' => '',
283
            'id' => $request->getVocab()->getId(),
284
            'title' => $request->getVocab()->getConfig()->getTitle(),
285
            'concepts' => array(
286
                'class' => 'http://www.w3.org/2004/02/skos/core#Concept',
287
                'label' => gettext('skos:Concept'),
288
                'count' => $vocabStats['http://www.w3.org/2004/02/skos/core#Concept']['count'],
289
            ),
290
            'subTypes' => $subTypes,
291
        );
292
293
        if (isset($vocabStats['http://www.w3.org/2004/02/skos/core#Collection'])) {
294
            $ret['conceptGroups'] = array(
295
                'class' => 'http://www.w3.org/2004/02/skos/core#Collection',
296
                'label' => gettext('skos:Collection'),
297
                'count' => $vocabStats['http://www.w3.org/2004/02/skos/core#Collection']['count'],
298
            );
299
        } else if (isset($vocabStats[$groupClass])) {
300
            $ret['conceptGroups'] = array(
301
                'class' => $groupClass,
302
                'label' => isset($vocabStats[$groupClass]['label']) ? $vocabStats[$groupClass]['label'] : gettext(EasyRdf\RdfNamespace::shorten($groupClass)),
303
                'count' => $vocabStats[$groupClass]['count'],
304
            );
305
        } else if (isset($vocabStats[$arrayClass])) {
306
            $ret['arrays'] = array(
307
                'class' => $arrayClass,
308
                'label' => isset($vocabStats[$arrayClass]['label']) ? $vocabStats[$arrayClass]['label'] : gettext(EasyRdf\RdfNamespace::shorten($arrayClass)),
309
                'count' => $vocabStats[$arrayClass]['count'],
310
            );
311
        }
312
313
        return $this->returnJson($ret);
314
    }
315
316
    /**
317
     * Loads the vocabulary metadata. And wraps the result in a json-ld object.
318
     * @param Request $request
319
     */
320
    public function labelStatistics($request)
321
    {
322
        $lang = $request->getLang();
323
        $this->setLanguageProperties($request->getLang());
324
        $vocabStats = $request->getVocab()->getLabelStatistics();
325
326
        /* encode the results in a JSON-LD compatible array */
327
        $counts = array();
328
        foreach ($vocabStats['terms'] as $proplang => $properties) {
329
            $langdata = array('language' => $proplang);
330
            if ($lang) {
331
                $langdata['literal'] = Punic\Language::getName($proplang, $lang);
332
            }
333
334
            $langdata['properties'] = array();
335
            foreach ($properties as $prop => $value) {
336
                $langdata['properties'][] = array('property' => $prop, 'labels' => $value);
337
            }
338
            $counts[] = $langdata;
339
        }
340
341
        $ret = array(
342
            '@context' => array(
343
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
344
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
345
                'void' => 'http://rdfs.org/ns/void#',
346
                'void-ext' => 'http://ldf.fi/void-ext#',
347
                'onki' => 'http://schema.onki.fi/onki#',
348
                'uri' => '@id',
349
                'id' => 'onki:vocabularyIdentifier',
350
                'languages' => 'void-ext:languagePartition',
351
                'language' => 'void-ext:language',
352
                'properties' => 'void:propertyPartition',
353
                'labels' => 'void:triples',
354
                '@base' => $this->getBaseHref() . "rest/v1/" . $request->getVocab()->getId() . "/",
355
            ),
356
            'uri' => '',
357
            'id' => $request->getVocab()->getId(),
358
            'title' => $request->getVocab()->getConfig()->getTitle($lang),
359
            'languages' => $counts,
360
        );
361
362
        if ($lang) {
363
            $ret['@context']['literal'] = array('@id' => 'rdfs:label', '@language' => $lang);
364
        }
365
366
        return $this->returnJson($ret);
367
    }
368
369
    /**
370
     * Loads the vocabulary type metadata. And wraps the result in a json-ld object.
371
     * @param Request $request
372
     */
373
    public function types($request)
374
    {
375
        $vocid = $request->getVocab() ? $request->getVocab()->getId() : null;
376
        if ($vocid === null && !$request->getLang()) {
377
            return $this->returnError(400, "Bad Request", "lang parameter missing");
378
        }
379
        $this->setLanguageProperties($request->getLang());
380
381
        $queriedtypes = $this->model->getTypes($vocid, $request->getLang());
382
383
        $types = array();
384
385
        /* encode the results in a JSON-LD compatible array */
386
        foreach ($queriedtypes as $uri => $typedata) {
387
            $type = array_merge(array('uri' => $uri), $typedata);
388
            $types[] = $type;
389
        }
390
391
        $base = $request->getVocab() ? $this->getBaseHref() . "rest/v1/" . $request->getVocab()->getId() . "/" : $this->getBaseHref() . "rest/v1/";
392
393
        $ret = array_merge_recursive($this->context, array(
394
            '@context' => array(
395
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
396
                'onki' => 'http://schema.onki.fi/onki#',
397
                'label' => 'rdfs:label',
398
                'superclass' => array('@id' => 'rdfs:subClassOf', '@type' => '@id'),
399
                'types' => 'onki:hasType',
400
                '@language' => $request->getLang(),
401
                '@base' => $base,
402
            ),
403
            'uri' => '',
404
            'types' => $types)
405
        );
406
407
        return $this->returnJson($ret);
408
    }
409
410
    private function findLookupHits($results, $label, $lang)
411
    {
412
        $hits = array();
413
        // case 1: exact match on preferred label
414
        foreach ($results as $res) {
415
            if ($res['prefLabel'] == $label) {
416
                $hits[] = $res;
417
            }
418
        }
419
        if (sizeof($hits) > 0) return $hits;
420
421
        // case 2: case-insensitive match on preferred label
422
        foreach ($results as $res) {
423
            if (strtolower($res['prefLabel']) == strtolower($label)) {
424
                $hits[] = $res;
425
            }
426
        }
427
        if (sizeof($hits) > 0) return $hits;
428
429
        if ($lang === null) {
430
            // case 1A: exact match on preferred label in any language
431 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...
432
                if ($res['matchedPrefLabel'] == $label) {
433
                    $res['prefLabel'] = $res['matchedPrefLabel'];
434
                    unset($res['matchedPrefLabel']);
435
                    $hits[] = $res;
436
                }
437
            }
438
            if (sizeof($hits) > 0) return $hits;
439
440
            // case 2A: case-insensitive match on preferred label in any language
441 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...
442
                if (strtolower($res['matchedPrefLabel']) == strtolower($label)) {
443
                    $res['prefLabel'] = $res['matchedPrefLabel'];
444
                    unset($res['matchedPrefLabel']);
445
                    $hits[] = $res;
446
                }
447
            }
448
            if (sizeof($hits) > 0) return $hits;
449
        }
450
451
        // case 3: exact match on alternate label
452 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...
453
            if (isset($res['altLabel']) && $res['altLabel'] == $label) {
454
                $hits[] = $res;
455
            }
456
        }
457
        if (sizeof($hits) > 0) return $hits;
458
459
460
        // case 4: case-insensitive match on alternate label
461 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...
462
            if (isset($res['altLabel']) && strtolower($res['altLabel']) == strtolower($label)) {
463
                $hits[] = $res;
464
            }
465
        }
466
        if (sizeof($hits) > 0) return $hits;
467
468
        return $hits;
469
    }
470
471
    private function transformLookupResults($lang, $hits)
472
    {
473
        if (sizeof($hits) == 0) {
474
            // no matches found
475
            return;
476
        }
477
478
        // found matches, getting rid of Vocabulary objects
479
        foreach ($hits as &$res) {
480
            unset($res['voc']);
481
        }
482
483
        $ret = array_merge_recursive($this->context, array(
484
            '@context' => array('onki' => 'http://schema.onki.fi/onki#', 'results' => array('@id' => 'onki:results'), 'prefLabel' => 'skos:prefLabel', 'altLabel' => 'skos:altLabel', 'hiddenLabel' => 'skos:hiddenLabel'),
485
            'result' => $hits)
486
        );
487
488
        if ($lang) {
489
            $ret['@context']['@language'] = $lang;
490
        }
491
492
        return $ret;
493
    }
494
495
    /**
496
     * Used for finding terms by their exact prefLabel. Wraps the result in a json-ld object.
497
     * @param Request $request
498
     */
499
    public function lookup($request)
500
    {
501
        $label = $request->getQueryParamRaw('label');
502
        if (!$label) {
503
            return $this->returnError(400, "Bad Request", "label parameter missing");
504
        }
505
506
        $lang = $request->getQueryParam('lang');
507
        $parameters = new ConceptSearchParameters($request, $this->model->getConfig(), true);
508
        $results = $this->model->searchConcepts($parameters);
509
        $hits = $this->findLookupHits($results, $label, $lang);
510
        $ret = $this->transformLookupResults($lang, $hits);
511
        if ($ret === null) {
512
            return $this->returnError(404, 'Not Found', "Could not find label '$label'");
513
        }
514
        return $this->returnJson($ret);
515
    }
516
517
    /**
518
     * Queries the top concepts of a vocabulary and wraps the results in a json-ld object.
519
     * @param Request $request
520
     * @return object json-ld object
521
     */
522
    public function topConcepts($request)
523
    {
524
        $vocab = $request->getVocab();
525
        $scheme = $request->getQueryParam('scheme');
526
        if (!$scheme) {
527
            $scheme = $vocab->getConfig()->showConceptSchemesInHierarchy() ? array_keys($vocab->getConceptSchemes()) : $vocab->getDefaultConceptScheme();
528
        }
529
530
        /* encode the results in a JSON-LD compatible array */
531
        $topconcepts = $vocab->getTopConcepts($scheme, $request->getLang());
532
533
        $ret = array_merge_recursive($this->context, array(
534
            '@context' => array('onki' => 'http://schema.onki.fi/onki#', 'topconcepts' => 'skos:hasTopConcept', 'notation' => 'skos:notation', 'label' => 'skos:prefLabel', '@language' => $request->getLang()),
535
            'uri' => $scheme,
536
            'topconcepts' => $topconcepts)
537
        );
538
539
        return $this->returnJson($ret);
540
    }
541
542
    private function redirectToVocabData($request) {
543
        $urls = $request->getVocab()->getConfig()->getDataURLs();
544
        if (sizeof($urls) == 0) {
545
            $vocid = $request->getVocab()->getId();
546
            return $this->returnError('404', 'Not Found', "No download source URL known for vocabulary $vocid");
547
        }
548
549
        $format = $this->negotiateFormat(array_keys($urls), $request->getServerConstant('HTTP_ACCEPT'), $request->getQueryParam('format'));
550
        if (!$format) {
551
            return $this->returnError(406, 'Not Acceptable', "Unsupported format. Supported MIME types are: " . implode(' ', array_keys($urls)));
552
        }
553
554
        header("Location: " . $urls[$format]);
555
    }
556
557
    private function returnDataResults($results, $format) {
558
        if ($format == 'application/ld+json' || $format == 'application/json') {
559
            // further compact JSON-LD document using a context
560
            $context = array(
561
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
562
                'isothes' => 'http://purl.org/iso25964/skos-thes#',
563
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
564
                'owl' => 'http://www.w3.org/2002/07/owl#',
565
                'dct' => 'http://purl.org/dc/terms/',
566
                'dc11' => 'http://purl.org/dc/elements/1.1/',
567
                'uri' => '@id',
568
                'type' => '@type',
569
                'lang' => '@language',
570
                'value' => '@value',
571
                'graph' => '@graph',
572
                'label' => 'rdfs:label',
573
                'prefLabel' => 'skos:prefLabel',
574
                'altLabel' => 'skos:altLabel',
575
                'hiddenLabel' => 'skos:hiddenLabel',
576
                'broader' => 'skos:broader',
577
                'narrower' => 'skos:narrower',
578
                'related' => 'skos:related',
579
                'inScheme' => 'skos:inScheme',
580
                'exactMatch' => 'skos:exactMatch',
581
                'closeMatch' => 'skos:closeMatch',
582
                'broadMatch' => 'skos:broadMatch',
583
                'narrowMatch' => 'skos:narrowMatch',
584
                'relatedMatch' => 'skos:relatedMatch',
585
            );
586
            $compactJsonLD = \ML\JsonLD\JsonLD::compact($results, json_encode($context));
587
            $results = \ML\JsonLD\JsonLD::toString($compactJsonLD);
588
        }
589
590
        header("Content-type: $format; charset=utf-8");
591
        echo $results;
592
    }
593
594
    /**
595
     * Download a concept as json-ld or redirect to download the whole vocabulary.
596
     * @param Request $request
597
     * @return object json-ld formatted concept.
598
     */
599
    public function data($request)
600
    {
601
        $vocab = $request->getVocab();
602
603
        if ($request->getUri()) {
604
            $uri = $request->getUri();
605
        } else if ($vocab !== null) { // whole vocabulary - redirect to download URL
606
            return $this->redirectToVocabData($request);
607
        } else {
608
            return $this->returnError(400, 'Bad Request', "uri parameter missing");
609
        }
610
611
        $format = $this->negotiateFormat(explode(' ', self::SUPPORTED_FORMATS), $request->getServerConstant('HTTP_ACCEPT'), $request->getQueryParam('format'));
612
        if (!$format) {
613
            return $this->returnError(406, 'Not Acceptable', "Unsupported format. Supported MIME types are: " . self::SUPPORTED_FORMATS);
614
        }
615
616
        $vocid = $vocab ? $vocab->getId() : null;
617
        $results = $this->model->getRDF($vocid, $uri, $format);
618
        if (empty($results)) {
619
            return $this->returnError(404, 'Bad Request', "no concept found with given uri");
620
        }
621
        return $this->returnDataResults($results, $format);
622
    }
623
624
    /**
625
     * Get the mappings associated with a concept, enriched with labels and notations.
626
     * Returns a JSKOS-compatible JSON object.
627
     * @param Request $request
628
     */
629
    public function mappings(Request $request)
630
    {
631
        $vocab = $request->getVocab();
632
633
        if ($request->getUri()) {
634
            $uri = $request->getUri();
635
        } else {
636
            return $this->returnError(400, 'Bad Request', "uri parameter missing");
637
        }
638
639
        $queryExVocabs = $request->getQueryParamBoolean('external', true);
640
641
        $results = $vocab->getConceptInfo($uri, $request->getContentLang());
642
        if (empty($results)) {
643
            return $this->returnError(404, 'Bad Request', "no concept found with given uri");
644
        }
645
646
        $concept = $results[0];
647
648
        $ret = [];
649
        foreach ($concept->getMappingProperties() as $mappingProperty) {
650
            foreach ($mappingProperty->getValues() as $mappingPropertyValue) {
651
                $ret[] = $mappingPropertyValue->asJskos($queryExVocabs);
652
            }
653
        }
654
655
        return $this->returnJson($ret);
656
    }
657
658
    /**
659
     * Used for querying labels for a uri.
660
     * @param Request $request
661
     * @return object json-ld wrapped labels.
662
     */
663
    public function label($request)
664
    {
665
        if (!$request->getUri()) {
666
            return $this->returnError(400, "Bad Request", "uri parameter missing");
667
        }
668
669
        $results = $request->getVocab()->getConceptLabel($request->getUri(), $request->getLang());
670
        if ($results === null) {
671
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
672
        }
673
674
        $ret = array_merge_recursive($this->context, array(
675
            '@context' => array('prefLabel' => 'skos:prefLabel', '@language' => $request->getLang()),
676
            'uri' => $request->getUri())
677
        );
678
679
        if (isset($results[$request->getLang()])) {
680
            $ret['prefLabel'] = $results[$request->getLang()]->getValue();
681
        }
682
683
        return $this->returnJson($ret);
684
    }
685
686
    private function transformPropertyResults($uri, $lang, $objects, $propname, $propuri)
687
    {
688
        $results = array();
689
        foreach ($objects as $objuri => $vals) {
690
            $results[] = array('uri' => $objuri, 'prefLabel' => $vals['label']);
691
        }
692
693
        $ret = array_merge_recursive($this->context, array(
694
            '@context' => array('prefLabel' => 'skos:prefLabel', $propname => $propuri, '@language' => $lang),
695
            'uri' => $uri,
696
            $propname => $results)
697
        );
698
        return $ret;
699
    }
700
701
    private function transformTransitivePropertyResults($uri, $lang, $objects, $tpropname, $tpropuri, $dpropname, $dpropuri)
702
    {
703
        $results = array();
704
        foreach ($objects as $objuri => $vals) {
705
            $result = array('uri' => $objuri, 'prefLabel' => $vals['label']);
706
            if (isset($vals['direct'])) {
707
                $result[$dpropname] = $vals['direct'];
708
            }
709
            $results[$objuri] = $result;
710
        }
711
712
        $ret = array_merge_recursive($this->context, array(
713
            '@context' => array('prefLabel' => 'skos:prefLabel', $dpropname => array('@id' => $dpropuri, '@type' => '@id'), $tpropname => array('@id' => $tpropuri, '@container' => '@index'), '@language' => $lang),
714
            'uri' => $uri,
715
            $tpropname => $results)
716
        );
717
        return $ret;
718
    }
719
720
    /**
721
     * Used for querying broader relations for a concept.
722
     * @param Request $request
723
     * @return object json-ld wrapped broader concept uris and labels.
724
     */
725
    public function broader($request)
726
    {
727
        $broaders = $request->getVocab()->getConceptBroaders($request->getUri(), $request->getLang());
728
        if ($broaders === null) {
729
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
730
        }
731
        $ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $broaders, "broader", "skos:broader");
732
        return $this->returnJson($ret);
733
    }
734
735
    /**
736
     * Used for querying broader transitive relations for a concept.
737
     * @param Request $request
738
     * @return object json-ld wrapped broader transitive concept uris and labels.
739
     */
740 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...
741
    {
742
        $broaders = $request->getVocab()->getConceptTransitiveBroaders($request->getUri(), $this->parseLimit(), false, $request->getLang());
743
        if (empty($broaders)) {
744
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
745
        }
746
        $ret = $this->transformTransitivePropertyResults($request->getUri(), $request->getLang(), $broaders, "broaderTransitive", "skos:broaderTransitive", "broader", "skos:broader");
747
        return $this->returnJson($ret);
748
    }
749
750
    /**
751
     * Used for querying narrower relations for a concept.
752
     * @param Request $request
753
     * @return object json-ld wrapped narrower concept uris and labels.
754
     */
755
    public function narrower($request)
756
    {
757
        $narrowers = $request->getVocab()->getConceptNarrowers($request->getUri(), $request->getLang());
758
        if ($narrowers === null) {
759
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
760
        }
761
        $ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $narrowers, "narrower", "skos:narrower");
762
        return $this->returnJson($ret);
763
    }
764
765
    /**
766
     * Used for querying narrower transitive relations for a concept.
767
     * @param Request $request
768
     * @return object json-ld wrapped narrower transitive concept uris and labels.
769
     */
770 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...
771
    {
772
        $narrowers = $request->getVocab()->getConceptTransitiveNarrowers($request->getUri(), $this->parseLimit(), $request->getLang());
773
        if (empty($narrowers)) {
774
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
775
        }
776
        $ret = $this->transformTransitivePropertyResults($request->getUri(), $request->getLang(), $narrowers, "narrowerTransitive", "skos:narrowerTransitive", "narrower", "skos:narrower");
777
        return $this->returnJson($ret);
778
    }
779
780
    /**
781
     * Used for querying broader transitive relations
782
     * and some narrowers for a concept in the hierarchy view.
783
     * @param Request $request
784
     * @return object json-ld wrapped hierarchical concept uris and labels.
785
     */
786
    public function hierarchy($request)
787
    {
788
        $results = $request->getVocab()->getConceptHierarchy($request->getUri(), $request->getLang());
789
        if (empty($results)) {
790
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
791
        }
792
793
794
        // set the "top" key from the "tops" key
795
        foreach ($results as $value) {
796
            $uri = $value['uri'];
797
            if (isset($value['tops'])) {
798
                if ($request->getVocab()->getConfig()->getMainConceptSchemeURI() != null) {
799
                    foreach ($results[$uri]['tops'] as $top) {
800
                        // if a value in 'tops' matches the main concept scheme of the vocabulary, take it
801
                        if ($top == $request->getVocab()->getConfig()->getMainConceptSchemeURI()) {
802
                            $results[$uri]['top'] = $top;
803
                            break;
804
                        }
805
                    }
806
                    // if the main concept scheme was not found, set 'top' to the first 'tops' (sorted alphabetically on the URIs)
807 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...
808
                        $results[$uri]['top'] = $results[$uri]['tops'][0];
809
                    }
810
                } else {
811
                    // no main concept scheme set on the vocab, take the first value of 'tops' (sorted alphabetically)
812
                    $results[$uri]['top'] = $results[$uri]['tops'][0];
813
                }
814
            }
815
        }
816
817
        if ($request->getVocab()->getConfig()->getShowHierarchy()) {
818
            $schemes = $request->getVocab()->getConceptSchemes($request->getLang());
819
            foreach ($schemes as $scheme) {
820
                if (!isset($scheme['title']) && !isset($scheme['label']) && !isset($scheme['prefLabel'])) {
821
                    unset($schemes[array_search($scheme, $schemes)]);
822
                }
823
824
            }
825
826
            /* encode the results in a JSON-LD compatible array */
827
            $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...
828
            foreach ($topconcepts as $top) {
829
                if (!isset($results[$top['uri']])) {
830
                    $results[$top['uri']] = array('uri' => $top['uri'], 'top'=>$top['topConceptOf'], 'tops'=>array($top['topConceptOf']), 'prefLabel' => $top['label'], 'hasChildren' => $top['hasChildren']);
831 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...
832
                        $results[$top['uri']]['notation'] = $top['notation'];
833
                    }
834
835
                }
836
            }
837
        }
838
839
        $ret = array_merge_recursive($this->context, array(
840
            '@context' => array(
841
                'onki' => 'http://schema.onki.fi/onki#',
842
                'prefLabel' => 'skos:prefLabel',
843
                'notation' => 'skos:notation',
844
                'narrower' => array('@id' => 'skos:narrower', '@type' => '@id'),
845
                'broader' => array('@id' => 'skos:broader', '@type' => '@id'),
846
                'broaderTransitive' => array('@id' => 'skos:broaderTransitive', '@container' => '@index'),
847
                'top' => array('@id' => 'skos:topConceptOf', '@type' => '@id'),
848
                // the tops key will contain all the concept scheme values, while top (singular) contains a single value
849
                'tops' => array('@id' => 'skos:topConceptOf', '@type' => '@id'),
850
                'hasChildren' => 'onki:hasChildren',
851
                '@language' => $request->getLang()
852
            ),
853
            'uri' => $request->getUri(),
854
            'broaderTransitive' => $results)
855
        );
856
857
        return $this->returnJson($ret);
858
    }
859
860
    /**
861
     * Used for querying group hierarchy for the sidebar group view.
862
     * @param Request $request
863
     * @return object json-ld wrapped hierarchical concept uris and labels.
864
     */
865
    public function groups($request)
866
    {
867
        $results = $request->getVocab()->listConceptGroups($request->getLang());
868
869
        $ret = array_merge_recursive($this->context, array(
870
            '@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()),
871
            'uri' => '',
872
            'groups' => $results)
873
        );
874
875
        return $this->returnJson($ret);
876
    }
877
878
    /**
879
     * Used for querying member relations for a group.
880
     * @param Request $request
881
     * @return object json-ld wrapped narrower concept uris and labels.
882
     */
883
    public function groupMembers($request)
884
    {
885
        $children = $request->getVocab()->listConceptGroupContents($request->getUri(), $request->getLang());
886
        if (empty($children)) {
887
            return $this->returnError('404', 'Not Found', "Could not find group <{$request->getUri()}>");
888
        }
889
890
        $ret = array_merge_recursive($this->context, array(
891
            '@context' => array('prefLabel' => 'skos:prefLabel', 'members' => 'skos:member', '@language' => $request->getLang()),
892
            'uri' => $request->getUri(),
893
            'members' => $children)
894
        );
895
896
        return $this->returnJson($ret);
897
    }
898
899
    /**
900
     * Used for querying narrower relations for a concept in the hierarchy view.
901
     * @param Request $request
902
     * @return object json-ld wrapped narrower concept uris and labels.
903
     */
904
    public function children($request)
905
    {
906
        $children = $request->getVocab()->getConceptChildren($request->getUri(), $request->getLang());
907
        if ($children === null) {
908
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
909
        }
910
911
        $ret = array_merge_recursive($this->context, array(
912
            '@context' => array('prefLabel' => 'skos:prefLabel', 'narrower' => 'skos:narrower', 'notation' => 'skos:notation', 'hasChildren' => 'onki:hasChildren', '@language' => $request->getLang()),
913
            'uri' => $request->getUri(),
914
            'narrower' => $children)
915
        );
916
917
        return $this->returnJson($ret);
918
    }
919
920
    /**
921
     * Used for querying narrower relations for a concept in the hierarchy view.
922
     * @param Request $request
923
     * @return object json-ld wrapped hierarchical concept uris and labels.
924
     */
925
    public function related($request)
926
    {
927
        $related = $request->getVocab()->getConceptRelateds($request->getUri(), $request->getLang());
928
        if ($related === null) {
929
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
930
        }
931
        $ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $related, "related", "skos:related");
932
        return $this->returnJson($ret);
933
    }
934
}
935