Completed
Push — master ( eb3b50...e1e7fd )
by Henri
03:00
created

GenericSparql::generateFromClause()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 12
nc 8
nop 1
1
<?php
2
3
/**
4
 * Generates SPARQL queries and provides access to the SPARQL endpoint.
5
 */
6
class GenericSparql {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
7
    /**
8
     * A SPARQL Client eg. an EasyRDF instance.
9
     * @property EasyRdf_Sparql_Client $client
10
     */
11
    protected $client;
12
    /**
13
     * Graph uri.
14
     * @property string $graph
15
     */
16
    protected $graph;
17
    /**
18
     * A SPARQL query graph part template.
19
     * @property string $graph
20
     */
21
    protected $graphClause;
22
    /**
23
     * Model instance.
24
     * @property Model $model
25
     */
26
    protected $model;
27
28
    /**
29
     * Cache used to avoid expensive shorten() calls
30
     * @property array $qnamecache
31
     */
32
    private $qnamecache = array();
33
34
    /**
35
     * Requires the following three parameters.
36
     * @param string $endpoint SPARQL endpoint address.
37
     * @param object $graph an EasyRDF SPARQL graph instance.
38
     * @param object $model a Model instance.
39
     */
40
    public function __construct($endpoint, $graph, $model) {
41
        $this->graph = $graph;
42
        $this->model = $model;
43
44
        // create the EasyRDF SPARQL client instance to use
45
        $this->initializeHttpClient();
46
        $this->client = new EasyRdf_Sparql_Client($endpoint);
47
48
        // set graphClause so that it can be used by all queries
49
        if ($this->isDefaultEndpoint()) // default endpoint; query any graph (and catch it in a variable)
50
        {
51
            $this->graphClause = "GRAPH $graph";
52
        } elseif ($graph) // query a specific graph
53
        {
54
            $this->graphClause = "GRAPH <$graph>";
55
        } else // query the default graph
56
        {
57
            $this->graphClause = "";
58
        }
59
60
    }
61
62
    
63
    /**
64
     * Generates FROM clauses for the queries 
65
     * @param Vocabulary[]|null $vocabs
66
     * @return string
67
     */
68
    protected function generateFromClause($vocabs=null) {
69
        $graphs = array();
70
        $clause = '';
71
        if (!$vocabs) {
72
            return $this->graph !== '?graph' ? "FROM <$this->graph>" : '';
73
        }
74
        foreach($vocabs as $vocab) {
75
            $graph = $vocab->getGraph();
76
            if (!in_array($graph, $graphs)) {
77
                array_push($graphs, $graph);
78
            }
79
        }
80
        foreach ($graphs as $graph) {
81
            $clause .= "FROM NAMED <$graph> "; 
82
        }
83
        return $clause;
84
    }
85
86
    protected function initializeHttpClient() {
87
        // configure the HTTP client used by EasyRdf_Sparql_Client
88
        $httpclient = EasyRdf_Http::getDefaultHttpClient();
89
        $httpclient->setConfig(array('timeout' => $this->model->getConfig()->getSparqlTimeout()));
90
91
        // if special cache control (typically no-cache) was requested by the
92
        // client, set the same type of cache control headers also in subsequent
93
        // in the SPARQL requests (this is useful for performance testing)
94
        // @codeCoverageIgnoreStart
95
        $cacheControl = filter_input(INPUT_SERVER, 'HTTP_CACHE_CONTROL', FILTER_SANITIZE_STRING);
96
        $pragma = filter_input(INPUT_SERVER, 'HTTP_PRAGMA', FILTER_SANITIZE_STRING);
97
        if ($cacheControl !== null || $pragma !== null) {
98
            $val = $pragma !== null ? $pragma : $cacheControl;
99
            $httpclient->setHeaders('Cache-Control', $val);
100
        }
101
        // @codeCoverageIgnoreEnd
102
103
        EasyRdf_Http::setDefaultHttpClient($httpclient); // actually redundant..
104
    }
105
106
    /**
107
     * Return true if this is the default SPARQL endpoint, used as the facade to query
108
     * all vocabularies.
109
     */
110
111
    protected function isDefaultEndpoint() {
112
        return $this->graph[0] == '?';
113
    }
114
115
    /**
116
     * Returns the graph instance
117
     * @return object EasyRDF graph instance.
118
     */
119
    public function getGraph() {
120
        return $this->graph;
121
    }
122
    
123
    /**
124
     * Shorten a URI
125
     * @param string $uri URI to shorten
126
     * @return string shortened URI, or original URI if it cannot be shortened
127
     */
128
    private function shortenUri($uri) {
129
        if (!array_key_exists($uri, $this->qnamecache)) {
130
            $res = new EasyRdf_Resource($uri);
131
            $qname = $res->shorten(); // returns null on failure
132
            $this->qnamecache[$uri] = ($qname !== null) ? $qname : $uri;
133
        }
134
        return $this->qnamecache[$uri];
135
    }
136
137
138
    /**
139
     * Generates the sparql query for retrieving concept and collection counts in a vocabulary.
140
     * @return string sparql query
141
     */
142
    private function generateCountConceptsQuery($array, $group) {
143
        $fcl = $this->generateFromClause();
144
        $optional = $array ? "UNION { ?type rdfs:subClassOf* <$array> }" : '';
145
        $optional .= $group ? "UNION { ?type rdfs:subClassOf* <$group> }" : '';
146
        $query = <<<EOQ
147
      SELECT (COUNT(?conc) as ?c) ?type ?typelabel $fcl WHERE {
148
        { ?conc a ?type .
149
        { ?type rdfs:subClassOf* skos:Concept . } UNION { ?type rdfs:subClassOf* skos:Collection . } $optional }
150
        OPTIONAL { ?type rdfs:label ?typelabel . }
151
      }
152
GROUP BY ?type ?typelabel
153
EOQ;
154
        return $query;
155
    }
156
157
    /**
158
     * Used for transforming the concept count query results.
159
     * @param EasyRdf_Sparql_Result $result query results to be transformed
160
     * @param string $lang language of labels
161
     * @return Array containing the label counts
162
     */
163
    private function transformCountConceptsResults($result, $lang) {
164
        $ret = array();
165
        foreach ($result as $row) {
166
            if (!isset($row->type)) {
167
                continue;
168
            }
169
            $ret[$row->type->getUri()]['type'] = $row->type->getUri();
170
            $ret[$row->type->getUri()]['count'] = $row->c->getValue();
171
            if (isset($row->typelabel) && $row->typelabel->getLang() === $lang) {
172
                $ret[$row->type->getUri()]['label'] = $row->typelabel->getValue();
173
            }
174
175
        }
176
        return $ret;
177
    }
178
179
    /**
180
     * Used for counting number of concepts and collections in a vocabulary.
181
     * @param string $lang language of labels
182
     * @return int number of concepts in this vocabulary
183
     */
184
    public function countConcepts($lang = null, $array = null, $group = null) {
185
        $query = $this->generateCountConceptsQuery($array, $group);
186
        $result = $this->client->query($query);
187
        return $this->transformCountConceptsResults($result, $lang);
188
    }
189
190
    /**
191
     * @param array $langs Languages to query for
192
     * @param string[] $props property names
193
     * @return string sparql query
194
     */
195
    private function generateCountLangConceptsQuery($langs, $classes, $props) {
196
        $gcl = $this->graphClause;
197
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
198
199
        $values = $this->formatValues('?type', $classes, 'uri');
200
        $valuesLang = $this->formatValues('?lang', $langs, 'literal');
201
        $valuesProp = $this->formatValues('?prop', $props, null);
202
203
        $query = <<<EOQ
204
SELECT ?lang ?prop
205
  (COUNT(?label) as ?count)
206
WHERE {
207
  $gcl {
208
    ?conc a ?type .
209
    ?conc ?prop ?label .
210
    FILTER (langMatches(lang(?label), ?lang))
211
    $valuesLang
212
    $valuesProp
213
  }
214
  $values
215
}
216
GROUP BY ?lang ?prop ?type
217
EOQ;
218
        return $query;
219
    }
220
221
    /**
222
     * Transforms the CountLangConcepts results into an array of label counts.
223
     * @param EasyRdf_Sparql_Result $result query results to be transformed
224
     * @param array $langs Languages to query for
225
     * @param string[] $props property names
226
     */
227
    private function transformCountLangConceptsResults($result, $langs, $props) {
228
        $ret = array();
229
        // set default count to zero; overridden below if query found labels
230
        foreach ($langs as $lang) {
231
            foreach ($props as $prop) {
232
                $ret[$lang][$prop] = 0;
233
            }
234
        }
235
        foreach ($result as $row) {
236
            if (isset($row->lang) && isset($row->prop) && isset($row->count)) {
237
                $ret[$row->lang->getValue()][$row->prop->shorten()] += 
238
                $row->count->getValue();
239
            }
240
241
        }
242
        ksort($ret);
243
        return $ret;
244
    }
245
246
    /**
247
     * Counts the number of concepts in a easyRDF graph with a specific language.
248
     * @param array $langs Languages to query for
249
     * @return Array containing count of concepts for each language and property.
250
     */
251
    public function countLangConcepts($langs, $classes = null) {
252
        $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel');
253
        $query = $this->generateCountLangConceptsQuery($langs, $classes, $props);
254
        // Count the number of terms in each language
255
        $result = $this->client->query($query);
256
        return $this->transformCountLangConceptsResults($result, $langs, $props);
257
    }
258
259
    /**
260
     * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one
261
     * of the constants given.
262
     * @param string $varname variable name, e.g. "?uri"
263
     * @param array $values the values
264
     * @param string $type type of values: "uri", "literal" or null (determines quoting style)
265
     */
266
    protected function formatValues($varname, $values, $type = null) {
267
        $constants = array();
268
        foreach ($values as $val) {
269
            if ($type == 'uri') {
270
                $val = "<$val>";
271
            }
272
273
            if ($type == 'literal') {
274
                $val = "'$val'";
275
            }
276
277
            $constants[] = "($val)";
278
        }
279
        $values = implode(" ", $constants);
280
281
        return "VALUES ($varname) { $values }";
282
    }
283
284
    /**
285
     * Filters multiple instances of the same vocabulary from the input array.
286
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
287
     * @return \Vocabulary[]
288
     */
289
    private function filterDuplicateVocabs($vocabs) {
290
        // filtering duplicates
291
        $uniqueVocabs = array();
292
        if ($vocabs !== null && sizeof($vocabs) > 0) {
293
            foreach ($vocabs as $voc) {
294
                $uniqueVocabs[$voc->getId()] = $voc;
295
            }
296
        }
297
298
        return $uniqueVocabs;
299
    }
300
301
    /**
302
     * Generates a sparql query for one or more concept URIs
303
     * @param mixed $uris concept URI (string) or array of URIs
304
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
305
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
306
     * @return string sparql query
307
     */
308
    private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) {
309
        $gcl = $this->graphClause;
310
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
311
        $values = $this->formatValues('?uri', $uris, 'uri');
312
        $uniqueVocabs = $this->filterDuplicateVocabs($vocabs);
313
        $valuesGraph = empty($vocabs) ? $this->formatValuesGraph($uniqueVocabs) : '';
314
315
        if ($arrayClass === null) {
316
            $construct = $optional = "";
317
        } else {
318
            // add information that can be used to format narrower concepts by
319
            // the array they belong to ("milk by source animal" use case)
320
            $construct = "\n ?x skos:member ?o . ?x skos:prefLabel ?xl . ?x a <$arrayClass> .";
321
            $optional = "\n OPTIONAL {
322
                      ?x skos:member ?o .
323
                      ?x a <$arrayClass> .
324
                      ?x skos:prefLabel ?xl .
325
                      FILTER NOT EXISTS {
326
                        ?x skos:member ?other .
327
                        FILTER NOT EXISTS { ?other skos:broader ?uri }
328
                      }
329
                    }";
330
        }
