Passed
Branch master (4b538d)
by Osma
10:57
created

GenericSparql::generateConceptSearchQuery()   F

Complexity

Conditions 17
Paths 3072

Size

Total Lines 92
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 17
eloc 60
nc 3072
nop 4
dl 0
loc 92
rs 1.0499
c 1
b 1
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Generates SPARQL queries and provides access to the SPARQL endpoint.
5
 */
6
class GenericSparql {
7
    /**
8
     * A SPARQL Client eg. an EasyRDF instance.
9
     * @property EasyRdf\Sparql\Client $client
10
     */
11
    protected $client;
12
    /**
13
     * Graph uri.
14
     * @property object $graph
15
     */
16
    protected $graph;
17
    /**
18
     * A SPARQL query graph part template.
19
     * @property string $graphClause
20
     */
21
    protected $graphClause;
22
    /**
23
     * Model instance.
24
     * @property Model $model
25
     */
26
    protected $model;
27
28
    /**
29
     * Cache used to avoid expensive shorten() calls
30
     * @property array $qnamecache
31
     */
32
    private $qnamecache = array();
33
34
    /**
35
     * Requires the following three parameters.
36
     * @param string $endpoint SPARQL endpoint address.
37
     * @param string|null $graph Which graph to query: Either an URI, the special value "?graph"
38
     *                           to use the default graph, or NULL to not use a GRAPH clause.
39
     * @param object $model a Model instance.
40
     */
41
    public function __construct($endpoint, $graph, $model) {
42
        $this->graph = $graph;
43
        $this->model = $model;
44
45
        // create the EasyRDF SPARQL client instance to use
46
        $this->initializeHttpClient();
47
        $this->client = new EasyRdf\Sparql\Client($endpoint);
48
49
        // set graphClause so that it can be used by all queries
50
        if ($this->isDefaultEndpoint()) // default endpoint; query any graph (and catch it in a variable)
51
        {
52
            $this->graphClause = "GRAPH $graph";
53
        } elseif ($graph !== null) // query a specific graph
54
        {
55
            $this->graphClause = "GRAPH <$graph>";
56
        } else // query the default graph
57
        {
58
            $this->graphClause = "";
59
        }
60
61
    }
62
63
    /**
64
     * Returns prefix-definitions for a query
65
     *
66
     * @param string $query
67
     * @return string
68
    */
69
    protected function generateQueryPrefixes($query)
70
    {
71
        // Check for undefined prefixes
72
        $prefixes = '';
73
        foreach (EasyRdf\RdfNamespace::namespaces() as $prefix => $uri) {
74
            if (strpos($query, "{$prefix}:") !== false and
75
                strpos($query, "PREFIX {$prefix}:") === false
76
            ) {
77
                $prefixes .= "PREFIX {$prefix}: <{$uri}>\n";
78
            }
79
        }
80
        return $prefixes;
81
    }
82
83
    /**
84
     * Execute the SPARQL query using the SPARQL client, logging it as well.
85
     * @param string $query SPARQL query to perform
86
     * @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...
87
     */
88
    protected function query($query) {
89
        $queryId = sprintf("%05d", rand(0, 99999));
90
        $logger = $this->model->getLogger();
91
        $logger->info("[qid $queryId] SPARQL query:\n" . $this->generateQueryPrefixes($query) . "\n$query\n");
92
        $starttime = microtime(true);
93
        $result = $this->client->query($query);
94
        $elapsed = intval(round((microtime(true) - $starttime) * 1000));
95
        if(method_exists($result, 'numRows')) {
96
            $numRows = $result->numRows();
97
            $logger->info("[qid $queryId] result: $numRows rows returned in $elapsed ms");
98
        } else { // graph result
99
            $numTriples = $result->countTriples();
100
            $logger->info("[qid $queryId] result: $numTriples triples returned in $elapsed ms");
101
        }
102
        return $result;
103
    }
104
105
106
    /**
107
     * Generates FROM clauses for the queries
108
     * @param Vocabulary[]|null $vocabs
109
     * @return string
110
     */
111
    protected function generateFromClause($vocabs=null) {
112
        $clause = '';
113
        if (!$vocabs) {
114
            return $this->graph !== '?graph' && $this->graph !== NULL ? "FROM <$this->graph>" : '';
115
        }
116
        $graphs = $this->getVocabGraphs($vocabs);
117
        foreach ($graphs as $graph) {
118
            $clause .= "FROM NAMED <$graph> ";
119
        }
120
        return $clause;
121
    }
122
123
    protected function initializeHttpClient() {
124
        // configure the HTTP client used by EasyRdf\Sparql\Client
125
        $httpclient = EasyRdf\Http::getDefaultHttpClient();
126
        $httpclient->setConfig(array('timeout' => $this->model->getConfig()->getSparqlTimeout()));
127
128
        // if special cache control (typically no-cache) was requested by the
129
        // client, set the same type of cache control headers also in subsequent
130
        // in the SPARQL requests (this is useful for performance testing)
131
        // @codeCoverageIgnoreStart
132
        $cacheControl = filter_input(INPUT_SERVER, 'HTTP_CACHE_CONTROL', FILTER_SANITIZE_STRING);
133
        $pragma = filter_input(INPUT_SERVER, 'HTTP_PRAGMA', FILTER_SANITIZE_STRING);
134
        if ($cacheControl !== null || $pragma !== null) {
135
            $val = $pragma !== null ? $pragma : $cacheControl;
136
            $httpclient->setHeaders('Cache-Control', $val);
137
        }
138
        // @codeCoverageIgnoreEnd
139
140
        EasyRdf\Http::setDefaultHttpClient($httpclient); // actually redundant..
141
    }
142
143
    /**
144
     * Return true if this is the default SPARQL endpoint, used as the facade to query
145
     * all vocabularies.
146
     */
147
148
    protected function isDefaultEndpoint() {
149
        return !is_null($this->graph) && $this->graph[0] == '?';
150
    }
151
152
    /**
153
     * Returns the graph instance
154
     * @return object EasyRDF graph instance.
155
     */
156
    public function getGraph() {
157
        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...
158
    }
159
160
    /**
161
     * Shorten a URI
162
     * @param string $uri URI to shorten
163
     * @return string shortened URI, or original URI if it cannot be shortened
164
     */
165
    private function shortenUri($uri) {
166
        if (!array_key_exists($uri, $this->qnamecache)) {
167
            $res = new EasyRdf\Resource($uri);
168
            $qname = $res->shorten(); // returns null on failure
169
            $this->qnamecache[$uri] = ($qname !== null) ? $qname : $uri;
0 ignored issues
show
introduced by
The condition $qname !== null is always true.
Loading history...
170
        }
171
        return $this->qnamecache[$uri];
172
    }
173
174
175
    /**
176
     * Generates the sparql query for retrieving concept and collection counts in a vocabulary.
177
     * @return string sparql query
178
     */
179
    private function generateCountConceptsQuery($array, $group) {
180
        $fcl = $this->generateFromClause();
181
        $optional = $array ? "UNION { ?type rdfs:subClassOf* <$array> }" : '';
182
        $optional .= $group ? "UNION { ?type rdfs:subClassOf* <$group> }" : '';
183
        $query = <<<EOQ
184
      SELECT (COUNT(?conc) as ?c) ?type ?typelabel $fcl WHERE {
185
        { ?conc a ?type .
186
        { ?type rdfs:subClassOf* skos:Concept . } UNION { ?type rdfs:subClassOf* skos:Collection . } $optional }
187
        OPTIONAL { ?type rdfs:label ?typelabel . }
188
      }
189
GROUP BY ?type ?typelabel
190
EOQ;
191
        return $query;
192
    }
193
194
    /**
195
     * Used for transforming the concept count query results.
196
     * @param EasyRdf\Sparql\Result $result query results to be transformed
197
     * @param string $lang language of labels
198
     * @return Array containing the label counts
199
     */
200
    private function transformCountConceptsResults($result, $lang) {
201
        $ret = array();
202
        foreach ($result as $row) {
203
            if (!isset($row->type)) {
204
                continue;
205
            }
206
            $ret[$row->type->getUri()]['type'] = $row->type->getUri();
207
            $ret[$row->type->getUri()]['count'] = $row->c->getValue();
208
            if (isset($row->typelabel) && $row->typelabel->getLang() === $lang) {
209
                $ret[$row->type->getUri()]['label'] = $row->typelabel->getValue();
210
            }
211
212
        }
213
        return $ret;
214
    }
215
216
    /**
217
     * Used for counting number of concepts and collections in a vocabulary.
218
     * @param string $lang language of labels
219
     * @return array with number of concepts in this vocabulary per label
220
     */
221
    public function countConcepts($lang = null, $array = null, $group = null) {
222
        $query = $this->generateCountConceptsQuery($array, $group);
223
        $result = $this->query($query);
224
        return $this->transformCountConceptsResults($result, $lang);
225
    }
226
227
    /**
228
     * @param array $langs Languages to query for
229
     * @param string[] $props property names
230
     * @return string sparql query
231
     */
232
    private function generateCountLangConceptsQuery($langs, $classes, $props) {
233
        $gcl = $this->graphClause;
234
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
235
236
	$quote_string = function($val) { return "'$val'"; };
237
	$quoted_values = array_map($quote_string, $langs);
238
	$langFilter = "FILTER(?lang IN (" . implode(',', $quoted_values) . "))";
239
240
        $values = $this->formatValues('?type', $classes, 'uri');
241
        $valuesProp = $this->formatValues('?prop', $props, null);
242
243
        $query = <<<EOQ
244
SELECT ?lang ?prop
245
  (COUNT(?label) as ?count)
246
WHERE {
247
  $gcl {
248
    $values
249
    $valuesProp
250
    ?conc a ?type .
251
    ?conc ?prop ?label .
252
    BIND(LANG(?label) AS ?lang)
253
    $langFilter
254
  }
255
}
256
GROUP BY ?lang ?prop ?type
257
EOQ;
258
        return $query;
259
    }
260
261
    /**
262
     * Transforms the CountLangConcepts results into an array of label counts.
263
     * @param EasyRdf\Sparql\Result $result query results to be transformed
264
     * @param array $langs Languages to query for
265
     * @param string[] $props property names
266
     */
267
    private function transformCountLangConceptsResults($result, $langs, $props) {
268
        $ret = array();
269
        // set default count to zero; overridden below if query found labels
270
        foreach ($langs as $lang) {
271
            foreach ($props as $prop) {
272
                $ret[$lang][$prop] = 0;
273
            }
274
        }
275
        foreach ($result as $row) {
276
            if (isset($row->lang) && isset($row->prop) && isset($row->count)) {
277
                $ret[$row->lang->getValue()][$row->prop->shorten()] +=
278
                $row->count->getValue();
279
            }
280
281
        }
282
        ksort($ret);
283
        return $ret;
284
    }
285
286
    /**
287
     * Counts the number of concepts in a easyRDF graph with a specific language.
288
     * @param array $langs Languages to query for
289
     * @return Array containing count of concepts for each language and property.
290
     */
291
    public function countLangConcepts($langs, $classes = null) {
292
        $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel');
293
        $query = $this->generateCountLangConceptsQuery($langs, $classes, $props);
294
        // Count the number of terms in each language
295
        $result = $this->query($query);
296
        return $this->transformCountLangConceptsResults($result, $langs, $props);
297
    }
298
299
    /**
300
     * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one
301
     * of the constants given.
302
     * @param string $varname variable name, e.g. "?uri"
303
     * @param array $values the values
304
     * @param string $type type of values: "uri", "literal" or null (determines quoting style)
305
     */
306
    protected function formatValues($varname, $values, $type = null) {
307
        $constants = array();
308
        foreach ($values as $val) {
309
            if ($type == 'uri') {
310
                $val = "<$val>";
311
            }
312
313
            if ($type == 'literal') {
314
                $val = "'$val'";
315
            }
316
317
            $constants[] = "($val)";
318
        }
319
        $values = implode(" ", $constants);
320
321
        return "VALUES ($varname) { $values }";
322
    }
323
324
    /**
325
     * Filters multiple instances of the same vocabulary from the input array.
326
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
327
     * @return \Vocabulary[]
328
     */
329
    private function filterDuplicateVocabs($vocabs) {
330
        // filtering duplicates
331
        $uniqueVocabs = array();
332
        if ($vocabs !== null && sizeof($vocabs) > 0) {
333
            foreach ($vocabs as $voc) {
334
                $uniqueVocabs[$voc->getId()] = $voc;
335
            }
336
        }
337
338
        return $uniqueVocabs;
339
    }
340
341
    /**
342
     * Generates a sparql query for one or more concept URIs
343
     * @param mixed $uris concept URI (string) or array of URIs
344
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
345
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
346
     * @return string sparql query
347
     */
348
    private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) {
349
        $gcl = $this->graphClause;
350
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
351
        $values = $this->formatValues('?uri', $uris, 'uri');
352
        $uniqueVocabs = $this->filterDuplicateVocabs($vocabs);
353
        $valuesGraph = empty($vocabs) ? $this->formatValuesGraph($uniqueVocabs) : '';
354
355
        if ($arrayClass === null) {
356
            $construct = $optional = "";
357
        } else {
358
            // add information that can be used to format narrower concepts by
359
            // the array they belong to ("milk by source animal" use case)
360
            $construct = "\n ?x skos:member ?o . ?x skos:prefLabel ?xl . ?x a <$arrayClass> .";
361
            $optional = "\n OPTIONAL {
362
                      ?x skos:member ?o .
363
                      ?x a <$arrayClass> .
364
                      ?x skos:prefLabel ?xl .
365
                      FILTER NOT EXISTS {
366
                        ?x skos:member ?other .
367
                        MINUS { ?other skos:broader ?uri }
368
                      }
369
                    }";
370
        }
