Passed
Push — sparql-query-cache-skosmos-2 ( 3aec9d )
by Osma
06:33
created

GenericSparql::listConceptGroupContents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 4
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Generates SPARQL queries and provides access to the SPARQL endpoint.
5
 */
6
class GenericSparql {
7
    /**
8
     * SPARQL endpoint URL
9
     * @property string $endpoint
10
     */
11
    protected $endpoint;
12
    /**
13
     * A SPARQL Client eg. an EasyRDF instance.
14
     * @property EasyRdf\Sparql\Client $client
15
     */
16
    protected $client;
17
    /**
18
     * Graph uri.
19
     * @property object $graph
20
     */
21
    protected $graph;
22
    /**
23
     * A SPARQL query graph part template.
24
     * @property string $graphClause
25
     */
26
    protected $graphClause;
27
    /**
28
     * Model instance.
29
     * @property Model $model
30
     */
31
    protected $model;
32
33
    /**
34
     * Cache used to avoid expensive shorten() calls
35
     * @property array $qnamecache
36
     */
37
    private $qnamecache = array();
38
39
    /**
40
     * Cache used to avoid duplicate SPARQL queries. The cache must be
41
     * static so that all GenericSparql instances have access to the
42
     * same shared cache.
43
     * @property array $querycache
44
     */
45
    private static $querycache = array();
46
47
    /**
48
     * Requires the following three parameters.
49
     * @param string $endpoint SPARQL endpoint address.
50
     * @param string|null $graph Which graph to query: Either an URI, the special value "?graph"
51
     *                           to use the default graph, or NULL to not use a GRAPH clause.
52
     * @param object $model a Model instance.
53
     */
54
    public function __construct($endpoint, $graph, $model)
55
    {
56
        $this->endpoint = $endpoint;
57
        $this->graph = $graph;
58
        $this->model = $model;
59
60
        // create the EasyRDF SPARQL client instance to use
61
        $this->initializeHttpClient();
62
        $this->client = new EasyRdf\Sparql\Client($endpoint);
63
64
        // set graphClause so that it can be used by all queries
65
        if ($this->isDefaultEndpoint()) // default endpoint; query any graph (and catch it in a variable)
66
        {
67
            $this->graphClause = "GRAPH $graph";
68
        } elseif ($graph !== null) // query a specific graph
69
        {
70
            $this->graphClause = "GRAPH <$graph>";
71
        } else // query the default graph
72
        {
73
            $this->graphClause = "";
74
        }
75
76
    }
77
78
    /**
79
     * Returns prefix-definitions for a query
80
     *
81
     * @param string $query
82
     * @return string
83
    */
84
    protected function generateQueryPrefixes($query)
85
    {
86
        // Check for undefined prefixes
87
        $prefixes = '';
88
        foreach (EasyRdf\RdfNamespace::namespaces() as $prefix => $uri) {
89
            if (strpos($query, "{$prefix}:") !== false and
90
                strpos($query, "PREFIX {$prefix}:") === false
91
            ) {
92
                $prefixes .= "PREFIX {$prefix}: <{$uri}>\n";
93
            }
94
        }
95
        return $prefixes;
96
    }
97
98
    /**
99
     * Execute the SPARQL query using the SPARQL client, logging it as well.
100
     * @param string $query SPARQL query to perform
101
     * @return \EasyRdf\Sparql\Result|\EasyRdf\Graph query result
0 ignored issues
show
Bug introduced by
The type EasyRdf\Sparql\Result was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
102
     */
103
    protected function doQuery($query)
104
    {
105
        $queryId = sprintf("%05d", rand(0, 99999));
106
        $logger = $this->model->getLogger();
107
        $logger->info("[qid $queryId] SPARQL query:\n" . $this->generateQueryPrefixes($query) . "\n$query\n");
108
        $starttime = microtime(true);
109
        $result = $this->client->query($query);
110
        $elapsed = intval(round((microtime(true) - $starttime) * 1000));
111
        if(method_exists($result, 'numRows')) {
112
            $numRows = $result->numRows();
113
            $logger->info("[qid $queryId] result: $numRows rows returned in $elapsed ms");
114
        } else { // graph result
115
            $numTriples = $result->countTriples();
116
            $logger->info("[qid $queryId] result: $numTriples triples returned in $elapsed ms");
117
        }
118
        return $result;
119
    }
120
121
122
    /**
123
     * Execute the SPARQL query, if not found in query cache.
124
     * @param string $query SPARQL query to perform
125
     * @return \EasyRdf\Sparql\Result|\EasyRdf\Graph query result
126
     */
127
    protected function query($query)
128
    {
129
        $key = $this->endpoint . " " . $query;
130
        if (!array_key_exists($key, self::$querycache)) {
131
            self::$querycache[$key] = $this->doQuery($query);
132
        }
133
        return self::$querycache[$key];
134
    }
135
136
137
    /**
138
     * Generates FROM clauses for the queries
139
     * @param Vocabulary[]|null $vocabs
140
     * @return string
141
     */
142
    protected function generateFromClause($vocabs=null) {
143
        $clause = '';
144
        if (!$vocabs) {
145
            return $this->graph !== '?graph' && $this->graph !== NULL ? "FROM <$this->graph>" : '';
146
        }
147
        $graphs = $this->getVocabGraphs($vocabs);
148
        foreach ($graphs as $graph) {
149
            $clause .= "FROM NAMED <$graph> ";
150
        }
151
        return $clause;
152
    }
153
154
    protected function initializeHttpClient() {
155
        // configure the HTTP client used by EasyRdf\Sparql\Client
156
        $httpclient = EasyRdf\Http::getDefaultHttpClient();
157
        $httpclient->setConfig(array('timeout' => $this->model->getConfig()->getSparqlTimeout()));
158
159
        // if special cache control (typically no-cache) was requested by the
160
        // client, set the same type of cache control headers also in subsequent
161
        // in the SPARQL requests (this is useful for performance testing)
162
        // @codeCoverageIgnoreStart
163
        $cacheControl = filter_input(INPUT_SERVER, 'HTTP_CACHE_CONTROL', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
0 ignored issues
show
Bug introduced by
The constant FILTER_SANITIZE_FULL_SPECIAL_CHARS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
164
        $pragma = filter_input(INPUT_SERVER, 'HTTP_PRAGMA', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
165
        if ($cacheControl !== null || $pragma !== null) {
166
            $val = $pragma !== null ? $pragma : $cacheControl;
167
            $httpclient->setHeaders('Cache-Control', $val);
168
        }
169
        // @codeCoverageIgnoreEnd
170
171
        EasyRdf\Http::setDefaultHttpClient($httpclient); // actually redundant..
172
    }
173
174
    /**
175
     * Return true if this is the default SPARQL endpoint, used as the facade to query
176
     * all vocabularies.
177
     */
178
179
    protected function isDefaultEndpoint() {
180
        return !is_null($this->graph) && $this->graph[0] == '?';
181
    }
182
183
    /**
184
     * Returns the graph instance
185
     * @return object EasyRDF graph instance.
186
     */
187
    public function getGraph() {
188
        return $this->graph;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->graph also could return the type string which is incompatible with the documented return type object.
Loading history...
189
    }
190
191
    /**
192
     * Shorten a URI
193
     * @param string $uri URI to shorten
194
     * @return string shortened URI, or original URI if it cannot be shortened
195
     */
196
    private function shortenUri($uri) {
197
        if (!array_key_exists($uri, $this->qnamecache)) {
198
            $res = new EasyRdf\Resource($uri);
199
            $qname = $res->shorten(); // returns null on failure
200
            // only URIs in the SKOS namespace are shortened
201
            $this->qnamecache[$uri] = ($qname !== null && strpos($qname, "skos:") === 0) ? $qname : $uri;
202
        }
203
        return $this->qnamecache[$uri];
204
    }
205
206
207
    /**
208
     * Generates the sparql query for retrieving concept and collection counts in a vocabulary.
209
     * @return string sparql query
210
     */
211
    private function generateCountConceptsQuery($array, $group) {
212
        $fcl = $this->generateFromClause();
213
        $optional = $array ? "(<$array>) " : '';
214
        $optional .= $group ? "(<$group>)" : '';
215
        $query = <<<EOQ
216
      SELECT (COUNT(DISTINCT(?conc)) as ?c) ?type ?typelabel (COUNT(?depr) as ?deprcount) $fcl WHERE {
217
        VALUES (?value) { (skos:Concept) (skos:Collection) $optional }
218
  	    ?type rdfs:subClassOf* ?value
219
        { ?type ^a ?conc .
220
          OPTIONAL { ?conc owl:deprecated ?depr .
221
  		    FILTER (?depr = True)
222
          }
223
        } UNION {SELECT * WHERE {
224
            ?type rdfs:label ?typelabel
225
          }
226
        }
227
      } GROUP BY ?type ?typelabel
228
EOQ;
229
        return $query;
230
    }
231
232
    /**
233
     * Used for transforming the concept count query results.
234
     * @param EasyRdf\Sparql\Result $result query results to be transformed
235
     * @param string $lang language of labels
236
     * @return Array containing the label counts
237
     */
238
    private function transformCountConceptsResults($result, $lang) {
239
        $ret = array();
240
        foreach ($result as $row) {
241
            if (!isset($row->type)) {
242
                continue;
243
            }
244
            $typeURI = $row->type->getUri();
245
            $ret[$typeURI]['type'] = $typeURI;
246
247
            if (!isset($row->typelabel)) {
248
                $ret[$typeURI]['count'] = $row->c->getValue();
249
                $ret[$typeURI]['deprecatedCount'] = $row->deprcount->getValue();
250
            }
251
252
            if (isset($row->typelabel) && $row->typelabel->getLang() === $lang) {
253
                $ret[$typeURI]['label'] = $row->typelabel->getValue();
254
            }
255
256
        }
257
        return $ret;
258
    }
259
260
    /**
261
     * Used for counting number of concepts and collections in a vocabulary.
262
     * @param string $lang language of labels
263
     * @param string $array the uri of the concept array class, eg. isothes:ThesaurusArray
264
     * @param string $group the uri of the  concept group class, eg. isothes:ConceptGroup
265
     * @return array with number of concepts in this vocabulary per label
266
     */
267
    public function countConcepts($lang = null, $array = null, $group = null) {
268
        $query = $this->generateCountConceptsQuery($array, $group);
269
        $result = $this->query($query);
270
        return $this->transformCountConceptsResults($result, $lang);
271
    }
272
273
    /**
274
     * @param array $langs Languages to query for
275
     * @param string[] $props property names
276
     * @return string sparql query
277
     */
278
    private function generateCountLangConceptsQuery($langs, $classes, $props) {
279
        $gcl = $this->graphClause;
280
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
281
282
	$quote_string = function($val) { return "'$val'"; };
283
	$quoted_values = array_map($quote_string, $langs);
284
	$langFilter = "FILTER(?lang IN (" . implode(',', $quoted_values) . "))";
285
286
        $values = $this->formatValues('?type', $classes, 'uri');
287
        $valuesProp = $this->formatValues('?prop', $props, null);
288
289
        $query = <<<EOQ
290
SELECT ?lang ?prop
291
  (COUNT(?label) as ?count)
292
WHERE {
293
  $gcl {
294
    $values
295
    $valuesProp
296
    ?conc a ?type .
297
    ?conc ?prop ?label .
298
    BIND(LANG(?label) AS ?lang)
299
    $langFilter
300
  }
301
}
302
GROUP BY ?lang ?prop ?type
303
EOQ;
304
        return $query;
305
    }
306
307
    /**
308
     * Transforms the CountLangConcepts results into an array of label counts.
309
     * @param EasyRdf\Sparql\Result $result query results to be transformed
310
     * @param array $langs Languages to query for
311
     * @param string[] $props property names
312
     */
313
    private function transformCountLangConceptsResults($result, $langs, $props) {
314
        $ret = array();
315
        // set default count to zero; overridden below if query found labels
316
        foreach ($langs as $lang) {
317
            foreach ($props as $prop) {
318
                $ret[$lang][$prop] = 0;
319
            }
320
        }
321
        foreach ($result as $row) {
322
            if (isset($row->lang) && isset($row->prop) && isset($row->count)) {
323
                $ret[$row->lang->getValue()][$row->prop->shorten()] +=
324
                $row->count->getValue();
325
            }
326
327
        }
328
        ksort($ret);
329
        return $ret;
330
    }
331
332
    /**
333
     * Counts the number of concepts in a easyRDF graph with a specific language.
334
     * @param array $langs Languages to query for
335
     * @return Array containing count of concepts for each language and property.
336
     */
337
    public function countLangConcepts($langs, $classes = null) {
338
        $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel');
339
        $query = $this->generateCountLangConceptsQuery($langs, $classes, $props);
340
        // Count the number of terms in each language
341
        $result = $this->query($query);
342
        return $this->transformCountLangConceptsResults($result, $langs, $props);
343
    }
344
345
    /**
346
     * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one
347
     * of the constants given.
348
     * @param string $varname variable name, e.g. "?uri"
349
     * @param array $values the values
350
     * @param string $type type of values: "uri", "literal" or null (determines quoting style)
351
     */
352
    protected function formatValues($varname, $values, $type = null) {
353
        $constants = array();
354
        foreach ($values as $val) {
355
            if ($type == 'uri') {
356
                $val = "<$val>";
357
            }
358
359
            if ($type == 'literal') {
360
                $val = "'$val'";
361
            }
362
363
            $constants[] = "($val)";
364
        }
365
        $values = implode(" ", $constants);
366
367
        return "VALUES ($varname) { $values }";
368
    }
369
370
    /**
371
     * Filters multiple instances of the same vocabulary from the input array.
372
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
373
     * @return \Vocabulary[]
374
     */
375
    private function filterDuplicateVocabs($vocabs) {
376
        // filtering duplicates
377
        $uniqueVocabs = array();
378
        if ($vocabs !== null && sizeof($vocabs) > 0) {
379
            foreach ($vocabs as $voc) {
380
                $uniqueVocabs[$voc->getId()] = $voc;
381
            }
382
        }
383
384
        return $uniqueVocabs;
385
    }
386
387
    /**
388
     * Generates a sparql query for one or more concept URIs
389
     * @param mixed $uris concept URI (string) or array of URIs
390
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
391
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
392
     * @return string sparql query
393
     */
394
    private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) {
395
        $gcl = $this->graphClause;
396
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
397
        $values = $this->formatValues('?uri', $uris, 'uri');
398
        $uniqueVocabs = $this->filterDuplicateVocabs($vocabs);
399
        $valuesGraph = empty($vocabs) ? $this->formatValuesGraph($uniqueVocabs) : '';
400
401
        if ($arrayClass === null) {
402
            $construct = $optional = "";
403
        } else {
404
            // add information that can be used to format narrower concepts by
405
            // the array they belong to ("milk by source animal" use case)
406
            $construct = "\n ?x skos:member ?o . ?x skos:prefLabel ?xl . ?x a <$arrayClass> .";
407
            $optional = "\n OPTIONAL {
408
                      ?x skos:member ?o .
409
                      ?x a <$arrayClass> .
410
                      ?x skos:prefLabel ?xl .
411
                      FILTER NOT EXISTS {
412
                        ?x skos:member ?other .
413
                        MINUS { ?other skos:broader ?uri }
414
                      }
415
                    }";
416
        }
417
        $query = <<<EOQ
418
CONSTRUCT {
419
 ?s ?p ?uri .
420
 ?sp ?uri ?op .
421
 ?uri ?p ?o .
422
 ?p rdfs:label ?proplabel .
423
 ?p rdfs:comment ?propcomm .
424
 ?p skos:definition ?propdef .
425
 ?p rdfs:subPropertyOf ?pp .
426
 ?pp rdfs:label ?plabel .
427
 ?o a ?ot .
428
 ?o skos:prefLabel ?opl .
429
 ?o rdfs:label ?ol .
430
 ?o rdf:value ?ov .
431
 ?o skos:notation ?on .
432
 ?o ?oprop ?oval .
433
 ?o ?xlprop ?xlval .
434
 ?dt rdfs:label ?dtlabel .
435
 ?directgroup skos:member ?uri .
436
 ?parent skos:member ?group .
437
 ?group skos:prefLabel ?grouplabel .
438
 ?b1 rdf:first ?item .
439
 ?b1 rdf:rest ?b2 .
440
 ?item a ?it .
441
 ?item skos:prefLabel ?il .
442
 ?group a ?grouptype . $construct
443
} $fcl WHERE {
444
 $values
445
 $gcl {
446
  {
447
    ?s ?p ?uri .
448
    FILTER(!isBlank(?s))
449
    FILTER(?p != skos:inScheme)
450
    FILTER NOT EXISTS { ?s owl:deprecated true . }
451
  }
452
  UNION
453
  { ?sp ?uri ?op . }
454
  UNION
455
  {
456
    ?directgroup skos:member ?uri .
457
    ?group skos:member+ ?uri .
458
    ?group skos:prefLabel ?grouplabel .
459
    ?group a ?grouptype .
460
    OPTIONAL { ?parent skos:member ?group }
461
  }
462
  UNION
463
  {
464
   ?uri ?p ?o .
465
   OPTIONAL {
466
     ?uri skos:notation ?nVal .
467
     FILTER(isLiteral(?nVal))
468
     BIND(datatype(?nVal) AS ?dt)
469
     ?dt rdfs:label ?dtlabel
470
   }
471
   OPTIONAL {
472
     ?o rdf:rest* ?b1 .
473
     ?b1 rdf:first ?item .
474
     ?b1 rdf:rest ?b2 .
475
     OPTIONAL { ?item a ?it . }
476
     OPTIONAL { ?item skos:prefLabel ?il . }
477
   }
478
   OPTIONAL {
479
     { ?p rdfs:label ?proplabel . }
480
     UNION
481
     { ?p rdfs:comment ?propcomm . }
482
     UNION
483
     { ?p skos:definition ?propdef . }
484
     UNION
485
     { ?p rdfs:subPropertyOf ?pp . }
486
   }
487
   OPTIONAL {
488
     { ?o a ?ot . }
489
     UNION
490
     { ?o skos:prefLabel ?opl . }
491
     UNION
492
     { ?o rdfs:label ?ol . }
493
     UNION
494
     { ?o rdf:value ?ov . 
495
       OPTIONAL { ?o ?oprop ?oval . }
496
     }
497
     UNION
498
     { ?o skos:notation ?on . }
499
     UNION
500
     { ?o a skosxl:Label .
501
       ?o ?xlprop ?xlval }
502
   } $optional
503
  }
504
 }
505
}
506
$valuesGraph
507
EOQ;
508
        return $query;
509
    }
510
511
    /**
512
     * Transforms ConceptInfo query results into an array of Concept objects
513
     * @param EasyRdf\Graph $result query results to be transformed
514
     * @param array $uris concept URIs
515
     * @param \Vocabulary[] $vocabs array of Vocabulary object
516
     * @param string|null $clang content language
517
     * @return Concept[] array of Concept objects
518
     */
519
    private function transformConceptInfoResults($result, $uris, $vocabs, $clang) {
520
        $conceptArray = array();
521
        foreach ($uris as $index => $uri) {
522
            $conc = $result->resource($uri);
523
            if (is_array($vocabs)) {
524
                $vocab = (sizeof($vocabs) == 1) ? $vocabs[0] : $vocabs[$index];
525
            } else {
526
                $vocab = null;
527
            }
528
            $conceptArray[] = new Concept($this->model, $vocab, $conc, $result, $clang);
529
        }
530
        return $conceptArray;
531
    }
532
533
    /**
534
     * Returns information (as a graph) for one or more concept URIs
535
     * @param mixed $uris concept URI (string) or array of URIs
536
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
537
     * @param \Vocabulary[]|null $vocabs vocabularies to target
538
     * @return \EasyRdf\Graph
539
     */
540
    public function queryConceptInfoGraph($uris, $arrayClass = null, $vocabs = array()) {
541
        // if just a single URI is given, put it in an array regardless
542
        if (!is_array($uris)) {
543
            $uris = array($uris);
544
        }
545
546
        $query = $this->generateConceptInfoQuery($uris, $arrayClass, $vocabs);
547
        $result = $this->query($query);
548
        return $result;
549
    }
550
551
    /**
552
     * Returns information (as an array of Concept objects) for one or more concept URIs
553
     * @param mixed $uris concept URI (string) or array of URIs
554
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
555
     * @param \Vocabulary[] $vocabs vocabularies to target
556
     * @param string|null $clang content language
557
     * @return Concept[]
558
     */
559
    public function queryConceptInfo($uris, $arrayClass = null, $vocabs = array(), $clang = null) {
560
        // if just a single URI is given, put it in an array regardless
561
        if (!is_array($uris)) {
562
            $uris = array($uris);
563
        }
564
        $result = $this->queryConceptInfoGraph($uris, $arrayClass, $vocabs);
565
        if ($result->isEmpty()) {
566
            return [];
567
        }
568
        return $this->transformConceptInfoResults($result, $uris, $vocabs, $clang);
569
    }
570
571
    /**
572
     * Generates the sparql query for queryTypes
573
     * @param string $lang
574
     * @return string sparql query
575
     */
576
    private function generateQueryTypesQuery($lang) {
577
        $fcl = $this->generateFromClause();
578
        $query = <<<EOQ
579
SELECT DISTINCT ?type ?label ?superclass $fcl
580
WHERE {
581
  {
582
    { BIND( skos:Concept as ?type ) }
583
    UNION
584
    { BIND( skos:Collection as ?type ) }
585
    UNION
586
    { BIND( isothes:ConceptGroup as ?type ) }
587
    UNION
588
    { BIND( isothes:ThesaurusArray as ?type ) }
589
    UNION
590
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Concept . }
591
    UNION
592
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Collection . }
593
  }
594
  OPTIONAL {
595
    ?type rdfs:label ?label .
596
    FILTER(langMatches(lang(?label), '$lang'))
597
  }
598
  OPTIONAL {
599
    ?type rdfs:subClassOf ?superclass .
600
  }
601
  FILTER EXISTS {
602
    ?s a ?type .
603
    ?s skos:prefLabel ?prefLabel .
604
  }
605
}
606
EOQ;
607
        return $query;