331
        $query = <<<EOQ
332
CONSTRUCT {
333
 ?s ?p ?uri .
334
 ?sp ?uri ?op .
335
 ?uri ?p ?o .
336
 ?p rdfs:label ?proplabel .
337
 ?p rdfs:subPropertyOf ?pp .
338
 ?pp rdfs:label ?plabel .
339
 ?o a ?ot .
340
 ?o skos:prefLabel ?opl .
341
 ?o rdfs:label ?ol .
342
 ?o rdf:value ?ov .
343
 ?o skos:notation ?on .
344
 ?o ?oprop ?oval .
345
 ?o ?xlprop ?xlval .
346
 ?directgroup skos:member ?uri .
347
 ?parent skos:member ?group .
348
 ?group skos:prefLabel ?grouplabel .
349
 ?b1 rdf:first ?item .
350
 ?b1 rdf:rest ?b2 .
351
 ?item a ?it .
352
 ?item skos:prefLabel ?il .
353
 ?group a ?grouptype . $construct
354
} $fcl WHERE {
355
 $gcl {
356
  {
357
    ?s ?p ?uri .
358
    FILTER(!isBlank(?s))
359
    FILTER(?p != skos:inScheme)
360
  }
361
  UNION
362
  { ?sp ?uri ?op . }
363
  UNION
364
  {
365
    ?directgroup skos:member ?uri .
366
    ?group skos:member+ ?uri .
367
    ?group skos:prefLabel ?grouplabel .
368
    ?group a ?grouptype .
369
    OPTIONAL { ?parent skos:member ?group }
370
  }
371
  UNION
372
  {
373
   ?uri ?p ?o .
374
   OPTIONAL {
375
     ?o rdf:rest* ?b1 .
376
     ?b1 rdf:first ?item .
377
     ?b1 rdf:rest ?b2 .
378
     OPTIONAL { ?item a ?it . }
379
     OPTIONAL { ?item skos:prefLabel ?il . }
380
   }
381
   OPTIONAL {
382
     { ?p rdfs:label ?proplabel . }
383
     UNION
384
     { ?p rdfs:subPropertyOf ?pp . }
385
     UNION
386
     { ?o a ?ot . }
387
     UNION
388
     { ?o skos:prefLabel ?opl . }
389
     UNION
390
     { ?o rdfs:label ?ol . }
391
     UNION
392
     { ?o rdf:value ?ov . 
393
       OPTIONAL { ?o ?oprop ?oval . }
394
     }
395
     UNION
396
     { ?o skos:notation ?on . }
397
     UNION
398
     { ?o a skosxl:Label .
399
       ?o ?xlprop ?xlval }
400
   } $optional
401
  }
402
 }
403
 $values
404
}
405
$valuesGraph
406
EOQ;
407
        return $query;
408
    }
409
410
    /**
411
     * Transforms ConceptInfo query results into an array of Concept objects
412
     * @param EasyRdf_Graph $result query results to be transformed
413
     * @param array $uris concept URIs
414
     * @param \Vocabulary[] $vocabs array of Vocabulary object
415
     * @param string|null $clang content language
416
     * @return mixed query result graph (EasyRdf_Graph), or array of Concept objects
417
     */
418
    private function transformConceptInfoResults($result, $uris, $vocabs, $clang) {
419
        $conceptArray = array();
420
        foreach ($uris as $index => $uri) {
421
            $conc = $result->resource($uri);
422
            $vocab = sizeof($vocabs) == 1 ? $vocabs[0] : $vocabs[$index];
423
            $conceptArray[] = new Concept($this->model, $vocab, $conc, $result, $clang);
424
        }
425
        return $conceptArray;
426
    }
427
428
    /**
429
     * Returns information (as a graph) for one or more concept URIs
430
     * @param mixed $uris concept URI (string) or array of URIs
431
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
432
     * @param \Vocabulary[]|null $vocabs vocabularies to target
433
     * @return \Concept[]
434
     */
435
    public function queryConceptInfoGraph($uris, $arrayClass = null, $vocabs = array()) {
436
        // if just a single URI is given, put it in an array regardless
437
        if (!is_array($uris)) {
438
            $uris = array($uris);
439
        }
440
441
        $query = $this->generateConceptInfoQuery($uris, $arrayClass, $vocabs);
442
        $result = $this->client->query($query);
443
        return $result;
444
    }
445
446
    /**
447
     * Returns information (as an array of Concept objects) for one or more concept URIs
448
     * @param mixed $uris concept URI (string) or array of URIs
449
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
450
     * @param \Vocabulary[] $vocabs vocabularies to target
451
     * @param string|null $clang content language
452
     * @return EasyRdf_Graph
453
     */
454
    public function queryConceptInfo($uris, $arrayClass = null, $vocabs = array(), $clang = null) {
455
        // if just a single URI is given, put it in an array regardless
456
        if (!is_array($uris)) {
457
            $uris = array($uris);
458
        }
459
        $result = $this->queryConceptInfoGraph($uris, $arrayClass, $vocabs);
460
        if ($result->isEmpty()) {
461
            return;
462
        }
463
        return $this->transformConceptInfoResults($result, $uris, $vocabs, $clang);
464
    }
465
466
    /**
467
     * Generates the sparql query for queryTypes
468
     * @param string $lang
469
     * @return string sparql query
470
     */
471
    private function generateQueryTypesQuery($lang) {
472
        $fcl = $this->generateFromClause();
473
        $query = <<<EOQ
474
SELECT DISTINCT ?type ?label ?superclass $fcl
475
WHERE {
476
  {
477
    { BIND( skos:Concept as ?type ) }
478
    UNION
479
    { BIND( skos:Collection as ?type ) }
480
    UNION
481
    { BIND( isothes:ConceptGroup as ?type ) }
482
    UNION
483
    { BIND( isothes:ThesaurusArray as ?type ) }
484
    UNION
485
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Concept . }
486
    UNION
487
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Collection . }
488
  }
489
  OPTIONAL {
490
    ?type rdfs:label ?label .
491
    FILTER(langMatches(lang(?label), '$lang'))
492
  }
493
  OPTIONAL {
494
    ?type rdfs:subClassOf ?superclass .
495
  }
496
  FILTER EXISTS {
497
    ?s a ?type .
498
    ?s skos:prefLabel ?prefLabel .
499
  }
500
}
501
EOQ;
502
        return $query;
503
    }
504
505
    /**
506
     * Transforms the results into an array format.
507
     * @param EasyRdf_Sparql_Result $result
508
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
509
     */
510 View Code Duplication
    private function transformQueryTypesResults($result) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
511
        $ret = array();
512
        foreach ($result as $row) {
513
            $type = array();
514
            if (isset($row->label)) {
515
                $type['label'] = $row->label->getValue();
516
            }
517
518
            if (isset($row->superclass)) {
519
                $type['superclass'] = $row->superclass->getUri();
520
            }
521
522
            $ret[$row->type->getURI()] = $type;
523
        }
524
        return $ret;
525
    }