371
        $query = <<<EOQ
372
CONSTRUCT {
373
 ?s ?p ?uri .
374
 ?sp ?uri ?op .
375
 ?uri ?p ?o .
376
 ?p rdfs:label ?proplabel .
377
 ?p rdfs:comment ?propcomm .
378
 ?p skos:definition ?propdef .
379
 ?p rdfs:subPropertyOf ?pp .
380
 ?pp rdfs:label ?plabel .
381
 ?o a ?ot .
382
 ?o skos:prefLabel ?opl .
383
 ?o rdfs:label ?ol .
384
 ?o rdf:value ?ov .
385
 ?o skos:notation ?on .
386
 ?o ?oprop ?oval .
387
 ?o ?xlprop ?xlval .
388
 ?directgroup skos:member ?uri .
389
 ?parent skos:member ?group .
390
 ?group skos:prefLabel ?grouplabel .
391
 ?b1 rdf:first ?item .
392
 ?b1 rdf:rest ?b2 .
393
 ?item a ?it .
394
 ?item skos:prefLabel ?il .
395
 ?group a ?grouptype . $construct
396
} $fcl WHERE {
397
 $values
398
 $gcl {
399
  {
400
    ?s ?p ?uri .
401
    FILTER(!isBlank(?s))
402
    FILTER(?p != skos:inScheme)
403
    FILTER NOT EXISTS { ?s owl:deprecated true . }
404
  }
405
  UNION
406
  { ?sp ?uri ?op . }
407
  UNION
408
  {
409
    ?directgroup skos:member ?uri .
410
    ?group skos:member+ ?uri .
411
    ?group skos:prefLabel ?grouplabel .
412
    ?group a ?grouptype .
413
    OPTIONAL { ?parent skos:member ?group }
414
  }
415
  UNION
416
  {
417
   ?uri ?p ?o .
418
   OPTIONAL {
419
     ?o rdf:rest* ?b1 .
420
     ?b1 rdf:first ?item .
421
     ?b1 rdf:rest ?b2 .
422
     OPTIONAL { ?item a ?it . }
423
     OPTIONAL { ?item skos:prefLabel ?il . }
424
   }
425
   OPTIONAL {
426
     { ?p rdfs:label ?proplabel . }
427
     UNION
428
     { ?p rdfs:comment ?propcomm . }
429
     UNION
430
     { ?p skos:definition ?propdef . }
431
     UNION
432
     { ?p rdfs:subPropertyOf ?pp . }
433
   }
434
   OPTIONAL {
435
     { ?o a ?ot . }
436
     UNION
437
     { ?o skos:prefLabel ?opl . }
438
     UNION
439
     { ?o rdfs:label ?ol . }
440
     UNION
441
     { ?o rdf:value ?ov . 
442
       OPTIONAL { ?o ?oprop ?oval . }
443
     }
444
     UNION
445
     { ?o skos:notation ?on . }
446
     UNION
447
     { ?o a skosxl:Label .
448
       ?o ?xlprop ?xlval }
449
   } $optional
450
  }
451
 }
452
}
453
$valuesGraph
454
EOQ;
455
        return $query;