608
    }
609
610
    /**
611
     * Transforms the results into an array format.
612
     * @param EasyRdf\Sparql\Result $result
613
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
614
     */
615
    private function transformQueryTypesResults($result) {
616
        $ret = array();
617
        foreach ($result as $row) {
618
            $type = array();
619
            if (isset($row->label)) {
620
                $type['label'] = $row->label->getValue();
621
            }
622
623
            if (isset($row->superclass)) {
624
                $type['superclass'] = $row->superclass->getUri();
625
            }
626
627
            $ret[$row->type->getURI()] = $type;
628
        }
629
        return $ret;
630
    }
631
632
    /**
633
     * Retrieve information about types from the endpoint
634
     * @param string $lang
635
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
636
     */
637
    public function queryTypes($lang) {
638
        $query = $this->generateQueryTypesQuery($lang);
639
        $result = $this->query($query);
640
        return $this->transformQueryTypesResults($result);
641
    }
642
643
    /**
644
     * Generates the concept scheme query.
645
     * @param string $conceptscheme concept scheme URI
646
     * @return string sparql query
647
     */
648
    private function generateQueryConceptSchemeQuery($conceptscheme) {
649
        $fcl = $this->generateFromClause();
650
        $query = <<<EOQ
651
CONSTRUCT {
652
  <$conceptscheme> ?property ?value .
653
} $fcl WHERE {
654
  <$conceptscheme> ?property ?value .
655
  FILTER (?property != skos:hasTopConcept)
656
}
657
EOQ;
658
        return $query;
659
    }
