Completed
Push — master ( 12a075...e821af )
by
unknown
01:58 queued 10s
created

RestController::transformLookupResults()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 5
nop 2
dl 0
loc 23
rs 9.552
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
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
        if (is_array($urls[$format])) {
554
            $arr = $urls[$format];
555
            $dataLang = $request->getLang();
556
            if (isset($arr[$dataLang])) {
557
                header("Location: " . $arr[$dataLang]);
558
            } else {
559
                $vocid = $request->getVocab()->getId();
560
                return $this->returnError('404', 'Not Found', "No download source URL known for vocabulary $vocid in language $dataLang");
561
            }
562
		} else {
563
            header("Location: " . $urls[$format]);
564
		}
565
    }
566
567
    private function returnDataResults($results, $format) {
568
        if ($format == 'application/ld+json' || $format == 'application/json') {
569
            // further compact JSON-LD document using a context
570
            $context = array(
571
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
572
                'isothes' => 'http://purl.org/iso25964/skos-thes#',
573
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
574
                'owl' => 'http://www.w3.org/2002/07/owl#',
575
                'dct' => 'http://purl.org/dc/terms/',
576
                'dc11' => 'http://purl.org/dc/elements/1.1/',
577
                'uri' => '@id',
578
                'type' => '@type',
579
                'lang' => '@language',
580
                'value' => '@value',
581
                'graph' => '@graph',
582
                'label' => 'rdfs:label',
583
                'prefLabel' => 'skos:prefLabel',
584
                'altLabel' => 'skos:altLabel',
585
                'hiddenLabel' => 'skos:hiddenLabel',
586
                'broader' => 'skos:broader',
587
                'narrower' => 'skos:narrower',
588
                'related' => 'skos:related',
589
                'inScheme' => 'skos:inScheme',
590
                'exactMatch' => 'skos:exactMatch',
591
                'closeMatch' => 'skos:closeMatch',
592
                'broadMatch' => 'skos:broadMatch',
593
                'narrowMatch' => 'skos:narrowMatch',
594
                'relatedMatch' => 'skos:relatedMatch',
595
            );
596
            $compactJsonLD = \ML\JsonLD\JsonLD::compact($results, json_encode($context));
597
            $results = \ML\JsonLD\JsonLD::toString($compactJsonLD);
598
        }
599
600
        header("Content-type: $format; charset=utf-8");
601
        echo $results;
602
    }
603
604
    /**
605
     * Download a concept as json-ld or redirect to download the whole vocabulary.
606
     * @param Request $request
607
     * @return object json-ld formatted concept.
608
     */
609
    public function data($request)
610
    {
611
        $vocab = $request->getVocab();
612
613
        if ($request->getUri()) {
614
            $uri = $request->getUri();
615
        } else if ($vocab !== null) { // whole vocabulary - redirect to download URL
616
            return $this->redirectToVocabData($request);
617
        } else {
618
            return $this->returnError(400, 'Bad Request', "uri parameter missing");
619
        }
620
621
        $format = $this->negotiateFormat(explode(' ', self::SUPPORTED_FORMATS), $request->getServerConstant('HTTP_ACCEPT'), $request->getQueryParam('format'));
622
        if (!$format) {
623
            return $this->returnError(406, 'Not Acceptable', "Unsupported format. Supported MIME types are: " . self::SUPPORTED_FORMATS);
624
        }
625
626
        $vocid = $vocab ? $vocab->getId() : null;
627
        $results = $this->model->getRDF($vocid, $uri, $format);
628
        if (empty($results)) {
629
            return $this->returnError(404, 'Bad Request', "no concept found with given uri");
630
        }
631
        return $this->returnDataResults($results, $format);
632
    }
633
634
    /**
635
     * Get the mappings associated with a concept, enriched with labels and notations.
636
     * Returns a JSKOS-compatible JSON object.
637
     * @param Request $request
638
     * @throws Exception if the vocabulary ID is not found in configuration
639
     */
640
    public function mappings(Request $request)
