Passed
Push — sparql-query-cache ( d29b6d )
by Osma
06:54
created

GenericSparql::transformAlphabeticalListResults()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 35
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 19
nc 8
nop 1
dl 0
loc 35
rs 9.0111
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
    /**
9
     * A SPARQL Client eg. an EasyRDF instance.
10
     * @property EasyRdf\Sparql\Client $client
11
     */
12
    protected $client;
13
    /**
14
     * Graph uri.
15
     * @property object $graph
16
     */
17
    protected $graph;
18
    /**
19
     * A SPARQL query graph part template.
20
     * @property string $graphClause
21
     */
22
    protected $graphClause;
23
    /**
24
     * Model instance.
25
     * @property Model $model
26
     */
27
    protected $model;
28
29
    /**
30
     * Cache used to avoid expensive shorten() calls
31
     * @property array $qnamecache
32
     */
33
    private $qnamecache = array();
34
35
    /**
36
     * Cache used to avoid duplicate SPARQL queries. The cache must be
37
     * static so that all GenericSparql instances have access to the
38
     * same shared cache.
39
     * @property array $querycache
40
     */
41
    private static $querycache = array();
42
43
    /**
44
     * Requires the following three parameters.
45
     * @param string $endpoint SPARQL endpoint address.
46
     * @param string|null $graph Which graph to query: Either an URI, the special value "?graph"
47
     *                           to use the default graph, or NULL to not use a GRAPH clause.
48
     * @param object $model a Model instance.
49
     */
50
    public function __construct($endpoint, $graph, $model)
51
    {
52
        $this->graph = $graph;
53
        $this->model = $model;
54
55
        // create the EasyRDF SPARQL client instance to use
56
        $this->initializeHttpClient();
57
        $this->client = new EasyRdf\Sparql\Client($endpoint);
58
59
        // set graphClause so that it can be used by all queries
60
        if ($this->isDefaultEndpoint()) { // default endpoint; query any graph (and catch it in a variable)
61
            $this->graphClause = "GRAPH $graph";
62
        } elseif ($graph !== null) { // query a specific graph
63
            $this->graphClause = "GRAPH <$graph>";
64
        } else { // query the default graph
65
            $this->graphClause = "";
66
        }
67
68
    }
69
70
    /**
71
     * Returns prefix-definitions for a query
72
     *
73
     * @param string $query
74
     * @return string
75
    */
76
    protected function generateQueryPrefixes($query)
77
    {
78
        // Check for undefined prefixes
79
        $prefixes = '';
80
        foreach (EasyRdf\RdfNamespace::namespaces() as $prefix => $uri) {
81
            if (strpos($query, "{$prefix}:") !== false and
82
                strpos($query, "PREFIX {$prefix}:") === false
83
            ) {
84
                $prefixes .= "PREFIX {$prefix}: <{$uri}>\n";
85
            }
86
        }
87
        return $prefixes;
88
    }
89
90
    /**
91
     * Execute the SPARQL query using the SPARQL client, logging it as well.
92
     * @param string $query SPARQL query to perform
93
     * @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...
94
     */
95
    protected function doQuery($query)
96
    {
97
        $queryId = sprintf("%05d", rand(0, 99999));
98
        $logger = $this->model->getLogger();
99
        $logger->info("[qid $queryId] SPARQL query:\n" . $this->generateQueryPrefixes($query) . "\n$query\n");
100
        $starttime = microtime(true);
101
        $result = $this->client->query($query);
102
        $elapsed = intval(round((microtime(true) - $starttime) * 1000));
103
        if (method_exists($result, 'numRows')) {
104
            $numRows = $result->numRows();
105
            $logger->info("[qid $queryId] result: $numRows rows returned in $elapsed ms");
106
        } else { // graph result
107
            $numTriples = $result->countTriples();
108
            $logger->info("[qid $queryId] result: $numTriples triples returned in $elapsed ms");
109
        }
110
        return $result;
111
    }
112
113
114
    /**
115
     * Execute the SPARQL query, if not found in query cache.
116
     * @param string $query SPARQL query to perform
117
     * @return \EasyRdf\Sparql\Result|\EasyRdf\Graph query result
118
     */
119
    protected function query($query)
120
    {
121
        $key = $this->endpoint . " " . $query;
0 ignored issues
show
Bug Best Practice introduced by
The property endpoint does not exist on GenericSparql. Did you maybe forget to declare it?
Loading history...
122
        if (!array_key_exists($key, self::$querycache)) {
123
            self::$querycache[$key] = $this->doQuery($query);
124
        }
125
        return self::$querycache[$key];
126
    }
127
128
129
    /**
130
     * Generates FROM clauses for the queries
131
     * @param Vocabulary[]|null $vocabs
132
     * @return string
133
     */
134
    protected function generateFromClause($vocabs = null)
135
    {
136
        $clause = '';
137
        if (!$vocabs) {
138
            return $this->graph !== '?graph' && $this->graph !== null ? "FROM <$this->graph>" : '';
139
        }
140
        $graphs = $this->getVocabGraphs($vocabs);
0 ignored issues
show
Bug introduced by
The method getVocabGraphs() does not exist on GenericSparql. ( Ignorable by Annotation )

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

140
        /** @scrutinizer ignore-call */ 
141
        $graphs = $this->getVocabGraphs($vocabs);

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...
141
        foreach ($graphs as $graph) {
142
            $clause .= "FROM NAMED <$graph> ";
143
        }
144
        return $clause;
145
    }
146
147
    protected function initializeHttpClient()
