Completed
Push — master ( 1acccb...bdff2a )
by Henri
03:25
created

GenericSparql::transformParentListResults()   F

Complexity

Conditions 22
Paths 7203

Size

Total Lines 73
Code Lines 43

Duplication

Lines 7
Ratio 9.59 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 7
loc 73
rs 2.4815
c 1
b 1
f 0
cc 22
eloc 43
nc 7203
nop 2

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Generates SPARQL queries and provides access to the SPARQL endpoint.
5
 */
6
class GenericSparql {
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 <$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;
0 ignored issues
show
Unused Code introduced by
$gcl is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
197
        $fcl = $this->generateFromClause();
198
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
199
200
        $values = $this->formatValues('?type', $classes, 'uri');
201
        $valuesLang = $this->formatValues('?lang', $langs, 'literal');
202
        $valuesProp = $this->formatValues('?prop', $props, null);
203
204
        $query = <<<EOQ
205
SELECT ?lang ?prop
206
  (COUNT(?label) as ?count)
207
$fcl
208
WHERE {
209
  {
210
    ?conc a ?type .
211
    ?conc ?prop ?label .
212
    FILTER (langMatches(lang(?label), ?lang))
213
    $valuesLang
214
    $valuesProp
215
  }
216
  $values
217
}
218
GROUP BY ?lang ?prop ?type
219
EOQ;
220
        return $query;
221
    }
222
223
    /**
224
     * Transforms the CountLangConcepts results into an array of label counts.
225
     * @param EasyRdf_Sparql_Result $result query results to be transformed
226
     * @param array $langs Languages to query for
227
     * @param string[] $props property names
228
     */
229
    private function transformCountLangConceptsResults($result, $langs, $props) {
230
        $ret = array();
231
        // set default count to zero; overridden below if query found labels
232
        foreach ($langs as $lang) {
233
            foreach ($props as $prop) {
234
                $ret[$lang][$prop] = 0;
235
            }
236
        }
237
        foreach ($result as $row) {
238
            if (isset($row->lang) && isset($row->prop) && isset($row->count)) {
239
                $ret[$row->lang->getValue()][$row->prop->shorten()] += 
240
                $row->count->getValue();
241
            }
242
243
        }
244
        ksort($ret);
245
        return $ret;
246
    }
247
248
    /**
249
     * Counts the number of concepts in a easyRDF graph with a specific language.
250
     * @param array $langs Languages to query for
251
     * @return Array containing count of concepts for each language and property.
252
     */
253
    public function countLangConcepts($langs, $classes = null) {
254
        $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel');
255
        $query = $this->generateCountLangConceptsQuery($langs, $classes, $props);
256
        // Count the number of terms in each language
257
        $result = $this->client->query($query);
258
        return $this->transformCountLangConceptsResults($result, $langs, $props);
259
    }
260
261
    /**
262
     * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one
263
     * of the constants given.
264
     * @param string $varname variable name, e.g. "?uri"
265
     * @param array $values the values
266
     * @param string $type type of values: "uri", "literal" or null (determines quoting style)
267
     */
268
    protected function formatValues($varname, $values, $type = null) {
269
        $constants = array();
270
        foreach ($values as $val) {
271
            if ($type == 'uri') {
272
                $val = "<$val>";
273
            }
274
275
            if ($type == 'literal') {
276
                $val = "'$val'";
277
            }
278
279
            $constants[] = "($val)";
280
        }
281
        $values = implode(" ", $constants);
282
283
        return "VALUES ($varname) { $values }";
284
    }
285
286
    /**
287
     * Filters multiple instances of the same vocabulary from the input array.
288
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
289
     * @return \Vocabulary[]
290
     */
291
    private function filterDuplicateVocabs($vocabs) {
292
        // filtering duplicates
293
        $uniqueVocabs = array();
294
        if ($vocabs !== null && sizeof($vocabs) > 0) {
295
            foreach ($vocabs as $voc) {
296
                $uniqueVocabs[$voc->getId()] = $voc;
297
            }
298
        }
299
300
        return $uniqueVocabs;
301
    }
302
303
    /**
304
     * Generates a sparql query for one or more concept URIs
305
     * @param mixed $uris concept URI (string) or array of URIs
306
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
307
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
308
     * @return string sparql query
309
     */
310
    private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) {
311
        $gcl = $this->graphClause;
312
        $values = $this->formatValues('?uri', $uris, 'uri');
313
        $uniqueVocabs = $this->filterDuplicateVocabs($vocabs);
314
        $valuesGraph = $this->formatValuesGraph($uniqueVocabs);
315
316
        if ($arrayClass === null) {
317
            $construct = $optional = "";
318
        } else {
319
            // add information that can be used to format narrower concepts by
320
            // the array they belong to ("milk by source animal" use case)
321
            $construct = "\n ?x skos:member ?o . ?x skos:prefLabel ?xl . ?x a <$arrayClass> .";
322
            $optional = "\n OPTIONAL {
323
                      ?x skos:member ?o .
324
                      ?x a <$arrayClass> .
325
                      ?x skos:prefLabel ?xl .
326
                      FILTER NOT EXISTS {
327
                        ?x skos:member ?other .
328
                        FILTER NOT EXISTS { ?other skos:broader ?uri }
329
                      }
330
                    }";
331
        }
332
        $query = <<<EOQ
333
CONSTRUCT {
334
 ?s ?p ?uri .
335
 ?sp ?uri ?op .
336
 ?uri ?p ?o .
337
 ?p rdfs:label ?proplabel .
338
 ?p rdfs:subPropertyOf ?pp .
339
 ?pp rdfs:label ?plabel .
340
 ?o a ?ot .
341
 ?o skos:prefLabel ?opl .
342
 ?o rdfs:label ?ol .
343
 ?o rdf:value ?ov .
344
 ?o skos:notation ?on .
345
 ?o ?oprop ?oval .
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
   } $optional
398
  }
399
 }
400
 $values
401
}
402
$valuesGraph
403
EOQ;
404
        return $query;
405
    }
406
407
    /**
408
     * Transforms ConceptInfo query results into an array of Concept objects
409
     * @param EasyRdf_Graph $result query results to be transformed
410
     * @param array $uris concept URIs
411
     * @param \Vocabulary[] $vocabs array of Vocabulary object
412
     * @param string|null $clang content language
413
     * @return mixed query result graph (EasyRdf_Graph), or array of Concept objects
414
     */
415
    private function transformConceptInfoResults($result, $uris, $vocabs, $clang) {
416
        $conceptArray = array();
417
        foreach ($uris as $index => $uri) {
418
            $conc = $result->resource($uri);
419
            $vocab = sizeof($vocabs) == 1 ? $vocabs[0] : $vocabs[$index];
420
            $conceptArray[] = new Concept($this->model, $vocab, $conc, $result, $clang);
421
        }
422
        return $conceptArray;
423
    }
424
425
    /**
426
     * Returns information (as a graph) for one or more concept URIs
427
     * @param mixed $uris concept URI (string) or array of URIs
428
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
429
     * @param \Vocabulary[]|null $vocabs vocabularies to target
430
     * @return \Concept[]
431
     */
432
    public function queryConceptInfoGraph($uris, $arrayClass = null, $vocabs = array()) {
433
        // if just a single URI is given, put it in an array regardless
434
        if (!is_array($uris)) {
435
            $uris = array($uris);
436
        }
437
438
        $query = $this->generateConceptInfoQuery($uris, $arrayClass, $vocabs);
439
        $result = $this->client->query($query);
440
        return $result;
441
    }
442
443
    /**
444
     * Returns information (as an array of Concept objects) for one or more concept URIs
445
     * @param mixed $uris concept URI (string) or array of URIs
446
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
447
     * @param \Vocabulary[] $vocabs vocabularies to target
448
     * @param string|null $clang content language
449
     * @return EasyRdf_Graph
450
     */
451
    public function queryConceptInfo($uris, $arrayClass = null, $vocabs = array(), $clang = null) {
452
        // if just a single URI is given, put it in an array regardless
453
        if (!is_array($uris)) {
454
            $uris = array($uris);
455
        }
456
        $result = $this->queryConceptInfoGraph($uris, $arrayClass, $vocabs);
457
        if ($result->isEmpty()) {
458
            return;
459
        }
460
        return $this->transformConceptInfoResults($result, $uris, $vocabs, $clang);
461
    }