641
    {
642
        $this->setLanguageProperties($request->getLang());
643
        $vocab = $request->getVocab();
644
645
        $uri = $request->getUri();
646
        if (!$uri) {
647
            return $this->returnError(400, 'Bad Request', "uri parameter missing");
648
        }
649
650
        $queryExVocabs = $request->getQueryParamBoolean('external', true);
651
652
        $results = $vocab->getConceptInfo($uri, $request->getContentLang());
653
        if (empty($results)) {
654
            return $this->returnError(404, 'Bad Request', "no concept found with given uri");
655
        }
656
657
        $concept = $results[0];
658
659
        $mappings = [];
660
        foreach ($concept->getMappingProperties() as $mappingProperty) {
661
            foreach ($mappingProperty->getValues() as $mappingPropertyValue) {
662
                $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...
663
                $mappings[] = $mappingPropertyValue->asJskos($queryExVocabs, $request->getLang(), $hrefLink);
664
            }
665
        }
666
667
        $ret = array(
668
            'mappings' => $mappings,
669
            'graph' => $concept->dumpJsonLd()
670
        );
671
672
        return $this->returnJson($ret);
673
    }
674
675
    /**
676
     * Used for querying labels for a uri.
677
     * @param Request $request
678
     * @return object json-ld wrapped labels.
679
     */
680
    public function label($request)
681
    {
682
        if (!$request->getUri()) {
683
            return $this->returnError(400, "Bad Request", "uri parameter missing");
684
        }
685
686
        $results = $request->getVocab()->getConceptLabel($request->getUri(), $request->getLang());
687
        if ($results === null) {
688
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
689
        }
690
691
        $ret = array_merge_recursive($this->context, array(
692
            '@context' => array('prefLabel' => 'skos:prefLabel', '@language' => $request->getLang()),
693
            'uri' => $request->getUri())
694
        );
695
696
        if (isset($results[$request->getLang()])) {
697
            $ret['prefLabel'] = $results[$request->getLang()]->getValue();
698
        }
699
700
        return $this->returnJson($ret);
701
    }
702
703
    /**
704
     * Query for the available letters in the alphabetical index.
705
     * @param Request $request
706
     * @return object JSON-LD wrapped list of letters
707
     */
708
709
    public function indexLetters($request)
710
    {
711
        $this->setLanguageProperties($request->getLang());
712
        $letters = $request->getVocab()->getAlphabet($request->getLang());
713
714
        $ret = array_merge_recursive($this->context, array(
715
            '@context' => array(
716
                'indexLetters' => array(
717
                    '@id' => 'skosmos:indexLetters',
718
                    '@container' => '@list',
719
                    '@language' => $request->getLang()
720
                )
721
            ),
722
            'uri' => '',
723
            'indexLetters' => $letters)
724
        );
725
        return $this->returnJson($ret);
726
    }
727
728
    /**
729
     * Query for the concepts with terms starting with a given letter in the
730
     * alphabetical index.
731
     * @param Request $request
732
     * @return object JSON-LD wrapped list of terms/concepts
733
     */
734
735
    public function indexConcepts($letter, $request)
736
    {
737
        $this->setLanguageProperties($request->getLang());
738
739
        $offset_param = $request->getQueryParam('offset');
740
        $offset = (is_numeric($offset_param) && $offset_param >= 0) ? $offset_param : 0;
741
        $limit_param = $request->getQueryParam('limit');
742
        $limit = (is_numeric($limit_param) && $limit_param >= 0) ? $limit_param : 0;
743
744
        $concepts = $request->getVocab()->searchConceptsAlphabetical($letter, $limit, $offset, $request->getLang());
745
746
        $ret = array_merge_recursive($this->context, array(
747
            '@context' => array(
748
                'indexConcepts' => array(
749
                    '@id' => 'skosmos:indexConcepts',
750
                    '@container' => '@list'
751
                )
752
            ),
753
            'uri' => '',
754
            'indexConcepts' => $concepts)
755
        );
756
        return $this->returnJson($ret);
757
    }
758
759
    private function transformPropertyResults($uri, $lang, $objects, $propname, $propuri)
760
    {
761
        $results = array();
762
        foreach ($objects as $objuri => $vals) {
763
            $results[] = array('uri' => $objuri, 'prefLabel' => $vals['label']);
764
        }
765
766
        return array_merge_recursive($this->context, array(
767
            '@context' => array('prefLabel' => 'skos:prefLabel', $propname => $propuri, '@language' => $lang),
768
            'uri' => $uri,
769
            $propname => $results)
770
        );
771
    }
772
773
    private function transformTransitivePropertyResults($uri, $lang, $objects, $tpropname, $tpropuri, $dpropname, $dpropuri)