148
    {
149
        // configure the HTTP client used by EasyRdf\Sparql\Client
150
        $httpclient = EasyRdf\Http::getDefaultHttpClient();
151
        $httpclient->setConfig(array('timeout' => $this->model->getConfig()->getSparqlTimeout(),
152
                                     'useragent' => 'Skosmos'));
153
154
        // if special cache control (typically no-cache) was requested by the
155
        // client, set the same type of cache control headers also in subsequent
156
        // in the SPARQL requests (this is useful for performance testing)
157
        // @codeCoverageIgnoreStart
158
        $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...
159
        $pragma = filter_input(INPUT_SERVER, 'HTTP_PRAGMA', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
160
        if ($cacheControl !== null || $pragma !== null) {
161
            $val = $pragma !== null ? $pragma : $cacheControl;
162
            $httpclient->setHeaders('Cache-Control', $val);
163
        }
164
        // @codeCoverageIgnoreEnd
165
166
        EasyRdf\Http::setDefaultHttpClient($httpclient); // actually redundant..
167
    }
168
169
    /**
170
     * Return true if this is the default SPARQL endpoint, used as the facade to query
171
     * all vocabularies.
172
     */
173
174
    protected function isDefaultEndpoint()
175
    {
176
        return !is_null($this->graph) && $this->graph[0] == '?';
177
    }
178
179
    /**
180
     * Returns the graph instance
181
     * @return object EasyRDF graph instance.
182
     */
183
    public function getGraph()
184
    {
185
        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...
186
    }
187
188
    /**
189
     * Shorten a URI
190
     * @param string $uri URI to shorten
191
     * @return string shortened URI, or original URI if it cannot be shortened
192
     */
193
    private function shortenUri($uri)
194
    {
195
        if (!array_key_exists($uri, $this->qnamecache)) {
196
            $res = new EasyRdf\Resource($uri);
197
            $qname = $res->shorten(); // returns null on failure
198
            // only URIs in the SKOS namespace are shortened
199
            $this->qnamecache[$uri] = ($qname !== null && strpos($qname, "skos:") === 0) ? $qname : $uri;
200
        }
201
        return $this->qnamecache[$uri];
202
    }
203
204
205
    /**
206
     * Generates the sparql query for retrieving concept and collection counts in a vocabulary.
207
     * @return string sparql query
208
     */
209
    private function generateCountConceptsQuery($array, $group)
210
    {
211
        $fcl = $this->generateFromClause();
212
        $optional = $array ? "(<$array>) " : '';
213
        $optional .= $group ? "(<$group>)" : '';
214
        $query = <<<EOQ
215
      SELECT (COUNT(DISTINCT(?conc)) as ?c) ?type ?typelabel (COUNT(?depr) as ?deprcount) $fcl WHERE {
216
        VALUES (?value) { (skos:Concept) (skos:Collection) $optional }
217
  	    ?type rdfs:subClassOf* ?value
218
        { ?type ^a ?conc .
219
          OPTIONAL { ?conc owl:deprecated ?depr .
220
  		    FILTER (?depr = True)
221
          }
222
        } UNION {SELECT * WHERE {
223
            ?type rdfs:label ?typelabel
224
          }
225
        }
226
      } GROUP BY ?type ?typelabel
227
EOQ;
228
        return $query;
229
    }
230
231
    /**
232
     * Used for transforming the concept count query results.
233
     * @param EasyRdf\Sparql\Result $result query results to be transformed
234
     * @param string $lang language of labels
235
     * @return Array containing the label counts
236
     */
237
    private function transformCountConceptsResults($result, $lang)
238
    {
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
    {
269
        $query = $this->generateCountConceptsQuery($array, $group);
270
        $result = $this->query($query);
271
        return $this->transformCountConceptsResults($result, $lang);
272
    }
273
274
    /**
275
     * @param array $langs Languages to query for
276
     * @param string[] $props property names
277
     * @return string sparql query
278
     */
279
    private function generateCountLangConceptsQuery($langs, $classes, $props)
280
    {
281
        $gcl = $this->graphClause;
282
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
283
284
        $quote_string = function ($val) { return "'$val'"; };
285
        $quoted_values = array_map($quote_string, $langs);
286
        $langFilter = "FILTER(?lang IN (" . implode(',', $quoted_values) . "))";
287
288
        $values = $this->formatValues('?type', $classes, 'uri');
289
        $valuesProp = $this->formatValues('?prop', $props, null);
290
291
        $query = <<<EOQ
292
SELECT ?lang ?prop
293
  (COUNT(?label) as ?count)
294
WHERE {
295
  $gcl {
296
    $values
297
    $valuesProp
298
    ?conc a ?type .
299
    ?conc ?prop ?label .
300
    BIND(LANG(?label) AS ?lang)
301
    $langFilter
302
  }
303
}
304
GROUP BY ?lang ?prop ?type
305
EOQ;
306
        return $query;
307
    }
308
309
    /**
310
     * Transforms the CountLangConcepts results into an array of label counts.
311
     * @param EasyRdf\Sparql\Result $result query results to be transformed
312
     * @param array $langs Languages to query for
313
     * @param string[] $props property names
314
     */
315
    private function transformCountLangConceptsResults($result, $langs, $props)
316
    {
317
        $ret = array();
318
        // set default count to zero; overridden below if query found labels
319
        foreach ($langs as $lang) {
320
            foreach ($props as $prop) {
321
                $ret[$lang][$prop] = 0;
322
            }
323
        }
324
        foreach ($result as $row) {
325
            if (isset($row->lang) && isset($row->prop) && isset($row->count)) {
326
                $ret[$row->lang->getValue()][$row->prop->shorten()] +=
327
                $row->count->getValue();
328
            }
329
330
        }
331
        ksort($ret);
332
        return $ret;
333
    }
334
335
    /**
336
     * Counts the number of concepts in a easyRDF graph with a specific language.
337
     * @param array $langs Languages to query for
338
     * @return Array containing count of concepts for each language and property.
339
     */
340
    public function countLangConcepts($langs, $classes = null)
341
    {
342
        $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel');
343
        $query = $this->generateCountLangConceptsQuery($langs, $classes, $props);
344
        // Count the number of terms in each language
345
        $result = $this->query($query);
346
        return $this->transformCountLangConceptsResults($result, $langs, $props);
347
    }
348
349
    /**
350
     * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one
351
     * of the constants given.
352
     * @param string $varname variable name, e.g. "?uri"
353
     * @param array $values the values
354
     * @param string $type type of values: "uri", "literal" or null (determines quoting style)
355
     */
356
    protected function formatValues($varname, $values, $type = null)
357
    {
358
        $constants = array();
359
        foreach ($values as $val) {
360
            if ($type == 'uri') {
361
                $val = "<$val>";
362
            }
363
364
            if ($type == 'literal') {
365
                $val = "'$val'";
366
            }
367
368
            $constants[] = "($val)";
369
        }
370
        $values = implode(" ", $constants);
371
372
        return "VALUES ($varname) { $values }";
373
    }
374
375
    /**
376
     * Filters multiple instances of the same vocabulary from the input array.
377
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
378
     * @return \Vocabulary[]
379
     */
380
    private function filterDuplicateVocabs($vocabs)
381
    {
382
        // filtering duplicates
383
        $uniqueVocabs = array();
384
        if ($vocabs !== null && sizeof($vocabs) > 0) {
385
            foreach ($vocabs as $voc) {
386
                $uniqueVocabs[$voc->getId()] = $voc;
387
            }
388
        }
389
390
        return $uniqueVocabs;
391
    }
392
393
    /**
394
     * Generates a sparql query for one or more concept URIs
395
     * @param mixed $uris concept URI (string) or array of URIs
396
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
397
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
398
     * @return string sparql query
399
     */
400
    private function generateConceptInfoQuery($uris, $arrayClass, $vocabs)
0 ignored issues
show
Unused Code introduced by
The method generateConceptInfoQuery() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
401
    {
402
        $gcl = $this->graphClause;
403
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
404
        $values = $this->formatValues('?uri', $uris, 'uri');
405
        $uniqueVocabs = $this->filterDuplicateVocabs($vocabs);
406
        $valuesGraph = empty($vocabs) ? $this->formatValuesGraph($uniqueVocabs) : '';
0 ignored issues
show
Bug introduced by
The method formatValuesGraph() does not exist on GenericSparql. Did you maybe mean formatValues()? ( Ignorable by Annotation )

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

406
        $valuesGraph = empty($vocabs) ? $this->/** @scrutinizer ignore-call */ formatValuesGraph($uniqueVocabs) : '';

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...
Unused Code introduced by
The assignment to $valuesGraph is dead and can be removed.
Loading history...
407
408
        if ($arrayClass === null) {
409
            $construct = $optional = "";
0 ignored issues
show
Unused Code introduced by
The assignment to $optional is dead and can be removed.
Loading history...
410
        } else {
411
            // add information that can be used to format narrower concepts by
412
            // the array they belong to ("milk by source animal" use case)
413
            $construct = "\n ?x skos:member ?o . ?x skos:prefLabel ?xl . ?x a <$arrayClass> .";
414
            $optional = "\n OPTIONAL {
415
                      ?x skos:member ?o .
416
                      ?x a <$arrayClass> .
417
                      ?x skos:prefLabel ?xl .
418
                      FILTER NOT EXISTS {
419
                        ?x skos:member ?other .
420
                        MINUS { ?other skos:broader ?uri }
421
                      }
422
                    }";
423
        }
424
        $query = <<<EOQ
425
CONSTRUCT {
426
 ?s ?p ?uri .
427
 ?sp ?uri ?op .
428
 ?uri ?p ?o .
429
 ?p rdfs:label ?proplabel .
430
 ?p rdfs:comment ?propcomm .
431
 ?p skos:definition ?propdef .
432
 ?p rdfs:subPropertyOf ?pp .
433
 ?pp rdfs:label ?plabel .
434
 ?o a ?ot .
435
 ?o skos:prefLabel ?opl .
436
 ?o rdfs:label ?ol .
437
 ?o rdf:value ?ov .
438
 ?o skos:notation ?on .
439
 ?o ?oprop ?oval .
440
 ?o ?xlprop ?xlval .
441
 ?dt rdfs:label ?dtlabel .
442
 ?directgroup skos:member ?uri .
443
 ?parent skos:member ?group .
444
 ?group skos:prefLabel ?grouplabel .
445
 ?b1 rdf:first ?item .
446
 ?b1 rdf:rest ?b2 .
447
 ?item a ?it .
448
 ?item skos:prefLabel ?il .
449
 ?group a ?grouptype . $construct
450
} $fcl WHERE {
451
 $values
452
 $gcl {
453
  {
454
    ?s ?p ?uri .
455
    FILTER(!isBlank(?s))
456
    FILTER(?p != skos:inScheme)
457
    FILTER NOT EXISTS { ?s owl:deprecated true . }
458
  }
459
  UNION
460
  { ?sp ?uri ?op . }
461
  UNION
462
  {
463
    ?directgroup skos:member ?uri .
464
    ?group skos:member+ ?uri .
465
    ?group skos:prefLabel ?grouplabel .
466
    ?group a ?grouptype .
467
    OPTIONAL { ?parent skos:member ?group }
468
  }
469
  UNION
470
  {
471
   ?uri ?p ?o .
472
   OPTIONAL {
473
     ?uri skos:notation ?nVal .
474
     FILTER(isLiteral(?nVal))
475
     BIND(datatype(?nVal) AS ?dt)
476
     ?dt rdfs:label ?dtlabel
477
   }
478
   OPTIONAL {
479
     ?o rdf:rest* ?b1 .
480
     ?b1 rdf:first ?item .
481
     ?b1 rdf:rest ?b2 .
482
     OPTIONAL { ?item a ?it . }
483
     OPTIONAL { ?item skos:prefLabel ?il . }
484
   }
485
   OPTIONAL {
486
     { ?p rdfs:label ?proplabel . }
487
     UNION
488
     { ?p rdfs:comment ?propcomm . }
489
     UNION
490
     { ?p skos:definition ?propdef . }
491
     UNION
492
     { ?p rdfs:subPropertyOf ?pp . }
493
   }
494
   OPTIONAL {
495
     { ?o a ?ot . }
496
     UNION
497
     { ?o skos:prefLabel ?opl . }
498
     UNION
499
     { ?o rdfs:label ?ol . }
500
     UNION
501
     { ?o rdf:value ?ov . 
502
       OPTIONAL { ?o ?oprop ?oval . }
503
     }
504
     UNION
505
     { ?o skos:notation ?on . }
506
     UNION
507
     { ?o a skosxl:Label .
508
       ?o ?xlprop ?xlval }
509
   } $optional
510
  }
511
 }
512
}
513
$valuesGraph
514
EOQ;
515
        return $query;
516
    }
517
518
    /**
519
     * Transforms ConceptInfo query results into an array of Concept objects
520
     * @param EasyRdf\Graph $result query results to be transformed
521
     * @param array $uris concept URIs
522
     * @param \Vocabulary[] $vocabs array of Vocabulary object
523
     * @param string|null $clang content language
524
     * @return Concept[] array of Concept objects
525
     */
526
    private function transformConceptInfoResults($result, $uris, $vocabs, $clang)
527
    {
528
        $conceptArray = array();
529
        foreach ($uris as $index => $uri) {
530
            $conc = $result->resource($uri);
531
            if (is_array($vocabs)) {
532
                $vocab = (sizeof($vocabs) == 1) ? $vocabs[0] : $vocabs[$index];
533
            } else {
534
                $vocab = null;
535
            }
536
            $conceptArray[] = new Concept($this->model, $vocab, $conc, $result, $clang);
537
        }
538
        return $conceptArray;
539
    }
540
541
    /**
542
     * Returns information (as a graph) for one or more concept URIs
543
     * @param mixed $uris concept URI (string) or array of URIs
544
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
545
     * @param \Vocabulary[]|null $vocabs vocabularies to target
546
     * @return \EasyRdf\Graph
547
     */
548
    public function queryConceptInfoGraph($uris, $arrayClass = null, $vocabs = array())
549
    {
550
        // if just a single URI is given, put it in an array regardless
551
        if (!is_array($uris)) {
552
            $uris = array($uris);
553
        }
554
555
        $query = $this->generateConceptInfoQuery($uris, $arrayClass, $vocabs);
556
        $result = $this->query($query);
557
        return $result;
558
    }
559
560
    /**
561
     * Returns information (as an array of Concept objects) for one or more concept URIs
562
     * @param mixed $uris concept URI (string) or array of URIs
563
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
564
     * @param \Vocabulary[] $vocabs vocabularies to target
565
     * @param string|null $clang content language
566
     * @return Concept[]
567
     */
568
    public function queryConceptInfo($uris, $arrayClass = null, $vocabs = array(), $clang = null)
569
    {
570
        // if just a single URI is given, put it in an array regardless
571
        if (!is_array($uris)) {
572
            $uris = array($uris);
573
        }
574
        $result = $this->queryConceptInfoGraph($uris, $arrayClass, $vocabs);
575
        if ($result->isEmpty()) {
576
            return [];
577
        }
578
        return $this->transformConceptInfoResults($result, $uris, $vocabs, $clang);
579
    }
580
581
    /**
582
     * Generates the sparql query for queryTypes
583
     * @param string $lang
584
     * @return string sparql query
585
     */
586
    private function generateQueryTypesQuery($lang)
587
    {
588
        $fcl = $this->generateFromClause();
589
        $query = <<<EOQ
590
SELECT DISTINCT ?type ?label ?superclass $fcl
591
WHERE {
592
  {
593
    { BIND( skos:Concept as ?type ) }
594
    UNION
595
    { BIND( skos:Collection as ?type ) }
596
    UNION
597
    { BIND( isothes:ConceptGroup as ?type ) }
598
    UNION
599
    { BIND( isothes:ThesaurusArray as ?type ) }
600
    UNION
601
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Concept . }
602
    UNION
603
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Collection . }
604
  }
605
  OPTIONAL {
606
    ?type rdfs:label ?label .
607
    FILTER(langMatches(lang(?label), '$lang'))
608
  }
609
  OPTIONAL {
610
    ?type rdfs:subClassOf ?superclass .
611
  }
612
  FILTER EXISTS {
613
    ?s a ?type .
614
    ?s skos:prefLabel ?prefLabel .
615
  }
616
}
617
EOQ;
618
        return $query;