456
    }
457
458
    /**
459
     * Transforms ConceptInfo query results into an array of Concept objects
460
     * @param EasyRdf\Graph $result query results to be transformed
461
     * @param array $uris concept URIs
462
     * @param \Vocabulary[] $vocabs array of Vocabulary object
463
     * @param string|null $clang content language
464
     * @return Concept[] array of Concept objects
465
     */
466
    private function transformConceptInfoResults($result, $uris, $vocabs, $clang) {
467
        $conceptArray = array();
468
        foreach ($uris as $index => $uri) {
469
            $conc = $result->resource($uri);
470
            if (is_array($vocabs)) {
471
                $vocab = (sizeof($vocabs) == 1) ? $vocabs[0] : $vocabs[$index];
472
            } else {
473
                $vocab = null;
474
            }
475
            $conceptArray[] = new Concept($this->model, $vocab, $conc, $result, $clang);
476
        }
477
        return $conceptArray;
478
    }
479
480
    /**
481
     * Returns information (as a graph) for one or more concept URIs
482
     * @param mixed $uris concept URI (string) or array of URIs
483
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
484
     * @param \Vocabulary[]|null $vocabs vocabularies to target
485
     * @return \EasyRdf\Graph
486
     */
487
    public function queryConceptInfoGraph($uris, $arrayClass = null, $vocabs = array()) {
488
        // if just a single URI is given, put it in an array regardless
489
        if (!is_array($uris)) {
490
            $uris = array($uris);
491
        }
492
493
        $query = $this->generateConceptInfoQuery($uris, $arrayClass, $vocabs);
494
        $result = $this->query($query);
495
        return $result;
496
    }
497
498
    /**
499
     * Returns information (as an array of Concept objects) for one or more concept URIs
500
     * @param mixed $uris concept URI (string) or array of URIs
501
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
502
     * @param \Vocabulary[] $vocabs vocabularies to target
503
     * @param string|null $clang content language
504
     * @return Concept[]
505
     */
506
    public function queryConceptInfo($uris, $arrayClass = null, $vocabs = array(), $clang = null) {
507
        // if just a single URI is given, put it in an array regardless
508
        if (!is_array($uris)) {
509
            $uris = array($uris);
510
        }
511
        $result = $this->queryConceptInfoGraph($uris, $arrayClass, $vocabs);
512
        if ($result->isEmpty()) {
513
            return [];
514
        }
515
        return $this->transformConceptInfoResults($result, $uris, $vocabs, $clang);
516
    }
517
518
    /**
519
     * Generates the sparql query for queryTypes
520
     * @param string $lang
521
     * @return string sparql query
522
     */
523
    private function generateQueryTypesQuery($lang) {
524
        $fcl = $this->generateFromClause();
525
        $query = <<<EOQ
526
SELECT DISTINCT ?type ?label ?superclass $fcl
527
WHERE {
528
  {
529
    { BIND( skos:Concept as ?type ) }
530
    UNION
531
    { BIND( skos:Collection as ?type ) }
532
    UNION
533
    { BIND( isothes:ConceptGroup as ?type ) }
534
    UNION
535
    { BIND( isothes:ThesaurusArray as ?type ) }
536
    UNION
537
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Concept . }
538
    UNION
539
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Collection . }
540
  }
541
  OPTIONAL {
542
    ?type rdfs:label ?label .
543
    FILTER(langMatches(lang(?label), '$lang'))
544
  }
545
  OPTIONAL {
546
    ?type rdfs:subClassOf ?superclass .
547
  }
548
  FILTER EXISTS {
549
    ?s a ?type .
550
    ?s skos:prefLabel ?prefLabel .
551
  }
552
}
553
EOQ;
554
        return $query;
555
    }
556
557
    /**
558
     * Transforms the results into an array format.
559
     * @param EasyRdf\Sparql\Result $result
560
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
561
     */
562
    private function transformQueryTypesResults($result) {
563
        $ret = array();
564
        foreach ($result as $row) {
565
            $type = array();
566
            if (isset($row->label)) {
567
                $type['label'] = $row->label->getValue();
568
            }
569
570
            if (isset($row->superclass)) {
571
                $type['superclass'] = $row->superclass->getUri();
572
            }
573
574
            $ret[$row->type->getURI()] = $type;
575
        }
576
        return $ret;
577
    }
578
579
    /**
580
     * Retrieve information about types from the endpoint
581
     * @param string $lang
582
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
583
     */
584
    public function queryTypes($lang) {
585
        $query = $this->generateQueryTypesQuery($lang);
586
        $result = $this->query($query);
587
        return $this->transformQueryTypesResults($result);
588
    }
