Completed
Push — master ( 6f828d...1f3723 )
by Henri
03:17
created

GenericSparql::generateChangeListQuery()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 17
Ratio 100 %

Importance

Changes 0
Metric Value
dl 17
loc 17
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 10
nc 2
nop 3
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
        $fcl = $this->generateFromClause();
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
$fcl
207
WHERE {
208
  {
209
    ?conc a ?type .
210
    ?conc ?prop ?label .
211
    FILTER (langMatches(lang(?label), ?lang))
212
    $valuesLang
213
    $valuesProp
214
  }
215
  $values
216
}
217
GROUP BY ?lang ?prop ?type
218
EOQ;
219
        return $query;
220
    }
221
222
    /**
223
     * Transforms the CountLangConcepts results into an array of label counts.
224
     * @param EasyRdf_Sparql_Result $result query results to be transformed
225
     * @param array $langs Languages to query for
226
     * @param string[] $props property names
227
     */
228
    private function transformCountLangConceptsResults($result, $langs, $props) {
229
        $ret = array();
230
        // set default count to zero; overridden below if query found labels
231
        foreach ($langs as $lang) {
232
            foreach ($props as $prop) {
233
                $ret[$lang][$prop] = 0;
234
            }
235
        }
236
        foreach ($result as $row) {
237
            if (isset($row->lang) && isset($row->prop) && isset($row->count)) {
238
                $ret[$row->lang->getValue()][$row->prop->shorten()] += 
239
                $row->count->getValue();
240
            }
241
242
        }
243
        ksort($ret);
244
        return $ret;
245
    }
246
247
    /**
248
     * Counts the number of concepts in a easyRDF graph with a specific language.
249
     * @param array $langs Languages to query for
250
     * @return Array containing count of concepts for each language and property.
251
     */
252
    public function countLangConcepts($langs, $classes = null) {
253
        $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel');
254
        $query = $this->generateCountLangConceptsQuery($langs, $classes, $props);
255
        // Count the number of terms in each language
256
        $result = $this->client->query($query);
257
        return $this->transformCountLangConceptsResults($result, $langs, $props);
258
    }
259
260
    /**
261
     * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one
262
     * of the constants given.
263
     * @param string $varname variable name, e.g. "?uri"
264
     * @param array $values the values
265
     * @param string $type type of values: "uri", "literal" or null (determines quoting style)
266
     */
267
    protected function formatValues($varname, $values, $type = null) {
268
        $constants = array();
269
        foreach ($values as $val) {
270
            if ($type == 'uri') {
271
                $val = "<$val>";
272
            }
273
274
            if ($type == 'literal') {
275
                $val = "'$val'";
276
            }
277
278
            $constants[] = "($val)";
279
        }
280
        $values = implode(" ", $constants);
281
282
        return "VALUES ($varname) { $values }";
283
    }
284
285
    /**
286
     * Filters multiple instances of the same vocabulary from the input array.
287
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
288
     * @return \Vocabulary[]
289
     */
290
    private function filterDuplicateVocabs($vocabs) {
291
        // filtering duplicates
292
        $uniqueVocabs = array();
293
        if ($vocabs !== null && sizeof($vocabs) > 0) {
294
            foreach ($vocabs as $voc) {
295
                $uniqueVocabs[$voc->getId()] = $voc;
296
            }
297
        }
298
299
        return $uniqueVocabs;
300
    }
301
302
    /**
303
     * Generates a sparql query for one or more concept URIs
304
     * @param mixed $uris concept URI (string) or array of URIs
305
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
306
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
307
     * @return string sparql query
308
     */
309
    private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) {
310
        $gcl = $this->graphClause;
311
        $values = $this->formatValues('?uri', $uris, 'uri');
312
        $uniqueVocabs = $this->filterDuplicateVocabs($vocabs);
313
        $valuesGraph = $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
} 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
        /*
852
         * This query does some tricks to obtain a list of unique concepts.
853
         * From each match generated by the text index, a string such as
854
         * "1en@example" is generated, where the first character is a number
855
         * encoding the property and priority, then comes the language tag and
856
         * finally the original literal after an @ sign. Of these, the MIN
857
         * function is used to pick the best match for each concept. Finally,
858
         * the structure is unpacked to get back the original string. Phew!
859
         */
860
        $hitvar = $unique ? '(MIN(?matchstr) AS ?hit)' : '(?matchstr AS ?hit)';
861
        $hitgroup = $unique ? 'GROUP BY ?s ?label ?notation' : '';
862
         
863
        $query = <<<EOQ
864
   SELECT DISTINCT ?s ?label ?notation $hitvar
865
   WHERE {
866
    $graphClause {
867
     $valuesProp
868
     VALUES (?prop ?pri) { (skos:prefLabel 1) (skos:altLabel 3) (skos:hiddenLabel 5)}
869
     { $textcond
870
     ?s ?prop ?match }
871
     UNION
872
     { ?s skos:notation "$rawterm" }
873
     OPTIONAL {
874
      ?s skos:prefLabel ?label .
875
      FILTER ($labelcondLabel)
876
     } $labelcondFallback
877
     BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri)
878
     BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr)
879
     OPTIONAL { ?s skos:notation ?notation }
880
    }
881
    $filterGraph
882
   }
883
   $hitgroup
884
EOQ;
885
886
        return $query;
887
    }
888
889
    /**
890
     * Query for concepts using a search term.
891
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
892
     * @param boolean $unique restrict results to unique concepts (default: false)
893
     * @param ConceptSearchParameters $params 
894
     * @return string sparql query
895
     */
896
    protected function generateConceptSearchQuery($fields, $unique, $params) {
897
        $vocabs = $params->getVocabs();
898
        $gcl = $this->graphClause;
899
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
900
        $formattedtype = $this->formatTypes($params->getTypeLimit());
901
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);
902
        $extravars = $formattedfields['extravars'];