619
    }
620
621
    /**
622
     * Transforms the results into an array format.
623
     * @param EasyRdf\Sparql\Result $result
624
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
625
     */
626
    private function transformQueryTypesResults($result)
627
    {
628
        $ret = array();
629
        foreach ($result as $row) {
630
            $type = array();
631
            if (isset($row->label)) {
632
                $type['label'] = $row->label->getValue();
633
            }
634
635
            if (isset($row->superclass)) {
636
                $type['superclass'] = $row->superclass->getUri();
637
            }
638
639
            $ret[$row->type->getURI()] = $type;
640
        }
641
        return $ret;
642
    }
643
644
    /**
645
     * Retrieve information about types from the endpoint
646
     * @param string $lang
647
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
648
     */
649
    public function queryTypes($lang)
650
    {
651
        $query = $this->generateQueryTypesQuery($lang);
652
        $result = $this->query($query);
653
        return $this->transformQueryTypesResults($result);
654
    }
655
656
    /**
657
     * Generates the concept scheme query.
658
     * @param string $conceptscheme concept scheme URI
659
     * @return string sparql query
660
     */
661
    private function generateQueryConceptSchemeQuery($conceptscheme)
662
    {
663
        $fcl = $this->generateFromClause();
664
        $query = <<<EOQ
665
CONSTRUCT {
666
  <$conceptscheme> ?property ?value .
667
} $fcl WHERE {
668
  <$conceptscheme> ?property ?value .
669
  FILTER (?property != skos:hasTopConcept)
670
}
671
EOQ;
672
        return $query;
673
    }
