Completed
Push — master ( 3f43c2...c76c51 )
by
unknown
27s queued 20s
created

RestController::broaderTransitive()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 12
Ratio 100 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 12
loc 12
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * RestController is responsible for handling all the requests directed to the /rest address.
5
 */
6
class RestController extends Controller
7
{
8
    /* supported MIME types that can be used to return RDF data */
9
    const SUPPORTED_FORMATS = 'application/rdf+xml text/turtle application/ld+json application/json application/marcxml+xml';
10
    /* context array template */
11
    private $context = array(
12
        '@context' => array(
13
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
14
            'uri' => '@id',
15
            'type' => '@type',
16
        ),
17
    );
18
19
    /**
20
     * Handles json encoding, adding the content type headers and optional callback function.
21
     * @param array $data the data to be returned.
22
     */
23
    private function returnJson($data)
24
    {
25
        // wrap with JSONP callback if requested
26
        if (filter_input(INPUT_GET, 'callback', FILTER_SANITIZE_STRING)) {
27
            header("Content-type: application/javascript; charset=utf-8");
28
            echo filter_input(INPUT_GET, 'callback', FILTER_UNSAFE_RAW) . "(" . json_encode($data) . ");";
29
            return;
30
        }
31
32
        // otherwise negotiate suitable format for the response and return that
33
        $negotiator = new \Negotiation\Negotiator();
34
        $priorities = array('application/json', 'application/ld+json');
35
        $best = filter_input(INPUT_SERVER, 'HTTP_ACCEPT', FILTER_SANITIZE_STRING) ? $negotiator->getBest(filter_input(INPUT_SERVER, 'HTTP_ACCEPT', FILTER_SANITIZE_STRING), $priorities) : null;
36
        $format = ($best !== null) ? $best->getValue() : $priorities[0];
37
        header("Content-type: $format; charset=utf-8");
38
        header("Vary: Accept"); // inform caches that we made a choice based on Accept header
39
        echo json_encode($data);
40
    }
41
42
    /**
43
     * Parses and returns the limit parameter. Returns and error if the parameter is missing.
44
     */
45
    private function parseLimit()
46
    {
47
        $limit = filter_input(INPUT_GET, 'limit', FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_GET, 'limit', FILTER_SANITIZE_NUMBER_INT) : $this->model->getConfig()->getDefaultTransitiveLimit();
48
        if ($limit <= 0) {
49
            return $this->returnError(400, "Bad Request", "Invalid limit parameter");
50
        }
51
52
        return $limit;
53
    }
54
55
56
/** Global REST methods **/
57
58
    /**
59
     * Returns all the vocabularies available on the server in a json object.
60
     */
61
    public function vocabularies($request)
62
    {
63
        if (!$request->getLang()) {
64
            return $this->returnError(400, "Bad Request", "lang parameter missing");
65
        }
66
67
        $this->setLanguageProperties($request->getLang());
68
69
        $vocabs = array();
70
        foreach ($this->model->getVocabularies() as $voc) {
71
            $vocabs[$voc->getId()] = $voc->getConfig()->getTitle($request->getLang());
72
        }
73
        ksort($vocabs);
74
        $results = array();
75
        foreach ($vocabs as $id => $title) {
76
            $results[] = array(
77
                'uri' => $id,
78
                'id' => $id,
79
                'title' => $title);
80
        }
81
82
        /* encode the results in a JSON-LD compatible array */
83
        $ret = array(
84
            '@context' => array(
85
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
86
                'onki' => 'http://schema.onki.fi/onki#',
87
                'title' => array('@id' => 'rdfs:label', '@language' => $request->getLang()),
88
                'vocabularies' => 'onki:hasVocabulary',
89
                'id' => 'onki:vocabularyIdentifier',
90
                'uri' => '@id',
91
                '@base' => $this->getBaseHref() . "rest/v1/vocabularies",
92
            ),
93
            'uri' => '',
94
            'vocabularies' => $results,
95
        );
96
97
        return $this->returnJson($ret);
98
    }
99
100
    private function constructSearchParameters($request)
101
    {
102
        $parameters = new ConceptSearchParameters($request, $this->model->getConfig(), true);
103
104
        $vocabs = $request->getQueryParam('vocab'); # optional
105
        // convert to vocids array to support multi-vocabulary search
106
        $vocids = ($vocabs !== null && $vocabs !== '') ? explode(' ', $vocabs) : array();
107
        $vocabObjects = array();
108
        foreach($vocids as $vocid) {
109
            $vocabObjects[] = $this->model->getVocabulary($vocid);
110
        }
111
        $parameters->setVocabularies($vocabObjects);
112
        return $parameters;
113
    }
114
115
    private function transformSearchResults($request, $results, $parameters)
116
    {
117
        // before serializing to JSON, get rid of the Vocabulary object that came with each resource
118
        foreach ($results as &$res) {
119
            unset($res['voc']);
120
        }
121
122
        $context = array(
123
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
124
            'isothes' => 'http://purl.org/iso25964/skos-thes#',
125
            'onki' => 'http://schema.onki.fi/onki#',
126
            'uri' => '@id',
127
            'type' => '@type',
128
            'results' => array(
129
                '@id' => 'onki:results',
130
                '@container' => '@list',
131
            ),
132
            'prefLabel' => 'skos:prefLabel',
133
            'altLabel' => 'skos:altLabel',
134
            'hiddenLabel' => 'skos:hiddenLabel',
135
        );
136
        foreach ($parameters->getAdditionalFields() as $field) {
137
138
            // Quick-and-dirty compactification
139
            $context[$field] = 'skos:' . $field;
140
            foreach ($results as &$result) {
141
                foreach ($result as $k => $v) {
142
                    if ($k == 'skos:' . $field) {
143
                        $result[$field] = $v;
144
                        unset($result['skos:' . $field]);
145
                    }
146
                }
147
            }
148
        }
149
150
        $ret = array(
151
            '@context' => $context,
152
            'uri' => '',
153
            'results' => $results,
154
        );
155
156
        if (isset($results[0]['prefLabels'])) {
157
            $ret['@context']['prefLabels'] = array('@id' => 'skos:prefLabel', '@container' => '@language');
158
        }
159
160
        if ($request->getQueryParam('labellang')) {
161
            $ret['@context']['@language'] = $request->getQueryParam('labellang');
162
        } elseif ($request->getQueryParam('lang')) {
163
            $ret['@context']['@language'] = $request->getQueryParam('lang');;
164
        }
165
        return $ret;
166
    }
167
168
    /**
169
     * Performs the search function calls. And wraps the result in a json-ld object.
170
     * @param Request $request
171
     */
172
    public function search($request)
173
    {
174
        $maxhits = $request->getQueryParam('maxhits');
175
        $offset = $request->getQueryParam('offset');
176
        $term = $request->getQueryParamRaw('query');
177
178
        if (!$term && (!$request->getQueryParam('group') && !$request->getQueryParam('parent'))) {
179
            return $this->returnError(400, "Bad Request", "query parameter missing");
180
        }
181
        if ($maxhits && (!is_numeric($maxhits) || $maxhits <= 0)) {
182
            return $this->returnError(400, "Bad Request", "maxhits parameter is invalid");
183
        }
184
        if ($offset && (!is_numeric($offset) || $offset < 0)) {
185
            return $this->returnError(400, "Bad Request", "offset parameter is invalid");
186
        }
187
188
        $parameters = $this->constructSearchParameters($request);
189
        $results = $this->model->searchConcepts($parameters);
190
        $ret = $this->transformSearchResults($request, $results, $parameters);
191
192
        return $this->returnJson($ret);
193
    }
194
195
/** Vocabulary-specific methods **/
196
197
    /**
198
     * Loads the vocabulary metadata. And wraps the result in a json-ld object.
199
     * @param Request $request
200
     */
201
    public function vocabularyInformation($request)
202
    {
203
        $vocab = $request->getVocab();
204
        if ($this->notModified($vocab)) {
205
            return null;
206
        }
207
208
        /* encode the results in a JSON-LD compatible array */
209
        $conceptschemes = array();
210
        foreach ($vocab->getConceptSchemes($request->getLang()) as $uri => $csdata) {
211
            $csdata['uri'] = $uri;
212
            $csdata['type'] = 'skos:ConceptScheme';
213
            $conceptschemes[] = $csdata;
214
        }
215
216
        $ret = array(
217
            '@context' => array(
218
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
219
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
220
                'onki' => 'http://schema.onki.fi/onki#',
221
                'dct' => 'http://purl.org/dc/terms/',
222
                'uri' => '@id',
223
                'type' => '@type',
224
                'conceptschemes' => 'onki:hasConceptScheme',
225
                'id' => 'onki:vocabularyIdentifier',
226
                'defaultLanguage' => 'onki:defaultLanguage',
227
                'languages' => 'onki:language',
228
                'label' => 'rdfs:label',
229
                'prefLabel' => 'skos:prefLabel',
230
                'title' => 'dct:title',
231
                '@language' => $request->getLang(),
232
                '@base' => $this->getBaseHref() . "rest/v1/" . $vocab->getId() . "/",
233
            ),
234
            'uri' => '',
235
            'id' => $vocab->getId(),
236
            'marcSource' => $vocab->getConfig()->getMarcSourceCode($request->getLang()),
237
            'title' => $vocab->getConfig()->getTitle($request->getLang()),
238
            'defaultLanguage' => $vocab->getConfig()->getDefaultLanguage(),
239
            'languages' => array_values($vocab->getConfig()->getLanguages()),
240
            'conceptschemes' => $conceptschemes,
241
        );
242
243
        if ($vocab->getConfig()->getTypes($request->getLang())) {
244
            $ret['type'] = $vocab->getConfig()->getTypes($request->getLang());
245
        }
246
247
        return $this->returnJson($ret);
248
    }
249
250
    /**
251
     * Loads the vocabulary metadata. And wraps the result in a json-ld object.
252
     * @param Request $request
253
     */
254
    public function vocabularyStatistics($request)
255
    {
256
        if ($this->notModified($request->getVocab())) {
257
            return null;
258
        }
259
        $this->setLanguageProperties($request->getLang());
260
        $arrayClass = $request->getVocab()->getConfig()->getArrayClassURI();
261
        $groupClass = $request->getVocab()->getConfig()->getGroupClassURI();
262
        $vocabStats = $request->getVocab()->getStatistics($request->getQueryParam('lang'), $arrayClass, $groupClass);
263
        $types = array('http://www.w3.org/2004/02/skos/core#Concept', 'http://www.w3.org/2004/02/skos/core#Collection', $arrayClass, $groupClass);
264
        $subTypes = array();
265
        foreach ($vocabStats as $subtype) {
266
            if (!in_array($subtype['type'], $types)) {
267
                $subTypes[] = $subtype;
268
            }
269
        }
270
271
        /* encode the results in a JSON-LD compatible array */
272
        $ret = array(
273
            '@context' => array(
274
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
275
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
276
                'void' => 'http://rdfs.org/ns/void#',
277
                'onki' => 'http://schema.onki.fi/onki#',
278
                'uri' => '@id',
279
                'id' => 'onki:vocabularyIdentifier',
280
                'concepts' => 'void:classPartition',
281
                'label' => 'rdfs:label',
282
                'class' => array('@id' => 'void:class', '@type' => '@id'),
283
                'subTypes' => array('@id' => 'void:class', '@type' => '@id'),
284
                'count' => 'void:entities',
285
                '@language' => $request->getLang(),
286
                '@base' => $this->getBaseHref() . "rest/v1/" . $request->getVocab()->getId() . "/",
287
            ),
288
            'uri' => '',
289
            'id' => $request->getVocab()->getId(),
290
            'title' => $request->getVocab()->getConfig()->getTitle(),
291
            'concepts' => array(
292
                'class' => 'http://www.w3.org/2004/02/skos/core#Concept',
293
                'label' => gettext('skos:Concept'),
294
                'count' => $vocabStats['http://www.w3.org/2004/02/skos/core#Concept']['count'],
295
            ),
296
            'subTypes' => $subTypes,
297
        );
298
299
        if (isset($vocabStats['http://www.w3.org/2004/02/skos/core#Collection'])) {
300
            $ret['conceptGroups'] = array(
301
                'class' => 'http://www.w3.org/2004/02/skos/core#Collection',
302
                'label' => gettext('skos:Collection'),
303
                'count' => $vocabStats['http://www.w3.org/2004/02/skos/core#Collection']['count'],
304
            );
305
        } else if (isset($vocabStats[$groupClass])) {
306
            $ret['conceptGroups'] = array(
307
                'class' => $groupClass,
308
                'label' => isset($vocabStats[$groupClass]['label']) ? $vocabStats[$groupClass]['label'] : gettext(EasyRdf\RdfNamespace::shorten($groupClass)),
309
                'count' => $vocabStats[$groupClass]['count'],
310
            );
311
        } else if (isset($vocabStats[$arrayClass])) {
312
            $ret['arrays'] = array(
313
                'class' => $arrayClass,
314
                'label' => isset($vocabStats[$arrayClass]['label']) ? $vocabStats[$arrayClass]['label'] : gettext(EasyRdf\RdfNamespace::shorten($arrayClass)),
315
                'count' => $vocabStats[$arrayClass]['count'],
316
            );
317
        }
318
319
        return $this->returnJson($ret);
320
    }
321
322
    /**
323
     * Loads the vocabulary metadata. And wraps the result in a json-ld object.
324
     * @param Request $request
325
     */
326
    public function labelStatistics($request)
327
    {
328
        if ($this->notModified($request->getVocab())) {
329
            return null;
330
        }
331
        $lang = $request->getLang();
332
        $this->setLanguageProperties($request->getLang());
333
        $vocabStats = $request->getVocab()->getLabelStatistics();
334
335
        /* encode the results in a JSON-LD compatible array */
336
        $counts = array();
337
        foreach ($vocabStats['terms'] as $proplang => $properties) {
338
            $langdata = array('language' => $proplang);
339
            if ($lang) {
340
                $langdata['literal'] = Punic\Language::getName($proplang, $lang);
341
            }
342
343
            $langdata['properties'] = array();
344
            foreach ($properties as $prop => $value) {
345
                $langdata['properties'][] = array('property' => $prop, 'labels' => $value);
346
            }
347
            $counts[] = $langdata;
348
        }
349
350
        $ret = array(
351
            '@context' => array(
352
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
353
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
354
                'void' => 'http://rdfs.org/ns/void#',
355
                'void-ext' => 'http://ldf.fi/void-ext#',
356
                'onki' => 'http://schema.onki.fi/onki#',
357
                'uri' => '@id',
358
                'id' => 'onki:vocabularyIdentifier',
359
                'languages' => 'void-ext:languagePartition',
360
                'language' => 'void-ext:language',
361
                'properties' => 'void:propertyPartition',
362
                'labels' => 'void:triples',
363
                '@base' => $this->getBaseHref() . "rest/v1/" . $request->getVocab()->getId() . "/",
364
            ),
365
            'uri' => '',
366
            'id' => $request->getVocab()->getId(),
367
            'title' => $request->getVocab()->getConfig()->getTitle($lang),
368
            'languages' => $counts,
369
        );
370
371
        if ($lang) {
372
            $ret['@context']['literal'] = array('@id' => 'rdfs:label', '@language' => $lang);
373
        }
374
375
        return $this->returnJson($ret);
376
    }
377
378
    /**
379
     * Loads the vocabulary type metadata. And wraps the result in a json-ld object.
380
     * @param Request $request
381
     */
382
    public function types($request)
383
    {
384
        $vocid = $request->getVocab() ? $request->getVocab()->getId() : null;
385
        if ($vocid === null && !$request->getLang()) {
386
            return $this->returnError(400, "Bad Request", "lang parameter missing");
387
        }
388
        if ($this->notModified($request->getVocab())) {
389
            return null;
390
        }
391
392
        $this->setLanguageProperties($request->getLang());
393
394
        $queriedtypes = $this->model->getTypes($vocid, $request->getLang());
395
396
        $types = array();
397
398
        /* encode the results in a JSON-LD compatible array */
399
        foreach ($queriedtypes as $uri => $typedata) {
400
            $type = array_merge(array('uri' => $uri), $typedata);
401
            $types[] = $type;
402
        }
403
404
        $base = $request->getVocab() ? $this->getBaseHref() . "rest/v1/" . $request->getVocab()->getId() . "/" : $this->getBaseHref() . "rest/v1/";
405
406
        $ret = array_merge_recursive($this->context, array(
407
            '@context' => array(
408
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
409
                'onki' => 'http://schema.onki.fi/onki#',
410
                'label' => 'rdfs:label',
411
                'superclass' => array('@id' => 'rdfs:subClassOf', '@type' => '@id'),
412
                'types' => 'onki:hasType',
413
                '@language' => $request->getLang(),
414
                '@base' => $base,
415
            ),
416
            'uri' => '',
417
            'types' => $types)
418
        );
419
420
        return $this->returnJson($ret);
421
    }
422
423
    private function findLookupHits($results, $label, $lang)
424
    {
425
        $hits = array();
426
        // case 1: exact match on preferred label
427
        foreach ($results as $res) {
428
            if ($res['prefLabel'] == $label) {
429
                $hits[] = $res;
430
            }
431
        }
432
        if (sizeof($hits) > 0) return $hits;
433
434
        // case 2: case-insensitive match on preferred label
435
        foreach ($results as $res) {
436
            if (strtolower($res['prefLabel']) == strtolower($label)) {
437
                $hits[] = $res;
438
            }
439
        }
440
        if (sizeof($hits) > 0) return $hits;
441
442
        if ($lang === null) {
443
            // case 1A: exact match on preferred label in any language
444 View Code Duplication
            foreach ($results as $res) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
445
                if ($res['matchedPrefLabel'] == $label) {
446
                    $res['prefLabel'] = $res['matchedPrefLabel'];
447
                    unset($res['matchedPrefLabel']);
448
                    $hits[] = $res;
449
                }
450
            }
451
            if (sizeof($hits) > 0) return $hits;
452
453
            // case 2A: case-insensitive match on preferred label in any language
454 View Code Duplication
            foreach ($results as $res) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
455
                if (strtolower($res['matchedPrefLabel']) == strtolower($label)) {
456
                    $res['prefLabel'] = $res['matchedPrefLabel'];
457
                    unset($res['matchedPrefLabel']);
458
                    $hits[] = $res;
459
                }
460
            }
461
            if (sizeof($hits) > 0) return $hits;
462
        }