903
        $extrafields = $formattedfields['extrafields'];
904
        $schemes = $params->getSchemeLimit();
905
906
        $schemecond = '';
907
        if (!empty($schemes)) {
908
            foreach($schemes as $scheme) {
909
                $schemecond .= "?s skos:inScheme <$scheme> . ";
910
            }
911
        }
912
913
        // extra conditions for parent and group, if specified
914
        $parentcond = ($params->getParentLimit()) ? "?s skos:broader+ <" . $params->getParentLimit() . "> ." : "";
915
        $groupcond = ($params->getGroupLimit()) ? "<" . $params->getGroupLimit() . "> skos:member ?s ." : "";
916
        $pgcond = $parentcond . $groupcond;
917
918
        $orderextra = $this->isDefaultEndpoint() ? $this->graph : '';
919
920
        # make VALUES clauses
921
        $props = array('skos:prefLabel', 'skos:altLabel');
922
        if ($params->getHidden()) {
923
            $props[] = 'skos:hiddenLabel';
924
        }
925
926
        $filterGraph = empty($vocabs) ? $this->formatFilterGraph($vocabs) : '';
927
928
        // remove futile asterisks from the search term
929
        $term = $params->getSearchTerm();
930
        while (strpos($term, '**') !== false) {
931
            $term = str_replace('**', '*', $term);
932
        }
933
        
934
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique, $filterGraph);
935
936
        $query = <<<EOQ
937
SELECT DISTINCT ?s ?label ?plabel ?alabel ?hlabel ?graph ?notation (GROUP_CONCAT(DISTINCT ?type) as ?types) $extravars 
938
$fcl
939
WHERE {
940
 $gcl {
941
  {
942
$innerquery
943
  }
944
  FILTER(BOUND(?s))
945
  BIND(STR(SUBSTR(?hit,1,1)) AS ?pri)
946
  BIND(STRLANG(STRAFTER(?hit, '@'), SUBSTR(STRBEFORE(?hit, '@'),2)) AS ?match)
947
  BIND(IF((?pri = "1" || ?pri = "2") && ?match != ?label, ?match, ?unbound) as ?plabel)
948
  BIND(IF((?pri = "3" || ?pri = "4"), ?match, ?unbound) as ?alabel)
949
  BIND(IF((?pri = "5" || ?pri = "6"), ?match, ?unbound) as ?hlabel)
950
  $formattedtype
951
  { $pgcond 
952
   ?s a ?type .
953
   $extrafields $schemecond
954
  }
955
  FILTER NOT EXISTS { ?s owl:deprecated true }
956
 }
957
 $filterGraph
958
}
959
GROUP BY ?s ?match ?label ?plabel ?alabel ?hlabel ?notation ?graph
960
ORDER BY LCASE(STR(?match)) LANG(?match) $orderextra
961
EOQ;
962
        return $query;
963
    }
964
965
    /**
966
     * Transform a single concept search query results into the skosmos desired return format.
967
     * @param $row SPARQL query result row
968
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
969
     * @return array query result object
970
     */
971
    private function transformConceptSearchResult($row, $vocabs, $fields)
972
    {
973
        $hit = array();
974
        $hit['uri'] = $row->s->getUri();
975
976
        if (isset($row->graph)) {
977
            $hit['graph'] = $row->graph->getUri();
978
        }
979
980
        foreach (explode(" ", $row->types->getValue()) as $typeuri) {
981
            $hit['type'][] = $this->shortenUri($typeuri);
982
        }
983
984
        if(!empty($fields)) {
985
            foreach ($fields as $prop) {
986
                $propname = $prop . 's';
987
                if (isset($row->$propname)) {
988
                    foreach (explode("\n", $row->$propname->getValue()) as $line) {
989
                        $rdata = str_getcsv($line, ',', '"', '"');
990
                        $propvals = array();
991
                        if ($rdata[0] != '') {
992
                            $propvals['uri'] = $rdata[0];
993
                        }
994
                        if ($rdata[1] != '') {
995
                            $propvals['prefLabel'] = $rdata[1];
996
                        }
997
                        if ($rdata[2] != '') {
998
                            $propvals = $rdata[2];
999
                        }
1000
1001
                        $hit['skos:' . $prop][] = $propvals;
1002
                    }
1003
                }
1004
            }
1005
        }
1006
1007
        
1008
        if (isset($row->preflabels)) {
1009
            foreach (explode("\n", $row->preflabels->getValue()) as $line) {
1010
                $pref = str_getcsv($line, ',', '"', '"');
1011
                $hit['prefLabels'][$pref[1]] = $pref[0];
1012
            }
1013
        }
1014
1015
        foreach ($vocabs as $vocab) { // looping the vocabulary objects and asking these for a localname for the concept.
1016
            $localname = $vocab->getLocalName($hit['uri']);
1017
            if ($localname !== $hit['uri']) { // only passing the result forward if the uri didn't boomerang right back.
1018
                $hit['localname'] = $localname;
1019
                break; // stopping the search when we find one that returns something valid.
1020
            }
1021
        }
1022
1023
        if (isset($row->label)) {
1024
            $hit['prefLabel'] = $row->label->getValue();
1025
        }
1026
1027
        if (isset($row->label)) {
1028
            $hit['lang'] = $row->label->getLang();
1029
        }
1030
1031
        if (isset($row->notation)) {
1032
            $hit['notation'] = $row->notation->getValue();
1033
        }
1034
1035
        if (isset($row->plabel)) {
1036
            $hit['matchedPrefLabel'] = $row->plabel->getValue();
1037
            $hit['lang'] = $row->plabel->getLang();
1038 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...
1039
            $hit['altLabel'] = $row->alabel->getValue();
1040
            $hit['lang'] = $row->alabel->getLang();
1041
        } elseif (isset($row->hlabel)) {
1042
            $hit['hiddenLabel'] = $row->hlabel->getValue();
1043
            $hit['lang'] = $row->hlabel->getLang();
1044
        }
1045
        return $hit;
1046
    }