462
463
    /**
464
     * Generates the sparql query for queryTypes
465
     * @param string $lang
466
     * @return string sparql query
467
     */
468
    private function generateQueryTypesQuery($lang) {
469
        $fcl = $this->generateFromClause();
470
        $query = <<<EOQ
471
SELECT DISTINCT ?type ?label ?superclass $fcl
472
WHERE {
473
  {
474
    { BIND( skos:Concept as ?type ) }
475
    UNION
476
    { BIND( skos:Collection as ?type ) }
477
    UNION
478
    { BIND( isothes:ConceptGroup as ?type ) }
479
    UNION
480
    { BIND( isothes:ThesaurusArray as ?type ) }
481
    UNION
482
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Concept . }
483
    UNION
484
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Collection . }
485
  }
486
  OPTIONAL {
487
    ?type rdfs:label ?label .
488
    FILTER(langMatches(lang(?label), '$lang'))
489
  }
490
  OPTIONAL {
491
    ?type rdfs:subClassOf ?superclass .
492
  }
493
  FILTER EXISTS {
494
    ?s a ?type .
495
    ?s skos:prefLabel ?prefLabel .
496
  }
497
}
498
EOQ;
499
        return $query;
500
    }
501
502
    /**
503
     * Transforms the results into an array format.
504
     * @param EasyRdf_Sparql_Result $result
505
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
506
     */
507 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...
508
        $ret = array();
509
        foreach ($result as $row) {
510
            $type = array();
511
            if (isset($row->label)) {
512
                $type['label'] = $row->label->getValue();
513
            }
514
515
            if (isset($row->superclass)) {
516
                $type['superclass'] = $row->superclass->getUri();
517
            }
518
519
            $ret[$row->type->getURI()] = $type;
520
        }
521
        return $ret;
522
    }
523
524
    /**
525
     * Retrieve information about types from the endpoint
526
     * @param string $lang
527
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
528
     */
529
    public function queryTypes($lang) {
530
        $query = $this->generateQueryTypesQuery($lang);
531
        $result = $this->client->query($query);
532
        return $this->transformQueryTypesResults($result);
533
    }
534
535
    /**
536
     * Generates the concept scheme query.
537
     * @param string $conceptscheme concept scheme URI
538
     * @return string sparql query
539
     */
540
    private function generateQueryConceptSchemeQuery($conceptscheme) {
541
        $gcl = $this->graphClause;
542
        $query = <<<EOQ
543
CONSTRUCT {
544
  <$conceptscheme> ?property ?value .
545
} WHERE {
546
  $gcl {
547
    <$conceptscheme> ?property ?value .
548
    FILTER (?property != skos:hasTopConcept)
549
  }
550
}
551
EOQ;
552
        return $query;
553
    }
554
555
    /**
556
     * Retrieves conceptScheme information from the endpoint.
557
     * @param string $conceptscheme concept scheme URI
558
     * @return EasyRDF_Graph query result graph
559
     */
560
    public function queryConceptScheme($conceptscheme) {
561
        $query = $this->generateQueryConceptSchemeQuery($conceptscheme);
562
        return $this->client->query($query);
563
    }
564
565
    /**
566
     * Generates the queryConceptSchemes sparql query.
567
     * @param string $lang language of labels
568
     * @return string sparql query
569
     */
570
    private function generateQueryConceptSchemesQuery($lang) {
571
        $fcl = $this->generateFromClause();
572
        $query = <<<EOQ
573
SELECT ?cs ?label ?preflabel ?title $fcl
574
WHERE {
575
 ?cs a skos:ConceptScheme .
576
 OPTIONAL {
577
   ?cs rdfs:label ?label .
578
   FILTER(langMatches(lang(?label), '$lang'))
579
 }
580
 OPTIONAL {
581
   ?cs skos:prefLabel ?preflabel .
582
   FILTER(langMatches(lang(?preflabel), '$lang'))
583
 }
584
 OPTIONAL {
585
   { ?cs dc11:title ?title }
586
   UNION
587
   { ?cs dc:title ?title }
588
   FILTER(langMatches(lang(?title), '$lang'))
589
 }
590
} ORDER BY ?cs
591
EOQ;
592
        return $query;
593
    }
594
595
    /**
596
     * Transforms the queryConceptScheme results into an array format.
597
     * @param EasyRdf_Sparql_Result $result
598
     * @return array
599
     */
600
    private function transformQueryConceptSchemesResults($result) {
601
        $ret = array();
602
        foreach ($result as $row) {
603
            $conceptscheme = array();
604
            if (isset($row->label)) {
605
                $conceptscheme['label'] = $row->label->getValue();
606
            }
607
608
            if (isset($row->preflabel)) {
609
                $conceptscheme['prefLabel'] = $row->preflabel->getValue();
610
            }
611
612
            if (isset($row->title)) {
613
                $conceptscheme['title'] = $row->title->getValue();
614
            }
615
616
            $ret[$row->cs->getURI()] = $conceptscheme;
617
        }
618
        return $ret;
619
    }
620
621
    /**
622
     * return a list of skos:ConceptScheme instances in the given graph
623
     * @param string $lang language of labels
624
     * @return array Array with concept scheme URIs (string) as keys and labels (string) as values
625
     */
626
    public function queryConceptSchemes($lang) {
627
        $query = $this->generateQueryConceptSchemesQuery($lang);
628
        $result = $this->client->query($query);
629
        return $this->transformQueryConceptSchemesResults($result);
630
    }
631
632
    /**
633
     * Generate a VALUES clause for limiting the targeted graphs.
634
     * @param Vocabulary[]|null $vocabs the vocabularies to target 
635
     * @return string[] array of graph URIs
636
     */
637
    protected function getVocabGraphs($vocabs) {
638
        if ($vocabs === null || sizeof($vocabs) == 0) {
639
            // searching from all vocabularies - limit to known graphs
640
            $vocabs = $this->model->getVocabularies();
641
        }
642
        $graphs = array();
643
        foreach ($vocabs as $voc) {
644
            $graphs[] = $voc->getGraph();
645
        }
646
        return $graphs;
647
    }
648
649
    /**
650
     * Generate a VALUES clause for limiting the targeted graphs.
651
     * @param Vocabulary[]|null $vocabs array of Vocabulary objects to target
652
     * @return string VALUES clause, or "" if not necessary to limit
653
     */
654
    protected function formatValuesGraph($vocabs) {
655
        if (!$this->isDefaultEndpoint()) {
656
            return "";
657
        }
658
        $graphs = $this->getVocabGraphs($vocabs);
659
        return $this->formatValues('?graph', $graphs, 'uri');
660
    }
661
662
    /**
663
     * Generate a FILTER clause for limiting the targeted graphs.
664
     * @param array $vocabs array of Vocabulary objects to target
665
     * @return string FILTER clause, or "" if not necessary to limit
666
     */
667
    protected function formatFilterGraph($vocabs) {
668
        if (!$this->isDefaultEndpoint()) {
669
            return "";
670
        }
671
        $graphs = $this->getVocabGraphs($vocabs);
672
        $values = array();
673
        foreach ($graphs as $graph) {
674
          $values[] = "<$graph>";
675
        }
676
        return "FILTER (?graph IN (" . implode(',', $values) . "))";
677
    }
678
679
    /**
680
     * Formats combined limit and offset clauses for the sparql query
681
     * @param int $limit maximum number of hits to retrieve; 0 for unlimited
682
     * @param int $offset offset of results to retrieve; 0 for beginning of list
683
     * @return string sparql query clauses
684
     */
685
    protected function formatLimitAndOffset($limit, $offset) {
686
        $limit = ($limit) ? 'LIMIT ' . $limit : '';
687
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
688
        // eliminating whitespace and line changes when the conditions aren't needed.
689
        $limitandoffset = '';
690
        if ($limit && $offset) {
691
            $limitandoffset = "\n" . $limit . "\n" . $offset;
692
        } elseif ($limit) {
693
            $limitandoffset = "\n" . $limit;
694
        } elseif ($offset) {
695
            $limitandoffset = "\n" . $offset;
696
        }
697
698
        return $limitandoffset;
699
    }