660
661
    /**
662
     * Retrieves conceptScheme information from the endpoint.
663
     * @param string $conceptscheme concept scheme URI
664
     * @return \EasyRdf\Sparql\Result|\EasyRdf\Graph query result graph
665
     */
666
    public function queryConceptScheme($conceptscheme) {
667
        $query = $this->generateQueryConceptSchemeQuery($conceptscheme);
668
        return $this->query($query);
669
    }
670
671
    /**
672
     * Generates the queryConceptSchemes sparql query.
673
     * @param string $lang language of labels
674
     * @return string sparql query
675
     */
676
    private function generateQueryConceptSchemesQuery($lang) {
677
        $fcl = $this->generateFromClause();
678
        $query = <<<EOQ
679
SELECT ?cs ?label ?preflabel ?title ?domain ?domainLabel $fcl
680
WHERE {
681
 ?cs a skos:ConceptScheme .
682
 OPTIONAL{
683
    ?cs dcterms:subject ?domain.
684
    ?domain skos:prefLabel ?domainLabel.
685
    FILTER(langMatches(lang(?domainLabel), '$lang'))
686
}
687
 OPTIONAL {
688
   ?cs rdfs:label ?label .
689
   FILTER(langMatches(lang(?label), '$lang'))
690
 }
691
 OPTIONAL {
692
   ?cs skos:prefLabel ?preflabel .
693
   FILTER(langMatches(lang(?preflabel), '$lang'))
694
 }
695
 OPTIONAL {
696
   { ?cs dc11:title ?title }
697
   UNION
698
   { ?cs dc:title ?title }
699
   FILTER(langMatches(lang(?title), '$lang'))
700
 }
701
} 
702
ORDER BY ?cs
703
EOQ;
704
        return $query;
705
    }
706
707
    /**
708
     * Transforms the queryConceptScheme results into an array format.
709
     * @param EasyRdf\Sparql\Result $result
710
     * @return array
711
     */
712
    private function transformQueryConceptSchemesResults($result) {
713
        $ret = array();
714
        foreach ($result as $row) {
715
            $conceptscheme = array();
716
            if (isset($row->label)) {
717
                $conceptscheme['label'] = $row->label->getValue();
718
            }
719
720
            if (isset($row->preflabel)) {
721
                $conceptscheme['prefLabel'] = $row->preflabel->getValue();
722
            }
723
724
            if (isset($row->title)) {
725
                $conceptscheme['title'] = $row->title->getValue();
726
            }
727
            // add dct:subject and their labels in the result
728
            if(isset($row->domain) && isset($row->domainLabel)){
729
                $conceptscheme['subject']['uri']=$row->domain->getURI();
730
                $conceptscheme['subject']['prefLabel']=$row->domainLabel->getValue();
731
            }
732
733
            $ret[$row->cs->getURI()] = $conceptscheme;
734
        }
735
        return $ret;
736
    }
737
738
    /**
739
     * return a list of skos:ConceptScheme instances in the given graph
740
     * @param string $lang language of labels
741
     * @return array Array with concept scheme URIs (string) as keys and labels (string) as values
742
     */
743
    public function queryConceptSchemes($lang) {
744
        $query = $this->generateQueryConceptSchemesQuery($lang);
745
        $result = $this->query($query);
746
        return $this->transformQueryConceptSchemesResults($result);
747
    }
748
749
    /**
750
     * Generate a VALUES clause for limiting the targeted graphs.
751
     * @param Vocabulary[]|null $vocabs the vocabularies to target
752
     * @return string[] array of graph URIs
753
     */
754
    protected function getVocabGraphs($vocabs) {
755
        if ($vocabs === null || sizeof($vocabs) == 0) {
756
            // searching from all vocabularies - limit to known graphs
757
            $vocabs = $this->model->getVocabularies();
758
        }
759
        $graphs = array();
760
        foreach ($vocabs as $voc) {
761
            $graph = $voc->getGraph();
762
            if (!is_null($graph) && !in_array($graph, $graphs)) {
763
                $graphs[] = $graph;
764
            }
765
        }
766
        return $graphs;
767
    }
768
769
    /**
770
     * Generate a VALUES clause for limiting the targeted graphs.
771
     * @param Vocabulary[]|null $vocabs array of Vocabulary objects to target
772
     * @return string VALUES clause, or "" if not necessary to limit
773
     */
774
    protected function formatValuesGraph($vocabs) {
775
        if (!$this->isDefaultEndpoint()) {
776
            return "";
777
        }
778
        $graphs = $this->getVocabGraphs($vocabs);
779
        return $this->formatValues('?graph', $graphs, 'uri');
780
    }
781
782
    /**
783
     * Generate a FILTER clause for limiting the targeted graphs.
784
     * @param array $vocabs array of Vocabulary objects to target
785
     * @return string FILTER clause, or "" if not necessary to limit
786
     */
787
    protected function formatFilterGraph($vocabs) {
788
        if (!$this->isDefaultEndpoint()) {
789
            return "";
790
        }
791
        $graphs = $this->getVocabGraphs($vocabs);
792
        $values = array();
793
        foreach ($graphs as $graph) {
794
          $values[] = "<$graph>";
795
        }
796
        if (count($values)) {
797
          return "FILTER (?graph IN (" . implode(',', $values) . "))";
798
        }
799
    }
800
801
    /**
802
     * Formats combined limit and offset clauses for the sparql query
803
     * @param int $limit maximum number of hits to retrieve; 0 for unlimited
804
     * @param int $offset offset of results to retrieve; 0 for beginning of list
805
     * @return string sparql query clauses
806
     */
807
    protected function formatLimitAndOffset($limit, $offset) {
808
        $limit = ($limit) ? 'LIMIT ' . $limit : '';
809
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
810
        // eliminating whitespace and line changes when the conditions aren't needed.
811
        $limitandoffset = '';
812
        if ($limit && $offset) {
813
            $limitandoffset = "\n" . $limit . "\n" . $offset;
814
        } elseif ($limit) {
815
            $limitandoffset = "\n" . $limit;
816
        } elseif ($offset) {
817
            $limitandoffset = "\n" . $offset;
818
        }
819
820
        return $limitandoffset;
821
    }
822
823
    /**
824
     * Formats a sparql query clause for limiting the search to specific concept types.
825
     * @param array $types limit search to concepts of the given type(s)
826
     * @return string sparql query clause
827
     */
828
    protected function formatTypes($types) {
829
        $typePatterns = array();
830
        if (!empty($types)) {
831
            foreach ($types as $type) {
832
                $unprefixed = EasyRdf\RdfNamespace::expand($type);
833
                $typePatterns[] = "{ ?s a <$unprefixed> }";
834
            }
835
        }
836
837
        return implode(' UNION ', $typePatterns);
838
    }
839
840
    /**
841
     * @param string $prop property to include in the result eg. 'broader' or 'narrower'
842
     * @return string sparql query clause
843
     */
844
    private function formatPropertyCsvClause($prop) {
845
        # This expression creates a CSV row containing pairs of (uri,prefLabel) values.
846
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
847
        $clause = <<<EOV
848
(GROUP_CONCAT(DISTINCT CONCAT(
849
 '"', IF(isIRI(?$prop),STR(?$prop),''), '"', ',',
850
 '"', REPLACE(IF(BOUND(?{$prop}lab),?{$prop}lab,''), '"', '""'), '"', ',',
851
 '"', REPLACE(IF(isLiteral(?{$prop}),?{$prop},''), '"', '""'), '"'
852
); separator='\\n') as ?{$prop}s)
853
EOV;
854
        return $clause;
855
    }
856
857
    /**
858
     * @return string sparql query clause
859
     */
860
    private function formatPrefLabelCsvClause() {
861
        # This expression creates a CSV row containing pairs of (prefLabel, lang) values.
862
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
863
        $clause = <<<EOV
864
(GROUP_CONCAT(DISTINCT CONCAT(
865
 '"', STR(?pref), '"', ',', '"', lang(?pref), '"'
866
); separator='\\n') as ?preflabels)
867
EOV;
868
        return $clause;
869
    }
870
871
    /**
872
     * @param string $lang language code of the returned labels
873
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
874
     * @return array sparql query clause
875
     */
876
    protected function formatExtraFields($lang, $fields) {
877
        // extra variable expressions to request and extra fields to query for
878
        $ret = array('extravars' => '', 'extrafields' => '');
879
880
        if ($fields === null) {
881
            return $ret;
882
        }
883
884
        if (in_array('prefLabel', $fields)) {
885
            $ret['extravars'] .= $this->formatPreflabelCsvClause();
886
            $ret['extrafields'] .= <<<EOF
887
OPTIONAL {
888
  ?s skos:prefLabel ?pref .
889
}
890
EOF;
891
            // removing the prefLabel from the fields since it has been handled separately
892
            $fields = array_diff($fields, array('prefLabel'));
893
        }
894
895
        foreach ($fields as $field) {
896
            $ret['extravars'] .= $this->formatPropertyCsvClause($field);
897
            $ret['extrafields'] .= <<<EOF
898
OPTIONAL {
899
  ?s skos:$field ?$field .
900
  FILTER(!isLiteral(?$field)||langMatches(lang(?{$field}), '$lang'))
901
  OPTIONAL { ?$field skos:prefLabel ?{$field}lab . FILTER(langMatches(lang(?{$field}lab), '$lang')) }
902
}
903
EOF;
904
        }
905
906
        return $ret;
907
    }
908
909
    /**
910
     * Generate condition for matching labels in SPARQL
911
     * @param string $term search term
912
     * @param string $searchLang language code used for matching labels (null means any language)
913
     * @return string sparql query snippet
914
     */
915
    protected function generateConceptSearchQueryCondition($term, $searchLang)
916
    {
917
        # use appropriate matching function depending on query type: =, strstarts, strends or full regex
918
        if (preg_match('/^[^\*]+$/', $term)) { // exact query
919
            $term = str_replace('\\', '\\\\', $term); // quote slashes
920
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
921
            $filtercond = "LCASE(STR(?match)) = '$term'";
922
        } elseif (preg_match('/^[^\*]+\*$/', $term)) { // prefix query
923
            $term = substr($term, 0, -1); // remove the final asterisk
924
            $term = str_replace('\\', '\\\\', $term); // quote slashes
925
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
926
            $filtercond = "STRSTARTS(LCASE(STR(?match)), '$term')";
927
        } elseif (preg_match('/^\*[^\*]+$/', $term)) { // suffix query
928
            $term = substr($term, 1); // remove the preceding asterisk
929
            $term = str_replace('\\', '\\\\', $term); // quote slashes
930
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
931
            $filtercond = "STRENDS(LCASE(STR(?match)), '$term')";
932
        } else { // too complicated - have to use a regex
933
            # make sure regex metacharacters are not passed through
934
            $term = str_replace('\\', '\\\\', preg_quote($term));
935
            $term = str_replace('\\\\*', '.*', $term); // convert asterisk to regex syntax
936
            $term = str_replace('\'', '\\\'', $term); // ensure single quotes are quoted
937
            $filtercond = "REGEX(STR(?match), '^$term$', 'i')";
938
        }
939
940
        $labelcondMatch = ($searchLang) ? "&& (?prop = skos:notation || LANGMATCHES(lang(?match), ?langParam))" : "";
941
942
        return "?s ?prop ?match . FILTER ($filtercond $labelcondMatch)";
943
    }
