Passed
Push — set-user-agent-version ( ede3e4 )
by Osma
07:35
created

GenericSparql::countLangConcepts()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

Loading history...
407
    {
408
        $gcl = $this->graphClause;
409
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
410
        $values = $this->formatValues('?uri', $uris, 'uri');
411
        $uniqueVocabs = $this->filterDuplicateVocabs($vocabs);
412
        $valuesGraph = empty($vocabs) ? $this->formatValuesGraph($uniqueVocabs) : '';
0 ignored issues
show
Unused Code introduced by
The assignment to $valuesGraph is dead and can be removed.
Loading history...
Bug introduced by
The method formatValuesGraph() does not exist on GenericSparql. Did you maybe mean formatValues()? ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

Loading history...
1072
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);
0 ignored issues
show
Bug introduced by
The method formatExtraFields() does not exist on GenericSparql. ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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