700
701
    /**
702
     * Formats a sparql query clause for limiting the search to specific concept types.
703
     * @param array $types limit search to concepts of the given type(s)
704
     * @return string sparql query clause
705
     */
706
    protected function formatTypes($types) {
707
        $typePatterns = array();
708
        if (!empty($types)) {
709
            foreach ($types as $type) {
710
                $unprefixed = EasyRdf_Namespace::expand($type);
711
                $typePatterns[] = "{ ?s a <$unprefixed> }";
712
            }
713
        }
714
715
        return implode(' UNION ', $typePatterns);;
716
    }
717
718
    /**
719
     * @param string $prop property to include in the result eg. 'broader' or 'narrower'
720
     * @return string sparql query clause
721
     */
722
    private function formatPropertyCsvClause($prop) {
723
        # This expression creates a CSV row containing pairs of (uri,prefLabel) values.
724
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
725
        $clause = <<<EOV
726
(GROUP_CONCAT(DISTINCT CONCAT(
727
 '"', IF(isIRI(?$prop),STR(?$prop),''), '"', ',',
728
 '"', REPLACE(IF(BOUND(?{$prop}lab),?{$prop}lab,''), '"', '""'), '"', ',',
729
 '"', REPLACE(IF(isLiteral(?{$prop}),?{$prop},''), '"', '""'), '"'
730
); separator='\\n') as ?{$prop}s)
731
EOV;
732
        return $clause;
733
    }
734
    
735
    /**
736
     * @return string sparql query clause
737
     */
738
    private function formatPrefLabelCsvClause() {
739
        # This expression creates a CSV row containing pairs of (prefLabel, lang) values.
740
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
741
        $clause = <<<EOV
742
(GROUP_CONCAT(DISTINCT CONCAT(
743
 '"', STR(?pref), '"', ',', '"', lang(?pref), '"'
744
); separator='\\n') as ?preflabels)
745
EOV;
746
        return $clause;
747
    }
748
749
    /**
750
     * @param string $lang language code of the returned labels
751
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
752
     * @return string sparql query clause
753
     */
754
    protected function formatExtraFields($lang, $fields) {
755
        // extra variable expressions to request and extra fields to query for
756
        $ret = array('extravars' => '', 'extrafields' => '');
757
758
        if ($fields === null) {
759
            return $ret; 
760
        }
761
762
        if (in_array('prefLabel', $fields)) {
763
            $ret['extravars'] .= $this->formatPreflabelCsvClause();
764
            $ret['extrafields'] .= <<<EOF
765
OPTIONAL {
766
  ?s skos:prefLabel ?pref .
767
}
768
EOF;
769
            // removing the prefLabel from the fields since it has been handled separately
770
            $fields = array_diff($fields, array('prefLabel'));
771
        }
772
773
        foreach ($fields as $field) {
774
            $ret['extravars'] .= $this->formatPropertyCsvClause($field);
775
            $ret['extrafields'] .= <<<EOF
776
OPTIONAL {
777
  ?s skos:$field ?$field .
778
  FILTER(!isLiteral(?$field)||langMatches(lang(?{$field}), '$lang'))
779
  OPTIONAL { ?$field skos:prefLabel ?{$field}lab . FILTER(langMatches(lang(?{$field}lab), '$lang')) }
780
}
781
EOF;
782
        }
783
784
        return $ret;
785
    }
786
787
    /**
788
     * Generate condition for matching labels in SPARQL
789
     * @param string $term search term
790
     * @param string $searchLang language code used for matching labels (null means any language)
791
     * @return string sparql query snippet
792
     */
793
    protected function generateConceptSearchQueryCondition($term, $searchLang)
794
    {
795
        # use appropriate matching function depending on query type: =, strstarts, strends or full regex
796
        if (preg_match('/^[^\*]+$/', $term)) { // exact query
797
            $term = str_replace('\\', '\\\\', $term); // quote slashes
798
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
799
            $filtercond = "LCASE(STR(?match)) = '$term'";
800
        } elseif (preg_match('/^[^\*]+\*$/', $term)) { // prefix query
801
            $term = substr($term, 0, -1); // remove the final asterisk
802
            $term = str_replace('\\', '\\\\', $term); // quote slashes
803
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
804
            $filtercond = "STRSTARTS(LCASE(STR(?match)), '$term')";
805
        } elseif (preg_match('/^\*[^\*]+$/', $term)) { // suffix query
806
            $term = substr($term, 1); // remove the preceding asterisk
807
            $term = str_replace('\\', '\\\\', $term); // quote slashes
808
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
809
            $filtercond = "STRENDS(LCASE(STR(?match)), '$term')";
810
        } else { // too complicated - have to use a regex
811
            # make sure regex metacharacters are not passed through
812
            $term = str_replace('\\', '\\\\', preg_quote($term));
813
            $term = str_replace('\\\\*', '.*', $term); // convert asterisk to regex syntax
814
            $term = str_replace('\'', '\\\'', $term); // ensure single quotes are quoted
815
            $filtercond = "REGEX(STR(?match), '^$term$', 'i')";
816
        }
817
818
        $labelcondMatch = ($searchLang) ? "&& LANGMATCHES(lang(?match), '$searchLang')" : "";
819
        
820
        return "?s ?prop ?match . FILTER ($filtercond $labelcondMatch)";
821
    }
822
823
824
    /**
825
     * Inner query for concepts using a search term.
826
     * @param string $term search term
827
     * @param string $lang language code of the returned labels
828
     * @param string $searchLang language code used for matching labels (null means any language)
829
     * @param string[] $props properties to target e.g. array('skos:prefLabel','skos:altLabel')
830
     * @param boolean $unique restrict results to unique concepts (default: false)
831
     * @return string sparql query
832
     */
833
    protected function generateConceptSearchQueryInner($term, $lang, $searchLang, $props, $unique, $filterGraph)
834
    {
835
        $valuesProp = $this->formatValues('?prop', $props);
836
        $textcond = $this->generateConceptSearchQueryCondition($term, $searchLang);
837
        $rawterm = str_replace('*', '', $term);
838
        
839
        // graph clause, if necessary
840
        $graphClause = $filterGraph != '' ? 'GRAPH ?graph' : '';
841
842
        // extra conditions for label language, if specified
843
        $labelcondLabel = ($lang) ? "LANGMATCHES(lang(?label), '$lang')" : "LANGMATCHES(lang(?label), lang(?match))";
844
        // if search language and UI/display language differ, must also consider case where there is no prefLabel in
845
        // the display language; in that case, should use the label with the same language as the matched label
846
        $labelcondFallback = ($searchLang != $lang) ?
847
          "OPTIONAL { # in case previous OPTIONAL block gives no labels\n" .
848
          "?s skos:prefLabel ?label . FILTER (LANGMATCHES(LANG(?label), LANG(?match))) }" : "";
849
850
        /*
851
         * This query does some tricks to obtain a list of unique concepts.
852
         * From each match generated by the text index, a string such as
853
         * "1en@example" is generated, where the first character is a number
854
         * encoding the property and priority, then comes the language tag and
855
         * finally the original literal after an @ sign. Of these, the MIN
856
         * function is used to pick the best match for each concept. Finally,
857
         * the structure is unpacked to get back the original string. Phew!
858
         */
859
        $hitvar = $unique ? '(MIN(?matchstr) AS ?hit)' : '(?matchstr AS ?hit)';
860
        $hitgroup = $unique ? 'GROUP BY ?s ?label ?notation' : '';
861
         
862
        $query = <<<EOQ
863
   SELECT DISTINCT ?s ?label ?notation $hitvar
864
   WHERE {
865
    $graphClause {
866
     $valuesProp
867
     VALUES (?prop ?pri) { (skos:prefLabel 1) (skos:altLabel 3) (skos:hiddenLabel 5)}
868
     { $textcond
869
     ?s ?prop ?match }
870
     UNION
871
     { ?s skos:notation "$rawterm" }
872
     OPTIONAL {
873
      ?s skos:prefLabel ?label .
874
      FILTER ($labelcondLabel)
875
     } $labelcondFallback
876
     BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri)
877
     BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr)
878
     OPTIONAL { ?s skos:notation ?notation }
879
    }