589
590
    /**
591
     * Generates the concept scheme query.
592
     * @param string $conceptscheme concept scheme URI
593
     * @return string sparql query
594
     */
595
    private function generateQueryConceptSchemeQuery($conceptscheme) {
596
        $fcl = $this->generateFromClause();
597
        $query = <<<EOQ
598
CONSTRUCT {
599
  <$conceptscheme> ?property ?value .
600
} $fcl WHERE {
601
  <$conceptscheme> ?property ?value .
602
  FILTER (?property != skos:hasTopConcept)
603
}
604
EOQ;
605
        return $query;
606
    }
607
608
    /**
609
     * Retrieves conceptScheme information from the endpoint.
610
     * @param string $conceptscheme concept scheme URI
611
     * @return \EasyRdf\Sparql\Result|\EasyRdf\Graph query result graph
612
     */
613
    public function queryConceptScheme($conceptscheme) {
614
        $query = $this->generateQueryConceptSchemeQuery($conceptscheme);
615
        return $this->query($query);
616
    }
617
618
    /**
619
     * Generates the queryConceptSchemes sparql query.
620
     * @param string $lang language of labels
621
     * @return string sparql query
622
     */
623
    private function generateQueryConceptSchemesQuery($lang) {
624
        $fcl = $this->generateFromClause();
625
        $query = <<<EOQ
626
SELECT ?cs ?label ?preflabel ?title ?domain ?domainLabel $fcl
627
WHERE {
628
 ?cs a skos:ConceptScheme .
629
 OPTIONAL{
630
    ?cs dcterms:subject ?domain.
631
    ?domain skos:prefLabel ?domainLabel.
632
    FILTER(langMatches(lang(?domainLabel), '$lang'))
633
}
634
 OPTIONAL {
635
   ?cs rdfs:label ?label .
636
   FILTER(langMatches(lang(?label), '$lang'))
637
 }
638
 OPTIONAL {
639
   ?cs skos:prefLabel ?preflabel .
640
   FILTER(langMatches(lang(?preflabel), '$lang'))
641
 }
642
 OPTIONAL {
643
   { ?cs dc11:title ?title }
644
   UNION
645
   { ?cs dc:title ?title }
646
   FILTER(langMatches(lang(?title), '$lang'))
647
 }
648
} 
649
ORDER BY ?cs
650
EOQ;
651
        return $query;
652
    }
653
654
    /**
655
     * Transforms the queryConceptScheme results into an array format.
656
     * @param EasyRdf\Sparql\Result $result
657
     * @return array
658
     */
659
    private function transformQueryConceptSchemesResults($result) {
660
        $ret = array();
661
        foreach ($result as $row) {
662
            $conceptscheme = array();
663
            if (isset($row->label)) {
664
                $conceptscheme['label'] = $row->label->getValue();
665
            }
666
667
            if (isset($row->preflabel)) {
668
                $conceptscheme['prefLabel'] = $row->preflabel->getValue();
669
            }
670
671
            if (isset($row->title)) {
672
                $conceptscheme['title'] = $row->title->getValue();
673
            }
674
            // add dct:subject and their labels in the result
675
            if(isset($row->domain) && isset($row->domainLabel)){
676
                $conceptscheme['subject']['uri']=$row->domain->getURI();
677
                $conceptscheme['subject']['prefLabel']=$row->domainLabel->getValue();
678
            }
679
680
            $ret[$row->cs->getURI()] = $conceptscheme;
681
        }
682
        return $ret;
683
    }
684
685
    /**
686
     * return a list of skos:ConceptScheme instances in the given graph
687
     * @param string $lang language of labels
688
     * @return array Array with concept scheme URIs (string) as keys and labels (string) as values
689
     */
690
    public function queryConceptSchemes($lang) {
691
        $query = $this->generateQueryConceptSchemesQuery($lang);
692
        $result = $this->query($query);
693
        return $this->transformQueryConceptSchemesResults($result);
694
    }
695
696
    /**
697
     * Generate a VALUES clause for limiting the targeted graphs.
698
     * @param Vocabulary[]|null $vocabs the vocabularies to target
699
     * @return string[] array of graph URIs
700
     */
701
    protected function getVocabGraphs($vocabs) {
702
        if ($vocabs === null || sizeof($vocabs) == 0) {
703
            // searching from all vocabularies - limit to known graphs
704
            $vocabs = $this->model->getVocabularies();
705
        }
706
        $graphs = array();
707
        foreach ($vocabs as $voc) {
708
            $graph = $voc->getGraph();
709
            if (!is_null($graph) && !in_array($graph, $graphs)) {
710
                $graphs[] = $graph;
711
            }
712
        }
713
        return $graphs;
714
    }
715
716
    /**
717
     * Generate a VALUES clause for limiting the targeted graphs.
718
     * @param Vocabulary[]|null $vocabs array of Vocabulary objects to target
719
     * @return string VALUES clause, or "" if not necessary to limit
720
     */
721
    protected function formatValuesGraph($vocabs) {
722
        if (!$this->isDefaultEndpoint()) {
723
            return "";
724
        }
725
        $graphs = $this->getVocabGraphs($vocabs);
726
        return $this->formatValues('?graph', $graphs, 'uri');
727
    }
728
729
    /**
730
     * Generate a FILTER clause for limiting the targeted graphs.
731
     * @param array $vocabs array of Vocabulary objects to target
732
     * @return string FILTER clause, or "" if not necessary to limit
733
     */
734
    protected function formatFilterGraph($vocabs) {
735
        if (!$this->isDefaultEndpoint()) {
736
            return "";
737
        }
738
        $graphs = $this->getVocabGraphs($vocabs);
739
        $values = array();
740
        foreach ($graphs as $graph) {
741
          $values[] = "<$graph>";
742
        }
743
        if (count($values)) {
744
          return "FILTER (?graph IN (" . implode(',', $values) . "))";
745
        }
746
    }
747
748
    /**
749
     * Formats combined limit and offset clauses for the sparql query
750
     * @param int $limit maximum number of hits to retrieve; 0 for unlimited
751
     * @param int $offset offset of results to retrieve; 0 for beginning of list
752
     * @return string sparql query clauses
753
     */
754
    protected function formatLimitAndOffset($limit, $offset) {
755
        $limit = ($limit) ? 'LIMIT ' . $limit : '';
756
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
757
        // eliminating whitespace and line changes when the conditions aren't needed.
758
        $limitandoffset = '';
759
        if ($limit && $offset) {
760
            $limitandoffset = "\n" . $limit . "\n" . $offset;
761
        } elseif ($limit) {
762
            $limitandoffset = "\n" . $limit;
763
        } elseif ($offset) {
764
            $limitandoffset = "\n" . $offset;
765
        }
766
767
        return $limitandoffset;
768
    }
769
770
    /**
771
     * Formats a sparql query clause for limiting the search to specific concept types.
772
     * @param array $types limit search to concepts of the given type(s)
773
     * @return string sparql query clause
774
     */