463
464
        // case 3: exact match on alternate label
465 View Code Duplication
        foreach ($results as $res) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
466
            if (isset($res['altLabel']) && $res['altLabel'] == $label) {
467
                $hits[] = $res;
468
            }
469
        }
470
        if (sizeof($hits) > 0) return $hits;
471
472
473
        // case 4: case-insensitive match on alternate label
474 View Code Duplication
        foreach ($results as $res) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
475
            if (isset($res['altLabel']) && strtolower($res['altLabel']) == strtolower($label)) {
476
                $hits[] = $res;
477
            }
478
        }
479
        if (sizeof($hits) > 0) return $hits;
480
481
        return $hits;
482
    }
483
484
    private function transformLookupResults($lang, $hits)
485
    {
486
        if (sizeof($hits) == 0) {
487
            // no matches found
488
            return;
489
        }
490
491
        // found matches, getting rid of Vocabulary objects
492
        foreach ($hits as &$res) {
493
            unset($res['voc']);
494
        }
495
496
        $ret = array_merge_recursive($this->context, array(
497
            '@context' => array('onki' => 'http://schema.onki.fi/onki#', 'results' => array('@id' => 'onki:results'), 'prefLabel' => 'skos:prefLabel', 'altLabel' => 'skos:altLabel', 'hiddenLabel' => 'skos:hiddenLabel'),
498
            'result' => $hits)
499
        );
500
501
        if ($lang) {
502
            $ret['@context']['@language'] = $lang;
503
        }
504
505
        return $ret;
506
    }
