GenericSparql::transformParentListResults()   F
last analyzed

Complexity

Conditions 24
Paths 7203

Size

Total Lines 73
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 44
dl 0
loc 73
rs 0
c 0
b 0
f 0
cc 24
nc 7203
nop 2

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

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