944
945
946
    /**
947
     * Inner query for concepts using a search term.
948
     * @param string $term search term
949
     * @param string $lang language code of the returned labels
950
     * @param string $searchLang language code used for matching labels (null means any language)
951
     * @param string[] $props properties to target e.g. array('skos:prefLabel','skos:altLabel')
952
     * @param boolean $unique restrict results to unique concepts (default: false)
953
     * @param string|null $distinguisher SPARQL property path from concept to distinguisher object in case of shared prefLabels
954
     * @return string sparql query
955
     */
956
    protected function generateConceptSearchQueryInner($term, $lang, $searchLang, $props, $unique, $filterGraph, $distinguisher=null)
957
    {
958
        $valuesProp = $this->formatValues('?prop', $props);
959
        $textcond = $this->generateConceptSearchQueryCondition($term, $searchLang);
960
961
        $rawterm = str_replace(array('\\', '*', '"'), array('\\\\', '', '\"'), $term);
962
        // graph clause, if necessary
963
        $graphClause = $filterGraph != '' ? 'GRAPH ?graph' : '';
964
965
        // extra conditions for label language, if specified
966
        $labelcondLabel = ($lang) ? "LANGMATCHES(lang(?label), '$lang')" : "lang(?match) = '' || LANGMATCHES(lang(?label), lang(?match))";
967
        // if search language and UI/display language differ, must also consider case where there is no prefLabel in
968
        // the display language; in that case, should use the label with the same language as the matched label
969
        $labelcondFallback = ($searchLang != $lang) ?
970
          "OPTIONAL { # in case previous OPTIONAL block gives no labels\n" .
971
          "?s skos:prefLabel ?label . FILTER (LANGMATCHES(LANG(?label), LANG(?match))) }" : "";
972
973
        //  Including the labels if there is no query term given.
974
        if ($rawterm === '') {
975
          $labelClause = "?s skos:prefLabel ?label .";
976
          $labelClause = ($lang) ? $labelClause . " FILTER (LANGMATCHES(LANG(?label), '$lang'))" : $labelClause . "";
977
          return $labelClause . " BIND(?label AS ?match)";
978
        }
979
980
        $distinguishercondLabel = "FILTER (LANG(?distLabel) = LANG(?label))";
981
        // Only include distinguisher labels in case of a shared prefLabel
982
        $distinguisherClause = $distinguisher ?
983
            "OPTIONAL {\n" .
984
            "  ?s skos:prefLabel ?label . ?s2 skos:prefLabel ?label . FILTER(?s2 != ?s)\n" .
985
            "  ?s $distinguisher ?distinguisher.\n" .
986
            "  FILTER (!isLiteral(?distinguisher) || (LANG(?distinguisher) = LANG(?label)))" .
987
            "  OPTIONAL {\n" .
988
            "    ?distinguisher skos:prefLabel ?distLabel .\n" .
989
            "    $distinguishercondLabel\n" .
990
            "  }\n" .
991
            "  OPTIONAL {\n" .
992
            "    ?distinguisher rdfs:label ?distLabel .\n" .
993
            "    $distinguishercondLabel\n" .
994
            "  }\n" .
995
            "} BIND(COALESCE(?distLabel,STR(?distinguisher)) AS ?distcoal) " : "";
996
997
        /*
998
         * This query does some tricks to obtain a list of unique concepts.
999
         * From each match generated by the text index, a string such as
1000
         * "1en@example" is generated, where the first character is a number
1001
         * encoding the property and priority, then comes the language tag and
1002
         * finally the original literal after an @ sign. Of these, the MIN
1003
         * function is used to pick the best match for each concept. Finally,
1004
         * the structure is unpacked to get back the original string. Phew!
1005
         */
1006
        $hitvar = $unique ? '(MIN(?matchstr) AS ?hit)' : '(?matchstr AS ?hit)';
1007
        $hitgroup = $unique ? 'GROUP BY ?s ?label ?notation ?distcoal' : '';
1008
1009
        $langClause = $this->generateLangClause($searchLang);
1010
1011
        $query = <<<EOQ
1012
   SELECT DISTINCT ?s ?label ?notation ?distcoal $hitvar
1013
   WHERE {
1014
    $graphClause {
1015
     { 
1016
     $valuesProp
1017
     VALUES (?prop ?pri ?langParam) { (skos:prefLabel 1 $langClause) (skos:altLabel 3 $langClause) (skos:notation 5 '') (skos:hiddenLabel 7 $langClause)}
1018
     $textcond
1019
     ?s ?prop ?match }
1020
     OPTIONAL {
1021
      ?s skos:prefLabel ?label .
1022
      FILTER ($labelcondLabel)
1023
     } $labelcondFallback
1024
     BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri)
1025
     BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr)
1026
     OPTIONAL { ?s skos:notation ?notation }
1027
     $distinguisherClause
1028
    }
1029
    $filterGraph
1030
   }
1031
   $hitgroup
1032
   ORDER BY LCASE(?distcoal)
1033
EOQ;
1034
        return $query;
1035
    }
1036
    /**
1037
    *  This function can be overwritten in other SPARQL dialects for the possibility of handling the different language clauses
1038
     * @param string $lang
1039
     * @return string formatted language clause
1040
     */
1041
    protected function generateLangClause($lang) {
1042
        return "'$lang'";
1043
    }
1044
1045
    /**
1046
     * Query for concepts using a search term.
1047
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
1048
     * @param boolean $unique restrict results to unique concepts (default: false)
1049
     * @param boolean $showDeprecated whether to include deprecated concepts in search results (default: false)
1050
     * @param ConceptSearchParameters $params
1051
     * @return string sparql query
1052
     */
1053
    protected function generateConceptSearchQuery($fields, $unique, $params, $showDeprecated = false) {
1054
        $vocabs = $params->getVocabs();
1055
        $gcl = $this->graphClause;
1056
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
1057
        $formattedtype = $this->formatTypes($params->getTypeLimit());
1058
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);
1059
        $extravars = $formattedfields['extravars'];
1060
        $extrafields = $formattedfields['extrafields'];
1061
        $schemes = $params->getSchemeLimit();
1062
1063
        // limit the search to only requested concept schemes
1064
        $schemecond = '';
1065
        if (!empty($schemes)) {
1066
            $conditions = array();
1067
            foreach($schemes as $scheme) {
1068
                $conditions[] = "{?s skos:inScheme <$scheme>}";
1069
            }
1070
            $schemecond = '{'.implode(" UNION ",$conditions).'}';
1071
        }
1072
        $filterDeprecated="";
1073
        //show or hide deprecated concepts
1074
        if(!$showDeprecated){
1075
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
1076
        }
1077
        // extra conditions for parent and group, if specified
1078
        $parentcond = ($params->getParentLimit()) ? "?s skos:broader+ <" . $params->getParentLimit() . "> ." : "";
1079
        $groupcond = ($params->getGroupLimit()) ? "<" . $params->getGroupLimit() . "> skos:member ?s ." : "";
1080
        $pgcond = $parentcond . $groupcond;
1081
1082
        $orderextra = $this->isDefaultEndpoint() ? $this->graph : '';
1083
1084
        # make VALUES clauses
1085
        $props = array('skos:prefLabel', 'skos:altLabel');
1086
1087
        // Only search for distinguisher labels if single vocabulary search has been initialized and it is set
1088
        $distinguisher = (is_array($vocabs) && count($vocabs) === 1) ? $vocabs[0]->getConfig()->getResultDistinguisher() : '';
1089
1090
        //add notation into searchable data for the vocabularies which have been configured for it
1091
        if ($vocabs) {
1092
            $searchByNotation = false;
1093
            foreach ($vocabs as $vocab) {
1094
                if ($vocab->getConfig()->searchByNotation()) {
1095
                    $searchByNotation = true;
1096
                }
1097
            }
1098
            if ($searchByNotation) {
1099
                $props[] = 'skos:notation';
1100
            }
1101
        }
1102
1103
        if ($params->getHidden()) {
1104
            $props[] = 'skos:hiddenLabel';
1105
        }
1106
        $filterGraph = empty($vocabs) ? $this->formatFilterGraph($vocabs) : '';
1107
1108
        // remove futile asterisks from the search term
1109
        $term = $params->getSearchTerm();
1110
        while (strpos($term, '**') !== false) {
1111
            $term = str_replace('**', '*', $term);
1112
        }
1113
1114
        $labelpriority = <<<EOQ
1115
  FILTER(BOUND(?s))
1116
  BIND(STR(SUBSTR(?hit,1,1)) AS ?pri)
1117
  BIND(IF((SUBSTR(STRBEFORE(?hit, '@'),1) != ?pri), STRLANG(STRAFTER(?hit, '@'), SUBSTR(STRBEFORE(?hit, '@'),2)), STRAFTER(?hit, '@')) AS ?match)
1118
  BIND(IF((?pri = "1" || ?pri = "2") && ?match != ?label, ?match, ?unbound) as ?plabel)
1119
  BIND(IF((?pri = "3" || ?pri = "4"), ?match, ?unbound) as ?alabel)
1120
  BIND(IF((?pri = "7" || ?pri = "8"), ?match, ?unbound) as ?hlabel)
1121
EOQ;
1122
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique, $filterGraph, $distinguisher);
1123
        if ($params->getSearchTerm() === '*' || $params->getSearchTerm() === '') {
1124
          $labelpriority = '';
1125
        }
1126
        $query = <<<EOQ
1127
SELECT DISTINCT ?s ?label ?plabel ?alabel ?hlabel ?graph ?notation (GROUP_CONCAT(DISTINCT STR(?distcoal);separator='|||') as ?distLabels) (GROUP_CONCAT(DISTINCT STR(?type);separator=' ') as ?types) $extravars
1128
$fcl
1129
WHERE {
1130
 $gcl {
1131
  {
1132
  $innerquery
1133
  }
1134
  $labelpriority
1135
  $formattedtype
1136
  { $pgcond 
1137
   ?s a ?type .
1138
   $extrafields $schemecond
1139
  }
1140
  $filterDeprecated
1141
 }
1142
 $filterGraph
1143
}
1144
GROUP BY ?s ?match ?label ?plabel ?alabel ?hlabel ?notation ?distLabels ?graph
1145
ORDER BY LCASE(STR(?match)) LANG(?match) LCASE(STR(?distLabels)) $orderextra
1146
EOQ;
1147
        return $query;
1148
    }
1149
1150
    /**
1151
     * Transform a single concept search query results into the skosmos desired return format.
1152
     * @param $row SPARQL query result row
0 ignored issues
show
Bug introduced by
The type SPARQL was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1153
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1154
     * @return array query result object
1155
     */
1156
    private function transformConceptSearchResult($row, $vocabs, $fields)