507
508
    /**
509
     * Used for finding terms by their exact prefLabel. Wraps the result in a json-ld object.
510
     * @param Request $request
511
     */
512
    public function lookup($request)
513
    {
514
        $label = $request->getQueryParamRaw('label');
515
        if (!$label) {
516
            return $this->returnError(400, "Bad Request", "label parameter missing");
517
        }
518
519
        $lang = $request->getQueryParam('lang');
520
        $parameters = new ConceptSearchParameters($request, $this->model->getConfig(), true);
521
        $results = $this->model->searchConcepts($parameters);
522
        $hits = $this->findLookupHits($results, $label, $lang);
523
        $ret = $this->transformLookupResults($lang, $hits);
524
        if ($ret === null) {
525
            return $this->returnError(404, 'Not Found', "Could not find label '$label'");
526
        }
527
        return $this->returnJson($ret);
528
    }
529
530
    /**
531
     * Queries the top concepts of a vocabulary and wraps the results in a json-ld object.
532
     * @param Request $request
533
     * @return object json-ld object
534
     */
535
    public function topConcepts($request)
536
    {
537
        $vocab = $request->getVocab();
538
        if ($this->notModified($vocab)) {
539
            return null;
540
        }
541
        $scheme = $request->getQueryParam('scheme');
542
        if (!$scheme) {
543
            $scheme = $vocab->getConfig()->showConceptSchemesInHierarchy() ? array_keys($vocab->getConceptSchemes()) : $vocab->getDefaultConceptScheme();
544
        }
545
546
        /* encode the results in a JSON-LD compatible array */
547
        $topconcepts = $vocab->getTopConcepts($scheme, $request->getLang());
548
549
        $ret = array_merge_recursive($this->context, array(
550
            '@context' => array('onki' => 'http://schema.onki.fi/onki#', 'topconcepts' => 'skos:hasTopConcept', 'notation' => 'skos:notation', 'label' => 'skos:prefLabel', '@language' => $request->getLang()),
551
            'uri' => $scheme,
552
            'topconcepts' => $topconcepts)
553
        );
554
555
        return $this->returnJson($ret);
556
    }