674
675
    /**
676
     * Retrieves conceptScheme information from the endpoint.
677
     * @param string $conceptscheme concept scheme URI
678
     * @return \EasyRdf\Sparql\Result|\EasyRdf\Graph query result graph
679
     */
680
    public function queryConceptScheme($conceptscheme)
681
    {
682
        $query = $this->generateQueryConceptSchemeQuery($conceptscheme);
683
        return $this->query($query);
684
    }
685
686
    /**
687
     * Generates the queryConceptSchemes sparql query.
688
     * @param string $lang language of labels
689
     * @return string sparql query
690
     */
691
    private function generateQueryConceptSchemesQuery($lang)
692
    {
693
        $fcl = $this->generateFromClause();
694
        $query = <<<EOQ
695
SELECT ?cs ?label ?preflabel ?title ?domain ?domainLabel $fcl
696
WHERE {
697
 ?cs a skos:ConceptScheme .
698
 OPTIONAL{
699
    ?cs dcterms:subject ?domain.
700
    ?domain skos:prefLabel ?domainLabel.
701
    FILTER(langMatches(lang(?domainLabel), '$lang'))
702
}
703
 OPTIONAL {
704
   ?cs rdfs:label ?label .
705
   FILTER(langMatches(lang(?label), '$lang'))
706
 }
707
 OPTIONAL {
708
   ?cs skos:prefLabel ?preflabel .
709
   FILTER(langMatches(lang(?preflabel), '$lang'))
710
 }
711
 OPTIONAL {
712
   { ?cs dc11:title ?title }
713
   UNION
714
   { ?cs dc:title ?title }
715
   FILTER(langMatches(lang(?title), '$lang'))
716
 }
717
} 
718
ORDER BY ?cs
719
EOQ;
720
        return $query;
721
    }
722
723
    /**
724
     * Transforms the queryConceptScheme results into an array format.
725
     * @param EasyRdf\Sparql\Result $result
726
     * @return array
727
     */
728
    private function transformQueryConceptSchemesResults($result)
729
    {
730
        $ret = array();
731
        foreach ($result as $row) {
732
            $conceptscheme = array();
733
            if (isset($row->label)) {
734
                $conceptscheme['label'] = $row->label->getValue();
735
            }
736
737
            if (isset($row->preflabel)) {
738
                $conceptscheme['prefLabel'] = $row->preflabel->getValue();
739
            }
740
741
            if (isset($row->title)) {
742
                $conceptscheme['title'] = $row->title->getValue();
743
            }
744
            // add dct:subject and their labels in the result
745
            if (isset($row->domain) && isset($row->domainLabel)) {
746
                $conceptscheme['subject']['uri'] = $row->domain->getURI();
747
                $conceptscheme['subject']['prefLabel'] = $row->domainLabel->getValue();
748
            }
749
750
            $ret[$row->cs->getURI()] = $conceptscheme;
751
        }
752
        return $ret;
753
    }
754
755
    /**
756
     * return a list of skos:ConceptScheme instances in the given graph
757
     * @param string $lang language of labels
758
     * @return array Array with concept scheme URIs (string) as keys and labels (string) as values
759
     */
760
    public function queryConceptSchemes($lang)
761
    {
762
        $query = $this->generateQueryConceptSchemesQuery($lang);
763
        $result = $this->query($query);
764
        return $this->transformQueryConceptSchemesResults($result);
765
    }
766
767
    /**
768
     * Generate a VALUES clause for limiting the targeted graphs.
769
     * @param Vocabulary[]|null $vocabs the vocabularies to target
770
     * @return string[] array of graph URIs
771
     */
772
    protected function getVocabGraphs($vocabs)
773
    {
774
        if ($vocabs === null || sizeof($vocabs) == 0) {
775
            // searching from all vocabularies - limit to known graphs
776
            $vocabs = $this->model->getVocabularies();
777
        }
778
        $graphs = array();
779
        foreach ($vocabs as $voc) {
780
            $graph = $voc->getGraph();
781
            if (!is_null($graph) && !in_array($graph, $graphs)) {
782
                $graphs[] = $graph;
783
            }
784
        }
785
        return $graphs;
786
    }
787
788
    /**
789
     * Generate a VALUES clause for limiting the targeted graphs.
790
     * @param Vocabulary[]|null $vocabs array of Vocabulary objects to target
791
     * @return string VALUES clause, or "" if not necessary to limit
792
     */
793
    protected function formatValuesGraph($vocabs)
794
    {
795
        if (!$this->isDefaultEndpoint()) {
796
            return "";
797
        }
798
        $graphs = $this->getVocabGraphs($vocabs);
799
        return $this->formatValues('?graph', $graphs, 'uri');
800
    }
801
802
    /**
803
     * Generate a FILTER clause for limiting the targeted graphs.
804
     * @param array $vocabs array of Vocabulary objects to target
805
     * @return string FILTER clause, or "" if not necessary to limit
806
     */
807
    protected function formatFilterGraph($vocabs)
808
    {
809
        if (!$this->isDefaultEndpoint()) {
810
            return "";
811
        }
812
        $graphs = $this->getVocabGraphs($vocabs);
813
        $values = array();
814
        foreach ($graphs as $graph) {
815
            $values[] = "<$graph>";
816
        }
817
        if (count($values)) {
818
            return "FILTER (?graph IN (" . implode(',', $values) . "))";
819
        }
820
    }
821
822
    /**
823
     * Formats combined limit and offset clauses for the sparql query
824
     * @param int $limit maximum number of hits to retrieve; 0 for unlimited
825
     * @param int $offset offset of results to retrieve; 0 for beginning of list
826
     * @return string sparql query clauses
827
     */
828
    protected function formatLimitAndOffset($limit, $offset)
829
    {
830
        $limit = ($limit) ? 'LIMIT ' . $limit : '';
831
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
832
        // eliminating whitespace and line changes when the conditions aren't needed.
833
        $limitandoffset = '';
834
        if ($limit && $offset) {
835
            $limitandoffset = "\n" . $limit . "\n" . $offset;
836
        } elseif ($limit) {
837
            $limitandoffset = "\n" . $limit;
838
        } elseif ($offset) {
839
            $limitandoffset = "\n" . $offset;
840
        }
841
842
        return $limitandoffset;
843
    }
844
845
    /**
846
     * Formats a sparql query clause for limiting the search to specific concept types.
847
     * @param array $types limit search to concepts of the given type(s)
848
     * @return string sparql query clause
849
     */
850
    protected function formatTypes($types)
851
    {
852
        $typePatterns = array();
853
        if (!empty($types)) {
854
            foreach ($types as $type) {
855
                $unprefixed = EasyRdf\RdfNamespace::expand($type);
856
                $typePatterns[] = "{ ?s a <$unprefixed> }";
857
            }
858
        }
859
860
        return implode(' UNION ', $typePatterns);
861
    }
862
863
    /**
864
     * @param string $prop property to include in the result eg. 'broader' or 'narrower'
865
     * @return string sparql query clause
866
     */
867
    private function formatPropertyCsvClause($prop)
868
    {
869
        # This expression creates a CSV row containing pairs of (uri,prefLabel) values.
870
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
871
        $clause = <<<EOV
872
(GROUP_CONCAT(DISTINCT CONCAT(
873
 '"', IF(isIRI(?$prop),STR(?$prop),''), '"', ',',
874
 '"', REPLACE(IF(BOUND(?{$prop}lab),?{$prop}lab,''), '"', '""'), '"', ',',
875
 '"', REPLACE(IF(isLiteral(?{$prop}),?{$prop},''), '"', '""'), '"'
876
); separator='\\n') as ?{$prop}s)
877
EOV;
878
        return $clause;
879
    }