1047
1048
    /**
1049
     * Transform the concept search query results into the skosmos desired return format.
1050
     * @param EasyRdf_Sparql_Result $results
1051
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1052
     * @return array query result object
1053
     */
1054
    private function transformConceptSearchResults($results, $vocabs, $fields) {
1055
        $ret = array();
1056
1057
        foreach ($results as $row) {
1058
            if (!isset($row->s)) {
1059
                // don't break if query returns a single dummy result
1060
                continue;
1061
            }
1062
            $ret[] = $this->transformConceptSearchResult($row, $vocabs, $fields);
1063
        }
1064
        return $ret;
1065
    }
1066
1067
    /**
1068
     * Query for concepts using a search term.
1069
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1070
     * @param array $fields extra fields to include in the result (array of strings). (default: null = none)
1071
     * @param boolean $unique restrict results to unique concepts (default: false)
1072
     * @param ConceptSearchParameters $params 
1073
     * @return array query result object
1074
     */
1075
    public function queryConcepts($vocabs, $fields = null, $unique = false, $params) {
1076
        $query = $this->generateConceptSearchQuery($fields, $unique, $params);
1077
        $results = $this->client->query($query);
1078
        return $this->transformConceptSearchResults($results, $vocabs, $fields);
1079
    }
1080
1081
    /**
1082
     * Generates sparql query clauses used for creating the alphabetical index.
1083
     * @param string $letter the letter (or special class) to search for
1084
     * @return array of sparql query clause strings
1085
     */
1086
    private function formatFilterConditions($letter) {
1087
        $useRegex = false;
1088
1089
        if ($letter == '*') {
1090
            $letter = '.*';
1091
            $useRegex = true;
1092
        } elseif ($letter == '0-9') {
1093
            $letter = '[0-9].*';
1094
            $useRegex = true;
1095
        } elseif ($letter == '!*') {
1096
            $letter = '[^\\\\p{L}\\\\p{N}].*';
1097
            $useRegex = true;
1098
        }
1099
1100
        # make text query clause
1101
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
1102
        if ($useRegex) {
1103
            $filtercondLabel = "regex(str(?label), '^$letter$', 'i')";
1104
            $filtercondALabel = "regex(str(?alabel), '^$letter$', 'i')";
1105
        } else {
1106
            $filtercondLabel = "strstarts(lcase(str(?label)), '$lcletter')";
1107
            $filtercondALabel = "strstarts(lcase(str(?alabel)), '$lcletter')";
1108
        }
1109
        return array('filterpref' => $filtercondLabel, 'filteralt' => $filtercondALabel);
1110
    }
1111
1112
    /**
1113
     * Generates the sparql query used for rendering the alphabetical index.
1114
     * @param string $letter the letter (or special class) to search for
1115
     * @param string $lang language of labels
1116
     * @param integer $limit limits the amount of results
1117
     * @param integer $offset offsets the result set
1118
     * @param array|null $classes
1119
     * @return string sparql query
1120
     */
1121
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes) {
1122
        $fcl = $this->generateFromClause();
1123
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1124
        $values = $this->formatValues('?type', $classes, 'uri');
1125
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
1126
        $conditions = $this->formatFilterConditions($letter);
1127
        $filtercondLabel = $conditions['filterpref'];
1128
        $filtercondALabel = $conditions['filteralt'];
1129
1130
        $query = <<<EOQ
1131
SELECT DISTINCT ?s ?label ?alabel $fcl
1132
WHERE {
1133
  {
1134
    ?s skos:prefLabel ?label .
1135
    FILTER (
1136
      $filtercondLabel
1137
      && langMatches(lang(?label), '$lang')
1138
    )
1139
  }
1140
  UNION
1141
  {
1142
    {
1143
      ?s skos:altLabel ?alabel .
1144
      FILTER (
1145
        $filtercondALabel
1146
        && langMatches(lang(?alabel), '$lang')
1147
      )
1148
    }
1149
    {
1150
      ?s skos:prefLabel ?label .
1151
      FILTER (langMatches(lang(?label), '$lang'))
1152
    }
1153
  }
1154
  ?s a ?type .
1155
  FILTER NOT EXISTS { ?s owl:deprecated true }
1156
  $values
1157
}
1158
ORDER BY LCASE(IF(BOUND(?alabel), STR(?alabel), STR(?label))) $limitandoffset
1159
EOQ;
1160
        return $query;
1161
    }
1162
1163
    /**
1164
     * Transforms the alphabetical list query results into an array format.
1165
     * @param EasyRdf_Sparql_Result $results
1166
     * @return array
1167
     */
1168
    private function transformAlphabeticalListResults($results) {
1169
        $ret = array();
1170
1171
        foreach ($results as $row) {
1172
            if (!isset($row->s)) {
1173
                continue;
1174
            }
1175
            // don't break if query returns a single dummy result
1176
1177
            $hit = array();
1178
            $hit['uri'] = $row->s->getUri();
1179
1180
            $hit['localname'] = $row->s->localName();
1181
1182
            $hit['prefLabel'] = $row->label->getValue();
1183
            $hit['lang'] = $row->label->getLang();
1184
1185 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...
1186
                $hit['altLabel'] = $row->alabel->getValue();
1187
                $hit['lang'] = $row->alabel->getLang();
1188
            }
1189
1190
            $ret[] = $hit;
1191
        }
1192
1193
        return $ret;
1194
    }
1195
1196
    /**
1197
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1198
     * '*!' (special characters) and '*' (everything) are accepted.
1199
     * @param string $letter the letter (or special class) to search for
1200
     * @param string $lang language of labels
1201
     * @param integer $limit limits the amount of results
1202
     * @param integer $offset offsets the result set
1203
     * @param array $classes
1204
     */