1157
    {
1158
        $hit = array();
1159
        $hit['uri'] = $row->s->getUri();
1160
1161
        if (isset($row->graph)) {
1162
            $hit['graph'] = $row->graph->getUri();
1163
        }
1164
1165
        foreach (explode(" ", $row->types->getValue()) as $typeuri) {
1166
            $hit['type'][] = $this->shortenUri($typeuri);
1167
        }
1168
1169
        if(!empty($fields)) {
1170
            foreach ($fields as $prop) {
1171
                $propname = $prop . 's';
1172
                if (isset($row->$propname)) {
1173
                    foreach (explode("\n", $row->$propname->getValue()) as $line) {
1174
                        $rdata = str_getcsv($line, ',', '"', '"');
1175
                        $propvals = array();
1176
                        if ($rdata[0] != '') {
1177
                            $propvals['uri'] = $rdata[0];
1178
                        }
1179
                        if ($rdata[1] != '') {
1180
                            $propvals['prefLabel'] = $rdata[1];
1181
                        }
1182
                        if ($rdata[2] != '') {
1183
                            $propvals = $rdata[2];
1184
                        }
1185
1186
                        $hit['skos:' . $prop][] = $propvals;
1187
                    }
1188
                }
1189
            }
1190
        }
1191
1192
1193
        if (isset($row->preflabels)) {
1194
            foreach (explode("\n", $row->preflabels->getValue()) as $line) {
1195
                $pref = str_getcsv($line, ',', '"', '"');
1196
                $hit['prefLabels'][$pref[1]] = $pref[0];
1197
            }
1198
        }
1199
1200
        foreach ($vocabs as $vocab) { // looping the vocabulary objects and asking these for a localname for the concept.
1201
            $localname = $vocab->getLocalName($hit['uri']);
1202
            if ($localname !== $hit['uri']) { // only passing the result forward if the uri didn't boomerang right back.
1203
                $hit['localname'] = $localname;
1204
                break; // stopping the search when we find one that returns something valid.
1205
            }
1206
        }
1207
1208
        if (isset($row->label)) {
1209
            $hit['prefLabel'] = $row->label->getValue();
1210
        }
1211
1212
        if (isset($row->label)) {
1213
            $hit['lang'] = $row->label->getLang();
1214
        }
1215
1216
        if (isset($row->notation)) {
1217
            $hit['notation'] = $row->notation->getValue();
1218
        }
1219
1220
        if (isset($row->distLabels)) {
1221
            $hit['distinguisherLabels'] = explode("|||", $row->distLabels->getValue());
1222
        }
1223
1224
        if (isset($row->plabel)) {
1225
            $hit['matchedPrefLabel'] = $row->plabel->getValue();
1226
            $hit['lang'] = $row->plabel->getLang();
1227
        } elseif (isset($row->alabel)) {
1228
            $hit['altLabel'] = $row->alabel->getValue();
1229
            $hit['lang'] = $row->alabel->getLang();
1230
        } elseif (isset($row->hlabel)) {
1231
            $hit['hiddenLabel'] = $row->hlabel->getValue();
1232
            $hit['lang'] = $row->hlabel->getLang();
1233
        }
1234
        return $hit;
1235
    }
1236
1237
    /**
1238
     * Transform the concept search query results into the skosmos desired return format.
1239
     * @param EasyRdf\Sparql\Result $results
1240
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1241
     * @return array query result object
1242
     */
1243
    private function transformConceptSearchResults($results, $vocabs, $fields) {
1244
        $ret = array();
1245
1246
        foreach ($results as $row) {
1247
            if (!isset($row->s)) {
1248
                // don't break if query returns a single dummy result
1249
                continue;
1250
            }
1251
            $ret[] = $this->transformConceptSearchResult($row, $vocabs, $fields);
1252
        }
1253
        return $ret;
1254
    }
1255
1256
    /**
1257
     * Query for concepts using a search term.
1258
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1259
     * @param array $fields extra fields to include in the result (array of strings or null).
1260
     * @param boolean $unique restrict results to unique concepts
1261
     * @param ConceptSearchParameters $params
1262
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1263
     * @return array query result object
1264
     */
1265
    public function queryConcepts($vocabs, $fields, $unique, $params, $showDeprecated = false) {
1266
        $query = $this->generateConceptSearchQuery($fields, $unique, $params,$showDeprecated);
1267
        $results = $this->query($query);
1268
        return $this->transformConceptSearchResults($results, $vocabs, $fields);
1269
    }
1270
1271
    /**
1272
     * Generates sparql query clauses used for creating the alphabetical index.
1273
     * @param string $letter the letter (or special class) to search for
1274
     * @return array of sparql query clause strings
1275
     */
1276
    private function formatFilterConditions($letter, $lang) {
1277
        $useRegex = false;
1278
1279
        if ($letter == '*') {
1280
            $letter = '.*';
1281
            $useRegex = true;
1282
        } elseif ($letter == '0-9') {
1283
            $letter = '[0-9].*';
1284
            $useRegex = true;
1285
        } elseif ($letter == '!*') {
1286
            $letter = '[^\\\\p{L}\\\\p{N}].*';
1287
            $useRegex = true;
1288
        }
1289
1290
        # make text query clause
1291
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
1292
        if ($useRegex) {
1293
            $filtercondLabel = $lang ? "regex(str(?label), '^$letter$', 'i') && langMatches(lang(?label), '$lang')" : "regex(str(?label), '^$letter$', 'i')";
1294
            $filtercondALabel = $lang ? "regex(str(?alabel), '^$letter$', 'i') && langMatches(lang(?alabel), '$lang')" : "regex(str(?alabel), '^$letter$', 'i')";
1295
        } else {
1296
            $filtercondLabel = $lang ? "strstarts(lcase(str(?label)), '$lcletter') && langMatches(lang(?label), '$lang')" : "strstarts(lcase(str(?label)), '$lcletter')";
1297
            $filtercondALabel = $lang ? "strstarts(lcase(str(?alabel)), '$lcletter') && langMatches(lang(?alabel), '$lang')" : "strstarts(lcase(str(?alabel)), '$lcletter')";
1298
        }
1299
        return array('filterpref' => $filtercondLabel, 'filteralt' => $filtercondALabel);
1300
    }
1301
1302
    /**
1303
     * Generates the sparql query used for rendering the alphabetical index.
1304
     * @param string $letter the letter (or special class) to search for
1305
     * @param string $lang language of labels
1306
     * @param integer $limit limits the amount of results
1307
     * @param integer $offset offsets the result set
1308
     * @param array|null $classes
1309
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1310
     * @param \EasyRdf\Resource|null $qualifier alphabetical list qualifier resource or null (default: null)
1311
     * @return string sparql query
1312
     */
1313
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated = false, $qualifier = null) {
1314
        $gcl = $this->graphClause;
1315
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1316
        $values = $this->formatValues('?type', $classes, 'uri');
1317
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
1318
        $conditions = $this->formatFilterConditions($letter, $lang);
1319
        $filtercondLabel = $conditions['filterpref'];
1320
        $filtercondALabel = $conditions['filteralt'];
1321
        $qualifierClause = $qualifier ? "OPTIONAL { ?s <" . $qualifier->getURI() . "> ?qualifier }" : "";
1322
        $filterDeprecated="";
1323
        if(!$showDeprecated){
1324
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
1325
        }
1326
        $query = <<<EOQ
1327
SELECT DISTINCT ?s ?label ?alabel ?qualifier
1328
WHERE {
1329
  $gcl {
1330
    {
1331
      ?s skos:prefLabel ?label .
1332
      FILTER (
1333
        $filtercondLabel
1334
      )
1335
    }
1336
    UNION
1337
    {
1338
      {
1339
        ?s skos:altLabel ?alabel .
1340
        FILTER (
1341
          $filtercondALabel
1342
        )
1343
      }
1344
      {
1345
        ?s skos:prefLabel ?label .
1346
        FILTER (langMatches(lang(?label), '$lang'))
1347
      }
1348
    }
1349
    ?s a ?type .
1350
    $qualifierClause
1351
    $filterDeprecated
1352
    $values
1353
  }
1354
}
1355
ORDER BY LCASE(STR(COALESCE(?alabel, ?label))) STR(?s) LCASE(STR(?qualifier)) $limitandoffset
1356
EOQ;
1357
        return $query;
1358
    }
1359
1360
    /**
1361
     * Transforms the alphabetical list query results into an array format.
1362
     * @param EasyRdf\Sparql\Result $results
1363
     * @return array
1364
     */
1365
    private function transformAlphabeticalListResults($results) {
1366
        $ret = array();
1367
1368
        foreach ($results as $row) {
1369
            if (!isset($row->s)) {
1370
                continue;
1371
            }
1372
            // don't break if query returns a single dummy result
1373
1374
            $hit = array();
1375
            $hit['uri'] = $row->s->getUri();
1376
1377
            $hit['localname'] = $row->s->localName();
1378
1379
            $hit['prefLabel'] = $row->label->getValue();
1380
            $hit['lang'] = $row->label->getLang();
1381
1382
            if (isset($row->alabel)) {
1383
                $hit['altLabel'] = $row->alabel->getValue();
1384
                $hit['lang'] = $row->alabel->getLang();
1385
            }
1386
1387
            if (isset($row->qualifier)) {
1388
                if ($row->qualifier instanceof EasyRdf\Literal) {
1389
                    $hit['qualifier'] = $row->qualifier->getValue();
1390
                }
1391
                else {
1392
                    $hit['qualifier'] = $row->qualifier->localName();
1393
                }
1394
            }
1395
1396
            $ret[] = $hit;
1397
        }
1398
1399
        return $ret;
1400
    }
1401
1402
    /**
1403
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1404
     * '*!' (special characters) and '*' (everything) are accepted.
1405
     * @param string $letter the letter (or special class) to search for
1406
     * @param string $lang language of labels
1407
     * @param integer $limit limits the amount of results
1408
     * @param integer $offset offsets the result set
1409
     * @param array $classes
1410
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1411
     * @param \EasyRdf\Resource|null $qualifier alphabetical list qualifier resource or null (default: null)
1412
     */
1413
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null, $showDeprecated = false, $qualifier = null) {
1414
        if ($letter === '') {
1415
            return array(); // special case: no letter given, return empty list
1416
        }
1417
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated, $qualifier);
1418
        $results = $this->query($query);
1419
        return $this->transformAlphabeticalListResults($results);
1420
    }
1421
1422
    /**
1423
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1424
     * Note that we force the datatype of the result variable otherwise Virtuoso does not properly interpret the DISTINCT and we have duplicated results
1425
     * @param string $lang language
1426
     * @return string sparql query
1427
     */
1428
    private function generateFirstCharactersQuery($lang, $classes) {
1429
        $gcl = $this->graphClause;
1430
        $classes = (isset($classes) && sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1431
        $values = $this->formatValues('?type', $classes, 'uri');
1432
        $query = <<<EOQ
1433
SELECT DISTINCT (ucase(str(substr(?label, 1, 1))) as ?l) WHERE {
1434
  $gcl {
1435
    ?c skos:prefLabel ?label .
1436
    ?c a ?type
1437
    FILTER(langMatches(lang(?label), '$lang'))
1438
    $values
1439
  }
1440
}
1441
EOQ;
1442
        return $query;
1443
    }
1444
1445
    /**
1446
     * Transforms the first characters query results into an array format.
1447
     * @param EasyRdf\Sparql\Result $result
1448
     * @return array
1449
     */
1450
    private function transformFirstCharactersResults($result) {
1451
        $ret = array();
1452
        foreach ($result as $row) {
1453
            $ret[] = $row->l->getValue();
1454
        }
1455
        return $ret;
1456
    }
1457
1458
    /**
1459
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1460
     * @param string $lang language
1461
     * @return array array of characters
1462
     */
1463
    public function queryFirstCharacters($lang, $classes = null) {
1464
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1465
        $result = $this->query($query);
1466
        return $this->transformFirstCharactersResults($result);
1467
    }
1468
1469
    /**
1470
     * @param string $uri
1471
     * @param string $lang
1472
     * @return string sparql query string
1473
     */
1474
    private function generateLabelQuery($uri, $lang) {
1475
        $fcl = $this->generateFromClause();
1476
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1477
        $query = <<<EOQ
1478
SELECT ?label $fcl
1479
WHERE {
1480
  <$uri> a ?type .
1481
  OPTIONAL {
1482
    <$uri> skos:prefLabel ?label .
1483
    $labelcondLabel
1484
  }
1485
  OPTIONAL {
1486
    <$uri> rdfs:label ?label .
1487
    $labelcondLabel
1488
  }
1489
  OPTIONAL {
1490
    <$uri> dc:title ?label .
1491
    $labelcondLabel
1492
  }
1493
  OPTIONAL {
1494
    <$uri> dc11:title ?label .
1495
    $labelcondLabel
1496
  }
1497
}
1498
EOQ;
1499
        return $query;
1500
    }
1501
1502
1503
    /**
1504
     * @param string $uri
1505
     * @param string $lang
1506
     * @return string sparql query string
1507
     */
1508
    private function generateAllLabelsQuery($uri, $lang) {
1509
        $fcl = $this->generateFromClause();
1510
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?val), '$lang') )" : "";
1511
        $query = <<<EOQ