880
881
    /**
882
     * @return string sparql query clause
883
     */
884
    private function formatPrefLabelCsvClause()
885
    {
886
        # This expression creates a CSV row containing pairs of (prefLabel, lang) values.
887
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
888
        $clause = <<<EOV
889
(GROUP_CONCAT(DISTINCT CONCAT(
890
 '"', STR(?pref), '"', ',', '"', lang(?pref), '"'
891
); separator='\\n') as ?preflabels)
892
EOV;
893
        return $clause;
894
    }
895
896
    /**
897
     * @param string $lang language code of the returned labels
898
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
899
     * @return array sparql query clause
900
     */
901
    protected function formatExtraFields($lang, $fields)
902
    {
903
        // extra variable expressions to request and extra fields to query for
904
        $ret = array('extravars' => '', 'extrafields' => '');
905
906
        if ($fields === null) {
907
            return $ret;
908
        }
909
910
        if (in_array('prefLabel', $fields)) {
911
            $ret['extravars'] .= $this->formatPreflabelCsvClause();
912
            $ret['extrafields'] .= <<<EOF
913
OPTIONAL {
914
  ?s skos:prefLabel ?pref .
915
}
916
EOF;
917
            // removing the prefLabel from the fields since it has been handled separately
918
            $fields = array_diff($fields, array('prefLabel'));
919
        }
920
921
        foreach ($fields as $field) {
922
            $ret['extravars'] .= $this->formatPropertyCsvClause($field);
923
            $ret['extrafields'] .= <<<EOF
924
OPTIONAL {
925
  ?s skos:$field ?$field .
926
  FILTER(!isLiteral(?$field)||langMatches(lang(?{$field}), '$lang'))
927
  OPTIONAL { ?$field skos:prefLabel ?{$field}lab . FILTER(langMatches(lang(?{$field}lab), '$lang')) }
928
}
929
EOF;
930
        }
931
932
        return $ret;
933
    }
934
935
    /**
936
     * Generate condition for matching labels in SPARQL
937
     * @param string $term search term
938
     * @param string $searchLang language code used for matching labels (null means any language)
939
     * @return string sparql query snippet
940
     */
941
    protected function generateConceptSearchQueryCondition($term, $searchLang)
942
    {
943
        # use appropriate matching function depending on query type: =, strstarts, strends or full regex
944
        if (preg_match('/^[^\*]+$/', $term)) { // exact query
945
            $term = str_replace('\\', '\\\\', $term); // quote slashes
946
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
947
            $filtercond = "LCASE(STR(?match)) = '$term'";
948
        } elseif (preg_match('/^[^\*]+\*$/', $term)) { // prefix query
949
            $term = substr($term, 0, -1); // remove the final asterisk
950
            $term = str_replace('\\', '\\\\', $term); // quote slashes
951
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
952
            $filtercond = "STRSTARTS(LCASE(STR(?match)), '$term')";
953
        } elseif (preg_match('/^\*[^\*]+$/', $term)) { // suffix query
954
            $term = substr($term, 1); // remove the preceding asterisk
955
            $term = str_replace('\\', '\\\\', $term); // quote slashes
956
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
957
            $filtercond = "STRENDS(LCASE(STR(?match)), '$term')";
958
        } else { // too complicated - have to use a regex
959
            # make sure regex metacharacters are not passed through
960
            $term = str_replace('\\', '\\\\', preg_quote($term));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $term seems to be never defined.
Loading history...
961
            $term = str_replace('\\\\*', '.*', $term); // convert asterisk to regex syntax
962
            $term = str_replace('\'', '\\\'', $term); // ensure single quotes are quoted
963
            $filtercond = "REGEX(STR(?match), '^$term$', 'i')";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $filtercond seems to be never defined.
Loading history...
964
        }
965
966
        $labelcondMatch = ($searchLang) ? "&& (?prop = skos:notation || LANGMATCHES(lang(?match), ?langParam))" : "";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $labelcondMatch seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $searchLang seems to be never defined.
Loading history...
967
968
        return "?s ?prop ?match . FILTER ($filtercond $labelcondMatch)";
969
    }
970
971
972
    /**
973
     * Inner query for concepts using a search term.
974
     * @param string $term search term
975
     * @param string $lang language code of the returned labels
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $lang seems to be never defined.
Loading history...
976
     * @param string $searchLang language code used for matching labels (null means any language)
977
     * @param string[] $props properties to target e.g. array('skos:prefLabel','skos:altLabel')
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $props seems to be never defined.
Loading history...
978
     * @param boolean $unique restrict results to unique concepts (default: false)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $unique does not exist. Did you maybe mean $uniqueVocabs?
Loading history...
979
     * @return string sparql query
980
     */
981
    protected function generateConceptSearchQueryInner($term, $lang, $searchLang, $props, $unique, $filterGraph)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $filterGraph seems to be never defined.
Loading history...
982
    {
983
        $valuesProp = $this->formatValues('?prop', $props);
0 ignored issues
show
Bug Best Practice introduced by
The property formatValues does not exist on GenericSparql. Did you maybe forget to declare it?
Loading history...
Comprehensibility Best Practice introduced by
The variable $valuesProp does not exist. Did you maybe mean $values?
Loading history...
984
        $textcond = $this->generateConceptSearchQueryCondition($term, $searchLang);
0 ignored issues
show
Bug Best Practice introduced by
The property generateConceptSearchQueryCondition does not exist on GenericSparql. Did you maybe forget to declare it?
Loading history...
Comprehensibility Best Practice introduced by
The variable $textcond seems to be never defined.
Loading history...
985
986
        $rawterm = str_replace(array('\\', '*', '"'), array('\\\\', '', '\"'), $term);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $rawterm seems to be never defined.
Loading history...
987
        // graph clause, if necessary
988
        $graphClause = $filterGraph != '' ? 'GRAPH ?graph' : '';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $graphClause seems to be never defined.
Loading history...
989
990
        // extra conditions for label language, if specified
991
        $labelcondLabel = ($lang) ? "LANGMATCHES(lang(?label), '$lang')" : "lang(?match) = '' || LANGMATCHES(lang(?label), lang(?match))";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $labelcondLabel seems to be never defined.
Loading history...
992
        // if search language and UI/display language differ, must also consider case where there is no prefLabel in
993
        // the display language; in that case, should use the label with the same language as the matched label
994
        $labelcondFallback = ($searchLang != $lang) ?
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $labelcondFallback seems to be never defined.
Loading history...
995
          "OPTIONAL { # in case previous OPTIONAL block gives no labels\n" .
996
          "?s skos:prefLabel ?label . FILTER (LANGMATCHES(LANG(?label), LANG(?match))) }" : "";
997
998
        //  Including the labels if there is no query term given.
999
        if ($rawterm === '') {
1000
            $labelClause = "?s skos:prefLabel ?label .";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $labelClause seems to be never defined.
Loading history...
1001
            $labelClause = ($lang) ? $labelClause . " FILTER (LANGMATCHES(LANG(?label), '$lang'))" : $labelClause . "";
1002
            return $labelClause . " BIND(?label AS ?match)";
1003
        }
1004
1005
        /*
1006
         * This query does some tricks to obtain a list of unique concepts.
1007
         * From each match generated by the text index, a string such as
1008
         * "1en@example" is generated, where the first character is a number
1009
         * encoding the property and priority, then comes the language tag and
1010
         * finally the original literal after an @ sign. Of these, the MIN
1011
         * function is used to pick the best match for each concept. Finally,
1012
         * the structure is unpacked to get back the original string. Phew!
1013
         */
1014
        $hitvar = $unique ? '(MIN(?matchstr) AS ?hit)' : '(?matchstr AS ?hit)';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $hitvar seems to be never defined.
Loading history...
1015
        $hitgroup = $unique ? 'GROUP BY ?s ?label ?notation' : '';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $hitgroup seems to be never defined.
Loading history...
1016
1017
        $langClause = $this->generateLangClause($searchLang);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $langClause seems to be never defined.
Loading history...
Bug Best Practice introduced by
The property generateLangClause does not exist on GenericSparql. Did you maybe forget to declare it?
Loading history...
1018
1019
        $query = <<<EOQ
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $query seems to be never defined.
Loading history...
1020
   SELECT DISTINCT ?s ?label ?notation $hitvar
1021
   WHERE {
1022
    $graphClause {
1023
     { 
1024
     $valuesProp
1025
     VALUES (?prop ?pri ?langParam) { (skos:prefLabel 1 $langClause) (skos:altLabel 3 $langClause) (skos:notation 5 '') (skos:hiddenLabel 7 $langClause)}
1026
     $textcond
1027
     ?s ?prop ?match }
1028
     OPTIONAL {
1029
      ?s skos:prefLabel ?label .
1030
      FILTER ($labelcondLabel)
1031
     } $labelcondFallback
1032
     BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri)
1033
     BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr)
1034
     OPTIONAL { ?s skos:notation ?notation }
1035
    }