880
    $filterGraph
881
   }
882
   $hitgroup
883
EOQ;
884
885
        return $query;
886
    }
887
888
    /**
889
     * Query for concepts using a search term.
890
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
891
     * @param boolean $unique restrict results to unique concepts (default: false)
892
     * @param ConceptSearchParameters $params 
893
     * @return string sparql query
894
     */
895
    protected function generateConceptSearchQuery($fields, $unique, $params) {
896
        $gcl = $this->graphClause;
897
        $formattedtype = $this->formatTypes($params->getTypeLimit());
898
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);
899
        $extravars = $formattedfields['extravars'];
900
        $extrafields = $formattedfields['extrafields'];
901
        $schemes = $params->getSchemeLimit();
902
903
        $schemecond = '';
904
        if (!empty($schemes)) {
905
            foreach($schemes as $scheme) {
906
                $schemecond .= "?s skos:inScheme <$scheme> . ";
907
            }
908
        }
909
910
        // extra conditions for parent and group, if specified
911
        $parentcond = ($params->getParentLimit()) ? "?s skos:broader+ <" . $params->getParentLimit() . "> ." : "";
912
        $groupcond = ($params->getGroupLimit()) ? "<" . $params->getGroupLimit() . "> skos:member ?s ." : "";
913
        $pgcond = $parentcond . $groupcond;
914
915
        $orderextra = $this->isDefaultEndpoint() ? $this->graph : '';
916
917
        # make VALUES clauses
918
        $props = array('skos:prefLabel', 'skos:altLabel');
919
        if ($params->getHidden()) {
920
            $props[] = 'skos:hiddenLabel';
921
        }
922
923
        $filterGraph = $this->formatFilterGraph($params->getVocabs());
924
925
        // remove futile asterisks from the search term
926
        $term = $params->getSearchTerm();
927
        while (strpos($term, '**') !== false) {
928
            $term = str_replace('**', '*', $term);
929
        }
930
        
931
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique, $filterGraph);
932
933
        $query = <<<EOQ
934
SELECT DISTINCT ?s ?label ?plabel ?alabel ?hlabel ?graph ?notation (GROUP_CONCAT(DISTINCT ?type) as ?types)
935
$extravars
936
WHERE {
937
 $gcl {
938
  {
939
$innerquery
940
  }
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
  $formattedtype
948
  { $pgcond 
949
   ?s a ?type .
950
   $extrafields $schemecond
951
  }
952
  FILTER NOT EXISTS { ?s owl:deprecated true }
953
 }
954
 $filterGraph
955
}
956
GROUP BY ?s ?match ?label ?plabel ?alabel ?hlabel ?notation ?graph
957
ORDER BY LCASE(STR(?match)) LANG(?match) $orderextra
958
EOQ;
959
        return $query;
960
    }
961
962
    /**
963
     * Transform a single concept search query results into the skosmos desired return format.
964
     * @param $row SPARQL query result row
965
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
966
     * @return array query result object
967
     */
968
    private function transformConceptSearchResult($row, $vocabs, $fields)
969
    {
970
        $hit = array();
971
        $hit['uri'] = $row->s->getUri();
972
973
        if (isset($row->graph)) {
974
            $hit['graph'] = $row->graph->getUri();
975
        }
976
977
        foreach (explode(" ", $row->types->getValue()) as $typeuri) {
978
            $hit['type'][] = $this->shortenUri($typeuri);
979
        }
980
981
        if(!empty($fields)) {
982
            foreach ($fields as $prop) {
983
                $propname = $prop . 's';
984
                if (isset($row->$propname)) {
985
                    foreach (explode("\n", $row->$propname->getValue()) as $line) {
986
                        $rdata = str_getcsv($line, ',', '"', '"');
987
                        $propvals = array();
988
                        if ($rdata[0] != '') {
989
                            $propvals['uri'] = $rdata[0];
990
                        }
991
                        if ($rdata[1] != '') {
992
                            $propvals['prefLabel'] = $rdata[1];
993
                        }
994
                        if ($rdata[2] != '') {
995
                            $propvals = $rdata[2];
996
                        }
997
998
                        $hit['skos:' . $prop][] = $propvals;
999
                    }
1000
                }
1001
            }
1002
        }
1003
1004
        
1005
        if (isset($row->preflabels)) {
1006
            foreach (explode("\n", $row->preflabels->getValue()) as $line) {
1007
                $pref = str_getcsv($line, ',', '"', '"');
1008
                $hit['prefLabels'][$pref[1]] = $pref[0];
1009
            }
1010
        }
1011
1012
        foreach ($vocabs as $vocab) { // looping the vocabulary objects and asking these for a localname for the concept.
1013
            $localname = $vocab->getLocalName($hit['uri']);
1014
            if ($localname !== $hit['uri']) { // only passing the result forward if the uri didn't boomerang right back.
1015
                $hit['localname'] = $localname;
1016
                break; // stopping the search when we find one that returns something valid.
1017
            }
1018
        }
1019
1020
        if (isset($row->label)) {
1021
            $hit['prefLabel'] = $row->label->getValue();
1022
        }
1023
1024
        if (isset($row->label)) {
1025
            $hit['lang'] = $row->label->getLang();
1026
        }
1027
1028
        if (isset($row->notation)) {
1029
            $hit['notation'] = $row->notation->getValue();
1030
        }
1031
1032
        if (isset($row->plabel)) {
1033
            $hit['matchedPrefLabel'] = $row->plabel->getValue();
1034
            $hit['lang'] = $row->plabel->getLang();
1035 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...
1036
            $hit['altLabel'] = $row->alabel->getValue();
1037
            $hit['lang'] = $row->alabel->getLang();
1038
        } elseif (isset($row->hlabel)) {
1039
            $hit['hiddenLabel'] = $row->hlabel->getValue();
1040
            $hit['lang'] = $row->hlabel->getLang();
1041
        }
1042
        return $hit;
1043
    }
1044
1045
    /**
1046
     * Transform the concept search query results into the skosmos desired return format.
1047
     * @param EasyRdf_Sparql_Result $results
1048
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1049
     * @return array query result object
1050
     */
1051
    private function transformConceptSearchResults($results, $vocabs, $fields) {
1052
        $ret = array();
1053
1054
        foreach ($results as $row) {
1055
            if (!isset($row->s)) {
1056
                // don't break if query returns a single dummy result
1057
                continue;
1058
            }
1059
            $ret[] = $this->transformConceptSearchResult($row, $vocabs, $fields);
1060
        }
1061
        return $ret;
1062
    }
1063
1064
    /**
1065
     * Query for concepts using a search term.
1066
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1067
     * @param array $fields extra fields to include in the result (array of strings). (default: null = none)
1068
     * @param boolean $unique restrict results to unique concepts (default: false)
1069
     * @param ConceptSearchParameters $params 
1070
     * @return array query result object
1071
     */
1072
    public function queryConcepts($vocabs, $fields = null, $unique = false, $params) {
1073
        $query = $this->generateConceptSearchQuery($fields, $unique, $params);
1074
        $results = $this->client->query($query);
1075
        return $this->transformConceptSearchResults($results, $vocabs, $fields);
1076
    }
1077
1078
    /**
1079
     * Generates sparql query clauses used for creating the alphabetical index.
1080
     * @param string $letter the letter (or special class) to search for
1081
     * @return array of sparql query clause strings
1082
     */