526
527
    /**
528
     * Retrieve information about types from the endpoint
529
     * @param string $lang
530
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
531
     */
532
    public function queryTypes($lang) {
533
        $query = $this->generateQueryTypesQuery($lang);
534
        $result = $this->client->query($query);
535
        return $this->transformQueryTypesResults($result);
536
    }
537
538
    /**
539
     * Generates the concept scheme query.
540
     * @param string $conceptscheme concept scheme URI
541
     * @return string sparql query
542
     */
543
    private function generateQueryConceptSchemeQuery($conceptscheme) {
544
        $fcl = $this->generateFromClause();
545
        $query = <<<EOQ
546
CONSTRUCT {
547
  <$conceptscheme> ?property ?value .
548
} $fcl WHERE {
549
  <$conceptscheme> ?property ?value .
550
  FILTER (?property != skos:hasTopConcept)
551
}
552
EOQ;
553
        return $query;
554
    }
555
556
    /**
557
     * Retrieves conceptScheme information from the endpoint.
558
     * @param string $conceptscheme concept scheme URI
559
     * @return EasyRDF_Graph query result graph
560
     */
561
    public function queryConceptScheme($conceptscheme) {
562
        $query = $this->generateQueryConceptSchemeQuery($conceptscheme);
563
        return $this->client->query($query);
564
    }
565
566
    /**
567
     * Generates the queryConceptSchemes sparql query.
568
     * @param string $lang language of labels
569
     * @return string sparql query
570
     */
571
    private function generateQueryConceptSchemesQuery($lang) {
572
        $fcl = $this->generateFromClause();
573
        $query = <<<EOQ
574
SELECT ?cs ?label ?preflabel ?title $fcl
575
WHERE {
576
 ?cs a skos:ConceptScheme .
577
 OPTIONAL {
578
   ?cs rdfs:label ?label .
579
   FILTER(langMatches(lang(?label), '$lang'))
580
 }
581
 OPTIONAL {
582
   ?cs skos:prefLabel ?preflabel .
583
   FILTER(langMatches(lang(?preflabel), '$lang'))
584
 }
585
 OPTIONAL {
586
   { ?cs dc11:title ?title }
587
   UNION
588
   { ?cs dc:title ?title }
589
   FILTER(langMatches(lang(?title), '$lang'))
590
 }
591
} ORDER BY ?cs
592
EOQ;
593
        return $query;
594
    }
595
596
    /**
597
     * Transforms the queryConceptScheme results into an array format.
598
     * @param EasyRdf_Sparql_Result $result
599
     * @return array
600
     */
601
    private function transformQueryConceptSchemesResults($result) {
602
        $ret = array();
603
        foreach ($result as $row) {
604
            $conceptscheme = array();
605
            if (isset($row->label)) {
606
                $conceptscheme['label'] = $row->label->getValue();
607
            }
608
609
            if (isset($row->preflabel)) {
610
                $conceptscheme['prefLabel'] = $row->preflabel->getValue();
611
            }
612
613
            if (isset($row->title)) {
614
                $conceptscheme['title'] = $row->title->getValue();
615
            }
616
617
            $ret[$row->cs->getURI()] = $conceptscheme;
618
        }
619
        return $ret;
620
    }
621
622
    /**
623
     * return a list of skos:ConceptScheme instances in the given graph
624
     * @param string $lang language of labels
625
     * @return array Array with concept scheme URIs (string) as keys and labels (string) as values
626
     */
627
    public function queryConceptSchemes($lang) {
628
        $query = $this->generateQueryConceptSchemesQuery($lang);
629
        $result = $this->client->query($query);
630
        return $this->transformQueryConceptSchemesResults($result);
631
    }
632
633
    /**
634
     * Generate a VALUES clause for limiting the targeted graphs.
635
     * @param Vocabulary[]|null $vocabs the vocabularies to target 
636
     * @return string[] array of graph URIs
637
     */
638
    protected function getVocabGraphs($vocabs) {
639
        if ($vocabs === null || sizeof($vocabs) == 0) {
640
            // searching from all vocabularies - limit to known graphs
641
            $vocabs = $this->model->getVocabularies();
642
        }
643
        $graphs = array();
644
        foreach ($vocabs as $voc) {
645
            $graphs[] = $voc->getGraph();
646
        }
647
        return $graphs;
648
    }
649
650
    /**
651
     * Generate a VALUES clause for limiting the targeted graphs.
652
     * @param Vocabulary[]|null $vocabs array of Vocabulary objects to target
653
     * @return string VALUES clause, or "" if not necessary to limit
654
     */
655
    protected function formatValuesGraph($vocabs) {
656
        if (!$this->isDefaultEndpoint()) {
657
            return "";
658
        }
659
        $graphs = $this->getVocabGraphs($vocabs);
660
        return $this->formatValues('?graph', $graphs, 'uri');
661
    }
662
663
    /**
664
     * Generate a FILTER clause for limiting the targeted graphs.
665
     * @param array $vocabs array of Vocabulary objects to target
666
     * @return string FILTER clause, or "" if not necessary to limit
667
     */
668
    protected function formatFilterGraph($vocabs) {
669
        if (!$this->isDefaultEndpoint()) {
670
            return "";
671
        }
672
        $graphs = $this->getVocabGraphs($vocabs);
673
        $values = array();
674
        foreach ($graphs as $graph) {
675
          $values[] = "<$graph>";
676
        }
677
        return "FILTER (?graph IN (" . implode(',', $values) . "))";
678
    }
679
680
    /**
681
     * Formats combined limit and offset clauses for the sparql query
682
     * @param int $limit maximum number of hits to retrieve; 0 for unlimited
683
     * @param int $offset offset of results to retrieve; 0 for beginning of list
684
     * @return string sparql query clauses
685
     */
686
    protected function formatLimitAndOffset($limit, $offset) {
687
        $limit = ($limit) ? 'LIMIT ' . $limit : '';
688
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
689
        // eliminating whitespace and line changes when the conditions aren't needed.
690
        $limitandoffset = '';
691
        if ($limit && $offset) {
692
            $limitandoffset = "\n" . $limit . "\n" . $offset;
693
        } elseif ($limit) {
694
            $limitandoffset = "\n" . $limit;
695
        } elseif ($offset) {
696
            $limitandoffset = "\n" . $offset;
697
        }
698
699
        return $limitandoffset;
700
    }
701
702
    /**
703
     * Formats a sparql query clause for limiting the search to specific concept types.
704
     * @param array $types limit search to concepts of the given type(s)
705
     * @return string sparql query clause
706
     */
707
    protected function formatTypes($types) {
708
        $typePatterns = array();
709
        if (!empty($types)) {
710
            foreach ($types as $type) {
711
                $unprefixed = EasyRdf_Namespace::expand($type);
712
                $typePatterns[] = "{ ?s a <$unprefixed> }";
713
            }
714
        }
715
716
        return implode(' UNION ', $typePatterns);;
717
    }
718
719
    /**
720
     * @param string $prop property to include in the result eg. 'broader' or 'narrower'
721
     * @return string sparql query clause
722
     */
723
    private function formatPropertyCsvClause($prop) {
724
        # This expression creates a CSV row containing pairs of (uri,prefLabel) values.
725
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
726
        $clause = <<<EOV
727
(GROUP_CONCAT(DISTINCT CONCAT(
728
 '"', IF(isIRI(?$prop),STR(?$prop),''), '"', ',',
729
 '"', REPLACE(IF(BOUND(?{$prop}lab),?{$prop}lab,''), '"', '""'), '"', ',',
730
 '"', REPLACE(IF(isLiteral(?{$prop}),?{$prop},''), '"', '""'), '"'
731
); separator='\\n') as ?{$prop}s)
732
EOV;
733
        return $clause;
734
    }
735
    
736
    /**
737
     * @return string sparql query clause
738
     */
739
    private function formatPrefLabelCsvClause() {
740
        # This expression creates a CSV row containing pairs of (prefLabel, lang) values.
741
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
742
        $clause = <<<EOV
743
(GROUP_CONCAT(DISTINCT CONCAT(
744
 '"', STR(?pref), '"', ',', '"', lang(?pref), '"'
745
); separator='\\n') as ?preflabels)
746
EOV;
747
        return $clause;
748
    }
749
750
    /**
751
     * @param string $lang language code of the returned labels
752
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
753
     * @return string sparql query clause
754
     */
755
    protected function formatExtraFields($lang, $fields) {
756
        // extra variable expressions to request and extra fields to query for
757
        $ret = array('extravars' => '', 'extrafields' => '');
758
759
        if ($fields === null) {
760
            return $ret; 
761
        }
762
763
        if (in_array('prefLabel', $fields)) {
764
            $ret['extravars'] .= $this->formatPreflabelCsvClause();
765
            $ret['extrafields'] .= <<<EOF
766
OPTIONAL {
767
  ?s skos:prefLabel ?pref .
768
}
769
EOF;
770
            // removing the prefLabel from the fields since it has been handled separately
771
            $fields = array_diff($fields, array('prefLabel'));
772
        }
773
774
        foreach ($fields as $field) {
775
            $ret['extravars'] .= $this->formatPropertyCsvClause($field);
776
            $ret['extrafields'] .= <<<EOF
777
OPTIONAL {
778
  ?s skos:$field ?$field .
779
  FILTER(!isLiteral(?$field)||langMatches(lang(?{$field}), '$lang'))
780
  OPTIONAL { ?$field skos:prefLabel ?{$field}lab . FILTER(langMatches(lang(?{$field}lab), '$lang')) }
781
}
782
EOF;
783
        }
784
785
        return $ret;
786
    }