1205
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null) {
1206
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes);
1207
        $results = $this->client->query($query);
1208
        return $this->transformAlphabeticalListResults($results);
1209
    }
1210
1211
    /**
1212
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1213
     * @param string $lang language
1214
     * @return string sparql query
1215
     */
1216
    private function generateFirstCharactersQuery($lang, $classes) {
1217
        $fcl = $this->generateFromClause();
1218
        $classes = (sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1219
        $values = $this->formatValues('?type', $classes, 'uri');
1220
        $query = <<<EOQ
1221
SELECT DISTINCT (substr(ucase(str(?label)), 1, 1) as ?l) $fcl WHERE {
1222
  ?c skos:prefLabel ?label .
1223
  ?c a ?type
1224
  FILTER(langMatches(lang(?label), '$lang'))
1225
  $values
1226
}
1227
EOQ;
1228
        return $query;
1229
    }
1230
1231
    /**
1232
     * Transforms the first characters query results into an array format.
1233
     * @param EasyRdf_Sparql_Result $result
1234
     * @return array
1235
     */
1236
    private function transformFirstCharactersResults($result) {
1237
        $ret = array();
1238
        foreach ($result as $row) {
1239
            $ret[] = $row->l->getValue();
1240
        }
1241
        return $ret;
1242
    }
1243
1244
    /**
1245
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1246
     * @param string $lang language
1247
     * @return array array of characters
1248
     */
1249
    public function queryFirstCharacters($lang, $classes = null) {
1250
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1251
        $result = $this->client->query($query);
1252
        return $this->transformFirstCharactersResults($result);
1253
    }
1254
1255
    /**
1256
     * @param string $uri
1257
     * @param string $lang
1258
     * @return string sparql query string
1259
     */
1260
    private function generateLabelQuery($uri, $lang) {
1261
        $fcl = $this->generateFromClause();
1262
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1263
        $query = <<<EOQ
1264
SELECT ?label $fcl
1265
WHERE {
1266
  <$uri> a ?type .
1267
  OPTIONAL {
1268
    <$uri> skos:prefLabel ?label .
1269
    $labelcondLabel
1270
  }
1271
  OPTIONAL {
1272
    <$uri> rdfs:label ?label .
1273
    $labelcondLabel
1274
  }
1275
  OPTIONAL {
1276
    <$uri> dc:title ?label .
1277
    $labelcondLabel
1278
  }
1279
  OPTIONAL {
1280
    <$uri> dc11:title ?label .
1281
    $labelcondLabel
1282
  }
1283
}
1284
EOQ;
1285
        return $query;
1286
    }
1287
1288
    /**
1289
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1290
     * @param string $uri
1291
     * @param string $lang
1292
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1293
     */
1294
    public function queryLabel($uri, $lang) {
1295
        $query = $this->generateLabelQuery($uri, $lang);
1296
        $result = $this->client->query($query);
1297
        $ret = array();
1298
        foreach ($result as $row) {
1299
            if (!isset($row->label)) {
1300
                // existing concept but no labels
1301
                return array();
1302
            }
1303
            $ret[$row->label->getLang()] = $row->label;
1304
        }
1305
1306
        if (sizeof($ret) > 0) {
1307
            // existing concept, with label(s)
1308
            return $ret;
1309
        } else {
1310
            // nonexistent concept
1311
            return null;
1312
        }
1313
    }
1314
1315
    /**
1316
     * Generates a sparql query for queryProperty.
1317
     * @param string $uri
1318
     * @param string $prop the name of the property eg. 'skos:broader'.
1319
     * @param string $lang
1320
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1321
     * @return string sparql query
1322
     */
1323 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...
1324
        $fcl = $this->generateFromClause();
1325
        $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1326
1327
        $query = <<<EOQ
1328
SELECT * $fcl
1329
WHERE {
1330
  <$uri> a skos:Concept .
1331
  OPTIONAL {
1332
    <$uri> $prop ?object .
1333
    OPTIONAL {
1334
      ?object skos:prefLabel ?label .
1335
      FILTER (langMatches(lang(?label), "$lang"))
1336
    }
1337
    OPTIONAL {
1338
      ?object skos:prefLabel ?label .
1339
      FILTER (lang(?label) = "")
1340
    }
1341
    $anylang
1342
  }
1343
}
1344
EOQ;
1345
        return $query;
1346
    }
1347
1348
    /**
1349
     * Transforms the sparql query result into an array or null if the concept doesn't exist.
1350
     * @param EasyRdf_Sparql_Result $result
1351
     * @param string $lang
1352
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1353
     */
1354
    private function transformPropertyQueryResults($result, $lang) {
1355
        $ret = array();
1356
        foreach ($result as $row) {
1357
            if (!isset($row->object)) {
1358
                return array();
1359
            }
1360
            // existing concept but no properties
1361
            if (isset($row->label)) {
1362
                if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1363
                    $ret[$row->object->getUri()]['label'] = $row->label->getValue();
1364
                }
1365
1366
            } else {
1367
                $ret[$row->object->getUri()]['label'] = null;
1368
            }
1369
        }
1370
        if (sizeof($ret) > 0) {
1371
            return $ret;
1372
        }
1373
        // existing concept, with properties
1374
        else {
1375
            return null;
1376
        }
1377
        // nonexistent concept
1378
    }
1379
1380
    /**
1381
     * Query a single property of a concept.
1382
     * @param string $uri
1383
     * @param string $prop the name of the property eg. 'skos:broader'.
1384
     * @param string $lang
1385
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1386
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1387
     */
1388
    public function queryProperty($uri, $prop, $lang, $anylang = false) {
1389
        $uri = is_array($uri) ? $uri[0] : $uri;
1390
        $query = $this->generatePropertyQuery($uri, $prop, $lang, $anylang);
1391
        $result = $this->client->query($query);
1392
        return $this->transformPropertyQueryResults($result, $lang);
1393
    }