557
558
    private function redirectToVocabData($request) {
559
        $urls = $request->getVocab()->getConfig()->getDataURLs();
560
        if (sizeof($urls) == 0) {
561
            $vocid = $request->getVocab()->getId();
562
            return $this->returnError('404', 'Not Found', "No download source URL known for vocabulary $vocid");
563
        }
564
565
        $format = $this->negotiateFormat(array_keys($urls), $request->getServerConstant('HTTP_ACCEPT'), $request->getQueryParam('format'));
566
        if (!$format) {
567
            return $this->returnError(406, 'Not Acceptable', "Unsupported format. Supported MIME types are: " . implode(' ', array_keys($urls)));
568
        }
569
        if (is_array($urls[$format])) {
570
            $arr = $urls[$format];
571
            $dataLang = $request->getLang();
572
            if (isset($arr[$dataLang])) {
573
                header("Location: " . $arr[$dataLang]);
574
            } else {
575
                $vocid = $request->getVocab()->getId();
576
                return $this->returnError('404', 'Not Found', "No download source URL known for vocabulary $vocid in language $dataLang");
577
            }
578
		} else {
579
            header("Location: " . $urls[$format]);
580
		}
581
    }
582
583
    private function returnDataResults($results, $format) {
584
        if ($format == 'application/ld+json' || $format == 'application/json') {
585
            // further compact JSON-LD document using a context
586
            $context = array(
587
                'skos' => 'http://www.w3.org/2004/02/skos/core#',
588
                'isothes' => 'http://purl.org/iso25964/skos-thes#',
589
                'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
590
                'owl' => 'http://www.w3.org/2002/07/owl#',
591
                'dct' => 'http://purl.org/dc/terms/',
592
                'dc11' => 'http://purl.org/dc/elements/1.1/',
593
                'uri' => '@id',
594
                'type' => '@type',
595
                'lang' => '@language',
596
                'value' => '@value',
597
                'graph' => '@graph',
598
                'label' => 'rdfs:label',
599
                'prefLabel' => 'skos:prefLabel',
600
                'altLabel' => 'skos:altLabel',
601
                'hiddenLabel' => 'skos:hiddenLabel',
602
                'broader' => 'skos:broader',
603
                'narrower' => 'skos:narrower',
604
                'related' => 'skos:related',
605
                'inScheme' => 'skos:inScheme',
606
                'exactMatch' => 'skos:exactMatch',
607
                'closeMatch' => 'skos:closeMatch',
608
                'broadMatch' => 'skos:broadMatch',
609
                'narrowMatch' => 'skos:narrowMatch',
610
                'relatedMatch' => 'skos:relatedMatch',
611
            );
612
            $compactJsonLD = \ML\JsonLD\JsonLD::compact($results, json_encode($context));
613
            $results = \ML\JsonLD\JsonLD::toString($compactJsonLD);
614
        }
615
616
        header("Content-type: $format; charset=utf-8");
617
        echo $results;
618
    }