787
788
    /**
789
     * Generate condition for matching labels in SPARQL
790
     * @param string $term search term
791
     * @param string $searchLang language code used for matching labels (null means any language)
792
     * @return string sparql query snippet
793
     */
794
    protected function generateConceptSearchQueryCondition($term, $searchLang)
795
    {
796
        # use appropriate matching function depending on query type: =, strstarts, strends or full regex
797
        if (preg_match('/^[^\*]+$/', $term)) { // exact query
798
            $term = str_replace('\\', '\\\\', $term); // quote slashes
799
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
800
            $filtercond = "LCASE(STR(?match)) = '$term'";
801
        } elseif (preg_match('/^[^\*]+\*$/', $term)) { // prefix query
802
            $term = substr($term, 0, -1); // remove the final asterisk
803
            $term = str_replace('\\', '\\\\', $term); // quote slashes
804
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
805
            $filtercond = "STRSTARTS(LCASE(STR(?match)), '$term')";
806
        } elseif (preg_match('/^\*[^\*]+$/', $term)) { // suffix query
807
            $term = substr($term, 1); // remove the preceding asterisk
808
            $term = str_replace('\\', '\\\\', $term); // quote slashes
809
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
810
            $filtercond = "STRENDS(LCASE(STR(?match)), '$term')";
811
        } else { // too complicated - have to use a regex
812
            # make sure regex metacharacters are not passed through
813
            $term = str_replace('\\', '\\\\', preg_quote($term));
814
            $term = str_replace('\\\\*', '.*', $term); // convert asterisk to regex syntax
815
            $term = str_replace('\'', '\\\'', $term); // ensure single quotes are quoted
816
            $filtercond = "REGEX(STR(?match), '^$term$', 'i')";
817
        }
818
819
        $labelcondMatch = ($searchLang) ? "&& LANGMATCHES(lang(?match), '$searchLang')" : "";
820
        
821
        return "?s ?prop ?match . FILTER ($filtercond $labelcondMatch)";
822
    }
823
824
825
    /**
826
     * Inner query for concepts using a search term.
827
     * @param string $term search term
828
     * @param string $lang language code of the returned labels
829
     * @param string $searchLang language code used for matching labels (null means any language)
830
     * @param string[] $props properties to target e.g. array('skos:prefLabel','skos:altLabel')
831
     * @param boolean $unique restrict results to unique concepts (default: false)
832
     * @return string sparql query
833
     */
834
    protected function generateConceptSearchQueryInner($term, $lang, $searchLang, $props, $unique, $filterGraph)
835
    {
836
        $valuesProp = $this->formatValues('?prop', $props);
837
        $textcond = $this->generateConceptSearchQueryCondition($term, $searchLang);
838
        $rawterm = str_replace('*', '', $term);
839
        
840
        // graph clause, if necessary
841
        $graphClause = $filterGraph != '' ? 'GRAPH ?graph' : '';
842
843
        // extra conditions for label language, if specified
844
        $labelcondLabel = ($lang) ? "LANGMATCHES(lang(?label), '$lang')" : "LANGMATCHES(lang(?label), lang(?match))";
845
        // if search language and UI/display language differ, must also consider case where there is no prefLabel in
846
        // the display language; in that case, should use the label with the same language as the matched label
847
        $labelcondFallback = ($searchLang != $lang) ?
848
          "OPTIONAL { # in case previous OPTIONAL block gives no labels\n" .
849
          "?s skos:prefLabel ?label . FILTER (LANGMATCHES(LANG(?label), LANG(?match))) }" : "";
850
          
851
        //  Including the labels if there is no query term given.
852
        if ($rawterm === '') {
853
          $labelClause = "?s skos:prefLabel ?label .";
854
          return ($lang) ? $labelClause . " FILTER (LANGMATCHES(LANG(?label), '$lang'))" : $labelClause . "";
855
        }
856
857
        /*
858
         * This query does some tricks to obtain a list of unique concepts.
859
         * From each match generated by the text index, a string such as
860
         * "1en@example" is generated, where the first character is a number
861
         * encoding the property and priority, then comes the language tag and
862
         * finally the original literal after an @ sign. Of these, the MIN
863
         * function is used to pick the best match for each concept. Finally,
864
         * the structure is unpacked to get back the original string. Phew!
865
         */
866
        $hitvar = $unique ? '(MIN(?matchstr) AS ?hit)' : '(?matchstr AS ?hit)';
867
        $hitgroup = $unique ? 'GROUP BY ?s ?label ?notation' : '';
868
         
869
        $query = <<<EOQ
870
   SELECT DISTINCT ?s ?label ?notation $hitvar
871
   WHERE {
872
    $graphClause {
873
     $valuesProp
874
     VALUES (?prop ?pri) { (skos:prefLabel 1) (skos:altLabel 3) (skos:hiddenLabel 5)}
875
     { $textcond
876
     ?s ?prop ?match }
877
     UNION
878
     { ?s skos:notation "$rawterm" }
879
     OPTIONAL {
880
      ?s skos:prefLabel ?label .
881
      FILTER ($labelcondLabel)
882
     } $labelcondFallback
883
     BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri)
884
     BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr)
885
     OPTIONAL { ?s skos:notation ?notation }
886
    }
887
    $filterGraph
888
   }
889
   $hitgroup
890
EOQ;
891
892
        return $query;
893
    }
894
895
    /**
896
     * Query for concepts using a search term.
897
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
898
     * @param boolean $unique restrict results to unique concepts (default: false)
899
     * @param ConceptSearchParameters $params 
900
     * @return string sparql query
901
     */
902
    protected function generateConceptSearchQuery($fields, $unique, $params) {
903
        $vocabs = $params->getVocabs();
904
        $gcl = $this->graphClause;
905
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
906
        $formattedtype = $this->formatTypes($params->getTypeLimit());
907
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);
908
        $extravars = $formattedfields['extravars'];
909
        $extrafields = $formattedfields['extrafields'];
910
        $schemes = $params->getSchemeLimit();
911
912
        $schemecond = '';
913
        if (!empty($schemes)) {
914
            foreach($schemes as $scheme) {
915
                $schemecond .= "?s skos:inScheme <$scheme> . ";
916
            }
917
        }
918
919
        // extra conditions for parent and group, if specified
920
        $parentcond = ($params->getParentLimit()) ? "?s skos:broader+ <" . $params->getParentLimit() . "> ." : "";
921
        $groupcond = ($params->getGroupLimit()) ? "<" . $params->getGroupLimit() . "> skos:member ?s ." : "";
922
        $pgcond = $parentcond . $groupcond;
923
924
        $orderextra = $this->isDefaultEndpoint() ? $this->graph : '';
925
926
        # make VALUES clauses
927
        $props = array('skos:prefLabel', 'skos:altLabel');
928
        if ($params->getHidden()) {
929
            $props[] = 'skos:hiddenLabel';
930
        }
931
932
        $filterGraph = empty($vocabs) ? $this->formatFilterGraph($vocabs) : '';
933
934
        // remove futile asterisks from the search term
935
        $term = $params->getSearchTerm();
936
        while (strpos($term, '**') !== false) {
937
            $term = str_replace('**', '*', $term);
938
        }
939
940
        $labelpriority = <<<EOQ
941
  FILTER(BOUND(?s))
942
  BIND(STR(SUBSTR(?hit,1,1)) AS ?pri)
943
  BIND(STRLANG(STRAFTER(?hit, '@'), SUBSTR(STRBEFORE(?hit, '@'),2)) AS ?match)
944
  BIND(IF((?pri = "1" || ?pri = "2") && ?match != ?label, ?match, ?unbound) as ?plabel)
945
  BIND(IF((?pri = "3" || ?pri = "4"), ?match, ?unbound) as ?alabel)
946
  BIND(IF((?pri = "5" || ?pri = "6"), ?match, ?unbound) as ?hlabel)
947
EOQ;
948
        
949
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique, $filterGraph);
950
        if ($params->getSearchTerm() === '*' || $params->getSearchTerm() === '') { 
951
          $labelpriority = ''; 
952
        }
953
        $query = <<<EOQ
954
SELECT DISTINCT ?s ?label ?plabel ?alabel ?hlabel ?graph ?notation (GROUP_CONCAT(DISTINCT ?type) as ?types) $extravars 
955
$fcl
956
WHERE {
957
 $gcl {
958
  {
959
  $innerquery
960
  }
961
  $labelpriority
962
  $formattedtype
963
  { $pgcond 
964
   ?s a ?type .
965
   $extrafields $schemecond
966
  }
967
  FILTER NOT EXISTS { ?s owl:deprecated true }
968
 }
969
 $filterGraph
970
}
971
GROUP BY ?s ?match ?label ?plabel ?alabel ?hlabel ?notation ?graph
972
ORDER BY LCASE(STR(?match)) LANG(?match) $orderextra
973
EOQ;
974
        return $query;
975
    }
976
977
    /**
978
     * Transform a single concept search query results into the skosmos desired return format.
979
     * @param $row SPARQL query result row
980
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
981
     * @return array query result object
982
     */
983
    private function transformConceptSearchResult($row, $vocabs, $fields)