1036
    $filterGraph
1037
   }
1038
   $hitgroup
1039
EOQ;
1040
        return $query;
1041
    }
1042
    /**
1043
    *  This function can be overwritten in other SPARQL dialects for the possibility of handling the different language clauses
1044
     * @param string $lang
1045
     * @return string formatted language clause
1046
     */
1047
    protected function generateLangClause($lang)
1048
    {
1049
        return "'$lang'";
1050
    }
1051
1052
    /**
1053
     * Query for concepts using a search term.
1054
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
1055
     * @param boolean $unique restrict results to unique concepts (default: false)
1056
     * @param boolean $showDeprecated whether to include deprecated concepts in search results (default: false)
1057
     * @param ConceptSearchParameters $params
1058
     * @return string sparql query
1059
     */
1060
    protected function generateConceptSearchQuery($fields, $unique, $params, $showDeprecated = false)
1061
    {
1062
        $vocabs = $params->getVocabs();
1063
        $gcl = $this->graphClause;
1064
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
1065
        $formattedtype = $this->formatTypes($params->getTypeLimit());
0 ignored issues
show
Bug introduced by
The method formatTypes() does not exist on GenericSparql. ( Ignorable by Annotation )

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

1065
        /** @scrutinizer ignore-call */ 
1066
        $formattedtype = $this->formatTypes($params->getTypeLimit());

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...
1066
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);
0 ignored issues
show
Bug introduced by
The method formatExtraFields() does not exist on GenericSparql. ( Ignorable by Annotation )

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

1066
        /** @scrutinizer ignore-call */ 
1067
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);

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...
1067
        $extravars = $formattedfields['extravars'];
1068
        $extrafields = $formattedfields['extrafields'];
1069
        $schemes = $params->getSchemeLimit();
1070
1071
        // limit the search to only requested concept schemes
1072
        $schemecond = '';
1073
        if (!empty($schemes)) {
1074
            $conditions = array();
1075
            foreach ($schemes as $scheme) {
1076
                $conditions[] = "{?s skos:inScheme <$scheme>}";
1077
            }
1078
            $schemecond = '{'.implode(" UNION ", $conditions).'}';
1079
        }
1080
        $filterDeprecated = "";
1081
        //show or hide deprecated concepts
1082
        if (!$showDeprecated) {
1083
            $filterDeprecated = "FILTER NOT EXISTS { ?s owl:deprecated true }";
1084
        }
1085
        // extra conditions for parent and group, if specified
1086
        $parentcond = ($params->getParentLimit()) ? "?s skos:broader+ <" . $params->getParentLimit() . "> ." : "";
1087
        $groupcond = ($params->getGroupLimit()) ? "<" . $params->getGroupLimit() . "> skos:member ?s ." : "";
1088
        $pgcond = $parentcond . $groupcond;
1089
1090
        $orderextra = $this->isDefaultEndpoint() ? $this->graph : '';
1091
1092
        # make VALUES clauses
1093
        $props = array('skos:prefLabel', 'skos:altLabel');
1094
1095
        //add notation into searchable data for the vocabularies which have been configured for it
1096
        if ($vocabs) {
1097
            $searchByNotation = false;
1098
            foreach ($vocabs as $vocab) {
1099
                if ($vocab->getConfig()->searchByNotation()) {
1100
                    $searchByNotation = true;
1101
                }
1102
            }
1103
            if ($searchByNotation) {
1104
                $props[] = 'skos:notation';
1105
            }
1106
        }
1107
1108
        if ($params->getHidden()) {
1109
            $props[] = 'skos:hiddenLabel';
1110
        }
1111
        $filterGraph = empty($vocabs) ? $this->formatFilterGraph($vocabs) : '';
0 ignored issues
show
Bug introduced by
The method formatFilterGraph() does not exist on GenericSparql. ( Ignorable by Annotation )

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

1111
        $filterGraph = empty($vocabs) ? $this->/** @scrutinizer ignore-call */ formatFilterGraph($vocabs) : '';

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...
1112
1113
        // remove futile asterisks from the search term
1114
        $term = $params->getSearchTerm();
1115
        while (strpos($term, '**') !== false) {
1116
            $term = str_replace('**', '*', $term);
1117
        }
1118
1119
        $labelpriority = <<<EOQ
1120
  FILTER(BOUND(?s))
1121
  BIND(STR(SUBSTR(?hit,1,1)) AS ?pri)
1122
  BIND(IF((SUBSTR(STRBEFORE(?hit, '@'),1) != ?pri), STRLANG(STRAFTER(?hit, '@'), SUBSTR(STRBEFORE(?hit, '@'),2)), STRAFTER(?hit, '@')) AS ?match)
1123
  BIND(IF((?pri = "1" || ?pri = "2") && ?match != ?label, ?match, ?unbound) as ?plabel)
1124
  BIND(IF((?pri = "3" || ?pri = "4"), ?match, ?unbound) as ?alabel)
1125
  BIND(IF((?pri = "7" || ?pri = "8"), ?match, ?unbound) as ?hlabel)
1126
EOQ;
1127
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique, $filterGraph);
0 ignored issues
show
Bug introduced by
The method generateConceptSearchQueryInner() does not exist on GenericSparql. Did you maybe mean generateConceptSearchQuery()? ( Ignorable by Annotation )

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

1127
        /** @scrutinizer ignore-call */ 
1128
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique, $filterGraph);

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...
1128
        if ($params->getSearchTerm() === '*' || $params->getSearchTerm() === '') {
1129
            $labelpriority = '';
1130
        }
1131
        $query = <<<EOQ
1132
SELECT DISTINCT ?s ?label ?plabel ?alabel ?hlabel ?graph ?notation (GROUP_CONCAT(DISTINCT STR(?type);separator=' ') as ?types) $extravars 
1133
$fcl
1134
WHERE {
1135
 $gcl {
1136
  {
1137
  $innerquery
1138
  }
1139
  $labelpriority
1140
  $formattedtype
1141
  { $pgcond 
1142
   ?s a ?type .
1143
   $extrafields $schemecond
1144
  }
1145
  $filterDeprecated
1146
 }
1147
 $filterGraph
1148
}
1149
GROUP BY ?s ?match ?label ?plabel ?alabel ?hlabel ?notation ?graph
1150
ORDER BY LCASE(STR(?match)) LANG(?match) $orderextra
1151
EOQ;
1152
        return $query;
1153
    }