1394
1395
    /**
1396
     * Query a single transitive property of a concept.
1397
     * @param string $uri
1398
     * @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...
1399
     * @param string $lang
1400
     * @param integer $limit
1401
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1402
     * @return string sparql query
1403
     */
1404
    private function generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang) {
1405
        $uri = is_array($uri) ? $uri[0] : $uri;
1406
        $fcl = $this->generateFromClause();
1407
        $propertyClause = implode('|', $props);
1408
        $filter = $anylang ? "" : "FILTER (langMatches(lang(?label), \"$lang\"))";
1409
        // need to do a SPARQL subquery because LIMIT needs to be applied /after/
1410
        // the direct relationships have been collapsed into one string
1411
        $query = <<<EOQ
1412
SELECT * $fcl
1413
WHERE {
1414
  SELECT ?object ?label (GROUP_CONCAT(?dir) as ?direct)
1415
  WHERE {
1416
    <$uri> a skos:Concept .
1417
    OPTIONAL {
1418
      <$uri> $propertyClause* ?object .
1419
      OPTIONAL {
1420
        ?object $propertyClause ?dir .
1421
      }
1422
    }
1423
    OPTIONAL {
1424
      ?object skos:prefLabel ?label .
1425
      $filter
1426
    }
1427
  }
1428
  GROUP BY ?object ?label
1429
}
1430
LIMIT $limit
1431
EOQ;
1432
        return $query;
1433
    }
1434
1435
    /**
1436
     * Transforms the sparql query result object into an array.
1437
     * @param EasyRdf_Sparql_Result $result
1438
     * @param string $lang
1439
     * @param string $fallbacklang language to use if label is not available in the preferred language
1440
     * @return array of property values (key: URI, val: label), or null if concept doesn't exist
1441
     */
1442
    private function transformTransitivePropertyResults($result, $lang, $fallbacklang) {
1443
        $ret = array();
1444
        foreach ($result as $row) {
1445
            if (!isset($row->object)) {
1446
                return array();
1447
            }
1448
            // existing concept but no properties
1449
            if (isset($row->label)) {
1450
                $val = array('label' => $row->label->getValue());
1451
            } else {
1452
                $val = array('label' => null);
1453
            }
1454 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...
1455
                $val['direct'] = explode(' ', $row->direct->getValue());
1456
            }
1457
            // Preventing labels in a non preferred language overriding the preferred language.
1458
            if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1459
                if (!isset($row->label) || $row->label->getLang() === $lang) {
1460
                    $ret[$row->object->getUri()] = $val;
1461
                } elseif ($row->label->getLang() === $fallbacklang) {
1462
                    $val['label'] .= ' (' . $row->label->getLang() . ')';
1463
                    $ret[$row->object->getUri()] = $val;
1464
                }
1465
            }
1466
        }
1467
1468
        // second iteration of results to find labels for the ones that didn't have one in the preferred languages
1469
        foreach ($result as $row) {
1470
            if (isset($row->object) && array_key_exists($row->object->getUri(), $ret) === false) {
1471
                $val = array('label' => $row->label->getValue());
1472 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...
1473
                    $val['direct'] = explode(' ', $row->direct->getValue());
1474
                }
1475
                $ret[$row->object->getUri()] = $val;
1476
            }
1477
        }
1478
1479
        if (sizeof($ret) > 0) {
1480
            return $ret;
1481
        }
1482
        // existing concept, with properties
1483
        else {
1484
            return null;
1485
        }
1486
        // nonexistent concept
1487
    }
1488
1489
    /**
1490
     * Query a single transitive property of a concept.
1491
     * @param string $uri
1492
     * @param array $props the property/properties.
1493
     * @param string $lang
1494
     * @param string $fallbacklang language to use if label is not available in the preferred language
1495
     * @param integer $limit
1496
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1497
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1498
     */
1499
    public function queryTransitiveProperty($uri, $props, $lang, $limit, $anylang = false, $fallbacklang = '') {
1500
        $query = $this->generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang);
1501
        $result = $this->client->query($query);
1502
        return $this->transformTransitivePropertyResults($result, $lang, $fallbacklang);
1503
    }
1504
1505
    /**
1506
     * Generates the query for a concepts skos:narrowers.
1507
     * @param string $uri
1508
     * @param string $lang
1509
     * @param string $fallback
1510
     * @return string sparql query
1511
     */
1512
    private function generateChildQuery($uri, $lang, $fallback, $props) {
1513
        $uri = is_array($uri) ? $uri[0] : $uri;
1514
        $fcl = $this->generateFromClause();
1515
        $propertyClause = implode('|', $props);
1516
        $query = <<<EOQ
1517
SELECT ?child ?label ?child ?grandchildren ?notation $fcl WHERE {
1518
  <$uri> a skos:Concept .
1519
  OPTIONAL {
1520
    ?child $propertyClause <$uri> .
1521
    OPTIONAL {
1522
      ?child skos:prefLabel ?label .
1523
      FILTER (langMatches(lang(?label), "$lang"))
1524
    }
1525
    OPTIONAL {
1526
      ?child skos:prefLabel ?label .
1527
      FILTER (langMatches(lang(?label), "$fallback"))
1528
    }
1529
    OPTIONAL { # other language case
1530
      ?child skos:prefLabel ?label .
1531
    }
1532
    OPTIONAL {
1533
      ?child skos:notation ?notation .
1534
    }
1535
    BIND ( EXISTS { ?a $propertyClause ?child . } AS ?grandchildren )
1536
  }
1537
}
1538
EOQ;
1539
        return $query;
1540
    }
1541
1542
    /**
1543
     * Transforms the sparql result object into an array.
1544
     * @param EasyRdf_Sparql_Result $result
1545
     * @param string $lang
1546
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1547
     */