775
    protected function formatTypes($types) {
776
        $typePatterns = array();
777
        if (!empty($types)) {
778
            foreach ($types as $type) {
779
                $unprefixed = EasyRdf\RdfNamespace::expand($type);
780
                $typePatterns[] = "{ ?s a <$unprefixed> }";
781
            }
782
        }
783
784
        return implode(' UNION ', $typePatterns);
785
    }
786
787
    /**
788
     * @param string $prop property to include in the result eg. 'broader' or 'narrower'
789
     * @return string sparql query clause
790
     */
791
    private function formatPropertyCsvClause($prop) {
792
        # This expression creates a CSV row containing pairs of (uri,prefLabel) values.
793
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
794
        $clause = <<<EOV
795
(GROUP_CONCAT(DISTINCT CONCAT(
796
 '"', IF(isIRI(?$prop),STR(?$prop),''), '"', ',',
797
 '"', REPLACE(IF(BOUND(?{$prop}lab),?{$prop}lab,''), '"', '""'), '"', ',',
798
 '"', REPLACE(IF(isLiteral(?{$prop}),?{$prop},''), '"', '""'), '"'
799
); separator='\\n') as ?{$prop}s)
800
EOV;
801
        return $clause;
802
    }
803
804
    /**
805
     * @return string sparql query clause
806
     */
807
    private function formatPrefLabelCsvClause() {
808
        # This expression creates a CSV row containing pairs of (prefLabel, lang) values.
809
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
810
        $clause = <<<EOV
811
(GROUP_CONCAT(DISTINCT CONCAT(
812
 '"', STR(?pref), '"', ',', '"', lang(?pref), '"'
813
); separator='\\n') as ?preflabels)
814
EOV;
815
        return $clause;
816
    }
817
818
    /**
819
     * @param string $lang language code of the returned labels
820
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
821
     * @return array sparql query clause
822
     */
823
    protected function formatExtraFields($lang, $fields) {
824
        // extra variable expressions to request and extra fields to query for
825
        $ret = array('extravars' => '', 'extrafields' => '');
826
827
        if ($fields === null) {
828
            return $ret;
829
        }
830
831
        if (in_array('prefLabel', $fields)) {
832
            $ret['extravars'] .= $this->formatPreflabelCsvClause();
833
            $ret['extrafields'] .= <<<EOF
834
OPTIONAL {
835
  ?s skos:prefLabel ?pref .
836
}
837
EOF;
838
            // removing the prefLabel from the fields since it has been handled separately
839
            $fields = array_diff($fields, array('prefLabel'));
840
        }
841
842
        foreach ($fields as $field) {
843
            $ret['extravars'] .= $this->formatPropertyCsvClause($field);
844
            $ret['extrafields'] .= <<<EOF
845
OPTIONAL {
846
  ?s skos:$field ?$field .
847
  FILTER(!isLiteral(?$field)||langMatches(lang(?{$field}), '$lang'))
848
  OPTIONAL { ?$field skos:prefLabel ?{$field}lab . FILTER(langMatches(lang(?{$field}lab), '$lang')) }
849
}
850
EOF;
851
        }
852
853
        return $ret;
854
    }
855
856
    /**
857
     * Generate condition for matching labels in SPARQL
858
     * @param string $term search term
859
     * @param string $searchLang language code used for matching labels (null means any language)
860
     * @return string sparql query snippet
861
     */
862
    protected function generateConceptSearchQueryCondition($term, $searchLang)
863
    {
864
        # use appropriate matching function depending on query type: =, strstarts, strends or full regex
865
        if (preg_match('/^[^\*]+$/', $term)) { // exact query
866
            $term = str_replace('\\', '\\\\', $term); // quote slashes
867
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
868
            $filtercond = "LCASE(STR(?match)) = '$term'";
869
        } elseif (preg_match('/^[^\*]+\*$/', $term)) { // prefix query
870
            $term = substr($term, 0, -1); // remove the final asterisk
871
            $term = str_replace('\\', '\\\\', $term); // quote slashes
872
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
873
            $filtercond = "STRSTARTS(LCASE(STR(?match)), '$term')";
874
        } elseif (preg_match('/^\*[^\*]+$/', $term)) { // suffix query
875
            $term = substr($term, 1); // remove the preceding asterisk
876
            $term = str_replace('\\', '\\\\', $term); // quote slashes
877
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
878
            $filtercond = "STRENDS(LCASE(STR(?match)), '$term')";
879
        } else { // too complicated - have to use a regex
880
            # make sure regex metacharacters are not passed through
881
            $term = str_replace('\\', '\\\\', preg_quote($term));
882
            $term = str_replace('\\\\*', '.*', $term); // convert asterisk to regex syntax
883
            $term = str_replace('\'', '\\\'', $term); // ensure single quotes are quoted
884
            $filtercond = "REGEX(STR(?match), '^$term$', 'i')";
885
        }
886
887
        $labelcondMatch = ($searchLang) ? "&& (?prop = skos:notation || LANGMATCHES(lang(?match), ?langParam))" : "";
888
889
        return "?s ?prop ?match . FILTER ($filtercond $labelcondMatch)";
890
    }
891
892
893
    /**
894
     * Inner query for concepts using a search term.
895
     * @param string $term search term
896
     * @param string $lang language code of the returned labels
897
     * @param string $searchLang language code used for matching labels (null means any language)
898
     * @param string[] $props properties to target e.g. array('skos:prefLabel','skos:altLabel')
899
     * @param boolean $unique restrict results to unique concepts (default: false)
900
     * @return string sparql query
901
     */
902
    protected function generateConceptSearchQueryInner($term, $lang, $searchLang, $props, $unique, $filterGraph)
903
    {
904
        $valuesProp = $this->formatValues('?prop', $props);
905
        $textcond = $this->generateConceptSearchQueryCondition($term, $searchLang);
906
907
        $rawterm = str_replace(array('\\', '*', '"'), array('\\\\', '', '\"'), $term);
908
        // graph clause, if necessary
909
        $graphClause = $filterGraph != '' ? 'GRAPH ?graph' : '';
910
911
        // extra conditions for label language, if specified
912
        $labelcondLabel = ($lang) ? "LANGMATCHES(lang(?label), '$lang')" : "lang(?match) = '' || LANGMATCHES(lang(?label), lang(?match))";
913
        // if search language and UI/display language differ, must also consider case where there is no prefLabel in
914
        // the display language; in that case, should use the label with the same language as the matched label
915
        $labelcondFallback = ($searchLang != $lang) ?
916
          "OPTIONAL { # in case previous OPTIONAL block gives no labels\n" .
917
          "?s skos:prefLabel ?label . FILTER (LANGMATCHES(LANG(?label), LANG(?match))) }" : "";
918
919
        //  Including the labels if there is no query term given.
920
        if ($rawterm === '') {
921
          $labelClause = "?s skos:prefLabel ?label .";
922
          $labelClause = ($lang) ? $labelClause . " FILTER (LANGMATCHES(LANG(?label), '$lang'))" : $labelClause . "";
923
          return $labelClause . " BIND(?label AS ?match)";
924
        }
925
926
        /*
927
         * This query does some tricks to obtain a list of unique concepts.
928
         * From each match generated by the text index, a string such as
929
         * "1en@example" is generated, where the first character is a number
930
         * encoding the property and priority, then comes the language tag and
931
         * finally the original literal after an @ sign. Of these, the MIN
932
         * function is used to pick the best match for each concept. Finally,
933
         * the structure is unpacked to get back the original string. Phew!
934
         */
935
        $hitvar = $unique ? '(MIN(?matchstr) AS ?hit)' : '(?matchstr AS ?hit)';
936
        $hitgroup = $unique ? 'GROUP BY ?s ?label ?notation' : '';
937
938
        $langClause = $this->generateLangClause($searchLang);
939
940
        $query = <<<EOQ
941
   SELECT DISTINCT ?s ?label ?notation $hitvar
942
   WHERE {
943
    $graphClause {
944
     { 
945
     $valuesProp
946
     VALUES (?prop ?pri ?langParam) { (skos:prefLabel 1 $langClause) (skos:altLabel 3 $langClause) (skos:notation 5 '') (skos:hiddenLabel 7 $langClause)}
947
     $textcond
948
     ?s ?prop ?match }
949
     OPTIONAL {
950
      ?s skos:prefLabel ?label .
951
      FILTER ($labelcondLabel)
952
     } $labelcondFallback
953
     BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri)
954
     BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr)
955
     OPTIONAL { ?s skos:notation ?notation }
956
    }