1154
1155
    /**
1156
     * Transform a single concept search query results into the skosmos desired return format.
1157
     * @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...
1158
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1159
     * @return array query result object
1160
     */
1161
    private function transformConceptSearchResult($row, $vocabs, $fields)
1162
    {
1163
        $hit = array();
1164
        $hit['uri'] = $row->s->getUri();
1165
1166
        if (isset($row->graph)) {
1167
            $hit['graph'] = $row->graph->getUri();
1168
        }
1169
1170
        foreach (explode(" ", $row->types->getValue()) as $typeuri) {
1171
            $hit['type'][] = $this->shortenUri($typeuri);
1172
        }
1173
1174
        if (!empty($fields)) {
1175
            foreach ($fields as $prop) {
1176
                $propname = $prop . 's';
1177
                if (isset($row->$propname)) {
1178
                    foreach (explode("\n", $row->$propname->getValue()) as $line) {
1179
                        $rdata = str_getcsv($line, ',', '"', '"');
1180
                        $propvals = array();
1181
                        if ($rdata[0] != '') {
1182
                            $propvals['uri'] = $rdata[0];
1183
                        }
1184
                        if ($rdata[1] != '') {
1185
                            $propvals['prefLabel'] = $rdata[1];
1186
                        }
1187
                        if ($rdata[2] != '') {
1188
                            $propvals = $rdata[2];
1189
                        }
1190
1191
                        $hit['skos:' . $prop][] = $propvals;
1192
                    }
1193
                }
1194
            }
1195
        }
1196
1197
1198
        if (isset($row->preflabels)) {
1199
            foreach (explode("\n", $row->preflabels->getValue()) as $line) {
1200
                $pref = str_getcsv($line, ',', '"', '"');
1201
                $hit['prefLabels'][$pref[1]] = $pref[0];
1202
            }
1203
        }
1204
1205
        foreach ($vocabs as $vocab) { // looping the vocabulary objects and asking these for a localname for the concept.
1206
            $localname = $vocab->getLocalName($hit['uri']);
1207
            if ($localname !== $hit['uri']) { // only passing the result forward if the uri didn't boomerang right back.
1208
                $hit['localname'] = $localname;
1209
                break; // stopping the search when we find one that returns something valid.
1210
            }
1211
        }
1212
1213
        if (isset($row->label)) {
1214
            $hit['prefLabel'] = $row->label->getValue();
1215
        }
1216
1217
        if (isset($row->label)) {
1218
            $hit['lang'] = $row->label->getLang();
1219
        }
1220
1221
        if (isset($row->notation)) {
1222
            $hit['notation'] = $row->notation->getValue();
1223
        }
1224
1225
        if (isset($row->plabel)) {
1226
            $hit['matchedPrefLabel'] = $row->plabel->getValue();
1227
            $hit['lang'] = $row->plabel->getLang();
1228
        } elseif (isset($row->alabel)) {
1229
            $hit['altLabel'] = $row->alabel->getValue();
1230
            $hit['lang'] = $row->alabel->getLang();
1231
        } elseif (isset($row->hlabel)) {
1232
            $hit['hiddenLabel'] = $row->hlabel->getValue();
1233
            $hit['lang'] = $row->hlabel->getLang();
1234
        }
1235
        return $hit;
1236
    }
1237
1238
    /**
1239
     * Transform the concept search query results into the skosmos desired return format.
1240
     * @param EasyRdf\Sparql\Result $results
1241
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1242
     * @return array query result object
1243
     */
1244
    private function transformConceptSearchResults($results, $vocabs, $fields)
1245
    {
1246
        $ret = array();
1247
1248
        foreach ($results as $row) {
1249
            if (!isset($row->s)) {
1250
                // don't break if query returns a single dummy result
1251
                continue;
1252
            }
1253
            $ret[] = $this->transformConceptSearchResult($row, $vocabs, $fields);
1254
        }
1255
        return $ret;
1256
    }
1257
1258
    /**
1259
     * Query for concepts using a search term.
1260
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1261
     * @param array $fields extra fields to include in the result (array of strings or null).
1262
     * @param boolean $unique restrict results to unique concepts
1263
     * @param ConceptSearchParameters $params
1264
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1265
     * @return array query result object
1266
     */
1267
    public function queryConcepts($vocabs, $fields, $unique, $params, $showDeprecated = false)
1268
    {
1269
        $query = $this->generateConceptSearchQuery($fields, $unique, $params, $showDeprecated);
1270
        $results = $this->query($query);
1271
        return $this->transformConceptSearchResults($results, $vocabs, $fields);
1272
    }
1273
1274
    /**
1275
     * Generates sparql query clauses used for creating the alphabetical index.
1276
     * @param string $letter the letter (or special class) to search for
1277
     * @return array of sparql query clause strings
1278
     */
1279
    private function formatFilterConditions($letter, $lang)
1280
    {
1281
        $useRegex = false;
1282
1283
        if ($letter == '*') {
1284
            $letter = '.*';
1285
            $useRegex = true;
1286
        } elseif ($letter == '0-9') {
1287
            $letter = '[0-9].*';
1288
            $useRegex = true;
1289
        } elseif ($letter == '!*') {
1290
            $letter = '[^\\\\p{L}\\\\p{N}].*';
1291
            $useRegex = true;
1292
        }
1293
1294
        # make text query clause
1295
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
1296
        if ($useRegex) {
1297
            $filtercondLabel = $lang ? "regex(str(?label), '^$letter$', 'i') && langMatches(lang(?label), '$lang')" : "regex(str(?label), '^$letter$', 'i')";
1298
            $filtercondALabel = $lang ? "regex(str(?alabel), '^$letter$', 'i') && langMatches(lang(?alabel), '$lang')" : "regex(str(?alabel), '^$letter$', 'i')";
1299
        } else {
1300
            $filtercondLabel = $lang ? "strstarts(lcase(str(?label)), '$lcletter') && langMatches(lang(?label), '$lang')" : "strstarts(lcase(str(?label)), '$lcletter')";
1301
            $filtercondALabel = $lang ? "strstarts(lcase(str(?alabel)), '$lcletter') && langMatches(lang(?alabel), '$lang')" : "strstarts(lcase(str(?alabel)), '$lcletter')";
1302
        }
1303
        return array('filterpref' => $filtercondLabel, 'filteralt' => $filtercondALabel);
1304
    }
1305
1306
    /**
1307
     * Generates the sparql query used for rendering the alphabetical index.
1308
     * @param string $letter the letter (or special class) to search for
1309
     * @param string $lang language of labels
1310
     * @param integer $limit limits the amount of results
1311
     * @param integer $offset offsets the result set
1312
     * @param array|null $classes
1313
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1314
     * @param \EasyRdf\Resource|null $qualifier alphabetical list qualifier resource or null (default: null)
1315
     * @return string sparql query
1316
     */
1317
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated = false, $qualifier = null)
1318
    {
1319
        $gcl = $this->graphClause;
1320
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1321
        $values = $this->formatValues('?type', $classes, 'uri');
1322
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
0 ignored issues
show
Bug introduced by
The method formatLimitAndOffset() does not exist on GenericSparql. ( Ignorable by Annotation )

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

1322
        /** @scrutinizer ignore-call */ 
1323
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);

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...
1323
        $conditions = $this->formatFilterConditions($letter, $lang);
1324
        $filtercondLabel = $conditions['filterpref'];
1325
        $filtercondALabel = $conditions['filteralt'];
1326
        $qualifierClause = $qualifier ? "OPTIONAL { ?s <" . $qualifier->getURI() . "> ?qualifier }" : "";
1327
        $filterDeprecated = "";
1328
        if (!$showDeprecated) {
1329
            $filterDeprecated = "FILTER NOT EXISTS { ?s owl:deprecated true }";
1330
        }
1331
        $query = <<<EOQ