619
620
    /**
621
     * Download a concept as json-ld or redirect to download the whole vocabulary.
622
     * @param Request $request
623
     * @return object json-ld formatted concept.
624
     */
625
    public function data($request)
626
    {
627
        $vocab = $request->getVocab();
628
        if ($this->notModified($request->getVocab())) {
629
            return null;
630
        }
631
632
        if ($request->getUri()) {
633
            $uri = $request->getUri();
634
        } else if ($vocab !== null) { // whole vocabulary - redirect to download URL
635
            return $this->redirectToVocabData($request);
636
        } else {
637
            return $this->returnError(400, 'Bad Request', "uri parameter missing");
638
        }
639
640
        $format = $this->negotiateFormat(explode(' ', self::SUPPORTED_FORMATS), $request->getServerConstant('HTTP_ACCEPT'), $request->getQueryParam('format'));
641
        if (!$format) {
642
            return $this->returnError(406, 'Not Acceptable', "Unsupported format. Supported MIME types are: " . self::SUPPORTED_FORMATS);
643
        }
644
645
        $vocid = $vocab ? $vocab->getId() : null;
646
        $results = $this->model->getRDF($vocid, $uri, $format);
647
        if (empty($results)) {
648
            return $this->returnError(404, 'Bad Request', "no concept found with given uri");
649
        }
650
        return $this->returnDataResults($results, $format);
651
    }
652
653
    /**
654
     * Get the mappings associated with a concept, enriched with labels and notations.
655
     * Returns a JSKOS-compatible JSON object.
656
     * @param Request $request
657
     * @throws Exception if the vocabulary ID is not found in configuration
658
     */
659
    public function mappings(Request $request)
660
    {
661
        $this->setLanguageProperties($request->getLang());
662
        $vocab = $request->getVocab();
663
        if ($this->notModified($vocab)) {
664
            return null;
665
        }
666
667
        $uri = $request->getUri();
668
        if (!$uri) {
669
            return $this->returnError(400, 'Bad Request', "uri parameter missing");
670
        }
671
672
        $queryExVocabs = $request->getQueryParamBoolean('external', true);
673
674
        $results = $vocab->getConceptInfo($uri, $request->getContentLang());
675
        if (empty($results)) {
676
            return $this->returnError(404, 'Bad Request', "no concept found with given uri");
677
        }
678
679
        $concept = $results[0];
680
681
        $mappings = [];
682
        foreach ($concept->getMappingProperties() as $mappingProperty) {
683
            foreach ($mappingProperty->getValues() as $mappingPropertyValue) {
684
                $hrefLink = $this->linkUrlFilter($mappingPropertyValue->getUri(), $mappingPropertyValue->getExVocab(), $request->getLang(), 'page', $request->getContentLang());
0 ignored issues
show
Bug introduced by
It seems like $mappingPropertyValue->getExVocab() can be null; however, linkUrlFilter() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
685
                $mappings[] = $mappingPropertyValue->asJskos($queryExVocabs, $request->getLang(), $hrefLink);
686
            }
687
        }
688
689
        $ret = array(
690
            'mappings' => $mappings,
691
            'graph' => $concept->dumpJsonLd()
692
        );
693
694
        return $this->returnJson($ret);
695
    }
696
697
    /**
698
     * Used for querying labels for a uri.
699
     * @param Request $request
700
     * @return object json-ld wrapped labels.
701
     */
702
    public function label($request)