774
    {
775
        $results = array();
776
        foreach ($objects as $objuri => $vals) {
777
            $result = array('uri' => $objuri, 'prefLabel' => $vals['label']);
778
            if (isset($vals['direct'])) {
779
                $result[$dpropname] = $vals['direct'];
780
            }
781
            $results[$objuri] = $result;
782
        }
783
784
        return array_merge_recursive($this->context, array(
785
            '@context' => array('prefLabel' => 'skos:prefLabel', $dpropname => array('@id' => $dpropuri, '@type' => '@id'), $tpropname => array('@id' => $tpropuri, '@container' => '@index'), '@language' => $lang),
786
            'uri' => $uri,
787
            $tpropname => $results)
788
        );
789
    }
790
791
    /**
792
     * Used for querying broader relations for a concept.
793
     * @param Request $request
794
     * @return object json-ld wrapped broader concept uris and labels.
795
     */
796
    public function broader($request)
797
    {
798
        $broaders = $request->getVocab()->getConceptBroaders($request->getUri(), $request->getLang());
799
        if ($broaders === null) {
800
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
801
        }
802
        $ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $broaders, "broader", "skos:broader");
803
        return $this->returnJson($ret);
804
    }
805
806
    /**
807
     * Used for querying broader transitive relations for a concept.
808
     * @param Request $request
809
     * @return object json-ld wrapped broader transitive concept uris and labels.
810
     */
811 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...
812
    {
813
        $broaders = $request->getVocab()->getConceptTransitiveBroaders($request->getUri(), $this->parseLimit(), false, $request->getLang());
814
        if (empty($broaders)) {
815
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
816
        }
817
        $ret = $this->transformTransitivePropertyResults($request->getUri(), $request->getLang(), $broaders, "broaderTransitive", "skos:broaderTransitive", "broader", "skos:broader");
818
        return $this->returnJson($ret);
819
    }
820
821
    /**
822
     * Used for querying narrower relations for a concept.
823
     * @param Request $request
824
     * @return object json-ld wrapped narrower concept uris and labels.
825
     */
826
    public function narrower($request)
827
    {
828
        $narrowers = $request->getVocab()->getConceptNarrowers($request->getUri(), $request->getLang());
829
        if ($narrowers === null) {
830
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
831
        }
832
        $ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $narrowers, "narrower", "skos:narrower");
833
        return $this->returnJson($ret);
834
    }
835
836
    /**
837
     * Used for querying narrower transitive relations for a concept.
838
     * @param Request $request
839
     * @return object json-ld wrapped narrower transitive concept uris and labels.
840
     */
841 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...
842
    {
843
        $narrowers = $request->getVocab()->getConceptTransitiveNarrowers($request->getUri(), $this->parseLimit(), $request->getLang());
844
        if (empty($narrowers)) {
845
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
846
        }
847
        $ret = $this->transformTransitivePropertyResults($request->getUri(), $request->getLang(), $narrowers, "narrowerTransitive", "skos:narrowerTransitive", "narrower", "skos:narrower");
848
        return $this->returnJson($ret);
849
    }
850
851
    /**
852
     * Used for querying broader transitive relations
853
     * and some narrowers for a concept in the hierarchy view.
854
     * @param Request $request
855
     * @return object json-ld wrapped hierarchical concept uris and labels.
856
     */
857
    public function hierarchy($request)
858
    {
859
        $results = $request->getVocab()->getConceptHierarchy($request->getUri(), $request->getLang());
860
        if (empty($results)) {
861
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
862
        }
863
864
865
        // set the "top" key from the "tops" key
866
        foreach ($results as $value) {
867
            $uri = $value['uri'];
868
            if (isset($value['tops'])) {
869
                if ($request->getVocab()->getConfig()->getMainConceptSchemeURI() != null) {
870
                    foreach ($results[$uri]['tops'] as $top) {
871
                        // if a value in 'tops' matches the main concept scheme of the vocabulary, take it
872
                        if ($top == $request->getVocab()->getConfig()->getMainConceptSchemeURI()) {
873
                            $results[$uri]['top'] = $top;
874
                            break;
875
                        }
876
                    }
877
                    // if the main concept scheme was not found, set 'top' to the first 'tops' (sorted alphabetically on the URIs)
878 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...
879
                        $results[$uri]['top'] = $results[$uri]['tops'][0];
880
                    }
881
                } else {
882
                    // no main concept scheme set on the vocab, take the first value of 'tops' (sorted alphabetically)
883
                    $results[$uri]['top'] = $results[$uri]['tops'][0];
884
                }
885
            }
886
        }