1512
SELECT DISTINCT ?prop ?val $fcl
1513
WHERE {
1514
  <$uri> a ?type .
1515
  OPTIONAL {
1516
      <$uri> ?prop ?val .
1517
      $labelcondLabel
1518
  }
1519
  VALUES ?prop { skos:prefLabel skos:altLabel skos:hiddenLabel }
1520
}
1521
EOQ;
1522
        return $query;
1523
    }
1524
1525
    /**
1526
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1527
     * @param string $uri
1528
     * @param string $lang
1529
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1530
     */
1531
    public function queryLabel($uri, $lang) {
1532
        $query = $this->generateLabelQuery($uri, $lang);
1533
        $result = $this->query($query);
1534
        $ret = array();
1535
        foreach ($result as $row) {
1536
            if (!isset($row->label)) {
1537
                // existing concept but no labels
1538
                return array();
1539
            }
1540
            $ret[$row->label->getLang()] = $row->label;
1541
        }
1542
1543
        if (sizeof($ret) > 0) {
1544
            // existing concept, with label(s)
1545
            return $ret;
1546
        } else {
1547
            // nonexistent concept
1548
            return null;
1549
        }
1550
    }
1551
1552
    /**
1553
     * Query for skos:prefLabels, skos:altLabels and skos:hiddenLabels of a resource.
1554
     * @param string $uri
1555
     * @param string $lang
1556
     * @return array array of prefLabels, altLabels and hiddenLabels - or null if resource doesn't exist
1557
     */
1558
    public function queryAllConceptLabels($uri, $lang) {
1559
        $query = $this->generateAllLabelsQuery($uri, $lang);
1560
        $result = $this->query($query);
1561
1562
        if ($result->numRows() == 0) {
0 ignored issues
show
Bug introduced by
The method numRows() does not exist on EasyRdf\Graph. ( Ignorable by Annotation )

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

1562
        if ($result->/** @scrutinizer ignore-call */ numRows() == 0) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1563
            // nonexistent concept
1564
            return null;
1565
        }
1566
1567
        $ret = array();
1568
        foreach ($result as $row) {
1569
            $labelName = $row->prop->localName();
1570
            if (isset($row->val)) {
1571
                $ret[$labelName][] = $row->val->getValue();
1572
            }
1573
        }
1574
        return $ret;
1575
    }
1576
1577
    /**
1578
     * Generates a SPARQL query to retrieve the super properties of a given property URI.
1579
     * Note this must be executed in the graph where this information is available.
1580
     * @param string $uri
1581
     * @return string sparql query string
1582
     */
1583
    private function generateSubPropertyOfQuery($uri) {
1584
        $fcl = $this->generateFromClause();
1585
        $query = <<<EOQ
1586
SELECT ?superProperty $fcl
1587
WHERE {
1588
  <$uri> rdfs:subPropertyOf ?superProperty
1589
}
1590
EOQ;
1591
        return $query;
1592
    }
1593
1594
    /**
1595
     * Query the super properties of a provided property URI.
1596
     * @param string $uri URI of a propertyes
1597
     * @return array array super properties, or null if none exist
1598
     */
1599
    public function querySuperProperties($uri) {
1600
        $query = $this->generateSubPropertyOfQuery($uri);
1601
        $result = $this->query($query);
1602
        $ret = array();
1603
        foreach ($result as $row) {
1604
            if (isset($row->superProperty)) {
1605
                $ret[] = $row->superProperty->getUri();
1606
            }
1607
1608
        }
1609
1610
        if (sizeof($ret) > 0) {
1611
            // return result
1612
            return $ret;
1613
        } else {
1614
            // no result, return null
1615
            return null;
1616
        }
1617
    }
1618
1619
1620
    /**
1621
     * Generates a sparql query for queryNotation.
1622
     * @param string $uri
1623
     * @return string sparql query
1624
     */
1625
    private function generateNotationQuery($uri) {
1626
        $fcl = $this->generateFromClause();
1627
1628
        $query = <<<EOQ
1629
SELECT * $fcl
1630
WHERE {
1631
  <$uri> skos:notation ?notation .
1632
}
1633
EOQ;
1634
        return $query;
1635
    }
1636
1637
    /**
1638
     * Query for the notation of the concept (skos:notation) of a resource.
1639
     * @param string $uri
1640
     * @return string notation or null if it doesn't exist
1641
     */
1642
    public function queryNotation($uri) {
1643
        $query = $this->generateNotationQuery($uri);
1644
        $result = $this->query($query);
1645
        foreach ($result as $row) {
1646
            if (isset($row->notation)) {
1647
                return $row->notation->getValue();
1648
            }
1649
        }
1650
        return null;
1651
    }
1652
1653
    /**
1654
     * Generates a sparql query for queryProperty.
1655
     * @param string $uri
1656
     * @param string $prop the name of the property eg. 'skos:broader'.
1657
     * @param string $lang
1658
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1659
     * @return string sparql query
1660
     */
1661
    private function generatePropertyQuery($uri, $prop, $lang, $anylang) {
1662
        $fcl = $this->generateFromClause();
1663
        $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1664
1665
        $query = <<<EOQ
1666
SELECT * $fcl
1667
WHERE {
1668
  <$uri> a skos:Concept .
1669
  OPTIONAL {
1670
    <$uri> $prop ?object .
1671
    OPTIONAL {
1672
      ?object skos:prefLabel ?label .
1673
      FILTER (langMatches(lang(?label), "$lang"))
1674
    }
1675
    OPTIONAL {
1676
      ?object skos:prefLabel ?label .
1677
      FILTER (lang(?label) = "")
1678
    }
1679
    $anylang
1680
  }
1681
}
1682
EOQ;
1683
        return $query;
1684
    }
1685
1686
    /**
1687
     * Transforms the sparql query result into an array or null if the concept doesn't exist.
1688
     * @param EasyRdf\Sparql\Result $result
1689
     * @param string $lang
1690
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1691
     */
1692
    private function transformPropertyQueryResults($result, $lang) {
1693
        $ret = array();
1694
        foreach ($result as $row) {
1695
            if (!isset($row->object)) {
1696
                return array();
1697
            }
1698
            // existing concept but no properties
1699
            if (isset($row->label)) {
1700
                if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1701
                    $ret[$row->object->getUri()]['label'] = $row->label->getValue();
1702
                }
1703
1704
            } else {
1705
                $ret[$row->object->getUri()]['label'] = null;
1706
            }
1707
        }
1708
        if (sizeof($ret) > 0) {
1709
            return $ret;
1710
        }
1711
        // existing concept, with properties
1712
        else {
1713
            return null;
1714
        }
1715
        // nonexistent concept
1716
    }
1717
1718
    /**
1719
     * Query a single property of a concept.
1720
     * @param string $uri
1721
     * @param string $prop the name of the property eg. 'skos:broader'.
1722
     * @param string $lang
1723
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1724
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1725
     */
1726
    public function queryProperty($uri, $prop, $lang, $anylang = false) {
1727
        $uri = is_array($uri) ? $uri[0] : $uri;
0 ignored issues
show
introduced by
The condition is_array($uri) is always false.
Loading history...
1728
        $query = $this->generatePropertyQuery($uri, $prop, $lang, $anylang);
1729
        $result = $this->query($query);
1730
        return $this->transformPropertyQueryResults($result, $lang);
1731
    }
1732
1733
    /**
1734
     * Query a single transitive property of a concept.
1735
     * @param string $uri
1736
     * @param array $props the name of the property eg. 'skos:broader'.
1737
     * @param string $lang
1738
     * @param integer $limit
1739
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1740
     * @return string sparql query
1741
     */
1742
    private function generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang) {
1743
        $uri = is_array($uri) ? $uri[0] : $uri;
0 ignored issues
show
introduced by
The condition is_array($uri) is always false.
Loading history...
1744
        $fcl = $this->generateFromClause();
1745
        $propertyClause = implode('|', $props);
1746
        $otherlang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1747
        // need to do a SPARQL subquery because LIMIT needs to be applied /after/
1748
        // the direct relationships have been collapsed into one string
1749
        $query = <<<EOQ
1750
SELECT * $fcl
1751
WHERE {
1752
  SELECT ?object ?label (GROUP_CONCAT(STR(?dir);separator=' ') as ?direct)
1753
  WHERE {
1754
    <$uri> a skos:Concept .
1755
    OPTIONAL {
1756
      <$uri> $propertyClause* ?object .
1757
      OPTIONAL {
1758
        ?object $propertyClause ?dir .
1759
      }
1760
    }
1761
    OPTIONAL {
1762
      ?object skos:prefLabel ?label .
1763
      FILTER (langMatches(lang(?label), "$lang"))
1764
    }
1765
    $otherlang
1766
  }
1767
  GROUP BY ?object ?label
1768
}
1769
LIMIT $limit
1770
EOQ;
1771
        return $query;
1772
    }
1773
1774
    /**
1775
     * Transforms the sparql query result object into an array.
1776
     * @param EasyRdf\Sparql\Result $result
1777
     * @param string $lang
1778
     * @param string $fallbacklang language to use if label is not available in the preferred language
1779
     * @return array of property values (key: URI, val: label), or null if concept doesn't exist
1780
     */
1781
    private function transformTransitivePropertyResults($result, $lang, $fallbacklang) {
1782
        $ret = array();
1783
        foreach ($result as $row) {
1784
            if (!isset($row->object)) {
1785
                return array();
1786
            }
1787
            // existing concept but no properties
1788
            if (isset($row->label)) {
1789
                $val = array('label' => $row->label->getValue());
1790
            } else {
1791
                $val = array('label' => null);
1792
            }
1793
            if (isset($row->direct) && $row->direct->getValue() != '') {
1794
                $val['direct'] = explode(' ', $row->direct->getValue());
1795
            }
1796
            // Preventing labels in a non preferred language overriding the preferred language.
1797
            if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (IssetNode && $row->labe...tUri(), $ret) === false, Probably Intended Meaning: IssetNode && ($row->labe...Uri(), $ret) === false)
Loading history...
1798
                if (!isset($row->label) || $row->label->getLang() === $lang) {
1799
                    $ret[$row->object->getUri()] = $val;
1800
                } elseif ($row->label->getLang() === $fallbacklang) {
1801
                    $val['label'] .= ' (' . $row->label->getLang() . ')';
1802
                    $ret[$row->object->getUri()] = $val;
1803
                }
1804
            }
1805
        }
1806
1807
        // second iteration of results to find labels for the ones that didn't have one in the preferred languages
1808
        foreach ($result as $row) {
1809
            if (isset($row->object) && array_key_exists($row->object->getUri(), $ret) === false) {
1810
                $val = array('label' => $row->label->getValue());
1811
                if (isset($row->direct) && $row->direct->getValue() != '') {
1812
                    $val['direct'] = explode(' ', $row->direct->getValue());
1813
                }
1814
                $ret[$row->object->getUri()] = $val;
1815
            }
1816
        }
1817
1818
        if (sizeof($ret) > 0) {
1819
            return $ret;
1820
        }
1821
        // existing concept, with properties
1822
        else {
1823
            return null;
1824
        }