957
    $filterGraph
958
   }
959
   $hitgroup
960
EOQ;
961
962
        return $query;
963
    }
964
    /**
965
    *  This function can be overwritten in other SPARQL dialects for the possibility of handling the different language clauses
966
     * @param string $lang
967
     * @return string formatted language clause
968
     */
969
    protected function generateLangClause($lang) {
970
        return "'$lang'";
971
    }
972
973
    /**
974
     * Query for concepts using a search term.
975
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
976
     * @param boolean $unique restrict results to unique concepts (default: false)
977
     * @param boolean $showDeprecated whether to include deprecated concepts in search results (default: false)
978
     * @param ConceptSearchParameters $params
979
     * @return string sparql query
980
     */
981
    protected function generateConceptSearchQuery($fields, $unique, $params, $showDeprecated = false) {
982
        $vocabs = $params->getVocabs();
983
        $gcl = $this->graphClause;
984
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
985
        $formattedtype = $this->formatTypes($params->getTypeLimit());
986
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);
987
        $extravars = $formattedfields['extravars'];
988
        $extrafields = $formattedfields['extrafields'];
989
        $schemes = $params->getSchemeLimit();
990
991
        // limit the search to only requested concept schemes
992
        $schemecond = '';
993
        if (!empty($schemes)) {
994
            $conditions = array();
995
            foreach($schemes as $scheme) {
996
                $conditions[] = "{?s skos:inScheme <$scheme>}";
997
            }
998
            $schemecond = '{'.implode(" UNION ",$conditions).'}';
999
        }
1000
        $filterDeprecated="";
1001
        //show or hide deprecated concepts
1002
        if(!$showDeprecated){
1003
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
1004
        }
1005
        // extra conditions for parent and group, if specified
1006
        $parentcond = ($params->getParentLimit()) ? "?s skos:broader+ <" . $params->getParentLimit() . "> ." : "";
1007
        $groupcond = ($params->getGroupLimit()) ? "<" . $params->getGroupLimit() . "> skos:member ?s ." : "";
1008
        $pgcond = $parentcond . $groupcond;
1009
1010
        $orderextra = $this->isDefaultEndpoint() ? $this->graph : '';
1011
1012
        # make VALUES clauses
1013
        $props = array('skos:prefLabel', 'skos:altLabel');
1014
1015
        //add notation into searchable data for the vocabularies which have been configured for it
1016
        if ($vocabs) {
1017
            $searchByNotation = false;
1018
            foreach ($vocabs as $vocab) {
1019
                if ($vocab->getConfig()->searchByNotation()) {
1020
                    $searchByNotation = true;
1021
                }
1022
            }
1023
            if ($searchByNotation) {
1024
                $props[] = 'skos:notation';
1025
            }
1026
        }
1027
1028
        if ($params->getHidden()) {
1029
            $props[] = 'skos:hiddenLabel';
1030
        }
1031
        $filterGraph = empty($vocabs) ? $this->formatFilterGraph($vocabs) : '';
1032
1033
        // remove futile asterisks from the search term
1034
        $term = $params->getSearchTerm();
1035
        while (strpos($term, '**') !== false) {
1036
            $term = str_replace('**', '*', $term);
1037
        }
1038
1039
        $labelpriority = <<<EOQ
1040
  FILTER(BOUND(?s))
1041
  BIND(STR(SUBSTR(?hit,1,1)) AS ?pri)
1042
  BIND(IF((SUBSTR(STRBEFORE(?hit, '@'),1) != ?pri), STRLANG(STRAFTER(?hit, '@'), SUBSTR(STRBEFORE(?hit, '@'),2)), STRAFTER(?hit, '@')) AS ?match)
1043
  BIND(IF((?pri = "1" || ?pri = "2") && ?match != ?label, ?match, ?unbound) as ?plabel)
1044
  BIND(IF((?pri = "3" || ?pri = "4"), ?match, ?unbound) as ?alabel)
1045
  BIND(IF((?pri = "7" || ?pri = "8"), ?match, ?unbound) as ?hlabel)
1046
EOQ;
1047
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique, $filterGraph);
1048
        if ($params->getSearchTerm() === '*' || $params->getSearchTerm() === '') {
1049
          $labelpriority = '';
1050
        }
1051
        $query = <<<EOQ
1052
SELECT DISTINCT ?s ?label ?plabel ?alabel ?hlabel ?graph ?notation (GROUP_CONCAT(DISTINCT STR(?type);separator=' ') as ?types) $extravars 
1053
$fcl
1054
WHERE {
1055
 $gcl {
1056
  {
1057
  $innerquery
1058
  }
1059
  $labelpriority
1060
  $formattedtype
1061
  { $pgcond 
1062
   ?s a ?type .
1063
   $extrafields $schemecond
1064
  }
1065
  $filterDeprecated
1066
 }
1067
 $filterGraph
1068
}
1069
GROUP BY ?s ?match ?label ?plabel ?alabel ?hlabel ?notation ?graph
1070
ORDER BY LCASE(STR(?match)) LANG(?match) $orderextra
1071
EOQ;
1072
        return $query;
1073
    }
1074
1075
    /**
1076
     * Transform a single concept search query results into the skosmos desired return format.
1077
     * @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...
1078
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1079
     * @return array query result object
1080
     */
1081
    private function transformConceptSearchResult($row, $vocabs, $fields)