887
888
        if ($request->getVocab()->getConfig()->getShowHierarchy()) {
889
            $schemes = $request->getVocab()->getConceptSchemes($request->getLang());
890
            foreach ($schemes as $scheme) {
891
                if (!isset($scheme['title']) && !isset($scheme['label']) && !isset($scheme['prefLabel'])) {
892
                    unset($schemes[array_search($scheme, $schemes)]);
893
                }
894
895
            }
896
897
            /* encode the results in a JSON-LD compatible array */
898
            $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...
899
            foreach ($topconcepts as $top) {
900
                if (!isset($results[$top['uri']])) {
901
                    $results[$top['uri']] = array('uri' => $top['uri'], 'top'=>$top['topConceptOf'], 'tops'=>array($top['topConceptOf']), 'prefLabel' => $top['label'], 'hasChildren' => $top['hasChildren']);
902 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...
903
                        $results[$top['uri']]['notation'] = $top['notation'];
904
                    }
905
906
                }
907
            }
908
        }
909
910
        $ret = array_merge_recursive($this->context, array(
911
            '@context' => array(
912
                'onki' => 'http://schema.onki.fi/onki#',
913
                'prefLabel' => 'skos:prefLabel',
914
                'notation' => 'skos:notation',
915
                'narrower' => array('@id' => 'skos:narrower', '@type' => '@id'),
916
                'broader' => array('@id' => 'skos:broader', '@type' => '@id'),
917
                'broaderTransitive' => array('@id' => 'skos:broaderTransitive', '@container' => '@index'),
918
                'top' => array('@id' => 'skos:topConceptOf', '@type' => '@id'),
919
                // the tops key will contain all the concept scheme values, while top (singular) contains a single value
920
                'tops' => array('@id' => 'skos:topConceptOf', '@type' => '@id'),
921
                'hasChildren' => 'onki:hasChildren',
922
                '@language' => $request->getLang()
923
            ),
924
            'uri' => $request->getUri(),
925
            'broaderTransitive' => $results)
926
        );
927
928
        return $this->returnJson($ret);
929
    }
930
931
    /**
932
     * Used for querying group hierarchy for the sidebar group view.
933
     * @param Request $request
934
     * @return object json-ld wrapped hierarchical concept uris and labels.
935
     */
936
    public function groups($request)
937
    {
938
        $results = $request->getVocab()->listConceptGroups($request->getLang());
939
940
        $ret = array_merge_recursive($this->context, array(
941
            '@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()),
942
            'uri' => '',
943
            'groups' => $results)
944
        );
945
946
        return $this->returnJson($ret);
947
    }
948
949
    /**
950
     * Used for querying member relations for a group.
951
     * @param Request $request
952
     * @return object json-ld wrapped narrower concept uris and labels.
953
     */
954
    public function groupMembers($request)
955
    {
956
        $children = $request->getVocab()->listConceptGroupContents($request->getUri(), $request->getLang());
957
        if (empty($children)) {
958
            return $this->returnError('404', 'Not Found', "Could not find group <{$request->getUri()}>");
959
        }
960
961
        $ret = array_merge_recursive($this->context, array(
962
            '@context' => array('prefLabel' => 'skos:prefLabel', 'members' => 'skos:member', '@language' => $request->getLang()),
963
            'uri' => $request->getUri(),
964
            'members' => $children)
965
        );
966
967
        return $this->returnJson($ret);
968
    }
969
970
    /**
971
     * Used for querying narrower relations for a concept in the hierarchy view.
972
     * @param Request $request
973
     * @return object json-ld wrapped narrower concept uris and labels.
974
     */
975
    public function children($request)
976
    {
977
        $children = $request->getVocab()->getConceptChildren($request->getUri(), $request->getLang());
978
        if ($children === null) {
979
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
980
        }
981
982
        $ret = array_merge_recursive($this->context, array(
983
            '@context' => array('prefLabel' => 'skos:prefLabel', 'narrower' => 'skos:narrower', 'notation' => 'skos:notation', 'hasChildren' => 'onki:hasChildren', '@language' => $request->getLang()),
984
            'uri' => $request->getUri(),
985
            'narrower' => $children)
986
        );
987
988
        return $this->returnJson($ret);
989
    }
990
991
    /**
992
     * Used for querying narrower relations for a concept in the hierarchy view.
993
     * @param Request $request
994
     * @return object json-ld wrapped hierarchical concept uris and labels.
995
     */
996
    public function related($request)
997
    {
998
        $related = $request->getVocab()->getConceptRelateds($request->getUri(), $request->getLang());
999
        if ($related === null) {
1000
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
1001
        }
1002
        $ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $related, "related", "skos:related");
1003
        return $this->returnJson($ret);
1004
    }
1005
}
1006