1825
        // nonexistent concept
1826
    }
1827
1828
    /**
1829
     * Query a single transitive property of a concept.
1830
     * @param string $uri
1831
     * @param array $props the property/properties.
1832
     * @param string $lang
1833
     * @param string $fallbacklang language to use if label is not available in the preferred language
1834
     * @param integer $limit
1835
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1836
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1837
     */
1838
    public function queryTransitiveProperty($uri, $props, $lang, $limit, $anylang = false, $fallbacklang = '') {
1839
        $query = $this->generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang);
1840
        $result = $this->query($query);
1841
        return $this->transformTransitivePropertyResults($result, $lang, $fallbacklang);
1842
    }
1843
1844
    /**
1845
     * Generates the query for a concepts skos:narrowers.
1846
     * @param string $uri
1847
     * @param string $lang
1848
     * @param string $fallback
1849
     * @return string sparql query
1850
     */
1851
    private function generateChildQuery($uri, $lang, $fallback, $props) {
1852
        $uri = is_array($uri) ? $uri[0] : $uri;
0 ignored issues
show
introduced by
The condition is_array($uri) is always false.
Loading history...
1853
        $fcl = $this->generateFromClause();
1854
        $propertyClause = implode('|', $props);
1855
        $query = <<<EOQ
1856
SELECT ?child ?label ?child ?grandchildren ?notation $fcl WHERE {
1857
  <$uri> a skos:Concept .
1858
  OPTIONAL {
1859
    ?child $propertyClause <$uri> .
1860
    OPTIONAL {
1861
      ?child skos:prefLabel ?label .
1862
      FILTER (langMatches(lang(?label), "$lang"))
1863
    }
1864
    OPTIONAL {
1865
      ?child skos:prefLabel ?label .
1866
      FILTER (langMatches(lang(?label), "$fallback"))
1867
    }
1868
    OPTIONAL { # other language case
1869
      ?child skos:prefLabel ?label .
1870
    }
1871
    OPTIONAL {
1872
      ?child skos:notation ?notation .
1873
    }
1874
    BIND ( EXISTS { ?a $propertyClause ?child . } AS ?grandchildren )
1875
  }
1876
}
1877
EOQ;
1878
        return $query;
1879
    }
1880
1881
    /**
1882
     * Transforms the sparql result object into an array.
1883
     * @param EasyRdf\Sparql\Result $result
1884
     * @param string $lang
1885
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1886
     */
1887
    private function transformNarrowerResults($result, $lang) {
1888
        $ret = array();
1889
        foreach ($result as $row) {
1890
            if (!isset($row->child)) {
1891
                return array();
1892
            }
1893
            // existing concept but no children
1894
1895
            $label = null;
1896
            if (isset($row->label)) {
1897
                if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
1898
                    $label = $row->label->getValue();
1899
                } else {
1900
                    $label = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1901
                }
1902
1903
            }
1904
            $childArray = array(
1905
                'uri' => $row->child->getUri(),
1906
                'prefLabel' => $label,
1907
                'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1908
            );
1909
            if (isset($row->notation)) {
1910
                $childArray['notation'] = $row->notation->getValue();
1911
            }
1912
1913
            $ret[] = $childArray;
1914
        }
1915
        if (sizeof($ret) > 0) {
1916
            return $ret;
1917
        }
1918
        // existing concept, with children
1919
        else {
1920
            return null;
1921
        }
1922
        // nonexistent concept
1923
    }
1924
1925
    /**
1926
     * Query the narrower concepts of a concept.
1927
     * @param string $uri
1928
     * @param string $lang
1929
     * @param string $fallback
1930
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1931
     */
1932
    public function queryChildren($uri, $lang, $fallback, $props) {
1933
        $query = $this->generateChildQuery($uri, $lang, $fallback, $props);
1934
        $result = $this->query($query);
1935
        return $this->transformNarrowerResults($result, $lang);
1936
    }
1937
1938
    /**
1939
     * Query the top concepts of a vocabulary.
1940
     * @param string $conceptSchemes concept schemes whose top concepts to query for
1941
     * @param string $lang language of labels
1942
     * @param string $fallback language to use if label is not available in the preferred language
1943
     */
1944
    public function queryTopConcepts($conceptSchemes, $lang, $fallback) {
1945
        if (!is_array($conceptSchemes)) {
0 ignored issues
show
introduced by
The condition is_array($conceptSchemes) is always false.
Loading history...
1946
            $conceptSchemes = array($conceptSchemes);
1947
        }
1948
1949
        $values = $this->formatValues('?topuri', $conceptSchemes, 'uri');
1950
1951
        $fcl = $this->generateFromClause();
1952
        $query = <<<EOQ
1953
SELECT DISTINCT ?top ?topuri ?label ?notation ?children $fcl WHERE {
1954
  ?top skos:topConceptOf ?topuri .
1955
  OPTIONAL {
1956
    ?top skos:prefLabel ?label .
1957
    FILTER (langMatches(lang(?label), "$lang"))
1958
  }
1959
  OPTIONAL {
1960
    ?top skos:prefLabel ?label .
1961
    FILTER (langMatches(lang(?label), "$fallback"))
1962
  }
1963
  OPTIONAL { # fallback - other language case
1964
    ?top skos:prefLabel ?label .
1965
  }
1966
  OPTIONAL { ?top skos:notation ?notation . }
1967
  BIND ( EXISTS { ?top skos:narrower ?a . } AS ?children )
1968
  $values
1969
}
1970
EOQ;
1971
        $result = $this->query($query);
1972
        $ret = array();
1973
        foreach ($result as $row) {
1974
            if (isset($row->top) && isset($row->label)) {
1975
                $label = $row->label->getValue();
1976
                if ($row->label->getLang() && $row->label->getLang() !== $lang && strpos($row->label->getLang(), $lang . "-") !== 0) {
1977
                    $label .= ' (' . $row->label->getLang() . ')';
1978
                }
1979
                $top = array('uri' => $row->top->getUri(), 'topConceptOf' => $row->topuri->getUri(), 'label' => $label, 'hasChildren' => filter_var($row->children->getValue(), FILTER_VALIDATE_BOOLEAN));
1980
                if (isset($row->notation)) {
1981
                    $top['notation'] = $row->notation->getValue();
1982
                }
1983
1984
                $ret[] = $top;
1985
            }
1986
        }
1987
1988
        return $ret;
1989
    }
1990
1991
    /**
1992
     * Generates a sparql query for finding the hierarchy for a concept.
1993
	 * A concept may be a top concept in multiple schemes, returned as a single whitespace-separated literal.
1994
     * @param string $uri concept uri.
1995
     * @param string $lang
1996
     * @param string $fallback language to use if label is not available in the preferred language
1997
     * @return string sparql query
1998
     */
1999
    private function generateParentListQuery($uri, $lang, $fallback, $props) {
2000
        $fcl = $this->generateFromClause();
2001
        $propertyClause = implode('|', $props);
2002
        $query = <<<EOQ
2003
SELECT ?broad ?parent ?children ?grandchildren
2004
(SAMPLE(?lab) as ?label) (SAMPLE(?childlab) as ?childlabel) (GROUP_CONCAT(?topcs; separator=" ") as ?tops) 
2005
(SAMPLE(?nota) as ?notation) (SAMPLE(?childnota) as ?childnotation) $fcl
2006
WHERE {
2007
  <$uri> a skos:Concept .
2008
  OPTIONAL {
2009
    <$uri> $propertyClause* ?broad .
2010
    OPTIONAL {
2011
      ?broad skos:prefLabel ?lab .
2012
      FILTER (langMatches(lang(?lab), "$lang"))
2013
    }
2014
    OPTIONAL {
2015
      ?broad skos:prefLabel ?lab .
2016
      FILTER (langMatches(lang(?lab), "$fallback"))
2017
    }
2018
    OPTIONAL { # fallback - other language case
2019
      ?broad skos:prefLabel ?lab .
2020
    }
2021
    OPTIONAL { ?broad skos:notation ?nota . }
2022
    OPTIONAL { ?broad $propertyClause ?parent . }
2023
    OPTIONAL { ?broad skos:narrower ?children .
2024
      OPTIONAL {
2025
        ?children skos:prefLabel ?childlab .
2026
        FILTER (langMatches(lang(?childlab), "$lang"))
2027
      }
2028
      OPTIONAL {
2029
        ?children skos:prefLabel ?childlab .
2030
        FILTER (langMatches(lang(?childlab), "$fallback"))
2031
      }
2032
      OPTIONAL { # fallback - other language case
2033
        ?children skos:prefLabel ?childlab .
2034
      }
2035
      OPTIONAL {
2036
        ?children skos:notation ?childnota .
2037
      }
2038
    }
2039
    BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren )
2040
    OPTIONAL { ?broad skos:topConceptOf ?topcs . }
2041
  }
2042
}
2043
GROUP BY ?broad ?parent ?member ?children ?grandchildren
2044
EOQ;
2045
        return $query;
2046
    }
2047
2048
    /**
2049
     * Transforms the result into an array.
2050
     * @param EasyRdf\Sparql\Result
2051
     * @param string $lang
2052
     * @return array|null an array for the REST controller to encode.
2053
     */
2054
    private function transformParentListResults($result, $lang)
2055
    {
2056
        $ret = array();
2057
        foreach ($result as $row) {
2058
            if (!isset($row->broad)) {
2059
                // existing concept but no broaders
2060
                return array();
2061
            }
2062
            $uri = $row->broad->getUri();
2063
            if (!isset($ret[$uri])) {
2064
                $ret[$uri] = array('uri' => $uri);
2065
            }
2066
            if (isset($row->exact)) {
2067
                $ret[$uri]['exact'] = $row->exact->getUri();
2068
            }
2069
            if (isset($row->tops)) {
2070
               $topConceptsList=explode(" ", $row->tops->getValue());
2071
               // sort to guarantee an alphabetical ordering of the URI
2072
               sort($topConceptsList);
2073
               $ret[$uri]['tops'] = $topConceptsList;
2074
            }
2075
            if (isset($row->children)) {
2076
                if (!isset($ret[$uri]['narrower'])) {
2077
                    $ret[$uri]['narrower'] = array();
2078
                }
2079
2080
                $label = null;
2081
                if (isset($row->childlabel)) {
2082
                    $label = $row->childlabel->getValue();
2083
                    if ($row->childlabel->getLang() !== $lang && strpos($row->childlabel->getLang(), $lang . "-") !== 0) {
2084
                        $label .= " (" . $row->childlabel->getLang() . ")";
2085
                    }
2086
2087
                }
2088
2089
                $childArr = array(
2090
                    'uri' => $row->children->getUri(),
2091
                    'label' => $label,
2092
                    'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
2093
                );
2094
                if (isset($row->childnotation)) {
2095
                    $childArr['notation'] = $row->childnotation->getValue();
2096
                }
2097
2098
                if (!in_array($childArr, $ret[$uri]['narrower'])) {
2099
                    $ret[$uri]['narrower'][] = $childArr;
2100
                }
2101
2102
            }
2103
            if (isset($row->label)) {
2104
                $preflabel = $row->label->getValue();
2105
                if ($row->label->getLang() && $row->label->getLang() !== $lang && strpos($row->label->getLang(), $lang . "-") !== 0) {
2106
                    $preflabel .= ' (' . $row->label->getLang() . ')';
2107
                }
2108
2109
                $ret[$uri]['prefLabel'] = $preflabel;
2110
            }
2111
            if (isset($row->notation)) {
2112
                $ret[$uri]['notation'] = $row->notation->getValue();
2113
            }
2114
2115
            if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) {
2116
                $ret[$uri]['broader'][] = $row->parent->getUri();
2117
            } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) {
2118
                $ret[$uri]['broader'][] = $row->parent->getUri();
2119
            }