1548
    private function transformNarrowerResults($result, $lang) {
1549
        $ret = array();
1550
        foreach ($result as $row) {
1551
            if (!isset($row->child)) {
1552
                return array();
1553
            }
1554
            // existing concept but no children
1555
1556
            $label = null;
1557 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...
1558
                if ($row->label->getLang() == $lang) {
1559
                    $label = $row->label->getValue();
1560
                } else {
1561
                    $label = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1562
                }
1563
1564
            }
1565
            $childArray = array(
1566
                'uri' => $row->child->getUri(),
1567
                'prefLabel' => $label,
1568
                'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1569
            );
1570
            if (isset($row->notation)) {
1571
                $childArray['notation'] = $row->notation->getValue();
1572
            }
1573
1574
            $ret[] = $childArray;
1575
        }
1576
        if (sizeof($ret) > 0) {
1577
            return $ret;
1578
        }
1579
        // existing concept, with children
1580
        else {
1581
            return null;
1582
        }
1583
        // nonexistent concept
1584
    }
1585
1586
    /**
1587
     * Query the narrower concepts of a concept.
1588
     * @param string $uri
1589
     * @param string $lang
1590
     * @param string $fallback
1591
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1592
     */
1593
    public function queryChildren($uri, $lang, $fallback, $props) {
1594
        $query = $this->generateChildQuery($uri, $lang, $fallback, $props);
1595
        $result = $this->client->query($query);
1596
        return $this->transformNarrowerResults($result, $lang);
1597
    }
1598
1599
    /**
1600
     * Query the top concepts of a vocabulary.
1601
     * @param string $conceptSchemes concept schemes whose top concepts to query for
1602
     * @param string $lang language of labels
1603
     */
1604
    public function queryTopConcepts($conceptSchemes, $lang) {
1605
        if (!is_array($conceptSchemes)) {
1606
            $conceptSchemes = array($conceptSchemes);
1607
        }
1608
1609
        $values = $this->formatValues('?topuri', $conceptSchemes, 'uri');
1610
1611
        $fcl = $this->generateFromClause();
1612
        $query = <<<EOQ
1613
SELECT DISTINCT ?top ?topuri ?label ?notation ?children $fcl WHERE {
1614
  ?top skos:topConceptOf ?topuri .
1615
  ?top skos:prefLabel ?label .
1616
  FILTER (langMatches(lang(?label), "$lang"))
1617
  OPTIONAL { ?top skos:notation ?notation . }
1618
  BIND ( EXISTS { ?top skos:narrower ?a . } AS ?children )
1619
  $values
1620
}
1621
EOQ;
1622
        $result = $this->client->query($query);
1623
        $ret = array();
1624
        foreach ($result as $row) {
1625
            if (isset($row->top) && isset($row->label)) {
1626
                $top = array('uri' => $row->top->getUri(), 'topConceptOf' => $row->topuri->getUri(), 'label' => $row->label->getValue(), 'hasChildren' => filter_var($row->children->getValue(), FILTER_VALIDATE_BOOLEAN));
1627
                if (isset($row->notation)) {
1628
                    $top['notation'] = $row->notation->getValue();
1629
                }
1630
1631
                $ret[] = $top;
1632
            }
1633
        }
1634
1635
        return $ret;
1636
    }
1637
1638
    /**
1639
     * Generates a sparql query for finding the hierarchy for a concept.
1640
     * @param string $uri concept uri.
1641
     * @param string $lang
1642
     * @param string $fallback language to use if label is not available in the preferred language
1643
     * @return string sparql query
1644
     */
1645
    private function generateParentListQuery($uri, $lang, $fallback, $props) {
1646
        $fcl = $this->generateFromClause();
1647
        $propertyClause = implode('|', $props);
1648
        $query = <<<EOQ
1649
SELECT ?broad ?parent ?member ?children ?grandchildren
1650
(SAMPLE(?lab) as ?label) (SAMPLE(?childlab) as ?childlabel) (SAMPLE(?topcs) AS ?top) (SAMPLE(?nota) as ?notation) (SAMPLE(?childnota) as ?childnotation) $fcl
1651
WHERE {
1652
  <$uri> a skos:Concept .
1653
  OPTIONAL {
1654
    <$uri> $propertyClause* ?broad .
1655
    OPTIONAL {
1656
      ?broad skos:prefLabel ?lab .
1657
      FILTER (langMatches(lang(?lab), "$lang"))
1658
    }
1659
    OPTIONAL {
1660
      ?broad skos:prefLabel ?lab .
1661
      FILTER (langMatches(lang(?lab), "$fallback"))
1662
    }
1663
    OPTIONAL { # fallback - other language case
1664
      ?broad skos:prefLabel ?lab .
1665
    }
1666
    OPTIONAL { ?broad skos:notation ?nota . }
1667
    OPTIONAL { ?broad $propertyClause ?parent . }
1668
    OPTIONAL { ?broad skos:narrower ?children .
1669
      OPTIONAL {
1670
        ?children skos:prefLabel ?childlab .
1671
        FILTER (langMatches(lang(?childlab), "$lang"))
1672
      }
1673
      OPTIONAL {
1674
        ?children skos:prefLabel ?childlab .
1675
        FILTER (langMatches(lang(?childlab), "$fallback"))
1676
      }
1677
      OPTIONAL { # fallback - other language case
1678
        ?children skos:prefLabel ?childlab .
1679
      }
1680
      OPTIONAL {
1681
        ?children skos:notation ?childnota .
1682
      }
1683
    }
1684
    BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren )
1685
    OPTIONAL { ?broad skos:topConceptOf ?topcs . }
1686
  }
1687
}
1688
GROUP BY ?broad ?parent ?member ?children ?grandchildren
1689
EOQ;
1690
        return $query;