1083
    private function formatFilterConditions($letter) {
1084
        $useRegex = false;
1085
1086
        if ($letter == '*') {
1087
            $letter = '.*';
1088
            $useRegex = true;
1089
        } elseif ($letter == '0-9') {
1090
            $letter = '[0-9].*';
1091
            $useRegex = true;
1092
        } elseif ($letter == '!*') {
1093
            $letter = '[^\\\\p{L}\\\\p{N}].*';
1094
            $useRegex = true;
1095
        }
1096
1097
        # make text query clause
1098
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
1099
        if ($useRegex) {
1100
            $filtercondLabel = "regex(str(?label), '^$letter$', 'i')";
1101
            $filtercondALabel = "regex(str(?alabel), '^$letter$', 'i')";
1102
        } else {
1103
            $filtercondLabel = "strstarts(lcase(str(?label)), '$lcletter')";
1104
            $filtercondALabel = "strstarts(lcase(str(?alabel)), '$lcletter')";
1105
        }
1106
        return array('filterpref' => $filtercondLabel, 'filteralt' => $filtercondALabel);
1107
    }
1108
1109
    /**
1110
     * Generates the sparql query used for rendering the alphabetical index.
1111
     * @param string $letter the letter (or special class) to search for
1112
     * @param string $lang language of labels
1113
     * @param integer $limit limits the amount of results
1114
     * @param integer $offset offsets the result set
1115
     * @param array|null $classes
1116
     * @return string sparql query
1117
     */
1118
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes) {
1119
        $fcl = $this->generateFromClause();
1120
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1121
        $values = $this->formatValues('?type', $classes, 'uri');
1122
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
1123
        $conditions = $this->formatFilterConditions($letter);
1124
        $filtercondLabel = $conditions['filterpref'];
1125
        $filtercondALabel = $conditions['filteralt'];
1126
1127
        $query = <<<EOQ
1128
SELECT DISTINCT ?s ?label ?alabel $fcl
1129
WHERE {
1130
  {
1131
    ?s skos:prefLabel ?label .
1132
    FILTER (
1133
      $filtercondLabel
1134
      && langMatches(lang(?label), '$lang')
1135
    )
1136
  }
1137
  UNION
1138
  {
1139
    {
1140
      ?s skos:altLabel ?alabel .
1141
      FILTER (
1142
        $filtercondALabel
1143
        && langMatches(lang(?alabel), '$lang')
1144
      )
1145
    }
1146
    {
1147
      ?s skos:prefLabel ?label .
1148
      FILTER (langMatches(lang(?label), '$lang'))
1149
    }
1150
  }
1151
  ?s a ?type .
1152
  FILTER NOT EXISTS { ?s owl:deprecated true }
1153
  $values
1154
}
1155
ORDER BY LCASE(IF(BOUND(?alabel), STR(?alabel), STR(?label))) $limitandoffset
1156
EOQ;
1157
        return $query;
1158
    }
1159
1160
    /**
1161
     * Transforms the alphabetical list query results into an array format.
1162
     * @param EasyRdf_Sparql_Result $results
1163
     * @return array
1164
     */
1165
    private function transformAlphabeticalListResults($results) {
1166
        $ret = array();
1167
1168
        foreach ($results as $row) {
1169
            if (!isset($row->s)) {
1170
                continue;
1171
            }
1172
            // don't break if query returns a single dummy result
1173
1174
            $hit = array();
1175
            $hit['uri'] = $row->s->getUri();
1176
1177
            $hit['localname'] = $row->s->localName();
1178
1179
            $hit['prefLabel'] = $row->label->getValue();
1180
            $hit['lang'] = $row->label->getLang();
1181
1182 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...
1183
                $hit['altLabel'] = $row->alabel->getValue();
1184
                $hit['lang'] = $row->alabel->getLang();
1185
            }
1186
1187
            $ret[] = $hit;
1188
        }
1189
1190
        return $ret;
1191
    }
1192
1193
    /**
1194
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1195
     * '*!' (special characters) and '*' (everything) are accepted.
1196
     * @param string $letter the letter (or special class) to search for
1197
     * @param string $lang language of labels
1198
     * @param integer $limit limits the amount of results
1199
     * @param integer $offset offsets the result set
1200
     * @param array $classes
1201
     */
1202
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null) {
1203
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes);
1204
        $results = $this->client->query($query);
1205
        return $this->transformAlphabeticalListResults($results);
1206
    }
1207
1208
    /**
1209
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1210
     * @param string $lang language
1211
     * @return string sparql query
1212
     */
1213
    private function generateFirstCharactersQuery($lang, $classes) {
1214
        $fcl = $this->generateFromClause();
1215
        $classes = (sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1216
        $values = $this->formatValues('?type', $classes, 'uri');
1217
        $query = <<<EOQ
1218
SELECT DISTINCT (substr(ucase(str(?label)), 1, 1) as ?l) $fcl WHERE {
1219
  ?c skos:prefLabel ?label .
1220
  ?c a ?type
1221
  FILTER(langMatches(lang(?label), '$lang'))
1222
  $values
1223
}
1224
EOQ;
1225
        return $query;
1226
    }
1227
1228
    /**
1229
     * Transforms the first characters query results into an array format.
1230
     * @param EasyRdf_Sparql_Result $result
1231
     * @return array
1232
     */
1233
    private function transformFirstCharactersResults($result) {
1234
        $ret = array();
1235
        foreach ($result as $row) {
1236
            $ret[] = $row->l->getValue();
1237
        }
1238
        return $ret;
1239
    }
1240
1241
    /**
1242
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1243
     * @param string $lang language
1244
     * @return array array of characters
1245
     */
1246
    public function queryFirstCharacters($lang, $classes = null) {
1247
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1248
        $result = $this->client->query($query);
1249
        return $this->transformFirstCharactersResults($result);
1250
    }
1251
1252
    /**
1253
     * @param string $uri
1254
     * @param string $lang
1255
     * @return string sparql query string
1256
     */
1257
    private function generateLabelQuery($uri, $lang) {
1258
        $fcl = $this->generateFromClause();
1259
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1260
        $query = <<<EOQ
1261
SELECT ?label $fcl
1262
WHERE {
1263
  <$uri> a ?type .
1264
  OPTIONAL {
1265
    <$uri> skos:prefLabel ?label .
1266
    $labelcondLabel
1267
  }
1268
  OPTIONAL {
1269
    <$uri> rdfs:label ?label .
1270
    $labelcondLabel
1271
  }
1272
  OPTIONAL {
1273
    <$uri> dc:title ?label .
1274
    $labelcondLabel
1275
  }
1276
  OPTIONAL {
1277
    <$uri> dc11:title ?label .
1278
    $labelcondLabel
1279
  }
1280
}
1281
EOQ;
1282
        return $query;
1283
    }
1284
1285
    /**
1286
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1287
     * @param string $uri
1288
     * @param string $lang
1289
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1290
     */
1291
    public function queryLabel($uri, $lang) {
1292
        $query = $this->generateLabelQuery($uri, $lang);
1293
        $result = $this->client->query($query);
1294
        $ret = array();
1295
        foreach ($result as $row) {
1296
            if (!isset($row->label)) {
1297
                // existing concept but no labels
1298
                return array();
1299
            }
1300
            $ret[$row->label->getLang()] = $row->label;
1301
        }
1302
1303
        if (sizeof($ret) > 0) {
1304
            // existing concept, with label(s)
1305
            return $ret;
1306
        } else {
1307
            // nonexistent concept
1308
            return null;
1309
        }
1310
    }
1311
1312
    /**
1313
     * Generates a sparql query for queryProperty.
1314
     * @param string $uri
1315
     * @param string $prop the name of the property eg. 'skos:broader'.
1316
     * @param string $lang
1317
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1318
     * @return string sparql query
1319
     */
1320 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...
1321
        $fcl = $this->generateFromClause();
1322
        $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1323
1324
        $query = <<<EOQ
1325
SELECT * $fcl
1326
WHERE {
1327
  <$uri> a skos:Concept .
1328
  OPTIONAL {
1329
    <$uri> $prop ?object .
1330
    OPTIONAL {
1331
      ?object skos:prefLabel ?label .
1332
      FILTER (langMatches(lang(?label), "$lang"))
1333
    }
1334
    OPTIONAL {
1335
      ?object skos:prefLabel ?label .
1336
      FILTER (lang(?label) = "")
1337
    }
1338
    $anylang
1339
  }
1340
}
1341
EOQ;
1342
        return $query;
1343
    }
1344
1345
    /**
1346
     * Transforms the sparql query result into an array or null if the concept doesn't exist.
1347
     * @param EasyRdf_Sparql_Result $result
1348
     * @param string $lang
1349
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1350
     */