703
    {
704
        if (!$request->getUri()) {
705
            return $this->returnError(400, "Bad Request", "uri parameter missing");
706
        }
707
708
        if ($this->notModified($request->getVocab())) {
709
            return null;
710
        }
711
712
        $vocab = $request->getVocab();
713
        if ($vocab === null) {
714
            $vocab = $this->model->guessVocabularyFromUri($request->getUri());
715
        }
716
        if ($vocab === null) {
717
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
718
        }
719
720
        $labelResults = $vocab->getAllConceptLabels($request->getUri(), $request->getLang());
721
        if ($labelResults === null) {
722
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
723
        }
724
725
        // there should be only one preferred label so no need for an array
726
        if (array_key_exists('prefLabel', $labelResults)) {
727
            $labelResults['prefLabel'] = $labelResults['prefLabel'][0];
728
        }
729
730
        $ret = array_merge_recursive($this->context,
731
                                    array('@context' => array('prefLabel' => 'skos:prefLabel', 'altLabel' => 'skos:altLabel', 'hiddenLabel' => 'skos:hiddenLabel', '@language' => $request->getLang()),
732
                                    'uri' => $request->getUri()),
733
                                    $labelResults);
734
735
        return $this->returnJson($ret);
736
    }
737
738
    /**
739
     * Query for the available letters in the alphabetical index.
740
     * @param Request $request
741
     * @return object JSON-LD wrapped list of letters
742
     */
743
744
    public function indexLetters($request)
745
    {
746
        $this->setLanguageProperties($request->getLang());
747
        $letters = $request->getVocab()->getAlphabet($request->getLang());
748
749
        $ret = array_merge_recursive($this->context, array(
750
            '@context' => array(
751
                'indexLetters' => array(
752
                    '@id' => 'skosmos:indexLetters',
753
                    '@container' => '@list',
754
                    '@language' => $request->getLang()
755
                )
756
            ),
757
            'uri' => '',
758
            'indexLetters' => $letters)
759
        );
760
        return $this->returnJson($ret);
761
    }
762
763
    /**
764
     * Query for the concepts with terms starting with a given letter in the
765
     * alphabetical index.
766
     * @param Request $request
767
     * @return object JSON-LD wrapped list of terms/concepts
768
     */
769
770
    public function indexConcepts($letter, $request)
771
    {
772
        $this->setLanguageProperties($request->getLang());
773
774
        $offset_param = $request->getQueryParam('offset');
775
        $offset = (is_numeric($offset_param) && $offset_param >= 0) ? $offset_param : 0;
776
        $limit_param = $request->getQueryParam('limit');
777
        $limit = (is_numeric($limit_param) && $limit_param >= 0) ? $limit_param : 0;
778
779
        $concepts = $request->getVocab()->searchConceptsAlphabetical($letter, $limit, $offset, $request->getLang());
780
781
        $ret = array_merge_recursive($this->context, array(
782
            '@context' => array(
783
                'indexConcepts' => array(
784
                    '@id' => 'skosmos:indexConcepts',
785
                    '@container' => '@list'
786
                )
787
            ),
788
            'uri' => '',
789
            'indexConcepts' => $concepts)
790
        );
791
        return $this->returnJson($ret);
792
    }
793
794
    private function transformPropertyResults($uri, $lang, $objects, $propname, $propuri)
795
    {
796
        $results = array();
797
        foreach ($objects as $objuri => $vals) {
798
            $results[] = array('uri' => $objuri, 'prefLabel' => $vals['label']);
799
        }
800
801
        return array_merge_recursive($this->context, array(
802
            '@context' => array('prefLabel' => 'skos:prefLabel', $propname => $propuri, '@language' => $lang),
803
            'uri' => $uri,
804
            $propname => $results)
805
        );
806
    }
807
808
    private function transformTransitivePropertyResults($uri, $lang, $objects, $tpropname, $tpropuri, $dpropname, $dpropuri)
809
    {
810
        $results = array();
811
        foreach ($objects as $objuri => $vals) {
812
            $result = array('uri' => $objuri, 'prefLabel' => $vals['label']);
813
            if (isset($vals['direct'])) {
814
                $result[$dpropname] = $vals['direct'];
815
            }
816
            $results[$objuri] = $result;
817
        }
818
819
        return array_merge_recursive($this->context, array(
820
            '@context' => array('prefLabel' => 'skos:prefLabel', $dpropname => array('@id' => $dpropuri, '@type' => '@id'), $tpropname => array('@id' => $tpropuri, '@container' => '@index'), '@language' => $lang),
821
            'uri' => $uri,
822
            $tpropname => $results)
823
        );
824
    }
825
826
    /**
827
     * Used for querying broader relations for a concept.
828
     * @param Request $request
829
     * @return object json-ld wrapped broader concept uris and labels.
830
     */
831
    public function broader($request)
832
    {
833
        if ($this->notModified($request->getVocab())) {
834
            return null;
835
        }
836
        $broaders = $request->getVocab()->getConceptBroaders($request->getUri(), $request->getLang());
837
        if ($broaders === null) {
838
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
839
        }
840
        $ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $broaders, "broader", "skos:broader");
841
        return $this->returnJson($ret);
842
    }
843
844
    /**
845
     * Used for querying broader transitive relations for a concept.
846
     * @param Request $request
847
     * @return object json-ld wrapped broader transitive concept uris and labels.
848
     */
849 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...
850
    {
851
        if ($this->notModified($request->getVocab())) {
852
            return null;
853
        }
854
        $broaders = $request->getVocab()->getConceptTransitiveBroaders($request->getUri(), $this->parseLimit(), false, $request->getLang());
855
        if (empty($broaders)) {
856
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
857
        }
858
        $ret = $this->transformTransitivePropertyResults($request->getUri(), $request->getLang(), $broaders, "broaderTransitive", "skos:broaderTransitive", "broader", "skos:broader");
859
        return $this->returnJson($ret);
860
    }