984
    {
985
        $hit = array();
986
        $hit['uri'] = $row->s->getUri();
987
988
        if (isset($row->graph)) {
989
            $hit['graph'] = $row->graph->getUri();
990
        }
991
992
        foreach (explode(" ", $row->types->getValue()) as $typeuri) {
993
            $hit['type'][] = $this->shortenUri($typeuri);
994
        }
995
996
        if(!empty($fields)) {
997
            foreach ($fields as $prop) {
998
                $propname = $prop . 's';
999
                if (isset($row->$propname)) {
1000
                    foreach (explode("\n", $row->$propname->getValue()) as $line) {
1001
                        $rdata = str_getcsv($line, ',', '"', '"');
1002
                        $propvals = array();
1003
                        if ($rdata[0] != '') {
1004
                            $propvals['uri'] = $rdata[0];
1005
                        }
1006
                        if ($rdata[1] != '') {
1007
                            $propvals['prefLabel'] = $rdata[1];
1008
                        }
1009
                        if ($rdata[2] != '') {
1010
                            $propvals = $rdata[2];
1011
                        }
1012
1013
                        $hit['skos:' . $prop][] = $propvals;
1014
                    }
1015
                }
1016
            }
1017
        }
1018
1019
        
1020
        if (isset($row->preflabels)) {
1021
            foreach (explode("\n", $row->preflabels->getValue()) as $line) {
1022
                $pref = str_getcsv($line, ',', '"', '"');
1023
                $hit['prefLabels'][$pref[1]] = $pref[0];
1024
            }
1025
        }
1026
1027
        foreach ($vocabs as $vocab) { // looping the vocabulary objects and asking these for a localname for the concept.
1028
            $localname = $vocab->getLocalName($hit['uri']);
1029
            if ($localname !== $hit['uri']) { // only passing the result forward if the uri didn't boomerang right back.
1030
                $hit['localname'] = $localname;
1031
                break; // stopping the search when we find one that returns something valid.
1032
            }
1033
        }
1034
1035
        if (isset($row->label)) {
1036
            $hit['prefLabel'] = $row->label->getValue();
1037
        }
1038
1039
        if (isset($row->label)) {
1040
            $hit['lang'] = $row->label->getLang();
1041
        }
1042
1043
        if (isset($row->notation)) {
1044
            $hit['notation'] = $row->notation->getValue();
1045
        }
1046
1047
        if (isset($row->plabel)) {
1048
            $hit['matchedPrefLabel'] = $row->plabel->getValue();
1049
            $hit['lang'] = $row->plabel->getLang();
1050 View Code Duplication
        } elseif (isset($row->alabel)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1051
            $hit['altLabel'] = $row->alabel->getValue();
1052
            $hit['lang'] = $row->alabel->getLang();
1053
        } elseif (isset($row->hlabel)) {
1054
            $hit['hiddenLabel'] = $row->hlabel->getValue();
1055
            $hit['lang'] = $row->hlabel->getLang();
1056
        }
1057
        return $hit;
1058
    }
1059
1060
    /**
1061
     * Transform the concept search query results into the skosmos desired return format.
1062
     * @param EasyRdf_Sparql_Result $results
1063
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1064
     * @return array query result object
1065
     */
1066
    private function transformConceptSearchResults($results, $vocabs, $fields) {
1067
        $ret = array();
1068
1069
        foreach ($results as $row) {
1070
            if (!isset($row->s)) {
1071
                // don't break if query returns a single dummy result
1072
                continue;
1073
            }
1074
            $ret[] = $this->transformConceptSearchResult($row, $vocabs, $fields);
1075
        }
1076
        return $ret;
1077
    }
1078
1079
    /**
1080
     * Query for concepts using a search term.
1081
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1082
     * @param array $fields extra fields to include in the result (array of strings). (default: null = none)
1083
     * @param boolean $unique restrict results to unique concepts (default: false)
1084
     * @param ConceptSearchParameters $params 
1085
     * @return array query result object
1086
     */
1087
    public function queryConcepts($vocabs, $fields = null, $unique = false, $params) {
1088
        $query = $this->generateConceptSearchQuery($fields, $unique, $params);
1089
        $results = $this->client->query($query);
1090
        return $this->transformConceptSearchResults($results, $vocabs, $fields);
1091
    }
1092
1093
    /**
1094
     * Generates sparql query clauses used for creating the alphabetical index.
1095
     * @param string $letter the letter (or special class) to search for
1096
     * @return array of sparql query clause strings
1097
     */
1098
    private function formatFilterConditions($letter) {
1099
        $useRegex = false;
1100
1101
        if ($letter == '*') {
1102
            $letter = '.*';
1103
            $useRegex = true;
1104
        } elseif ($letter == '0-9') {
1105
            $letter = '[0-9].*';
1106
            $useRegex = true;
1107
        } elseif ($letter == '!*') {
1108
            $letter = '[^\\\\p{L}\\\\p{N}].*';
1109
            $useRegex = true;
1110
        }
1111
1112
        # make text query clause
1113
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
1114
        if ($useRegex) {
1115
            $filtercondLabel = "regex(str(?label), '^$letter$', 'i')";
1116
            $filtercondALabel = "regex(str(?alabel), '^$letter$', 'i')";
1117
        } else {
1118
            $filtercondLabel = "strstarts(lcase(str(?label)), '$lcletter')";
1119
            $filtercondALabel = "strstarts(lcase(str(?alabel)), '$lcletter')";
1120
        }
1121
        return array('filterpref' => $filtercondLabel, 'filteralt' => $filtercondALabel);
1122
    }
1123
1124
    /**
1125
     * Generates the sparql query used for rendering the alphabetical index.
1126
     * @param string $letter the letter (or special class) to search for
1127
     * @param string $lang language of labels
1128
     * @param integer $limit limits the amount of results
1129
     * @param integer $offset offsets the result set
1130
     * @param array|null $classes
1131
     * @return string sparql query
1132
     */
1133
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes) {
1134
        $fcl = $this->generateFromClause();
1135
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1136
        $values = $this->formatValues('?type', $classes, 'uri');
1137
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
1138
        $conditions = $this->formatFilterConditions($letter);
1139
        $filtercondLabel = $conditions['filterpref'];
1140
        $filtercondALabel = $conditions['filteralt'];
1141
1142
        $query = <<<EOQ
1143
SELECT DISTINCT ?s ?label ?alabel $fcl
1144
WHERE {
1145
  {
1146
    ?s skos:prefLabel ?label .
1147
    FILTER (
1148
      $filtercondLabel
1149
      && langMatches(lang(?label), '$lang')
1150
    )
1151
  }
1152
  UNION
1153
  {
1154
    {
1155
      ?s skos:altLabel ?alabel .
1156
      FILTER (
1157
        $filtercondALabel
1158
        && langMatches(lang(?alabel), '$lang')
1159
      )
1160
    }
1161
    {
1162
      ?s skos:prefLabel ?label .
1163
      FILTER (langMatches(lang(?label), '$lang'))
1164
    }
1165
  }
1166
  ?s a ?type .
1167
  FILTER NOT EXISTS { ?s owl:deprecated true }
1168
  $values
1169
}
1170
ORDER BY LCASE(IF(BOUND(?alabel), STR(?alabel), STR(?label))) $limitandoffset
1171
EOQ;
1172
        return $query;
1173
    }
1174
1175
    /**
1176
     * Transforms the alphabetical list query results into an array format.
1177
     * @param EasyRdf_Sparql_Result $results
1178
     * @return array
1179
     */
1180
    private function transformAlphabeticalListResults($results) {
1181
        $ret = array();
1182
1183
        foreach ($results as $row) {
1184
            if (!isset($row->s)) {
1185
                continue;
1186
            }
1187
            // don't break if query returns a single dummy result
1188
1189
            $hit = array();
1190
            $hit['uri'] = $row->s->getUri();
1191
1192
            $hit['localname'] = $row->s->localName();
1193
1194
            $hit['prefLabel'] = $row->label->getValue();
1195
            $hit['lang'] = $row->label->getLang();
1196
1197 View Code Duplication
            if (isset($row->alabel)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1198
                $hit['altLabel'] = $row->alabel->getValue();
1199
                $hit['lang'] = $row->alabel->getLang();
1200
            }
1201
1202
            $ret[] = $hit;
1203
        }
1204
1205
        return $ret;
1206
    }
1207
1208
    /**
1209
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1210
     * '*!' (special characters) and '*' (everything) are accepted.
1211
     * @param string $letter the letter (or special class) to search for
1212
     * @param string $lang language of labels
1213
     * @param integer $limit limits the amount of results
1214
     * @param integer $offset offsets the result set
1215
     * @param array $classes
1216
     */
1217
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null) {
1218
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes);
1219
        $results = $this->client->query($query);
1220
        return $this->transformAlphabeticalListResults($results);
1221
    }
1222
1223
    /**
1224
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1225
     * @param string $lang language
1226
     * @return string sparql query
1227
     */