1351
    private function transformPropertyQueryResults($result, $lang) {
1352
        $ret = array();
1353
        foreach ($result as $row) {
1354
            if (!isset($row->object)) {
1355
                return array();
1356
            }
1357
            // existing concept but no properties
1358
            if (isset($row->label)) {
1359
                if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1360
                    $ret[$row->object->getUri()]['label'] = $row->label->getValue();
1361
                }
1362
1363
            } else {
1364
                $ret[$row->object->getUri()]['label'] = null;
1365
            }
1366
        }
1367
        if (sizeof($ret) > 0) {
1368
            return $ret;
1369
        }
1370
        // existing concept, with properties
1371
        else {
1372
            return null;
1373
        }
1374
        // nonexistent concept
1375
    }
1376
1377
    /**
1378
     * Query a single property of a concept.
1379
     * @param string $uri
1380
     * @param string $prop the name of the property eg. 'skos:broader'.
1381
     * @param string $lang
1382
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1383
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1384
     */
1385
    public function queryProperty($uri, $prop, $lang, $anylang = false) {
1386
        $uri = is_array($uri) ? $uri[0] : $uri;
1387
        $query = $this->generatePropertyQuery($uri, $prop, $lang, $anylang);
1388
        $result = $this->client->query($query);
1389
        return $this->transformPropertyQueryResults($result, $lang);
1390
    }
1391
1392
    /**
1393
     * Query a single transitive property of a concept.
1394
     * @param string $uri
1395
     * @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...
1396
     * @param string $lang
1397
     * @param integer $limit
1398
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1399
     * @return string sparql query
1400
     */
1401
    private function generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang) {
1402
        $uri = is_array($uri) ? $uri[0] : $uri;
1403
        $fcl = $this->generateFromClause();
1404
        $propertyClause = implode('|', $props);
1405
        $filter = $anylang ? "" : "FILTER (langMatches(lang(?label), \"$lang\"))";
1406
        // need to do a SPARQL subquery because LIMIT needs to be applied /after/
1407
        // the direct relationships have been collapsed into one string
1408
        $query = <<<EOQ
1409
SELECT * $fcl
1410
WHERE {
1411
  SELECT ?object ?label (GROUP_CONCAT(?dir) as ?direct)
1412
  WHERE {
1413
    <$uri> a skos:Concept .
1414
    OPTIONAL {
1415
      <$uri> $propertyClause* ?object .
1416
      OPTIONAL {
1417
        ?object $propertyClause ?dir .
1418
      }
1419
    }
1420
    OPTIONAL {
1421
      ?object skos:prefLabel ?label .
1422
      $filter
1423
    }
1424
  }
1425
  GROUP BY ?object ?label
1426
}
1427
LIMIT $limit
1428
EOQ;
1429
        return $query;
1430
    }
1431
1432
    /**
1433
     * Transforms the sparql query result object into an array.
1434
     * @param EasyRdf_Sparql_Result $result
1435
     * @param string $lang
1436
     * @param string $fallbacklang language to use if label is not available in the preferred language
1437
     * @return array of property values (key: URI, val: label), or null if concept doesn't exist
1438
     */
1439
    private function transformTransitivePropertyResults($result, $lang, $fallbacklang) {
1440
        $ret = array();
1441
        foreach ($result as $row) {
1442
            if (!isset($row->object)) {
1443
                return array();
1444
            }
1445
            // existing concept but no properties
1446
            if (isset($row->label)) {
1447
                $val = array('label' => $row->label->getValue());
1448
            } else {
1449
                $val = array('label' => null);
1450
            }
1451 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...
1452
                $val['direct'] = explode(' ', $row->direct->getValue());
1453
            }
1454
            // Preventing labels in a non preferred language overriding the preferred language.
1455
            if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1456
                if (!isset($row->label) || $row->label->getLang() === $lang) {
1457
                    $ret[$row->object->getUri()] = $val;
1458
                } elseif ($row->label->getLang() === $fallbacklang) {
1459
                    $val['label'] .= ' (' . $row->label->getLang() . ')';
1460
                    $ret[$row->object->getUri()] = $val;
1461
                }
1462
            }
1463
        }
1464
1465
        // second iteration of results to find labels for the ones that didn't have one in the preferred languages
1466
        foreach ($result as $row) {
1467
            if (isset($row->object) && array_key_exists($row->object->getUri(), $ret) === false) {
1468
                $val = array('label' => $row->label->getValue());
1469 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...
1470
                    $val['direct'] = explode(' ', $row->direct->getValue());
1471
                }
1472
                $ret[$row->object->getUri()] = $val;
1473
            }
1474
        }
1475
1476
        if (sizeof($ret) > 0) {
1477
            return $ret;
1478
        }
1479
        // existing concept, with properties
1480
        else {
1481
            return null;
1482
        }
1483
        // nonexistent concept
1484
    }
1485
1486
    /**
1487
     * Query a single transitive property of a concept.
1488
     * @param string $uri
1489
     * @param array $props the property/properties.
1490
     * @param string $lang
1491
     * @param string $fallbacklang language to use if label is not available in the preferred language
1492
     * @param integer $limit
1493
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1494
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1495
     */
1496
    public function queryTransitiveProperty($uri, $props, $lang, $limit, $anylang = false, $fallbacklang = '') {
1497
        $query = $this->generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang);
1498
        $result = $this->client->query($query);
1499
        return $this->transformTransitivePropertyResults($result, $lang, $fallbacklang);
1500
    }
1501
1502
    /**
1503
     * Generates the query for a concepts skos:narrowers.
1504
     * @param string $uri
1505
     * @param string $lang
1506
     * @param string $fallback
1507
     * @return string sparql query
1508
     */
1509
    private function generateChildQuery($uri, $lang, $fallback, $props) {
1510
        $uri = is_array($uri) ? $uri[0] : $uri;
1511
        $fcl = $this->generateFromClause();
1512
        $propertyClause = implode('|', $props);
1513
        $query = <<<EOQ
1514
SELECT ?child ?label ?child ?grandchildren ?notation $fcl WHERE {
1515
  <$uri> a skos:Concept .
1516
  OPTIONAL {
1517
    ?child $propertyClause <$uri> .
1518
    OPTIONAL {
1519
      ?child skos:prefLabel ?label .
1520
      FILTER (langMatches(lang(?label), "$lang"))
1521
    }
1522
    OPTIONAL {
1523
      ?child skos:prefLabel ?label .
1524
      FILTER (langMatches(lang(?label), "$fallback"))
1525
    }
1526
    OPTIONAL { # other language case
1527
      ?child skos:prefLabel ?label .
1528
    }
1529
    OPTIONAL {
1530
      ?child skos:notation ?notation .
1531
    }
1532
    BIND ( EXISTS { ?a $propertyClause ?child . } AS ?grandchildren )
1533
  }
1534
}
1535
EOQ;
1536
        return $query;
1537
    }
1538
1539
    /**
1540
     * Transforms the sparql result object into an array.
1541
     * @param EasyRdf_Sparql_Result $result
1542
     * @param string $lang
1543
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1544
     */
1545
    private function transformNarrowerResults($result, $lang) {
1546
        $ret = array();
1547
        foreach ($result as $row) {
1548
            if (!isset($row->child)) {
1549
                return array();
1550
            }
1551
            // existing concept but no children
1552
1553
            $label = null;
1554 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...
1555
                if ($row->label->getLang() == $lang) {
1556
                    $label = $row->label->getValue();
1557
                } else {
1558
                    $label = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1559
                }
1560
1561
            }
1562
            $childArray = array(
1563
                'uri' => $row->child->getUri(),
1564
                'prefLabel' => $label,
1565
                'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1566
            );
1567
            if (isset($row->notation)) {
1568
                $childArray['notation'] = $row->notation->getValue();
1569
            }
1570
1571
            $ret[] = $childArray;
1572
        }
1573
        if (sizeof($ret) > 0) {
1574
            return $ret;
1575
        }
1576
        // existing concept, with children
1577
        else {
1578
            return null;
1579
        }
1580
        // nonexistent concept
1581
    }