1332
SELECT DISTINCT ?s ?label ?alabel ?qualifier
1333
WHERE {
1334
  $gcl {
1335
    {
1336
      ?s skos:prefLabel ?label .
1337
      FILTER (
1338
        $filtercondLabel
1339
      )
1340
    }
1341
    UNION
1342
    {
1343
      {
1344
        ?s skos:altLabel ?alabel .
1345
        FILTER (
1346
          $filtercondALabel
1347
        )
1348
      }
1349
      {
1350
        ?s skos:prefLabel ?label .
1351
        FILTER (langMatches(lang(?label), '$lang'))
1352
      }
1353
    }
1354
    ?s a ?type .
1355
    $qualifierClause
1356
    $filterDeprecated
1357
    $values
1358
  }
1359
}
1360
ORDER BY LCASE(STR(COALESCE(?alabel, ?label))) STR(?s) LCASE(STR(?qualifier)) $limitandoffset
1361
EOQ;
1362
        return $query;
1363
    }
1364
1365
    /**
1366
     * Transforms the alphabetical list query results into an array format.
1367
     * @param EasyRdf\Sparql\Result $results
1368
     * @return array
1369
     */
1370
    private function transformAlphabeticalListResults($results)
1371
    {
1372
        $ret = array();
1373
1374
        foreach ($results as $row) {
1375
            if (!isset($row->s)) {
1376
                continue;
1377
            }
1378
            // don't break if query returns a single dummy result
1379
1380
            $hit = array();
1381
            $hit['uri'] = $row->s->getUri();
1382
1383
            $hit['localname'] = $row->s->localName();
1384
1385
            $hit['prefLabel'] = $row->label->getValue();
1386
            $hit['lang'] = $row->label->getLang();
1387
1388
            if (isset($row->alabel)) {
1389
                $hit['altLabel'] = $row->alabel->getValue();
1390
                $hit['lang'] = $row->alabel->getLang();
1391
            }
1392
1393
            if (isset($row->qualifier)) {
1394
                if ($row->qualifier instanceof EasyRdf\Literal) {
1395
                    $hit['qualifier'] = $row->qualifier->getValue();
1396
                } else {
1397
                    $hit['qualifier'] = $row->qualifier->localName();
1398
                }
1399
            }
1400
1401
            $ret[] = $hit;
1402
        }
1403
1404
        return $ret;
1405
    }
1406
1407
    /**
1408
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1409
     * '*!' (special characters) and '*' (everything) are accepted.
1410
     * @param string $letter the letter (or special class) to search for
1411
     * @param string $lang language of labels
1412
     * @param integer $limit limits the amount of results
1413
     * @param integer $offset offsets the result set
1414
     * @param array $classes
1415
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1416
     * @param \EasyRdf\Resource|null $qualifier alphabetical list qualifier resource or null (default: null)
1417
     */
1418
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null, $showDeprecated = false, $qualifier = null)
1419
    {
1420
        if ($letter === '') {
1421
            return array(); // special case: no letter given, return empty list
1422
        }
1423
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated, $qualifier);
1424
        $results = $this->query($query);
1425
        return $this->transformAlphabeticalListResults($results);
1426
    }
1427
1428
    /**
1429
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1430
     * Note that we force the datatype of the result variable otherwise Virtuoso does not properly interpret the DISTINCT and we have duplicated results
1431
     * @param string $lang language
1432
     * @return string sparql query
1433
     */
1434
    private function generateFirstCharactersQuery($lang, $classes)
1435
    {
1436
        $gcl = $this->graphClause;
1437
        $classes = (isset($classes) && sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1438
        $values = $this->formatValues('?type', $classes, 'uri');
1439
        $query = <<<EOQ
1440
SELECT DISTINCT (ucase(str(substr(?label, 1, 1))) as ?l) WHERE {
1441
  $gcl {
1442
    ?c skos:prefLabel ?label .
1443
    ?c a ?type
1444
    FILTER(langMatches(lang(?label), '$lang'))
1445
    $values
1446
  }
1447
}
1448
EOQ;
1449
        return $query;
1450
    }
1451
1452
    /**
1453
     * Transforms the first characters query results into an array format.
1454
     * @param EasyRdf\Sparql\Result $result
1455
     * @return array
1456
     */
1457
    private function transformFirstCharactersResults($result)
1458
    {
1459
        $ret = array();
1460
        foreach ($result as $row) {
1461
            $ret[] = $row->l->getValue();
1462
        }
1463
        return $ret;
1464
    }
1465
1466
    /**
1467
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1468
     * @param string $lang language
1469
     * @return array array of characters
1470
     */
1471
    public function queryFirstCharacters($lang, $classes = null)
1472
    {
1473
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1474
        $result = $this->query($query);
1475
        return $this->transformFirstCharactersResults($result);
1476
    }
1477
1478
    /**
1479
     * @param string $uri
1480
     * @param string $lang
1481
     * @return string sparql query string
1482
     */
1483
    private function generateLabelQuery($uri, $lang)
1484
    {
1485
        $fcl = $this->generateFromClause();
1486
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1487
        $query = <<<EOQ
1488
SELECT ?label $fcl
1489
WHERE {
1490
  <$uri> a ?type .
1491
  OPTIONAL {
1492
    <$uri> skos:prefLabel ?label .
1493
    $labelcondLabel
1494
  }
1495
  OPTIONAL {
1496
    <$uri> rdfs:label ?label .
1497
    $labelcondLabel
1498
  }
1499
  OPTIONAL {
1500
    <$uri> dc:title ?label .
1501
    $labelcondLabel
1502
  }
1503
  OPTIONAL {
1504
    <$uri> dc11:title ?label .
1505
    $labelcondLabel
1506
  }
1507
}
1508
EOQ;
1509
        return $query;
1510
    }
1511
1512
1513
    /**
1514
     * @param string $uri
1515
     * @param string $lang
1516
     * @return string sparql query string
1517
     */
1518
    private function generateAllLabelsQuery($uri, $lang)
1519
    {
1520
        $fcl = $this->generateFromClause();
1521
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?val), '$lang') )" : "";
1522
        $query = <<<EOQ
1523
SELECT DISTINCT ?prop ?val $fcl
1524
WHERE {
1525
  <$uri> a ?type .
1526
  OPTIONAL {
1527
      <$uri> ?prop ?val .
1528
      $labelcondLabel
1529
  }
1530
  VALUES ?prop { skos:prefLabel skos:altLabel skos:hiddenLabel }
1531
}
1532
EOQ;
1533
        return $query;
1534
    }
1535
1536
    /**
1537
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1538
     * @param string $uri
1539
     * @param string $lang
1540
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1541
     */
1542
    public function queryLabel($uri, $lang)
1543
    {
1544
        $query = $this->generateLabelQuery($uri, $lang);
1545
        $result = $this->query($query);
1546
        $ret = array();
1547
        foreach ($result as $row) {
1548
            if (!isset($row->label)) {
1549
                // existing concept but no labels
1550
                return array();
1551
            }
1552
            $ret[$row->label->getLang()] = $row->label;
1553
        }
1554
1555
        if (sizeof($ret) > 0) {
1556
            // existing concept, with label(s)
1557
            return $ret;
1558
        } else {
1559
            // nonexistent concept
1560
            return null;
1561
        }
1562
    }
1563
1564
    /**
1565
     * Query for skos:prefLabels, skos:altLabels and skos:hiddenLabels of a resource.
1566
     * @param string $uri
1567
     * @param string $lang
1568
     * @return array array of prefLabels, altLabels and hiddenLabels - or null if resource doesn't exist
1569
     */
1570
    public function queryAllConceptLabels($uri, $lang)
1571
    {
1572
        $query = $this->generateAllLabelsQuery($uri, $lang);
1573
        $result = $this->query($query);
1574
1575
        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

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