1691
    }
1692
1693
    /**
1694
     * Transforms the result into an array.
1695
     * @param EasyRdf_Sparql_Result
1696
     * @param string $lang
1697
     * @return an array for the REST controller to encode.
1698
     */
1699
    private function transformParentListResults($result, $lang)
1700
    {
1701
        $ret = array();
1702
        foreach ($result as $row) {
1703
            if (!isset($row->broad)) {
1704
                // existing concept but no broaders
1705
                return array();
1706
            }
1707
            $uri = $row->broad->getUri();
1708
            if (!isset($ret[$uri])) {
1709
                $ret[$uri] = array('uri' => $uri);
1710
            }
1711
            if (isset($row->exact)) {
1712
                $ret[$uri]['exact'] = $row->exact->getUri();
1713
            }
1714
            if (isset($row->top)) {
1715
                $ret[$uri]['top'] = $row->top->getUri();
1716
            }
1717
            if (isset($row->children)) {
1718
                if (!isset($ret[$uri]['narrower'])) {
1719
                    $ret[$uri]['narrower'] = array();
1720
                }
1721
1722
                $label = null;
1723 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...
1724
                    $label = $row->childlabel->getValue();
1725
                    if ($row->childlabel->getLang() !== $lang) {
1726
                        $label .= " (" . $row->childlabel->getLang() . ")";
1727
                    }
1728
1729
                }
1730
1731
                $childArr = array(
1732
                    'uri' => $row->children->getUri(),
1733
                    'label' => $label,
1734
                    'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1735
                );
1736
                if (isset($row->childnotation)) {
1737
                    $childArr['notation'] = $row->childnotation->getValue();
1738
                }
1739
1740
                if (!in_array($childArr, $ret[$uri]['narrower'])) {
1741
                    $ret[$uri]['narrower'][] = $childArr;
1742
                }
1743
1744
            }
1745
            if (isset($row->label)) {
1746
                $preflabel = $row->label->getValue();
1747
                if ($row->label->getLang() !== $lang && strpos($row->label->getLang(), $lang) !== 0) {
1748
                    $preflabel .= ' (' . $row->label->getLang() . ')';
1749
                }
1750
1751
                $ret[$uri]['prefLabel'] = $preflabel;
1752
            }
1753
            if (isset($row->notation)) {
1754
                $ret[$uri]['notation'] = $row->notation->getValue();
1755
            }
1756
1757
            if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) {
1758
                $ret[$uri]['broader'][] = $row->parent->getUri();
1759
            } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) {
1760
                $ret[$uri]['broader'][] = $row->parent->getUri();
1761
            }
1762
        }
1763
        if (sizeof($ret) > 0) {
1764
            // existing concept, with children
1765
            return $ret;
1766
        }
1767
        else {
1768
            // nonexistent concept
1769
            return null;
1770
        }
1771
    }
1772
1773
    /**
1774
     * Query for finding the hierarchy for a concept.
1775
     * @param string $uri concept uri.
1776
     * @param string $lang
1777
     * @param string $fallback language to use if label is not available in the preferred language
1778
     * @param array $props the hierarchy property/properties to use
1779
     * @return an array for the REST controller to encode.
1780
     */
1781
    public function queryParentList($uri, $lang, $fallback, $props) {
1782
        $query = $this->generateParentListQuery($uri, $lang, $fallback, $props);
1783
        $result = $this->client->query($query);
1784
        return $this->transformParentListResults($result, $lang);
1785
    }
1786
1787
    /**
1788
     * return a list of concept group instances, sorted by label
1789
     * @param string $groupClass URI of concept group class
1790
     * @param string $lang language of labels to return
1791
     * @return string sparql query
1792
     */
1793
    private function generateConceptGroupsQuery($groupClass, $lang) {
1794
        $fcl = $this->generateFromClause();
1795
        $query = <<<EOQ
1796
SELECT ?group (GROUP_CONCAT(DISTINCT STR(?child)) as ?children) ?label ?members ?notation $fcl
1797
WHERE {
1798
  ?group a <$groupClass> .
1799
  OPTIONAL { ?group skos:member|isothes:subGroup ?child .
1800
             ?child a <$groupClass> }
1801
  BIND(EXISTS{?group skos:member ?submembers} as ?members)
1802
  OPTIONAL { ?group skos:prefLabel ?label }
1803
  OPTIONAL { ?group rdfs:label ?label }
1804
  FILTER (langMatches(lang(?label), '$lang'))
1805
  OPTIONAL { ?group skos:notation ?notation }
1806
}
1807
GROUP BY ?group ?label ?members ?notation
1808
ORDER BY lcase(?label)
1809
EOQ;
1810
        return $query;
1811
    }
1812
1813
    /**
1814
     * Transforms the sparql query result into an array.
1815
     * @param EasyRdf_Sparql_Result $result
1816
     * @return array
1817
     */
1818
    private function transformConceptGroupsResults($result) {
1819
        $ret = array();
1820
        foreach ($result as $row) {
1821
            if (!isset($row->group)) {
1822
                # no groups found, see issue #357
1823
                continue;
1824
            }
1825
            $group = array('uri' => $row->group->getURI());
1826
            if (isset($row->label)) {
1827
                $group['prefLabel'] = $row->label->getValue();
1828
            }
1829
1830
            if (isset($row->children)) {
1831
                $group['childGroups'] = explode(' ', $row->children->getValue());
1832
            }
1833
1834
            if (isset($row->members)) {
1835
                $group['hasMembers'] = $row->members->getValue();
1836
            }
1837
1838
            if (isset($row->notation)) {
1839
                $group['notation'] = $row->notation->getValue();
1840
            }
1841
1842
            $ret[] = $group;
1843
        }
1844
        return $ret;
1845
    }