2120
        }
2121
        if (sizeof($ret) > 0) {
2122
            // existing concept, with children
2123
            return $ret;
2124
        }
2125
        else {
2126
            // nonexistent concept
2127
            return null;
2128
        }
2129
    }
2130
2131
    /**
2132
     * Query for finding the hierarchy for a concept.
2133
     * @param string $uri concept uri.
2134
     * @param string $lang
2135
     * @param string $fallback language to use if label is not available in the preferred language
2136
     * @param array $props the hierarchy property/properties to use
2137
     * @return an array for the REST controller to encode.
2138
     */
2139
    public function queryParentList($uri, $lang, $fallback, $props) {
2140
        $query = $this->generateParentListQuery($uri, $lang, $fallback, $props);
2141
        $result = $this->query($query);
2142
        return $this->transformParentListResults($result, $lang);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->transformP...Results($result, $lang) also could return the type array which is incompatible with the documented return type an.
Loading history...
2143
    }
2144
2145
    /**
2146
     * return a list of concept group instances, sorted by label
2147
     * @param string $groupClass URI of concept group class
2148
     * @param string $lang language of labels to return
2149
     * @return string sparql query
2150
     */
2151
    private function generateConceptGroupsQuery($groupClass, $lang) {
2152
        $fcl = $this->generateFromClause();
2153
        $query = <<<EOQ
2154
SELECT ?group (GROUP_CONCAT(DISTINCT STR(?child);separator=' ') as ?children) ?label ?members ?notation $fcl
2155
WHERE {
2156
  ?group a <$groupClass> .
2157
  OPTIONAL { ?group skos:member|isothes:subGroup ?child .
2158
             ?child a <$groupClass> }
2159
  BIND(EXISTS{?group skos:member ?submembers} as ?members)
2160
  OPTIONAL { ?group skos:prefLabel ?label }
2161
  OPTIONAL { ?group rdfs:label ?label }
2162
  FILTER (langMatches(lang(?label), '$lang'))
2163
  OPTIONAL { ?group skos:notation ?notation }
2164
}
2165
GROUP BY ?group ?label ?members ?notation
2166
ORDER BY lcase(?label)
2167
EOQ;
2168
        return $query;
2169
    }
2170
2171
    /**
2172
     * Transforms the sparql query result into an array.
2173
     * @param EasyRdf\Sparql\Result $result
2174
     * @return array
2175
     */
2176
    private function transformConceptGroupsResults($result) {
2177
        $ret = array();
2178
        foreach ($result as $row) {
2179
            if (!isset($row->group)) {
2180
                # no groups found, see issue #357
2181
                continue;
2182
            }
2183
            $group = array('uri' => $row->group->getURI());
2184
            if (isset($row->label)) {
2185
                $group['prefLabel'] = $row->label->getValue();
2186
            }
2187
2188
            if (isset($row->children)) {
2189
                $group['childGroups'] = explode(' ', $row->children->getValue());
2190
            }
2191
2192
            if (isset($row->members)) {
2193
                $group['hasMembers'] = $row->members->getValue();
2194
            }
2195
2196
            if (isset($row->notation)) {
2197
                $group['notation'] = $row->notation->getValue();
2198
            }
2199
2200
            $ret[] = $group;
2201
        }
2202
        return $ret;
2203
    }
2204
2205
    /**
2206
     * return a list of concept group instances, sorted by label
2207
     * @param string $groupClass URI of concept group class
2208
     * @param string $lang language of labels to return
2209
     * @return array Result array with group URI as key and group label as value
2210
     */
2211
    public function listConceptGroups($groupClass, $lang) {
2212
        $query = $this->generateConceptGroupsQuery($groupClass, $lang);
2213
        $result = $this->query($query);
2214
        return $this->transformConceptGroupsResults($result);
2215
    }
2216
2217
    /**
2218
     * Generates the sparql query for listConceptGroupContents
2219
     * @param string $groupClass URI of concept group class
2220
     * @param string $group URI of the concept group instance
2221
     * @param string $lang language of labels to return
2222
     * @param boolean $showDeprecated whether to include deprecated in the result
2223
     * @return string sparql query
2224
     */
2225
    private function generateConceptGroupContentsQuery($groupClass, $group, $lang, $showDeprecated = false) {
2226
        $fcl = $this->generateFromClause();
2227
        $filterDeprecated="";
2228
        if(!$showDeprecated){
2229
            $filterDeprecated="  FILTER NOT EXISTS { ?conc owl:deprecated true }";
2230
        }
2231
        $query = <<<EOQ
2232
SELECT ?conc ?super ?label ?members ?type ?notation $fcl
2233
WHERE {
2234
 <$group> a <$groupClass> .
2235
 { <$group> skos:member ?conc . } UNION { ?conc isothes:superGroup <$group> }
2236
$filterDeprecated
2237
 ?conc a ?type .
2238
 OPTIONAL { ?conc skos:prefLabel ?label .
2239
  FILTER (langMatches(lang(?label), '$lang'))
2240
 }
2241
 OPTIONAL { ?conc skos:prefLabel ?label . }
2242
 OPTIONAL { ?conc skos:notation ?notation }
2243
 BIND(EXISTS{?submembers isothes:superGroup ?conc} as ?super)
2244
 BIND(EXISTS{?conc skos:member ?submembers} as ?members)
2245
} ORDER BY lcase(?label)
2246
EOQ;
2247
        return $query;
2248
    }
2249
2250
    /**
2251
     * Transforms the sparql query result into an array.
2252
     * @param EasyRdf\Sparql\Result $result
2253
     * @param string $lang language of labels to return
2254
     * @return array
2255
     */
2256
    private function transformConceptGroupContentsResults($result, $lang) {
2257
        $ret = array();
2258
        $values = array();
2259
        foreach ($result as $row) {
2260
            if (!array_key_exists($row->conc->getURI(), $values)) {
2261
                $values[$row->conc->getURI()] = array(
2262
                    'uri' => $row->conc->getURI(),
2263
                    'isSuper' => $row->super->getValue(),
2264
                    'hasMembers' => $row->members->getValue(),
2265
                    'type' => array($row->type->shorten()),
2266
                );
2267
                if (isset($row->label)) {
2268
                    if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
2269
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue();
2270
                    } else {
2271
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue() . " (" . $row->label->getLang() . ")";
2272
                    }
2273
2274
                }
2275
                if (isset($row->notation)) {
2276
                    $values[$row->conc->getURI()]['notation'] = $row->notation->getValue();
2277
                }
2278
2279
            } else {
2280
                $values[$row->conc->getURI()]['type'][] = $row->type->shorten();
2281
            }
2282
        }
2283
2284
        foreach ($values as $val) {
2285
            $ret[] = $val;
2286
        }
2287
2288
        return $ret;
2289
    }
2290
2291
    /**
2292
     * return a list of concepts in a concept group
2293
     * @param string $groupClass URI of concept group class
2294
     * @param string $group URI of the concept group instance
2295
     * @param string $lang language of labels to return
2296
     * @param boolean $showDeprecated whether to include deprecated concepts in search results
2297
     * @return array Result array with concept URI as key and concept label as value
2298
     */
2299
    public function listConceptGroupContents($groupClass, $group, $lang,$showDeprecated = false) {
2300
        $query = $this->generateConceptGroupContentsQuery($groupClass, $group, $lang,$showDeprecated);
2301
        $result = $this->query($query);
2302
        return $this->transformConceptGroupContentsResults($result, $lang);
2303
    }
2304
2305
    /**
2306
     * Generates the sparql query for queryChangeList.
2307
     * @param string $prop the property uri pointing to timestamps, eg. 'dc:modified'
2308
     * @param string $lang language of labels to return
2309
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2310
     * @param int $limit maximum number of results to return
2311
     * @param boolean $showDeprecated whether to include deprecated concepts in the change list
2312
     * @return string sparql query
2313
     */
2314
    private function generateChangeListQuery($prop, $lang, $offset, $limit=200, $showDeprecated=false) {
2315
        $fcl = $this->generateFromClause();
2316
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
2317
2318
        //Additional clauses when deprecated concepts need to be included in the results
2319
        $deprecatedOptions = '';
2320
        $deprecatedVars = '';
2321
        if ($showDeprecated) {
2322
            $deprecatedVars = '?replacedBy ?deprecated ?replacingLabel';
2323
            $deprecatedOptions = <<<EOQ
2324
UNION {
2325
    ?concept owl:deprecated True; dc:modified ?date2 .
2326
    BIND(True as ?deprecated)
2327
    BIND(COALESCE(?date2, ?date) AS ?date)
2328
    OPTIONAL {
2329
        ?concept dc:isReplacedBy ?replacedBy .
2330
        OPTIONAL {
2331
            ?replacedBy skos:prefLabel ?replacingLabel .
2332
            FILTER (langMatches(lang(?replacingLabel), '$lang'))
2333
        }
2334
    }
2335
}
2336
EOQ;
2337
        }
2338
2339
        $query = <<<EOQ
2340
SELECT ?concept ?date ?label $deprecatedVars $fcl
2341
WHERE {
2342
    ?concept a skos:Concept ;
2343
    skos:prefLabel ?label .
2344
    FILTER (langMatches(lang(?label), '$lang'))
2345
    {
2346
        ?concept $prop ?date .
2347
        MINUS { ?concept owl:deprecated True . }
2348
    }
2349
    $deprecatedOptions
2350
}
2351
ORDER BY DESC(YEAR(?date)) DESC(MONTH(?date)) LCASE(?label) DESC(?concept)
2352
LIMIT $limit $offset
2353
EOQ;
2354
2355
        return $query;
2356
    }
2357
2358
    /**
2359
     * Transforms the sparql query result into an array.
2360
     * @param EasyRdf\Sparql\Result $result
2361
     * @return array
2362
     */
2363
    private function transformChangeListResults($result) {
2364
        $ret = array();
2365
        foreach ($result as $row) {
2366
            $concept = array('uri' => $row->concept->getURI());
2367
            if (isset($row->label)) {
2368
                $concept['prefLabel'] = $row->label->getValue();
2369
            }
2370
2371
            if (isset($row->date)) {
2372
                try {
2373
                    $concept['date'] = $row->date->getValue();
2374
                } catch (Exception $e) {
2375
                    //don't record concepts with malformed dates e.g. 1986-21-00
2376
                    continue;
2377
                }
2378
            }
2379
2380
            if (isset($row->deprecated)) {
2381
                $concept['deprecated'] = $row->deprecated->getValue();
2382
            } else {
2383
                $concept['deprecated'] = false;
2384
            }
2385
            if (isset($row->replacedBy)) {
2386
                $concept['replacedBy'] = $row->replacedBy->getURI();
2387
            }
2388
            if (isset($row->replacingLabel)) {
2389
                $concept['replacingLabel'] = $row->replacingLabel->getValue();
2390
            }
2391
2392
            $ret[] = $concept;
2393
        }
2394
        return $ret;
2395
    }
2396
2397
    /**
2398
     * return a list of recently changed or entirely new concepts
2399
     * @param string $prop the property uri pointing to timestamps, eg. 'dc:modified'
2400
     * @param string $lang language of labels to return
2401
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2402
     * @param int $limit maximum number of results to return
2403
     * @param boolean $showDeprecated whether to include deprecated concepts in the change list
2404
     * @return array Result array
2405
     */
2406
    public function queryChangeList($prop, $lang, $offset, $limit, $showDeprecated=false) {
2407
        $query = $this->generateChangeListQuery($prop, $lang, $offset, $limit, $showDeprecated);
2408
2409
        $result = $this->query($query);
2410
        return $this->transformChangeListResults($result);
2411
    }
2412
}
2413