1582
1583
    /**
1584
     * Query the narrower concepts of a concept.
1585
     * @param string $uri
1586
     * @param string $lang
1587
     * @param string $fallback
1588
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1589
     */
1590
    public function queryChildren($uri, $lang, $fallback, $props) {
1591
        $query = $this->generateChildQuery($uri, $lang, $fallback, $props);
1592
        $result = $this->client->query($query);
1593
        return $this->transformNarrowerResults($result, $lang);
1594
    }
1595
1596
    /**
1597
     * Query the top concepts of a vocabulary.
1598
     * @param string $conceptSchemes concept schemes whose top concepts to query for
1599
     * @param string $lang language of labels
1600
     */
1601
    public function queryTopConcepts($conceptSchemes, $lang) {
1602
        if (!is_array($conceptSchemes)) {
1603
            $conceptSchemes = array($conceptSchemes);
1604
        }
1605
1606
        $values = $this->formatValues('?topuri', $conceptSchemes, 'uri');
1607
1608
        $fcl = $this->generateFromClause();
1609
        $query = <<<EOQ
1610
SELECT DISTINCT ?top ?topuri ?label ?notation ?children $fcl WHERE {
1611
  ?top skos:topConceptOf ?topuri .
1612
  ?top skos:prefLabel ?label .
1613
  FILTER (langMatches(lang(?label), "$lang"))
1614
  OPTIONAL { ?top skos:notation ?notation . }
1615
  BIND ( EXISTS { ?top skos:narrower ?a . } AS ?children )
1616
  $values
1617
}
1618
EOQ;
1619
        $result = $this->client->query($query);
1620
        $ret = array();
1621
        foreach ($result as $row) {
1622
            if (isset($row->top) && isset($row->label)) {
1623
                $top = array('uri' => $row->top->getUri(), 'topConceptOf' => $row->topuri->getUri(), 'label' => $row->label->getValue(), 'hasChildren' => filter_var($row->children->getValue(), FILTER_VALIDATE_BOOLEAN));
1624
                if (isset($row->notation)) {
1625
                    $top['notation'] = $row->notation->getValue();
1626
                }
1627
1628
                $ret[] = $top;
1629
            }
1630
        }
1631
1632
        return $ret;
1633
    }
1634
1635
    /**
1636
     * Generates a sparql query for finding the hierarchy for a concept.
1637
     * @param string $uri concept uri.
1638
     * @param string $lang
1639
     * @param string $fallback language to use if label is not available in the preferred language
1640
     * @return string sparql query
1641
     */
1642
    private function generateParentListQuery($uri, $lang, $fallback, $props) {
1643
        $fcl = $this->generateFromClause();
1644
        $propertyClause = implode('|', $props);
1645
        $query = <<<EOQ
1646
SELECT ?broad ?parent ?member ?children ?grandchildren
1647
(SAMPLE(?lab) as ?label) (SAMPLE(?childlab) as ?childlabel) (SAMPLE(?topcs) AS ?top) (SAMPLE(?nota) as ?notation) (SAMPLE(?childnota) as ?childnotation) $fcl
1648
WHERE {
1649
  <$uri> a skos:Concept .
1650
  OPTIONAL {
1651
    <$uri> $propertyClause* ?broad .
1652
    OPTIONAL {
1653
      ?broad skos:prefLabel ?lab .
1654
      FILTER (langMatches(lang(?lab), "$lang"))
1655
    }
1656
    OPTIONAL {
1657
      ?broad skos:prefLabel ?lab .
1658
      FILTER (langMatches(lang(?lab), "$fallback"))
1659
    }
1660
    OPTIONAL { # fallback - other language case
1661
      ?broad skos:prefLabel ?lab .
1662
    }
1663
    OPTIONAL { ?broad skos:notation ?nota . }
1664
    OPTIONAL { ?broad $propertyClause ?parent . }
1665
    OPTIONAL { ?broad skos:narrower ?children .
1666
      OPTIONAL {
1667
        ?children skos:prefLabel ?childlab .
1668
        FILTER (langMatches(lang(?childlab), "$lang"))
1669
      }
1670
      OPTIONAL {
1671
        ?children skos:prefLabel ?childlab .
1672
        FILTER (langMatches(lang(?childlab), "$fallback"))
1673
      }
1674
      OPTIONAL { # fallback - other language case
1675
        ?children skos:prefLabel ?childlab .
1676
      }
1677
      OPTIONAL {
1678
        ?children skos:notation ?childnota .
1679
      }
1680
    }
1681
    BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren )
1682
    OPTIONAL { ?broad skos:topConceptOf ?topcs . }
1683
  }
1684
}
1685
GROUP BY ?broad ?parent ?member ?children ?grandchildren
1686
EOQ;
1687
        return $query;
1688
    }
1689
1690
    /**
1691
     * Transforms the result into an array.
1692
     * @param EasyRdf_Sparql_Result
1693
     * @param string $lang
1694
     * @return an array for the REST controller to encode.
1695
     */
1696
    private function transformParentListResults($result, $lang)
1697
    {
1698
        $ret = array();
1699
        foreach ($result as $row) {
1700
            if (!isset($row->broad)) {
1701
                // existing concept but no broaders
1702
                return array();
1703
            }
1704
            $uri = $row->broad->getUri();
1705
            if (!isset($ret[$uri])) {
1706
                $ret[$uri] = array('uri' => $uri);
1707
            }
1708
            if (isset($row->exact)) {
1709
                $ret[$uri]['exact'] = $row->exact->getUri();
1710
            }
1711
            if (isset($row->top)) {
1712
                $ret[$uri]['top'] = $row->top->getUri();
1713
            }
1714
            if (isset($row->children)) {
1715
                if (!isset($ret[$uri]['narrower'])) {
1716
                    $ret[$uri]['narrower'] = array();
1717
                }
1718
1719
                $label = null;
1720 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...
1721
                    $label = $row->childlabel->getValue();
1722
                    if ($row->childlabel->getLang() !== $lang) {
1723
                        $label .= " (" . $row->childlabel->getLang() . ")";
1724
                    }
1725
1726
                }
1727
1728
                $childArr = array(
1729
                    'uri' => $row->children->getUri(),
1730
                    'label' => $label,
1731
                    'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1732
                );
1733
                if (isset($row->childnotation)) {
1734
                    $childArr['notation'] = $row->childnotation->getValue();
1735
                }
1736
1737
                if (!in_array($childArr, $ret[$uri]['narrower'])) {
1738
                    $ret[$uri]['narrower'][] = $childArr;
1739
                }
1740
1741
            }
1742
            if (isset($row->label)) {
1743
                $preflabel = $row->label->getValue();
1744
                if ($row->label->getLang() !== $lang && strpos($row->label->getLang(), $lang) !== 0) {
1745
                    $preflabel .= ' (' . $row->label->getLang() . ')';
1746
                }
1747
1748
                $ret[$uri]['prefLabel'] = $preflabel;
1749
            }
1750
            if (isset($row->notation)) {
1751
                $ret[$uri]['notation'] = $row->notation->getValue();
1752
            }
1753
1754
            if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) {
1755
                $ret[$uri]['broader'][] = $row->parent->getUri();
1756
            } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) {
1757
                $ret[$uri]['broader'][] = $row->parent->getUri();
1758
            }
1759
        }
1760
        if (sizeof($ret) > 0) {
1761
            // existing concept, with children
1762
            return $ret;
1763
        }
1764
        else {
1765
            // nonexistent concept
1766
            return null;
1767
        }
1768
    }
1769
1770
    /**
1771
     * Query for finding the hierarchy for a concept.
1772
     * @param string $uri concept uri.
1773
     * @param string $lang
1774
     * @param string $fallback language to use if label is not available in the preferred language
1775
     * @param array $props the hierarchy property/properties to use
1776
     * @return an array for the REST controller to encode.
1777
     */
1778
    public function queryParentList($uri, $lang, $fallback, $props) {
1779
        $query = $this->generateParentListQuery($uri, $lang, $fallback, $props);
1780
        $result = $this->client->query($query);
1781
        return $this->transformParentListResults($result, $lang);
1782
    }