1846
1847
    /**
1848
     * return a list of concept group instances, sorted by label
1849
     * @param string $groupClass URI of concept group class
1850
     * @param string $lang language of labels to return
1851
     * @return array Result array with group URI as key and group label as value
1852
     */
1853
    public function listConceptGroups($groupClass, $lang) {
1854
        $query = $this->generateConceptGroupsQuery($groupClass, $lang);
1855
        $result = $this->client->query($query);
1856
        return $this->transformConceptGroupsResults($result);
1857
    }
1858
1859
    /**
1860
     * Generates the sparql query for listConceptGroupContents
1861
     * @param string $groupClass URI of concept group class
1862
     * @param string $group URI of the concept group instance
1863
     * @param string $lang language of labels to return
1864
     * @return string sparql query
1865
     */
1866
    private function generateConceptGroupContentsQuery($groupClass, $group, $lang) {
1867
        $fcl = $this->generateFromClause();
1868
        $query = <<<EOQ
1869
SELECT ?conc ?super ?label ?members ?type ?notation $fcl
1870
WHERE {
1871
 <$group> a <$groupClass> .
1872
 { <$group> skos:member ?conc . } UNION { ?conc isothes:superGroup <$group> }
1873
 FILTER NOT EXISTS { ?conc owl:deprecated true }
1874
 ?conc a ?type .
1875
 OPTIONAL { ?conc skos:prefLabel ?label .
1876
  FILTER (langMatches(lang(?label), '$lang'))
1877
 }
1878
 OPTIONAL { ?conc skos:prefLabel ?label . }
1879
 OPTIONAL { ?conc skos:notation ?notation }
1880
 BIND(EXISTS{?submembers isothes:superGroup ?conc} as ?super)
1881
 BIND(EXISTS{?conc skos:member ?submembers} as ?members)
1882
} ORDER BY lcase(?label)
1883
EOQ;
1884
        return $query;
1885
    }
1886
1887
    /**
1888
     * Transforms the sparql query result into an array.
1889
     * @param EasyRdf_Sparql_Result $result
1890
     * @param string $lang language of labels to return
1891
     * @return array
1892
     */
1893
    private function transformConceptGroupContentsResults($result, $lang) {
1894
        $ret = array();
1895
        $values = array();
1896
        foreach ($result as $row) {
1897
            if (!array_key_exists($row->conc->getURI(), $values)) {
1898
                $values[$row->conc->getURI()] = array(
1899
                    'uri' => $row->conc->getURI(),
1900
                    'isSuper' => $row->super->getValue(),
1901
                    'hasMembers' => $row->members->getValue(),
1902
                    'type' => array($row->type->shorten()),
1903
                );
1904
                if (isset($row->label)) {
1905
                    if ($row->label->getLang() == $lang) {
1906
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue();
1907
                    } else {
1908
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1909
                    }
1910
1911
                }
1912
                if (isset($row->notation)) {
1913
                    $values[$row->conc->getURI()]['notation'] = $row->notation->getValue();
1914
                }
1915
1916
            } else {
1917
                $values[$row->conc->getURI()]['type'][] = $row->type->shorten();
1918
            }
1919
        }
1920
1921
        foreach ($values as $val) {
1922
            $ret[] = $val;
1923
        }
1924
1925
        return $ret;
1926
    }
1927
1928
    /**
1929
     * return a list of concepts in a concept group
1930
     * @param string $groupClass URI of concept group class
1931
     * @param string $group URI of the concept group instance
1932
     * @param string $lang language of labels to return
1933
     * @return array Result array with concept URI as key and concept label as value
1934
     */
1935
    public function listConceptGroupContents($groupClass, $group, $lang) {
1936
        $query = $this->generateConceptGroupContentsQuery($groupClass, $group, $lang);
1937
        $result = $this->client->query($query);
1938
        return $this->transformConceptGroupContentsResults($result, $lang);
1939
    }
1940
1941
    /**
1942
     * Generates the sparql query for queryChangeList.
1943
     * @param string $lang language of labels to return.
1944
     * @param int $offset offset of results to retrieve; 0 for beginning of list
1945
     * @return string sparql query
1946
     */
1947 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...
1948
        $fcl = $this->generateFromClause();
1949
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
1950
1951
        $query = <<<EOQ
1952
SELECT DISTINCT ?concept ?date ?label $fcl
1953
WHERE {
1954
  ?concept a skos:Concept .
1955
  ?concept $prop ?date .
1956
  ?concept skos:prefLabel ?label .
1957
  FILTER (langMatches(lang(?label), '$lang'))
1958
}
1959
ORDER BY DESC(YEAR(?date)) DESC(MONTH(?date)) LCASE(?label)
1960
LIMIT 200 $offset
1961
EOQ;
1962
        return $query;
1963
    }
1964
1965
    /**
1966
     * Transforms the sparql query result into an array.
1967
     * @param EasyRdf_Sparql_Result $result
1968
     * @return array
1969
     */
1970 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...
1971
        $ret = array();
1972
        foreach ($result as $row) {
1973
            $concept = array('uri' => $row->concept->getURI());
1974
            if (isset($row->label)) {
1975
                $concept['prefLabel'] = $row->label->getValue();
1976
            }
1977
1978
            if (isset($row->date)) {
1979
                $concept['date'] = $row->date->getValue();
1980
            }
1981
1982
            $ret[] = $concept;
1983
        }
1984
        return $ret;
1985
    }
1986
1987
    /**
1988
     * return a list of recently changed or entirely new concepts
1989
     * @param string $lang language of labels to return
1990
     * @param int $offset offset of results to retrieve; 0 for beginning of list
1991
     * @return array Result array
1992
     */
1993
    public function queryChangeList($lang, $offset, $prop) {
1994
        $query = $this->generateChangeListQuery($lang, $offset, $prop);
1995
        $result = $this->client->query($query);
1996
        return $this->transformChangeListResults($result);
1997
    }
1998
}
1999