1082
    {
1083
        $hit = array();
1084
        $hit['uri'] = $row->s->getUri();
1085
1086
        if (isset($row->graph)) {
1087
            $hit['graph'] = $row->graph->getUri();
1088
        }
1089
1090
        foreach (explode(" ", $row->types->getValue()) as $typeuri) {
1091
            $hit['type'][] = $this->shortenUri($typeuri);
1092
        }
1093
1094
        if(!empty($fields)) {
1095
            foreach ($fields as $prop) {
1096
                $propname = $prop . 's';
1097
                if (isset($row->$propname)) {
1098
                    foreach (explode("\n", $row->$propname->getValue()) as $line) {
1099
                        $rdata = str_getcsv($line, ',', '"', '"');
1100
                        $propvals = array();
1101
                        if ($rdata[0] != '') {
1102
                            $propvals['uri'] = $rdata[0];
1103
                        }
1104
                        if ($rdata[1] != '') {
1105
                            $propvals['prefLabel'] = $rdata[1];
1106
                        }
1107
                        if ($rdata[2] != '') {
1108
                            $propvals = $rdata[2];
1109
                        }
1110
1111
                        $hit['skos:' . $prop][] = $propvals;
1112
                    }
1113
                }
1114
            }
1115
        }
1116
1117
1118
        if (isset($row->preflabels)) {
1119
            foreach (explode("\n", $row->preflabels->getValue()) as $line) {
1120
                $pref = str_getcsv($line, ',', '"', '"');
1121
                $hit['prefLabels'][$pref[1]] = $pref[0];
1122
            }
1123
        }
1124
1125
        foreach ($vocabs as $vocab) { // looping the vocabulary objects and asking these for a localname for the concept.
1126
            $localname = $vocab->getLocalName($hit['uri']);
1127
            if ($localname !== $hit['uri']) { // only passing the result forward if the uri didn't boomerang right back.
1128
                $hit['localname'] = $localname;
1129
                break; // stopping the search when we find one that returns something valid.
1130
            }
1131
        }
1132
1133
        if (isset($row->label)) {
1134
            $hit['prefLabel'] = $row->label->getValue();
1135
        }
1136
1137
        if (isset($row->label)) {
1138
            $hit['lang'] = $row->label->getLang();
1139
        }
1140
1141
        if (isset($row->notation)) {
1142
            $hit['notation'] = $row->notation->getValue();
1143
        }
1144
1145
        if (isset($row->plabel)) {
1146
            $hit['matchedPrefLabel'] = $row->plabel->getValue();
1147
            $hit['lang'] = $row->plabel->getLang();
1148
        } elseif (isset($row->alabel)) {
1149
            $hit['altLabel'] = $row->alabel->getValue();
1150
            $hit['lang'] = $row->alabel->getLang();
1151
        } elseif (isset($row->hlabel)) {
1152
            $hit['hiddenLabel'] = $row->hlabel->getValue();
1153
            $hit['lang'] = $row->hlabel->getLang();
1154
        }
1155
        return $hit;
1156
    }
1157
1158
    /**
1159
     * Transform the concept search query results into the skosmos desired return format.
1160
     * @param EasyRdf\Sparql\Result $results
1161
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1162
     * @return array query result object
1163
     */
1164
    private function transformConceptSearchResults($results, $vocabs, $fields) {
1165
        $ret = array();
1166
1167
        foreach ($results as $row) {
1168
            if (!isset($row->s)) {
1169
                // don't break if query returns a single dummy result
1170
                continue;
1171
            }
1172
            $ret[] = $this->transformConceptSearchResult($row, $vocabs, $fields);
1173
        }
1174
        return $ret;
1175
    }
1176
1177
    /**
1178
     * Query for concepts using a search term.
1179
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1180
     * @param array $fields extra fields to include in the result (array of strings). (default: null = none)
1181
     * @param boolean $unique restrict results to unique concepts (default: false)
1182
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1183
     * @param ConceptSearchParameters $params
1184
     * @return array query result object
1185
     */
1186
    public function queryConcepts($vocabs, $fields = null, $unique = false, $params, $showDeprecated = false) {
1187
        $query = $this->generateConceptSearchQuery($fields, $unique, $params,$showDeprecated);
1188
        $results = $this->query($query);
1189
        return $this->transformConceptSearchResults($results, $vocabs, $fields);
1190
    }
1191
1192
    /**
1193
     * Generates sparql query clauses used for creating the alphabetical index.
1194
     * @param string $letter the letter (or special class) to search for
1195
     * @return array of sparql query clause strings
1196
     */
1197
    private function formatFilterConditions($letter, $lang) {
1198
        $useRegex = false;
1199
1200
        if ($letter == '*') {
1201
            $letter = '.*';
1202
            $useRegex = true;
1203
        } elseif ($letter == '0-9') {
1204
            $letter = '[0-9].*';
1205
            $useRegex = true;
1206
        } elseif ($letter == '!*') {
1207
            $letter = '[^\\\\p{L}\\\\p{N}].*';
1208
            $useRegex = true;
1209
        }
1210
1211
        # make text query clause
1212
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
1213
        if ($useRegex) {
1214
            $filtercondLabel = $lang ? "regex(str(?label), '^$letter$', 'i') && langMatches(lang(?label), '$lang')" : "regex(str(?label), '^$letter$', 'i')";
1215
            $filtercondALabel = $lang ? "regex(str(?alabel), '^$letter$', 'i') && langMatches(lang(?alabel), '$lang')" : "regex(str(?alabel), '^$letter$', 'i')";
1216
        } else {
1217
            $filtercondLabel = $lang ? "strstarts(lcase(str(?label)), '$lcletter') && langMatches(lang(?label), '$lang')" : "strstarts(lcase(str(?label)), '$lcletter')";
1218
            $filtercondALabel = $lang ? "strstarts(lcase(str(?alabel)), '$lcletter') && langMatches(lang(?alabel), '$lang')" : "strstarts(lcase(str(?alabel)), '$lcletter')";
1219
        }
1220
        return array('filterpref' => $filtercondLabel, 'filteralt' => $filtercondALabel);
1221
    }
1222
1223
    /**
1224
     * Generates the sparql query used for rendering the alphabetical index.
1225
     * @param string $letter the letter (or special class) to search for
1226
     * @param string $lang language of labels
1227
     * @param integer $limit limits the amount of results
1228
     * @param integer $offset offsets the result set
1229
     * @param array|null $classes
1230
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1231
     * @param \EasyRdf\Resource|null $qualifier alphabetical list qualifier resource or null (default: null)
1232
     * @return string sparql query
1233
     */
1234
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated = false, $qualifier = null) {
1235
        $gcl = $this->graphClause;
1236
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1237
        $values = $this->formatValues('?type', $classes, 'uri');
1238
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
1239
        $conditions = $this->formatFilterConditions($letter, $lang);
1240
        $filtercondLabel = $conditions['filterpref'];
1241
        $filtercondALabel = $conditions['filteralt'];
1242
        $qualifierClause = $qualifier ? "OPTIONAL { ?s <" . $qualifier->getURI() . "> ?qualifier }" : "";
1243
        $filterDeprecated="";
1244
        if(!$showDeprecated){
1245
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
1246
        }
1247
        $query = <<<EOQ
1248
SELECT DISTINCT ?s ?label ?alabel ?qualifier
1249
WHERE {
1250
  $gcl {
1251
    {
1252
      ?s skos:prefLabel ?label .
1253
      FILTER (
1254
        $filtercondLabel
1255
      )
1256
    }
1257
    UNION
1258
    {
1259
      {
1260
        ?s skos:altLabel ?alabel .
1261
        FILTER (
1262
          $filtercondALabel
1263
        )
1264
      }
1265
      {
1266
        ?s skos:prefLabel ?label .
1267
        FILTER (langMatches(lang(?label), '$lang'))
1268
      }
1269
    }
1270
    ?s a ?type .
1271
    $qualifierClause
1272
    $filterDeprecated
1273
    $values
1274
  }
1275
}
1276
ORDER BY LCASE(STR(COALESCE(?alabel, ?label))) STR(?s) LCASE(STR(?qualifier)) $limitandoffset
1277
EOQ;
1278
        return $query;
1279
    }