1228
    private function generateFirstCharactersQuery($lang, $classes) {
1229
        $fcl = $this->generateFromClause();
1230
        $classes = (sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1231
        $values = $this->formatValues('?type', $classes, 'uri');
1232
        $query = <<<EOQ
1233
SELECT DISTINCT (substr(ucase(str(?label)), 1, 1) as ?l) $fcl WHERE {
1234
  ?c skos:prefLabel ?label .
1235
  ?c a ?type
1236
  FILTER(langMatches(lang(?label), '$lang'))
1237
  $values
1238
}
1239
EOQ;
1240
        return $query;
1241
    }
1242
1243
    /**
1244
     * Transforms the first characters query results into an array format.
1245
     * @param EasyRdf_Sparql_Result $result
1246
     * @return array
1247
     */
1248
    private function transformFirstCharactersResults($result) {
1249
        $ret = array();
1250
        foreach ($result as $row) {
1251
            $ret[] = $row->l->getValue();
1252
        }
1253
        return $ret;
1254
    }
1255
1256
    /**
1257
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1258
     * @param string $lang language
1259
     * @return array array of characters
1260
     */
1261
    public function queryFirstCharacters($lang, $classes = null) {
1262
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1263
        $result = $this->client->query($query);
1264
        return $this->transformFirstCharactersResults($result);
1265
    }
1266
1267
    /**
1268
     * @param string $uri
1269
     * @param string $lang
1270
     * @return string sparql query string
1271
     */
1272
    private function generateLabelQuery($uri, $lang) {
1273
        $fcl = $this->generateFromClause();
1274
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1275
        $query = <<<EOQ
1276
SELECT ?label $fcl
1277
WHERE {
1278
  <$uri> a ?type .
1279
  OPTIONAL {
1280
    <$uri> skos:prefLabel ?label .
1281
    $labelcondLabel
1282
  }
1283
  OPTIONAL {
1284
    <$uri> rdfs:label ?label .
1285
    $labelcondLabel
1286
  }
1287
  OPTIONAL {
1288
    <$uri> dc:title ?label .
1289
    $labelcondLabel
1290
  }
1291
  OPTIONAL {
1292
    <$uri> dc11:title ?label .
1293
    $labelcondLabel
1294
  }
1295
}
1296
EOQ;
1297
        return $query;
1298
    }
1299
1300
    /**
1301
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1302
     * @param string $uri
1303
     * @param string $lang
1304
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1305
     */
1306
    public function queryLabel($uri, $lang) {
1307
        $query = $this->generateLabelQuery($uri, $lang);
1308
        $result = $this->client->query($query);
1309
        $ret = array();
1310
        foreach ($result as $row) {
1311
            if (!isset($row->label)) {
1312
                // existing concept but no labels
1313
                return array();
1314
            }
1315
            $ret[$row->label->getLang()] = $row->label;
1316
        }
1317
1318
        if (sizeof($ret) > 0) {
1319
            // existing concept, with label(s)
1320
            return $ret;
1321
        } else {
1322
            // nonexistent concept
1323
            return null;
1324
        }
1325
    }
1326
1327
    /**
1328
     * Generates a sparql query for queryProperty.
1329
     * @param string $uri
1330
     * @param string $prop the name of the property eg. 'skos:broader'.
1331
     * @param string $lang
1332
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1333
     * @return string sparql query
1334
     */
1335 View Code Duplication
    private function generatePropertyQuery($uri, $prop, $lang, $anylang) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1336
        $fcl = $this->generateFromClause();
1337
        $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1338
1339
        $query = <<<EOQ
1340
SELECT * $fcl
1341
WHERE {
1342
  <$uri> a skos:Concept .
1343
  OPTIONAL {
1344
    <$uri> $prop ?object .
1345
    OPTIONAL {
1346
      ?object skos:prefLabel ?label .
1347
      FILTER (langMatches(lang(?label), "$lang"))
1348
    }
1349
    OPTIONAL {
1350
      ?object skos:prefLabel ?label .
1351
      FILTER (lang(?label) = "")
1352
    }
1353
    $anylang
1354
  }
1355
}
1356
EOQ;
1357
        return $query;
1358
    }
1359
1360
    /**
1361
     * Transforms the sparql query result into an array or null if the concept doesn't exist.
1362
     * @param EasyRdf_Sparql_Result $result
1363
     * @param string $lang
1364
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1365
     */
1366
    private function transformPropertyQueryResults($result, $lang) {
1367
        $ret = array();
1368
        foreach ($result as $row) {
1369
            if (!isset($row->object)) {
1370
                return array();
1371
            }
1372
            // existing concept but no properties
1373
            if (isset($row->label)) {
1374
                if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1375
                    $ret[$row->object->getUri()]['label'] = $row->label->getValue();
1376
                }
1377
1378
            } else {
1379
                $ret[$row->object->getUri()]['label'] = null;
1380
            }
1381
        }
1382
        if (sizeof($ret) > 0) {
1383
            return $ret;
1384
        }
1385
        // existing concept, with properties
1386
        else {
1387
            return null;
1388
        }
1389
        // nonexistent concept
1390
    }
1391
1392
    /**
1393
     * Query a single property of a concept.
1394
     * @param string $uri
1395
     * @param string $prop the name of the property eg. 'skos:broader'.
1396
     * @param string $lang
1397
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1398
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1399
     */
1400
    public function queryProperty($uri, $prop, $lang, $anylang = false) {
1401
        $uri = is_array($uri) ? $uri[0] : $uri;
1402
        $query = $this->generatePropertyQuery($uri, $prop, $lang, $anylang);
1403
        $result = $this->client->query($query);
1404
        return $this->transformPropertyQueryResults($result, $lang);
1405
    }
1406
1407
    /**
1408
     * Query a single transitive property of a concept.
1409
     * @param string $uri
1410
     * @param string $prop the name of the property eg. 'skos:broader'.
0 ignored issues
show
Bug introduced by
There is no parameter named $prop. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1411
     * @param string $lang
1412
     * @param integer $limit
1413
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1414
     * @return string sparql query
1415
     */
1416
    private function generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang) {
1417
        $uri = is_array($uri) ? $uri[0] : $uri;
1418
        $fcl = $this->generateFromClause();
1419
        $propertyClause = implode('|', $props);
1420
        $filter = $anylang ? "" : "FILTER (langMatches(lang(?label), \"$lang\"))";
1421
        // need to do a SPARQL subquery because LIMIT needs to be applied /after/
1422
        // the direct relationships have been collapsed into one string
1423
        $query = <<<EOQ
1424
SELECT * $fcl
1425
WHERE {
1426
  SELECT ?object ?label (GROUP_CONCAT(?dir) as ?direct)
1427
  WHERE {
1428
    <$uri> a skos:Concept .
1429
    OPTIONAL {
1430
      <$uri> $propertyClause* ?object .
1431
      OPTIONAL {
1432
        ?object $propertyClause ?dir .
1433
      }
1434
    }
1435
    OPTIONAL {
1436
      ?object skos:prefLabel ?label .
1437
      $filter
1438
    }
1439
  }
1440
  GROUP BY ?object ?label
1441
}
1442
LIMIT $limit
1443
EOQ;
1444
        return $query;
1445
    }
1446
1447
    /**
1448
     * Transforms the sparql query result object into an array.
1449
     * @param EasyRdf_Sparql_Result $result
1450
     * @param string $lang
1451
     * @param string $fallbacklang language to use if label is not available in the preferred language
1452
     * @return array of property values (key: URI, val: label), or null if concept doesn't exist
1453
     */
1454
    private function transformTransitivePropertyResults($result, $lang, $fallbacklang) {
1455
        $ret = array();
1456
        foreach ($result as $row) {
1457
            if (!isset($row->object)) {
1458
                return array();
1459
            }
1460
            // existing concept but no properties
1461
            if (isset($row->label)) {
1462
                $val = array('label' => $row->label->getValue());
1463
            } else {
1464
                $val = array('label' => null);
1465
            }
1466 View Code Duplication
            if (isset($row->direct) && $row->direct->getValue() != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1467
                $val['direct'] = explode(' ', $row->direct->getValue());
1468
            }
1469
            // Preventing labels in a non preferred language overriding the preferred language.
1470
            if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1471
                if (!isset($row->label) || $row->label->getLang() === $lang) {
1472
                    $ret[$row->object->getUri()] = $val;
1473
                } elseif ($row->label->getLang() === $fallbacklang) {
1474
                    $val['label'] .= ' (' . $row->label->getLang() . ')';
1475
                    $ret[$row->object->getUri()] = $val;
1476
                }
1477
            }
1478
        }
1479
1480
        // second iteration of results to find labels for the ones that didn't have one in the preferred languages
1481
        foreach ($result as $row) {
1482
            if (isset($row->object) && array_key_exists($row->object->getUri(), $ret) === false) {
1483
                $val = array('label' => $row->label->getValue());
1484 View Code Duplication
                if (isset($row->direct) && $row->direct->getValue() != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1485
                    $val['direct'] = explode(' ', $row->direct->getValue());
1486
                }
1487
                $ret[$row->object->getUri()] = $val;
1488
            }
1489
        }
1490
1491
        if (sizeof($ret) > 0) {
1492
            return $ret;
1493
        }
1494
        // existing concept, with properties
1495
        else {
1496
            return null;
1497
        }
1498
        // nonexistent concept
1499
    }
1500
1501
    /**
1502
     * Query a single transitive property of a concept.
1503
     * @param string $uri
1504
     * @param array $props the property/properties.
1505
     * @param string $lang
1506
     * @param string $fallbacklang language to use if label is not available in the preferred language
1507
     * @param integer $limit
1508
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1509
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1510
     */
1511
    public function queryTransitiveProperty($uri, $props, $lang, $limit, $anylang = false, $fallbacklang = '') {
1512
        $query = $this->generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang);
1513
        $result = $this->client->query($query);
1514
        return $this->transformTransitivePropertyResults($result, $lang, $fallbacklang);
1515
    }
1516
1517
    /**
1518
     * Generates the query for a concepts skos:narrowers.
1519
     * @param string $uri
1520
     * @param string $lang
1521
     * @param string $fallback
1522
     * @return string sparql query
1523
     */