1783
1784
    /**
1785
     * return a list of concept group instances, sorted by label
1786
     * @param string $groupClass URI of concept group class
1787
     * @param string $lang language of labels to return
1788
     * @return string sparql query
1789
     */
1790
    private function generateConceptGroupsQuery($groupClass, $lang) {
1791
        $fcl = $this->generateFromClause();
1792
        $query = <<<EOQ
1793
SELECT ?group (GROUP_CONCAT(DISTINCT STR(?child)) as ?children) ?label ?members ?notation $fcl
1794
WHERE {
1795
  ?group a <$groupClass> .
1796
  OPTIONAL { ?group skos:member|isothes:subGroup ?child .
1797
             ?child a <$groupClass> }
1798
  BIND(EXISTS{?group skos:member ?submembers} as ?members)
1799
  OPTIONAL { ?group skos:prefLabel ?label }
1800
  OPTIONAL { ?group rdfs:label ?label }
1801
  FILTER (langMatches(lang(?label), '$lang'))
1802
  OPTIONAL { ?group skos:notation ?notation }
1803
}
1804
GROUP BY ?group ?label ?members ?notation
1805
ORDER BY lcase(?label)
1806
EOQ;
1807
        return $query;
1808
    }
1809
1810
    /**
1811
     * Transforms the sparql query result into an array.
1812
     * @param EasyRdf_Sparql_Result $result
1813
     * @return array
1814
     */
1815
    private function transformConceptGroupsResults($result) {
1816
        $ret = array();
1817
        foreach ($result as $row) {
1818
            if (!isset($row->group)) {
1819
                # no groups found, see issue #357
1820
                continue;
1821
            }
1822
            $group = array('uri' => $row->group->getURI());
1823
            if (isset($row->label)) {
1824
                $group['prefLabel'] = $row->label->getValue();
1825
            }
1826
1827
            if (isset($row->children)) {
1828
                $group['childGroups'] = explode(' ', $row->children->getValue());
1829
            }
1830
1831
            if (isset($row->members)) {
1832
                $group['hasMembers'] = $row->members->getValue();
1833
            }
1834
1835
            if (isset($row->notation)) {
1836
                $group['notation'] = $row->notation->getValue();
1837
            }
1838
1839
            $ret[] = $group;
1840
        }
1841
        return $ret;
1842
    }
1843
1844
    /**
1845
     * return a list of concept group instances, sorted by label
1846
     * @param string $groupClass URI of concept group class
1847
     * @param string $lang language of labels to return
1848
     * @return array Result array with group URI as key and group label as value
1849
     */
1850
    public function listConceptGroups($groupClass, $lang) {
1851
        $query = $this->generateConceptGroupsQuery($groupClass, $lang);
1852
        $result = $this->client->query($query);
1853
        return $this->transformConceptGroupsResults($result);
1854
    }
1855
1856
    /**
1857
     * Generates the sparql query for listConceptGroupContents
1858
     * @param string $groupClass URI of concept group class
1859
     * @param string $group URI of the concept group instance
1860
     * @param string $lang language of labels to return
1861
     * @return string sparql query
1862
     */
1863
    private function generateConceptGroupContentsQuery($groupClass, $group, $lang) {
1864
        $fcl = $this->generateFromClause();
1865
        $query = <<<EOQ
1866
SELECT ?conc ?super ?label ?members ?type ?notation $fcl
1867
WHERE {
1868
 <$group> a <$groupClass> .
1869
 { <$group> skos:member ?conc . } UNION { ?conc isothes:superGroup <$group> }
1870
 FILTER NOT EXISTS { ?conc owl:deprecated true }
1871
 ?conc a ?type .
1872
 OPTIONAL { ?conc skos:prefLabel ?label .
1873
  FILTER (langMatches(lang(?label), '$lang'))
1874
 }
1875
 OPTIONAL { ?conc skos:prefLabel ?label . }
1876
 OPTIONAL { ?conc skos:notation ?notation }
1877
 BIND(EXISTS{?submembers isothes:superGroup ?conc} as ?super)
1878
 BIND(EXISTS{?conc skos:member ?submembers} as ?members)
1879
} ORDER BY lcase(?label)
1880
EOQ;
1881
        return $query;
1882
    }
1883
1884
    /**
1885
     * Transforms the sparql query result into an array.
1886
     * @param EasyRdf_Sparql_Result $result
1887
     * @param string $lang language of labels to return
1888
     * @return array
1889
     */
1890
    private function transformConceptGroupContentsResults($result, $lang) {
1891
        $ret = array();
1892
        $values = array();
1893
        foreach ($result as $row) {
1894
            if (!array_key_exists($row->conc->getURI(), $values)) {
1895
                $values[$row->conc->getURI()] = array(
1896
                    'uri' => $row->conc->getURI(),
1897
                    'isSuper' => $row->super->getValue(),
1898
                    'hasMembers' => $row->members->getValue(),
1899
                    'type' => array($row->type->shorten()),
1900
                );
1901
                if (isset($row->label)) {
1902
                    if ($row->label->getLang() == $lang) {
1903
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue();
1904
                    } else {
1905
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1906
                    }
1907
1908
                }
1909
                if (isset($row->notation)) {
1910
                    $values[$row->conc->getURI()]['notation'] = $row->notation->getValue();
1911
                }
1912
1913
            } else {
1914
                $values[$row->conc->getURI()]['type'][] = $row->type->shorten();
1915
            }
1916
        }
1917
1918
        foreach ($values as $val) {
1919
            $ret[] = $val;
1920
        }
1921
1922
        return $ret;
1923
    }
1924
1925
    /**
1926
     * return a list of concepts in a concept group
1927
     * @param string $groupClass URI of concept group class
1928
     * @param string $group URI of the concept group instance
1929
     * @param string $lang language of labels to return
1930
     * @return array Result array with concept URI as key and concept label as value
1931
     */
1932
    public function listConceptGroupContents($groupClass, $group, $lang) {
1933
        $query = $this->generateConceptGroupContentsQuery($groupClass, $group, $lang);
1934
        $result = $this->client->query($query);
1935
        return $this->transformConceptGroupContentsResults($result, $lang);
1936
    }
1937
1938
    /**
1939
     * Generates the sparql query for queryChangeList.
1940
     * @param string $lang language of labels to return.
1941
     * @param int $offset offset of results to retrieve; 0 for beginning of list
1942
     * @return string sparql query
1943
     */
1944 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...
1945
        $fcl = $this->generateFromClause();
1946
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
1947
1948
        $query = <<<EOQ
1949
SELECT DISTINCT ?concept ?date ?label $fcl
1950
WHERE {
1951
  ?concept a skos:Concept .
1952
  ?concept $prop ?date .
1953
  ?concept skos:prefLabel ?label .
1954
  FILTER (langMatches(lang(?label), '$lang'))
1955
}
1956
ORDER BY DESC(YEAR(?date)) DESC(MONTH(?date)) LCASE(?label)
1957
LIMIT 200 $offset
1958
EOQ;
1959
        return $query;
1960
    }
1961
1962
    /**
1963
     * Transforms the sparql query result into an array.
1964
     * @param EasyRdf_Sparql_Result $result
1965
     * @return array
1966
     */
1967 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...
1968
        $ret = array();
1969
        foreach ($result as $row) {
1970
            $concept = array('uri' => $row->concept->getURI());
1971
            if (isset($row->label)) {
1972
                $concept['prefLabel'] = $row->label->getValue();
1973
            }
1974
1975
            if (isset($row->date)) {
1976
                $concept['date'] = $row->date->getValue();
1977
            }
1978
1979
            $ret[] = $concept;
1980
        }
1981
        return $ret;
1982
    }
1983
1984
    /**
1985
     * return a list of recently changed or entirely new concepts
1986
     * @param string $lang language of labels to return
1987
     * @param int $offset offset of results to retrieve; 0 for beginning of list
1988
     * @return array Result array
1989
     */
1990
    public function queryChangeList($lang, $offset, $prop) {
1991
        $query = $this->generateChangeListQuery($lang, $offset, $prop);
1992
        $result = $this->client->query($query);
1993
        return $this->transformChangeListResults($result);
1994
    }
1995
}
1996