1280
1281
    /**
1282
     * Transforms the alphabetical list query results into an array format.
1283
     * @param EasyRdf\Sparql\Result $results
1284
     * @return array
1285
     */
1286
    private function transformAlphabeticalListResults($results) {
1287
        $ret = array();
1288
1289
        foreach ($results as $row) {
1290
            if (!isset($row->s)) {
1291
                continue;
1292
            }
1293
            // don't break if query returns a single dummy result
1294
1295
            $hit = array();
1296
            $hit['uri'] = $row->s->getUri();
1297
1298
            $hit['localname'] = $row->s->localName();
1299
1300
            $hit['prefLabel'] = $row->label->getValue();
1301
            $hit['lang'] = $row->label->getLang();
1302
1303
            if (isset($row->alabel)) {
1304
                $hit['altLabel'] = $row->alabel->getValue();
1305
                $hit['lang'] = $row->alabel->getLang();
1306
            }
1307
1308
            if (isset($row->qualifier)) {
1309
                if ($row->qualifier instanceof EasyRdf\Literal) {
1310
                    $hit['qualifier'] = $row->qualifier->getValue();
1311
                }
1312
                else {
1313
                    $hit['qualifier'] = $row->qualifier->localName();
1314
                }
1315
            }
1316
1317
            $ret[] = $hit;
1318
        }
1319
1320
        return $ret;
1321
    }
1322
1323
    /**
1324
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1325
     * '*!' (special characters) and '*' (everything) are accepted.
1326
     * @param string $letter the letter (or special class) to search for
1327
     * @param string $lang language of labels
1328
     * @param integer $limit limits the amount of results
1329
     * @param integer $offset offsets the result set
1330
     * @param array $classes
1331
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1332
     * @param \EasyRdf\Resource|null $qualifier alphabetical list qualifier resource or null (default: null)
1333
     */
1334
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null, $showDeprecated = false, $qualifier = null) {
1335
        if ($letter === '') {
1336
            return array(); // special case: no letter given, return empty list
1337
        }
1338
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated, $qualifier);
1339
        $results = $this->query($query);
1340
        return $this->transformAlphabeticalListResults($results);
1341
    }
1342
1343
    /**
1344
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1345
     * Note that we force the datatype of the result variable otherwise Virtuoso does not properly interpret the DISTINCT and we have duplicated results
1346
     * @param string $lang language
1347
     * @return string sparql query
1348
     */
1349
    private function generateFirstCharactersQuery($lang, $classes) {
1350
        $gcl = $this->graphClause;
1351
        $classes = (isset($classes) && sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1352
        $values = $this->formatValues('?type', $classes, 'uri');
1353
        $query = <<<EOQ
1354
SELECT DISTINCT (ucase(str(substr(?label, 1, 1))) as ?l) WHERE {
1355
  $gcl {
1356
    ?c skos:prefLabel ?label .
1357
    ?c a ?type
1358
    FILTER(langMatches(lang(?label), '$lang'))
1359
    $values
1360
  }
1361
}
1362
EOQ;
1363
        return $query;
1364
    }
1365
1366
    /**
1367
     * Transforms the first characters query results into an array format.
1368
     * @param EasyRdf\Sparql\Result $result
1369
     * @return array
1370
     */
1371
    private function transformFirstCharactersResults($result) {
1372
        $ret = array();
1373
        foreach ($result as $row) {
1374
            $ret[] = $row->l->getValue();
1375
        }
1376
        return $ret;
1377
    }
1378
1379
    /**
1380
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1381
     * @param string $lang language
1382
     * @return array array of characters
1383
     */
1384
    public function queryFirstCharacters($lang, $classes = null) {
1385
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1386
        $result = $this->query($query);
1387
        return $this->transformFirstCharactersResults($result);
1388
    }
1389
1390
    /**
1391
     * @param string $uri
1392
     * @param string $lang
1393
     * @return string sparql query string
1394
     */
1395
    private function generateLabelQuery($uri, $lang) {
1396
        $fcl = $this->generateFromClause();
1397
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1398
        $query = <<<EOQ
1399
SELECT ?label $fcl
1400
WHERE {
1401
  <$uri> a ?type .
1402
  OPTIONAL {
1403
    <$uri> skos:prefLabel ?label .
1404
    $labelcondLabel
1405
  }
1406
  OPTIONAL {
1407
    <$uri> rdfs:label ?label .
1408
    $labelcondLabel
1409
  }
1410
  OPTIONAL {
1411
    <$uri> dc:title ?label .
1412
    $labelcondLabel
1413
  }
1414
  OPTIONAL {
1415
    <$uri> dc11:title ?label .
1416
    $labelcondLabel
1417
  }
1418
}
1419
EOQ;
1420
        return $query;
1421
    }
1422
1423
1424
    /**
1425
     * @param string $uri
1426
     * @param string $lang
1427
     * @return string sparql query string
1428
     */
1429
    private function generateAllLabelsQuery($uri, $lang) {
1430
        $fcl = $this->generateFromClause();
1431
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?val), '$lang') )" : "";
1432
        $query = <<<EOQ
1433
SELECT DISTINCT ?prop ?val $fcl
1434
WHERE {
1435
  <$uri> a ?type .
1436
  OPTIONAL {
1437
      <$uri> ?prop ?val .
1438
      $labelcondLabel
1439
  }
1440
  VALUES ?prop { skos:prefLabel skos:altLabel skos:hiddenLabel }
1441
}
1442
EOQ;
1443
        return $query;
1444
    }
1445
1446
    /**
1447
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1448
     * @param string $uri
1449
     * @param string $lang
1450
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1451
     */
1452
    public function queryLabel($uri, $lang) {
1453
        $query = $this->generateLabelQuery($uri, $lang);
1454
        $result = $this->query($query);
1455
        $ret = array();
1456
        foreach ($result as $row) {
1457
            if (!isset($row->label)) {
1458
                // existing concept but no labels
1459
                return array();
1460
            }
1461
            $ret[$row->label->getLang()] = $row->label;
1462
        }
1463
1464
        if (sizeof($ret) > 0) {
1465
            // existing concept, with label(s)
1466
            return $ret;
1467
        } else {
1468
            // nonexistent concept
1469
            return null;
1470
        }
1471
    }
1472
1473
    /**
1474
     * Query for skos:prefLabels, skos:altLabels and skos:hiddenLabels of a resource.
1475
     * @param string $uri
1476
     * @param string $lang
1477
     * @return array array of prefLabels, altLabels and hiddenLabels - or null if resource doesn't exist
1478
     */
1479
    public function queryAllConceptLabels($uri, $lang) {
1480
        $query = $this->generateAllLabelsQuery($uri, $lang);
1481
        $result = $this->query($query);
1482
1483
        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

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