1524
    private function generateChildQuery($uri, $lang, $fallback, $props) {
1525
        $uri = is_array($uri) ? $uri[0] : $uri;
1526
        $fcl = $this->generateFromClause();
1527
        $propertyClause = implode('|', $props);
1528
        $query = <<<EOQ
1529
SELECT ?child ?label ?child ?grandchildren ?notation $fcl WHERE {
1530
  <$uri> a skos:Concept .
1531
  OPTIONAL {
1532
    ?child $propertyClause <$uri> .
1533
    OPTIONAL {
1534
      ?child skos:prefLabel ?label .
1535
      FILTER (langMatches(lang(?label), "$lang"))
1536
    }
1537
    OPTIONAL {
1538
      ?child skos:prefLabel ?label .
1539
      FILTER (langMatches(lang(?label), "$fallback"))
1540
    }
1541
    OPTIONAL { # other language case
1542
      ?child skos:prefLabel ?label .
1543
    }
1544
    OPTIONAL {
1545
      ?child skos:notation ?notation .
1546
    }
1547
    BIND ( EXISTS { ?a $propertyClause ?child . } AS ?grandchildren )
1548
  }
1549
}
1550
EOQ;
1551
        return $query;
1552
    }
1553
1554
    /**
1555
     * Transforms the sparql result object into an array.
1556
     * @param EasyRdf_Sparql_Result $result
1557
     * @param string $lang
1558
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1559
     */
1560
    private function transformNarrowerResults($result, $lang) {
1561
        $ret = array();
1562
        foreach ($result as $row) {
1563
            if (!isset($row->child)) {
1564
                return array();
1565
            }
1566
            // existing concept but no children
1567
1568
            $label = null;
1569 View Code Duplication
            if (isset($row->label)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1570
                if ($row->label->getLang() == $lang) {
1571
                    $label = $row->label->getValue();
1572
                } else {
1573
                    $label = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1574
                }
1575
1576
            }
1577
            $childArray = array(
1578
                'uri' => $row->child->getUri(),
1579
                'prefLabel' => $label,
1580
                'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1581
            );
1582
            if (isset($row->notation)) {
1583
                $childArray['notation'] = $row->notation->getValue();
1584
            }
1585
1586
            $ret[] = $childArray;
1587
        }
1588
        if (sizeof($ret) > 0) {
1589
            return $ret;
1590
        }
1591
        // existing concept, with children
1592
        else {
1593
            return null;
1594
        }
1595
        // nonexistent concept
1596
    }
1597
1598
    /**
1599
     * Query the narrower concepts of a concept.
1600
     * @param string $uri
1601
     * @param string $lang
1602
     * @param string $fallback
1603
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1604
     */
1605
    public function queryChildren($uri, $lang, $fallback, $props) {
1606
        $query = $this->generateChildQuery($uri, $lang, $fallback, $props);
1607
        $result = $this->client->query($query);
1608
        return $this->transformNarrowerResults($result, $lang);
1609
    }
1610
1611
    /**
1612
     * Query the top concepts of a vocabulary.
1613
     * @param string $conceptSchemes concept schemes whose top concepts to query for
1614
     * @param string $lang language of labels
1615
     */
1616
    public function queryTopConcepts($conceptSchemes, $lang) {
1617
        if (!is_array($conceptSchemes)) {
1618
            $conceptSchemes = array($conceptSchemes);
1619
        }
1620
1621
        $values = $this->formatValues('?topuri', $conceptSchemes, 'uri');
1622
1623
        $fcl = $this->generateFromClause();
1624
        $query = <<<EOQ
1625
SELECT DISTINCT ?top ?topuri ?label ?notation ?children $fcl WHERE {
1626
  ?top skos:topConceptOf ?topuri .
1627
  ?top skos:prefLabel ?label .
1628
  FILTER (langMatches(lang(?label), "$lang"))
1629
  OPTIONAL { ?top skos:notation ?notation . }
1630
  BIND ( EXISTS { ?top skos:narrower ?a . } AS ?children )
1631
  $values
1632
}
1633
EOQ;
1634
        $result = $this->client->query($query);
1635
        $ret = array();
1636
        foreach ($result as $row) {
1637
            if (isset($row->top) && isset($row->label)) {
1638
                $top = array('uri' => $row->top->getUri(), 'topConceptOf' => $row->topuri->getUri(), 'label' => $row->label->getValue(), 'hasChildren' => filter_var($row->children->getValue(), FILTER_VALIDATE_BOOLEAN));
1639
                if (isset($row->notation)) {
1640
                    $top['notation'] = $row->notation->getValue();
1641
                }
1642
1643
                $ret[] = $top;
1644
            }
1645
        }
1646
1647
        return $ret;
1648
    }
1649
1650
    /**
1651
     * Generates a sparql query for finding the hierarchy for a concept.
1652
     * @param string $uri concept uri.
1653
     * @param string $lang
1654
     * @param string $fallback language to use if label is not available in the preferred language
1655
     * @return string sparql query
1656
     */
1657
    private function generateParentListQuery($uri, $lang, $fallback, $props) {
1658
        $fcl = $this->generateFromClause();
1659
        $propertyClause = implode('|', $props);
1660
        $query = <<<EOQ
1661
SELECT ?broad ?parent ?member ?children ?grandchildren
1662
(SAMPLE(?lab) as ?label) (SAMPLE(?childlab) as ?childlabel) (SAMPLE(?topcs) AS ?top) (SAMPLE(?nota) as ?notation) (SAMPLE(?childnota) as ?childnotation) $fcl
1663
WHERE {
1664
  <$uri> a skos:Concept .
1665
  OPTIONAL {
1666
    <$uri> $propertyClause* ?broad .
1667
    OPTIONAL {
1668
      ?broad skos:prefLabel ?lab .
1669
      FILTER (langMatches(lang(?lab), "$lang"))
1670
    }
1671
    OPTIONAL {
1672
      ?broad skos:prefLabel ?lab .
1673
      FILTER (langMatches(lang(?lab), "$fallback"))
1674
    }
1675
    OPTIONAL { # fallback - other language case
1676
      ?broad skos:prefLabel ?lab .
1677
    }
1678
    OPTIONAL { ?broad skos:notation ?nota . }
1679
    OPTIONAL { ?broad $propertyClause ?parent . }
1680
    OPTIONAL { ?broad skos:narrower ?children .
1681
      OPTIONAL {
1682
        ?children skos:prefLabel ?childlab .
1683
        FILTER (langMatches(lang(?childlab), "$lang"))
1684
      }
1685
      OPTIONAL {
1686
        ?children skos:prefLabel ?childlab .
1687
        FILTER (langMatches(lang(?childlab), "$fallback"))
1688
      }
1689
      OPTIONAL { # fallback - other language case
1690
        ?children skos:prefLabel ?childlab .
1691
      }
1692
      OPTIONAL {
1693
        ?children skos:notation ?childnota .
1694
      }
1695
    }
1696
    BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren )
1697
    OPTIONAL { ?broad skos:topConceptOf ?topcs . }
1698
  }
1699
}
1700
GROUP BY ?broad ?parent ?member ?children ?grandchildren
1701
EOQ;
1702
        return $query;
1703
    }
1704
1705
    /**
1706
     * Transforms the result into an array.
1707
     * @param EasyRdf_Sparql_Result
1708
     * @param string $lang
1709
     * @return an array for the REST controller to encode.
1710
     */
1711
    private function transformParentListResults($result, $lang)
1712
    {
1713
        $ret = array();
1714
        foreach ($result as $row) {
1715
            if (!isset($row->broad)) {
1716
                // existing concept but no broaders
1717
                return array();
1718
            }
1719
            $uri = $row->broad->getUri();
1720
            if (!isset($ret[$uri])) {
1721
                $ret[$uri] = array('uri' => $uri);
1722
            }
1723
            if (isset($row->exact)) {
1724
                $ret[$uri]['exact'] = $row->exact->getUri();
1725
            }
1726
            if (isset($row->top)) {
1727
                $ret[$uri]['top'] = $row->top->getUri();
1728
            }
1729
            if (isset($row->children)) {
1730
                if (!isset($ret[$uri]['narrower'])) {
1731
                    $ret[$uri]['narrower'] = array();
1732
                }
1733
1734
                $label = null;
1735 View Code Duplication
                if (isset($row->childlabel)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1736
                    $label = $row->childlabel->getValue();
1737
                    if ($row->childlabel->getLang() !== $lang) {
1738
                        $label .= " (" . $row->childlabel->getLang() . ")";
1739
                    }
1740
1741
                }
1742
1743
                $childArr = array(
1744
                    'uri' => $row->children->getUri(),
1745
                    'label' => $label,
1746
                    'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1747
                );
1748
                if (isset($row->childnotation)) {
1749
                    $childArr['notation'] = $row->childnotation->getValue();
1750
                }
1751
1752
                if (!in_array($childArr, $ret[$uri]['narrower'])) {
1753
                    $ret[$uri]['narrower'][] = $childArr;
1754
                }
1755
1756
            }
1757
            if (isset($row->label)) {
1758
                $preflabel = $row->label->getValue();
1759
                if ($row->label->getLang() !== $lang && strpos($row->label->getLang(), $lang) !== 0) {
1760
                    $preflabel .= ' (' . $row->label->getLang() . ')';
1761
                }
1762
1763
                $ret[$uri]['prefLabel'] = $preflabel;
1764
            }
1765
            if (isset($row->notation)) {
1766
                $ret[$uri]['notation'] = $row->notation->getValue();
1767
            }
1768
1769
            if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) {
1770
                $ret[$uri]['broader'][] = $row->parent->getUri();
1771
            } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) {
1772
                $ret[$uri]['broader'][] = $row->parent->getUri();
1773
            }