861
862
    /**
863
     * Used for querying narrower relations for a concept.
864
     * @param Request $request
865
     * @return object json-ld wrapped narrower concept uris and labels.
866
     */
867
    public function narrower($request)
868
    {
869
        if ($this->notModified($request->getVocab())) {
870
            return null;
871
        }
872
        $narrowers = $request->getVocab()->getConceptNarrowers($request->getUri(), $request->getLang());
873
        if ($narrowers === null) {
874
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
875
        }
876
        $ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $narrowers, "narrower", "skos:narrower");
877
        return $this->returnJson($ret);
878
    }
879
880
    /**
881
     * Used for querying narrower transitive relations for a concept.
882
     * @param Request $request
883
     * @return object json-ld wrapped narrower transitive concept uris and labels.
884
     */
885 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...
886
    {
887
        if ($this->notModified($request->getVocab())) {
888
            return null;
889
        }
890
        $narrowers = $request->getVocab()->getConceptTransitiveNarrowers($request->getUri(), $this->parseLimit(), $request->getLang());
891
        if (empty($narrowers)) {
892
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
893
        }
894
        $ret = $this->transformTransitivePropertyResults($request->getUri(), $request->getLang(), $narrowers, "narrowerTransitive", "skos:narrowerTransitive", "narrower", "skos:narrower");
895
        return $this->returnJson($ret);
896
    }
897
898
    /**
899
     * Used for querying broader transitive relations
900
     * and some narrowers for a concept in the hierarchy view.
901
     * @param Request $request
902
     * @return object json-ld wrapped hierarchical concept uris and labels.
903
     */
904
    public function hierarchy($request)
905
    {
906
        if ($this->notModified($request->getVocab())) {
907
            return null;
908
        }
909
        $results = $request->getVocab()->getConceptHierarchy($request->getUri(), $request->getLang());
910
        if (empty($results)) {
911
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
912
        }
913
914
        // set the "top" key from the "tops" key
915
        foreach ($results as $value) {
916
            $uri = $value['uri'];
917
            if (isset($value['tops'])) {
918
                if ($request->getVocab()->getConfig()->getMainConceptSchemeURI() != null) {
919
                    foreach ($results[$uri]['tops'] as $top) {
920
                        // if a value in 'tops' matches the main concept scheme of the vocabulary, take it
921
                        if ($top == $request->getVocab()->getConfig()->getMainConceptSchemeURI()) {
922
                            $results[$uri]['top'] = $top;
923
                            break;
924
                        }
925
                    }
926
                    // if the main concept scheme was not found, set 'top' to the first 'tops' (sorted alphabetically on the URIs)
927 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...
928
                        $results[$uri]['top'] = $results[$uri]['tops'][0];
929
                    }
930
                } else {
931
                    // no main concept scheme set on the vocab, take the first value of 'tops' (sorted alphabetically)
932
                    $results[$uri]['top'] = $results[$uri]['tops'][0];
933
                }
934
            }
935
        }
936
937
        if ($request->getVocab()->getConfig()->getShowHierarchy()) {
938
            $schemes = $request->getVocab()->getConceptSchemes($request->getLang());
939
            foreach ($schemes as $scheme) {
940
                if (!isset($scheme['title']) && !isset($scheme['label']) && !isset($scheme['prefLabel'])) {
941
                    unset($schemes[array_search($scheme, $schemes)]);
942
                }
943
944
            }
945
946
            /* encode the results in a JSON-LD compatible array */
947
            $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...
948
            foreach ($topconcepts as $top) {
949
                if (!isset($results[$top['uri']])) {
950
                    $results[$top['uri']] = array('uri' => $top['uri'], 'top'=>$top['topConceptOf'], 'tops'=>array($top['topConceptOf']), 'prefLabel' => $top['label'], 'hasChildren' => $top['hasChildren']);
951 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...
952
                        $results[$top['uri']]['notation'] = $top['notation'];
953
                    }
954
955
                }
956
            }
957
        }
958
959
        $ret = array_merge_recursive($this->context, array(
960
            '@context' => array(
961
                'onki' => 'http://schema.onki.fi/onki#',
962
                'prefLabel' => 'skos:prefLabel',
963
                'notation' => 'skos:notation',
964
                'narrower' => array('@id' => 'skos:narrower', '@type' => '@id'),
965
                'broader' => array('@id' => 'skos:broader', '@type' => '@id'),
966
                'broaderTransitive' => array('@id' => 'skos:broaderTransitive', '@container' => '@index'),
967
                'top' => array('@id' => 'skos:topConceptOf', '@type' => '@id'),
968
                // the tops key will contain all the concept scheme values, while top (singular) contains a single value
969
                'tops' => array('@id' => 'skos:topConceptOf', '@type' => '@id'),
970
                'hasChildren' => 'onki:hasChildren',
971
                '@language' => $request->getLang()
972
            ),
973
            'uri' => $request->getUri(),
974
            'broaderTransitive' => $results)
975
        );
976
977
        return $this->returnJson($ret);
978
    }
979
980
    /**
981
     * Used for querying group hierarchy for the sidebar group view.
982
     * @param Request $request
983
     * @return object json-ld wrapped hierarchical concept uris and labels.
984
     */
985
    public function groups($request)
986
    {
987
        if ($this->notModified($request->getVocab())) {
988
            return null;
989
        }
990
        $results = $request->getVocab()->listConceptGroups($request->getLang());
991
992
        $ret = array_merge_recursive($this->context, array(
993
            '@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()),
994
            'uri' => '',
995
            'groups' => $results)
996
        );
997
998
        return $this->returnJson($ret);
999
    }
1000
1001
    /**
1002
     * Used for querying member relations for a group.
1003
     * @param Request $request
1004
     * @return object json-ld wrapped narrower concept uris and labels.
1005
     */
1006
    public function groupMembers($request)
1007
    {
1008
        if ($this->notModified($request->getVocab())) {
1009
            return null;
1010
        }
1011
        $children = $request->getVocab()->listConceptGroupContents($request->getUri(), $request->getLang());
1012
        if (empty($children)) {
1013
            return $this->returnError('404', 'Not Found', "Could not find group <{$request->getUri()}>");
1014
        }
1015
1016
        $ret = array_merge_recursive($this->context, array(
1017
            '@context' => array('prefLabel' => 'skos:prefLabel', 'members' => 'skos:member', '@language' => $request->getLang()),
1018
            'uri' => $request->getUri(),
1019
            'members' => $children)
1020
        );
1021
1022
        return $this->returnJson($ret);
1023
    }
1024
1025
    /**
1026
     * Used for querying narrower relations for a concept in the hierarchy view.
1027
     * @param Request $request
1028
     * @return object json-ld wrapped narrower concept uris and labels.
1029
     */
1030
    public function children($request)
1031
    {
1032
        if ($this->notModified($request->getVocab())) {
1033
            return null;
1034
        }
1035
        $children = $request->getVocab()->getConceptChildren($request->getUri(), $request->getLang());
1036
        if ($children === null) {
1037
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
1038
        }
1039
1040
        $ret = array_merge_recursive($this->context, array(
1041
            '@context' => array('prefLabel' => 'skos:prefLabel', 'narrower' => 'skos:narrower', 'notation' => 'skos:notation', 'hasChildren' => 'onki:hasChildren', '@language' => $request->getLang()),
1042
            'uri' => $request->getUri(),
1043
            'narrower' => $children)
1044
        );
1045
1046
        return $this->returnJson($ret);
1047
    }
1048
1049
    /**
1050
     * Used for querying narrower relations for a concept in the hierarchy view.
1051
     * @param Request $request
1052
     * @return object json-ld wrapped hierarchical concept uris and labels.
1053
     */
1054
    public function related($request)
1055
    {
1056
        if ($this->notModified($request->getVocab())) {
1057
            return null;
1058
        }
1059
        $related = $request->getVocab()->getConceptRelateds($request->getUri(), $request->getLang());
1060
        if ($related === null) {
1061
            return $this->returnError('404', 'Not Found', "Could not find concept <{$request->getUri()}>");
1062
        }
1063
        $ret = $this->transformPropertyResults($request->getUri(), $request->getLang(), $related, "related", "skos:related");
1064
        return $this->returnJson($ret);
1065
    }
1066
1067
    /**
1068
     * Used for querying new concepts in the vocabulary
1069
     * @param Request $request
1070
     * @return object json-ld wrapped list of changed concepts
1071
     */
1072 View Code Duplication
    public function newConcepts($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...
1073
    {
1074
        $offset = ($request->getQueryParam('offset') && is_numeric($request->getQueryParam('offset')) && $request->getQueryParam('offset') >= 0) ? $request->getQueryParam('offset') : 0;
1075
        $limit = ($request->getQueryParam('limit') && is_numeric($request->getQueryParam('limit')) && $request->getQueryParam('limit') >= 0) ? $request->getQueryParam('limit') : 200;
1076
1077
        return $this->changedConcepts($request, 'dc:created', $offset, $limit);
1078
    }
1079
1080
    /**
1081
     * Used for querying modified concepts in the vocabulary
1082
     * @param Request $request
1083
     * @return object json-ld wrapped list of changed concepts
1084
     */
1085 View Code Duplication
    public function modifiedConcepts($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...
1086
    {
1087
        $offset = ($request->getQueryParam('offset') && is_numeric($request->getQueryParam('offset')) && $request->getQueryParam('offset') >= 0) ? $request->getQueryParam('offset') : 0;
1088
        $limit = ($request->getQueryParam('limit') && is_numeric($request->getQueryParam('limit')) && $request->getQueryParam('limit') >= 0) ? $request->getQueryParam('limit') : 200;
1089
1090
        return $this->changedConcepts($request, 'dc:modified', $offset, $limit);
1091
    }
1092
1093
    /**
1094
     * Used for querying changed concepts in the vocabulary
1095
     * @param Request $request
1096
     * @param int $offset starting index offset
1097
     * @param int $limit maximum number of concepts to return
1098
     * @return object json-ld wrapped list of changed concepts
1099
     */
1100
    private function changedConcepts($request, $prop, $offset, $limit)
1101
    {
1102
        $changeList = $request->getVocab()->getChangeList($prop, $request->getLang(), $offset, $limit);
1103
1104
        $simpleChangeList = array();
1105
        foreach($changeList as $conceptInfo) {
1106
            if (array_key_exists('date', $conceptInfo)) {
1107
                $simpleChangeList[] =  array( 'uri' => $conceptInfo['uri'],
1108
                                               'prefLabel' => $conceptInfo['prefLabel'],
1109
                                               'date' => $conceptInfo['date']->format("Y-m-d\TH:i:sO") );
1110
            }
1111
        }
1112
        return $this->returnJson(array_merge_recursive($this->context,
1113
                                                        array('@context' => array( '@language' => $request->getLang(),
1114
                                                                                     'prefLabel' => 'skos:prefLabel',
1115
                                                                                     'xsd' => 'http://www.w3.org/2001/XMLSchema#',
1116
                                                                                     'date' => array( '@id' => 'http://purl.org/dc/terms/date', '@type' => 'http://www.w3.org/2001/XMLSchema#dateTime') )
1117
                                                        ),
1118
                                                        array('changeList' => $simpleChangeList)));
1119
1120
    }
1121
}
1122