1774
        }
1775
        if (sizeof($ret) > 0) {
1776
            // existing concept, with children
1777
            return $ret;
1778
        }
1779
        else {
1780
            // nonexistent concept
1781
            return null;
1782
        }
1783
    }
1784
1785
    /**
1786
     * Query for finding the hierarchy for a concept.
1787
     * @param string $uri concept uri.
1788
     * @param string $lang
1789
     * @param string $fallback language to use if label is not available in the preferred language
1790
     * @param array $props the hierarchy property/properties to use
1791
     * @return an array for the REST controller to encode.
1792
     */
1793
    public function queryParentList($uri, $lang, $fallback, $props) {
1794
        $query = $this->generateParentListQuery($uri, $lang, $fallback, $props);
1795
        $result = $this->client->query($query);
1796
        return $this->transformParentListResults($result, $lang);
1797
    }
1798
1799
    /**
1800
     * return a list of concept group instances, sorted by label
1801
     * @param string $groupClass URI of concept group class
1802
     * @param string $lang language of labels to return
1803
     * @return string sparql query
1804
     */
1805
    private function generateConceptGroupsQuery($groupClass, $lang) {
1806
        $fcl = $this->generateFromClause();
1807
        $query = <<<EOQ
1808
SELECT ?group (GROUP_CONCAT(DISTINCT STR(?child)) as ?children) ?label ?members ?notation $fcl
1809
WHERE {
1810
  ?group a <$groupClass> .
1811
  OPTIONAL { ?group skos:member|isothes:subGroup ?child .
1812
             ?child a <$groupClass> }
1813
  BIND(EXISTS{?group skos:member ?submembers} as ?members)
1814
  OPTIONAL { ?group skos:prefLabel ?label }
1815
  OPTIONAL { ?group rdfs:label ?label }
1816
  FILTER (langMatches(lang(?label), '$lang'))
1817
  OPTIONAL { ?group skos:notation ?notation }
1818
}
1819
GROUP BY ?group ?label ?members ?notation
1820
ORDER BY lcase(?label)
1821
EOQ;
1822
        return $query;
1823
    }
1824
1825
    /**
1826
     * Transforms the sparql query result into an array.
1827
     * @param EasyRdf_Sparql_Result $result
1828
     * @return array
1829
     */
1830
    private function transformConceptGroupsResults($result) {
1831
        $ret = array();
1832
        foreach ($result as $row) {
1833
            if (!isset($row->group)) {
1834
                # no groups found, see issue #357
1835
                continue;
1836
            }
1837
            $group = array('uri' => $row->group->getURI());
1838
            if (isset($row->label)) {
1839
                $group['prefLabel'] = $row->label->getValue();
1840
            }
1841
1842
            if (isset($row->children)) {
1843
                $group['childGroups'] = explode(' ', $row->children->getValue());
1844
            }
1845
1846
            if (isset($row->members)) {
1847
                $group['hasMembers'] = $row->members->getValue();
1848
            }
1849
1850
            if (isset($row->notation)) {
1851
                $group['notation'] = $row->notation->getValue();
1852
            }
1853
1854
            $ret[] = $group;
1855
        }
1856
        return $ret;
1857
    }
1858
1859
    /**
1860
     * return a list of concept group instances, sorted by label
1861
     * @param string $groupClass URI of concept group class
1862
     * @param string $lang language of labels to return
1863
     * @return array Result array with group URI as key and group label as value
1864
     */
1865
    public function listConceptGroups($groupClass, $lang) {
1866
        $query = $this->generateConceptGroupsQuery($groupClass, $lang);
1867
        $result = $this->client->query($query);
1868
        return $this->transformConceptGroupsResults($result);
1869
    }
1870
1871
    /**
1872
     * Generates the sparql query for listConceptGroupContents
1873
     * @param string $groupClass URI of concept group class
1874
     * @param string $group URI of the concept group instance
1875
     * @param string $lang language of labels to return
1876
     * @return string sparql query
1877
     */
1878
    private function generateConceptGroupContentsQuery($groupClass, $group, $lang) {
1879
        $fcl = $this->generateFromClause();
1880
        $query = <<<EOQ
1881
SELECT ?conc ?super ?label ?members ?type ?notation $fcl
1882
WHERE {
1883
 <$group> a <$groupClass> .
1884
 { <$group> skos:member ?conc . } UNION { ?conc isothes:superGroup <$group> }
1885
 FILTER NOT EXISTS { ?conc owl:deprecated true }
1886
 ?conc a ?type .
1887
 OPTIONAL { ?conc skos:prefLabel ?label .
1888
  FILTER (langMatches(lang(?label), '$lang'))
1889
 }
1890
 OPTIONAL { ?conc skos:prefLabel ?label . }
1891
 OPTIONAL { ?conc skos:notation ?notation }
1892
 BIND(EXISTS{?submembers isothes:superGroup ?conc} as ?super)
1893
 BIND(EXISTS{?conc skos:member ?submembers} as ?members)
1894
} ORDER BY lcase(?label)
1895
EOQ;
1896
        return $query;
1897
    }
1898
1899
    /**
1900
     * Transforms the sparql query result into an array.
1901
     * @param EasyRdf_Sparql_Result $result
1902
     * @param string $lang language of labels to return
1903
     * @return array
1904
     */
1905
    private function transformConceptGroupContentsResults($result, $lang) {
1906
        $ret = array();
1907
        $values = array();
1908
        foreach ($result as $row) {
1909
            if (!array_key_exists($row->conc->getURI(), $values)) {
1910
                $values[$row->conc->getURI()] = array(
1911
                    'uri' => $row->conc->getURI(),
1912
                    'isSuper' => $row->super->getValue(),
1913
                    'hasMembers' => $row->members->getValue(),
1914
                    'type' => array($row->type->shorten()),
1915
                );
1916
                if (isset($row->label)) {
1917
                    if ($row->label->getLang() == $lang) {
1918
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue();
1919
                    } else {
1920
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1921
                    }
1922
1923
                }
1924
                if (isset($row->notation)) {
1925
                    $values[$row->conc->getURI()]['notation'] = $row->notation->getValue();
1926
                }
1927
1928
            } else {
1929
                $values[$row->conc->getURI()]['type'][] = $row->type->shorten();
1930
            }
1931
        }
1932
1933
        foreach ($values as $val) {
1934
            $ret[] = $val;
1935
        }
1936
1937
        return $ret;
1938
    }
1939
1940
    /**
1941
     * return a list of concepts in a concept group
1942
     * @param string $groupClass URI of concept group class
1943
     * @param string $group URI of the concept group instance
1944
     * @param string $lang language of labels to return
1945
     * @return array Result array with concept URI as key and concept label as value
1946
     */
1947
    public function listConceptGroupContents($groupClass, $group, $lang) {
1948
        $query = $this->generateConceptGroupContentsQuery($groupClass, $group, $lang);
1949
        $result = $this->client->query($query);
1950
        return $this->transformConceptGroupContentsResults($result, $lang);
1951
    }
1952
1953
    /**
1954
     * Generates the sparql query for queryChangeList.
1955
     * @param string $lang language of labels to return.
1956
     * @param int $offset offset of results to retrieve; 0 for beginning of list
1957
     * @return string sparql query
1958
     */
1959 View Code Duplication
    private function generateChangeListQuery($lang, $offset, $prop) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1960
        $fcl = $this->generateFromClause();
1961
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
1962
1963
        $query = <<<EOQ
1964
SELECT DISTINCT ?concept ?date ?label $fcl
1965
WHERE {
1966
  ?concept a skos:Concept .
1967
  ?concept $prop ?date .
1968
  ?concept skos:prefLabel ?label .
1969
  FILTER (langMatches(lang(?label), '$lang'))
1970
}
1971
ORDER BY DESC(YEAR(?date)) DESC(MONTH(?date)) LCASE(?label)
1972
LIMIT 200 $offset
1973
EOQ;
1974
        return $query;
1975
    }
1976
1977
    /**
1978
     * Transforms the sparql query result into an array.
1979
     * @param EasyRdf_Sparql_Result $result
1980
     * @return array
1981
     */
1982 View Code Duplication
    private function transformChangeListResults($result) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1983
        $ret = array();
1984
        foreach ($result as $row) {
1985
            $concept = array('uri' => $row->concept->getURI());
1986
            if (isset($row->label)) {
1987
                $concept['prefLabel'] = $row->label->getValue();
1988
            }
1989
1990
            if (isset($row->date)) {
1991
                $concept['date'] = $row->date->getValue();
1992
            }
1993
1994
            $ret[] = $concept;
1995
        }
1996
        return $ret;
1997
    }
1998
1999
    /**
2000
     * return a list of recently changed or entirely new concepts
2001
     * @param string $lang language of labels to return
2002
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2003
     * @return array Result array
2004
     */
2005
    public function queryChangeList($lang, $offset, $prop) {
2006
        $query = $this->generateChangeListQuery($lang, $offset, $prop);
2007
        $result = $this->client->query($query);
2008
        return $this->transformChangeListResults($result);
2009
    }
2010
}
2011