Completed
Push — master ( 8e7a23...4643ef )
by Henri
03:15
created

generateConceptSearchQueryCondition()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 29
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 29
rs 8.439
cc 5
eloc 22
nc 8
nop 2
1
<?php
2
/**
3
 * Copyright (c) 2012-2013 Aalto University and University of Helsinki
4
 * MIT License
5
 * see LICENSE.txt for more information
6
 */
7
8
/**
9
 * Generates SPARQL queries and provides access to the SPARQL endpoint.
10
 */
11
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...
12
    /**
13
     * A SPARQL Client eg. an EasyRDF instance.
14
     * @property EasyRdf_Sparql_Client $client
15
     */
16
    protected $client;
17
    /**
18
     * Graph uri.
19
     * @property string $graph
20
     */
21
    protected $graph;
22
    /**
23
     * A SPARQL query graph part template.
24
     * @property string $graph
25
     */
26
    protected $graphClause;
27
    /**
28
     * Model instance.
29
     * @property Model $model
30
     */
31
    protected $model;
32
33
    /**
34
     * Cache used to avoid expensive shorten() calls
35
     * @property array $qnamecache
36
     */
37
    private $qnamecache = array();
38
39
    /**
40
     * Requires the following three parameters.
41
     * @param string $endpoint SPARQL endpoint address.
42
     * @param object $graph an EasyRDF SPARQL graph instance.
43
     * @param object $model a Model instance.
44
     */
45
    public function __construct($endpoint, $graph, $model) {
46
        // if special cache control (typically no-cache) was requested by the
47
        // client, set the same type of cache control headers also in subsequent
48
        // in the SPARQL requests (this is useful for performance testing)
49
        // @codeCoverageIgnoreStart
50
        $cacheControl = filter_input(INPUT_SERVER, 'HTTP_CACHE_CONTROL', FILTER_SANITIZE_STRING);
51
        $pragma = filter_input(INPUT_SERVER, 'HTTP_PRAGMA', FILTER_SANITIZE_STRING);
52
        if ($cacheControl !== null || $pragma !== null) {
53
            $val = $pragma !== null ? $pragma : $cacheControl;
54
            // configure the HTTP client used by EasyRdf_Sparql_Client
55
            $httpclient = EasyRdf_Http::getDefaultHttpClient();
56
            $httpclient->setHeaders('Cache-Control', $val);
57
            EasyRdf_Http::setDefaultHttpClient($httpclient); // actually redundant..
58
        }
59
        // @codeCoverageIgnoreEnd
60
61
        // create the EasyRDF SPARQL client instance to use
62
        $this->client = new EasyRdf_Sparql_Client($endpoint);
63
        $this->graph = $graph;
64
        $this->model = $model;
65
66
        // set graphClause so that it can be used by all queries
67
        if ($this->isDefaultEndpoint()) // default endpoint; query any graph (and catch it in a variable)
68
        {
69
            $this->graphClause = "GRAPH $graph";
70
        } elseif ($graph) // query a specific graph
71
        {
72
            $this->graphClause = "GRAPH <$graph>";
73
        } else // query the default graph
74
        {
75
            $this->graphClause = "";
76
        }
77
78
    }
79
80
    /**
81
     * Return true if this is the default SPARQL endpoint, used as the facade to query
82
     * all vocabularies.
83
     */
84
85
    protected function isDefaultEndpoint() {
86
        return $this->graph[0] == '?';
87
    }
88
89
    /**
90
     * Returns the graph instance
91
     * @return object EasyRDF graph instance.
92
     */
93
    public function getGraph() {
94
        return $this->graph;
95
    }
96
    
97
    /**
98
     * Shorten a URI
99
     * @param string $uri URI to shorten
100
     * @return string shortened URI, or original URI if it cannot be shortened
101
     */
102
    private function shortenUri($uri) {
103
        if (!array_key_exists($uri, $this->qnamecache)) {
104
            $res = new EasyRdf_Resource($uri);
105
            $qname = $res->shorten(); // returns null on failure
106
            $this->qnamecache[$uri] = ($qname !== null) ? $qname : $uri;
107
        }
108
        return $this->qnamecache[$uri];
109
    }
110
111
112
    /**
113
     * Generates the sparql query for retrieving concept and collection counts in a vocabulary.
114
     * @return string sparql query
115
     */
116
    private function generateCountConceptsQuery($array, $group) {
117
        $gcl = $this->graphClause;
118
        $optional = $array ? "UNION { ?type rdfs:subClassOf* <$array> }" : '';
119
        $optional .= $group ? "UNION { ?type rdfs:subClassOf* <$group> }" : '';
120
        $query = <<<EOQ
121
      SELECT (COUNT(?conc) as ?c) ?type ?typelabel WHERE {
122
        $gcl {
123
          { ?conc a ?type .
124
          { ?type rdfs:subClassOf* skos:Concept . } UNION { ?type rdfs:subClassOf* skos:Collection . } $optional }
125
          OPTIONAL { ?type rdfs:label ?typelabel . }
126
        }
127
      }
128
GROUP BY ?type ?typelabel
129
EOQ;
130
        return $query;
131
    }
132
133
    /**
134
     * Used for transforming the concept count query results.
135
     * @param EasyRdf_Sparql_Result $result query results to be transformed
136
     * @param string $lang language of labels
137
     * @return Array containing the label counts
138
     */
139
    private function transformCountConceptsResults($result, $lang) {
140
        $ret = array();
141
        foreach ($result as $row) {
142
            if (!isset($row->type)) {
143
                continue;
144
            }
145
            $ret[$row->type->getUri()]['type'] = $row->type->getUri();
146
            $ret[$row->type->getUri()]['count'] = $row->c->getValue();
147
            if (isset($row->typelabel) && $row->typelabel->getLang() === $lang) {
148
                $ret[$row->type->getUri()]['label'] = $row->typelabel->getValue();
149
            }
150
151
        }
152
        return $ret;
153
    }
154
155
    /**
156
     * Used for counting number of concepts and collections in a vocabulary.
157
     * @param string $lang language of labels
158
     * @return int number of concepts in this vocabulary
159
     */
160
    public function countConcepts($lang = null, $array = null, $group = null) {
161
        $query = $this->generateCountConceptsQuery($array, $group);
162
        $result = $this->client->query($query);
163
        return $this->transformCountConceptsResults($result, $lang);
164
    }
165
166
    /**
167
     * @param array $langs Languages to query for
168
     * @param string[] $props property names
169
     * @return string sparql query
170
     */
171
    private function generateCountLangConceptsQuery($langs, $classes, $props) {
172
        $gcl = $this->graphClause;
173
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
174
175
        $values = $this->formatValues('?type', $classes, 'uri');
176
        $valuesLang = $this->formatValues('?lang', $langs, 'literal');
177
        $valuesProp = $this->formatValues('?prop', $props, null);
178
179
        $query = <<<EOQ
180
SELECT ?lang ?prop
181
  (COUNT(?label) as ?count)
182
WHERE {
183
  $gcl {
184
    ?conc a ?type .
185
    ?conc ?prop ?label .
186
    FILTER (langMatches(lang(?label), ?lang))
187
    $valuesLang
188
    $valuesProp
189
  }
190
  $values
191
}
192
GROUP BY ?lang ?prop ?type
193
EOQ;
194
        return $query;
195
    }
196
197
    /**
198
     * Transforms the CountLangConcepts results into an array of label counts.
199
     * @param EasyRdf_Sparql_Result $result query results to be transformed
200
     * @param array $langs Languages to query for
201
     * @param string[] $props property names
202
     */
203
    private function transformCountLangConceptsResults($result, $langs, $props) {
204
        $ret = array();
205
        // set default count to zero; overridden below if query found labels
206
        foreach ($langs as $lang) {
207
            foreach ($props as $prop) {
208
                $ret[$lang][$prop] = 0;
209
            }
210
        }
211
        foreach ($result as $row) {
212
            if (isset($row->lang) && isset($row->prop) && isset($row->count)) {
213
                $ret[$row->lang->getValue()][$row->prop->shorten()] += 
214
                $row->count->getValue();
215
            }
216
217
        }
218
        ksort($ret);
219
        return $ret;
220
    }
221
222
    /**
223
     * Counts the number of concepts in a easyRDF graph with a specific language.
224
     * @param array $langs Languages to query for
225
     * @return Array containing count of concepts for each language and property.
226
     */
227
    public function countLangConcepts($langs, $classes = null) {
228
        $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel');
229
        $query = $this->generateCountLangConceptsQuery($langs, $classes, $props);
230
        // Count the number of terms in each language
231
        $result = $this->client->query($query);
232
        return $this->transformCountLangConceptsResults($result, $langs, $props);
233
    }
234
235
    /**
236
     * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one
237
     * of the constants given.
238
     * @param string $varname variable name, e.g. "?uri"
239
     * @param array $values the values
240
     * @param string $type type of values: "uri", "literal" or null (determines quoting style)
241
     */
242
    protected function formatValues($varname, $values, $type = null) {
243
        $constants = array();
244
        foreach ($values as $val) {
245
            if ($type == 'uri') {
246
                $val = "<$val>";
247
            }
248
249
            if ($type == 'literal') {
250
                $val = "'$val'";
251
            }
252
253
            $constants[] = "($val)";
254
        }
255
        $values = implode(" ", $constants);
256
257
        return "VALUES ($varname) { $values }";
258
    }
259
260
    /**
261
     * Filters multiple instances of the same vocabulary from the input array.
262
     * @param \Vocabulary[] $vocabs array of Vocabulary objects
263
     * @return \Vocabulary[]
264
     */
265
    private function filterDuplicateVocabs($vocabs) {
266
        // filtering duplicates
267
        $uniqueVocabs = array();
268
        if (sizeof($vocabs) > 0) {
269
            foreach ($vocabs as $voc) {
270
                $uniqueVocabs[$voc->getId()] = $voc;
271
            }
272
        }
273
274
        return $uniqueVocabs;
275
    }
276
277
    /**
278
     * Generates a sparql query for one or more concept URIs
279
     * @param mixed $uris concept URI (string) or array of URIs
280
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
281
     * @param \Vocabulary[] $vocabs array of Vocabulary objects
282
     * @return string sparql query
283
     */
284
    private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) {
285
        $gcl = $this->graphClause;
286
        $values = $this->formatValues('?uri', $uris, 'uri');
287
        $uniqueVocabs = $this->filterDuplicateVocabs($vocabs);
288
        $valuesGraph = $this->formatValuesGraph($uniqueVocabs);
289
290
        if ($arrayClass === null) {
291
            $construct = $optional = "";
292
        } else {
293
            // add information that can be used to format narrower concepts by
294
            // the array they belong to ("milk by source animal" use case)
295
            $construct = "\n ?x skos:member ?o . ?x skos:prefLabel ?xl . ?x a <$arrayClass> .";
296
            $optional = "\n OPTIONAL {
297
                      ?x skos:member ?o .
298
                      ?x a <$arrayClass> .
299
                      ?x skos:prefLabel ?xl .
300
                    }";
301
        }
302
        $query = <<<EOQ
303
CONSTRUCT {
304
 ?s ?p ?uri .
305
 ?sp ?uri ?op .
306
 ?uri ?p ?o .
307
 ?p rdfs:label ?proplabel .
308
 ?p rdfs:subPropertyOf ?pp .
309
 ?pp rdfs:label ?plabel .
310
 ?o a ?ot .
311
 ?o skos:prefLabel ?opl .
312
 ?o rdfs:label ?ol .
313
 ?o rdf:value ?ov .
314
 ?o skos:notation ?on .
315
 ?directgroup skos:member ?uri .
316
 ?parent skos:member ?group .
317
 ?group skos:prefLabel ?grouplabel .
318
 ?b1 rdf:first ?item .
319
 ?b1 rdf:rest ?b2 .
320
 ?item a ?it .
321
 ?item skos:prefLabel ?il .
322
 ?group a ?grouptype . $construct
323
} WHERE {
324
 $gcl {
325
  {
326
    ?s ?p ?uri .
327
    FILTER(!isBlank(?s))
328
  }
329
  UNION
330
  { ?sp ?uri ?op . }
331
  UNION
332
  {
333
    ?directgroup skos:member ?uri .
334
    ?group skos:member+ ?uri .
335
    ?group skos:prefLabel ?grouplabel .
336
    ?group a ?grouptype .
337
    OPTIONAL { ?parent skos:member ?group }
338
  }
339
  UNION
340
  {
341
   ?uri ?p ?o .
342
   OPTIONAL {
343
     ?o rdf:rest* ?b1 .
344
     ?b1 rdf:first ?item .
345
     ?b1 rdf:rest ?b2 .
346
     OPTIONAL { ?item a ?it . }
347
     OPTIONAL { ?item skos:prefLabel ?il . }
348
   }
349
   OPTIONAL {
350
     { ?p rdfs:label ?proplabel . }
351
     UNION
352
     { ?p rdfs:subPropertyOf ?pp . }
353
     UNION
354
     { ?o a ?ot . }
355
     UNION
356
     { ?o skos:prefLabel ?opl . }
357
     UNION
358
     { ?o rdfs:label ?ol . }
359
     UNION
360
     { ?o rdf:value ?ov . }
361
     UNION
362
     { ?o skos:notation ?on . }
363
   } $optional
364
  }
365
 }
366
 $values
367
}
368
$valuesGraph
369
EOQ;
370
        return $query;
371
    }
372
373
    /**
374
     * Transforms ConceptInfo query results into an array of Concept objects
375
     * @param EasyRdf_Graph $result query results to be transformed
376
     * @param mixed $uris concept URI (string) or array of URIs
377
     * @param \Vocabulary[] $vocabs array of Vocabulary object
378
     * @param string|null $clang content language
379
     * @return mixed query result graph (EasyRdf_Graph), or array of Concept objects
380
     */
381
    private function transformConceptInfoResults($result, $uris, $vocabs, $clang) {
382
        $conceptArray = array();
383
        foreach ($uris as $index => $uri) {
384
            $conc = $result->resource($uri);
385
            $vocab = sizeof($vocabs) == 1 ? $vocabs[0] : $vocabs[$index];
386
            $conceptArray[] = new Concept($this->model, $vocab, $conc, $result, $clang);
387
        }
388
        return $conceptArray;
389
    }
390
391
    /**
392
     * Returns information (as a graph) for one or more concept URIs
393
     * @param mixed $uris concept URI (string) or array of URIs
394
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
395
     * @param \Vocabulary[]|null $vocabs array of Vocabulary object
396
     * @param boolean $asGraph whether to return a graph (true) or array of Concepts (false)
397
     * @param string|null $clang content language
398
     * @return mixed query result graph (EasyRdf_Graph), or array of Concept objects
399
     */
400
    public function queryConceptInfo($uris, $arrayClass = null, $vocabs = array(), $asGraph = false, $clang = null) {
401
        // if just a single URI is given, put it in an array regardless
402
        if (!is_array($uris)) {
403
            $uris = array($uris);
404
        }
405
406
        $query = $this->generateConceptInfoQuery($uris, $arrayClass, $vocabs);
0 ignored issues
show
Bug introduced by
It seems like $vocabs defined by parameter $vocabs on line 400 can also be of type null; however, GenericSparql::generateConceptInfoQuery() does only seem to accept array<integer,object<Vocabulary>>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
407
        $result = $this->client->query($query);
408
        if ($asGraph) {
409
            return $result;
410
        }
411
412
        if ($result->isEmpty()) {
413
            return;
414
        }
415
416
        return $this->transformConceptInfoResults($result, $uris, $vocabs, $clang);
0 ignored issues
show
Bug introduced by
It seems like $vocabs defined by parameter $vocabs on line 400 can also be of type null; however, GenericSparql::transformConceptInfoResults() does only seem to accept array<integer,object<Vocabulary>>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

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

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1012
        $results = $this->client->query($query);
1013
        return $this->transformConceptSearchResults($results, $vocabs, $fields);
1014
    }
1015
1016
    /**
1017
     * Generates sparql query clauses used for creating the alphabetical index.
1018
     * @param string $letter the letter (or special class) to search for
1019
     * @return array of sparql query clause strings
1020
     */
1021
    private function formatFilterConditions($letter) {
1022
        $useRegex = false;
1023
1024
        if ($letter == '*') {
1025
            $letter = '.*';
1026
            $useRegex = true;
1027
        } elseif ($letter == '0-9') {
1028
            $letter = '[0-9].*';
1029
            $useRegex = true;
1030
        } elseif ($letter == '!*') {
1031
            $letter = '[^\\\\p{L}\\\\p{N}].*';
1032
            $useRegex = true;
1033
        }
1034
1035
        # make text query clause
1036
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
1037
        if ($useRegex) {
1038
            $filtercondLabel = "regex(str(?label), '^$letter$', 'i')";
1039
            $filtercondALabel = "regex(str(?alabel), '^$letter$', 'i')";
1040
        } else {
1041
            $filtercondLabel = "strstarts(lcase(str(?label)), '$lcletter')";
1042
            $filtercondALabel = "strstarts(lcase(str(?alabel)), '$lcletter')";
1043
        }
1044
        return array('filterpref' => $filtercondLabel, 'filteralt' => $filtercondALabel);
1045
    }
1046
1047
    /**
1048
     * Generates the sparql query used for rendering the alphabetical index.
1049
     * @param string $letter the letter (or special class) to search for
1050
     * @param string $lang language of labels
1051
     * @param integer $limit limits the amount of results
1052
     * @param integer $offset offsets the result set
1053
     * @param array $classes
1054
     * @return string sparql query
1055
     */
1056
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes) {
1057
        $gcl = $this->graphClause;
1058
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1059
        $values = $this->formatValues('?type', $classes, 'uri');
1060
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
1061
        $conditions = $this->formatFilterConditions($letter);
1062
        $filtercondLabel = $conditions['filterpref'];
1063
        $filtercondALabel = $conditions['filteralt'];
1064
1065
        $query = <<<EOQ
1066
SELECT DISTINCT ?s ?label ?alabel
1067
WHERE {
1068
  $gcl {
1069
    {
1070
      ?s skos:prefLabel ?label .
1071
      FILTER (
1072
        $filtercondLabel
1073
        && langMatches(lang(?label), '$lang')
1074
      )
1075
    }
1076
    UNION
1077
    {
1078
      {
1079
        ?s skos:altLabel ?alabel .
1080
        FILTER (
1081
          $filtercondALabel
1082
          && langMatches(lang(?alabel), '$lang')
1083
        )
1084
      }
1085
      {
1086
        ?s skos:prefLabel ?label .
1087
        FILTER (langMatches(lang(?label), '$lang'))
1088
      }
1089
    }
1090
    ?s a ?type .
1091
    FILTER NOT EXISTS { ?s owl:deprecated true }
1092
  } $values
1093
}
1094
ORDER BY LCASE(IF(BOUND(?alabel), STR(?alabel), STR(?label))) $limitandoffset
1095
EOQ;
1096
        return $query;
1097
    }
1098
1099
    /**
1100
     * Transforms the alphabetical list query results into an array format.
1101
     * @param EasyRdf_Sparql_Result $results
1102
     * @return array
1103
     */
1104
    private function transformAlphabeticalListResults($results) {
1105
        $ret = array();
1106
1107
        foreach ($results as $row) {
1108
            if (!isset($row->s)) {
1109
                continue;
1110
            }
1111
            // don't break if query returns a single dummy result
1112
1113
            $hit = array();
1114
            $hit['uri'] = $row->s->getUri();
1115
1116
            $hit['localname'] = $row->s->localName();
1117
1118
            $hit['prefLabel'] = $row->label->getValue();
1119
            $hit['lang'] = $row->label->getLang();
1120
1121 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...
1122
                $hit['altLabel'] = $row->alabel->getValue();
1123
                $hit['lang'] = $row->alabel->getLang();
1124
            }
1125
1126
            $ret[] = $hit;
1127
        }
1128
1129
        return $ret;
1130
    }
1131
1132
    /**
1133
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1134
     * '*!' (special characters) and '*' (everything) are accepted.
1135
     * @param string $letter the letter (or special class) to search for
1136
     * @param string $lang language of labels
1137
     * @param integer $limit limits the amount of results
1138
     * @param integer $offset offsets the result set
1139
     * @param array $classes
1140
     */
1141
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null) {
1142
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes);
0 ignored issues
show
Bug introduced by
It seems like $classes defined by parameter $classes on line 1141 can also be of type null; however, GenericSparql::generateAlphabeticalListQuery() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1143
        $results = $this->client->query($query);
1144
        return $this->transformAlphabeticalListResults($results);
1145
    }
1146
1147
    /**
1148
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1149
     * @param string $lang language
1150
     * @return string sparql query
1151
     */
1152
    private function generateFirstCharactersQuery($lang, $classes) {
1153
        $gcl = $this->graphClause;
1154
        $classes = (sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1155
        $values = $this->formatValues('?type', $classes, 'uri');
1156
        $query = <<<EOQ
1157
SELECT DISTINCT (substr(ucase(str(?label)), 1, 1) as ?l) WHERE {
1158
  $gcl {
1159
    ?c skos:prefLabel ?label .
1160
    ?c a ?type
1161
    FILTER(langMatches(lang(?label), '$lang'))
1162
  }
1163
  $values
1164
}
1165
EOQ;
1166
        return $query;
1167
    }
1168
1169
    /**
1170
     * Transforms the first characters query results into an array format.
1171
     * @param EasyRdf_Sparql_Result $result
1172
     * @return array
1173
     */
1174
    private function transformFirstCharactersResults($result) {
1175
        $ret = array();
1176
        foreach ($result as $row) {
1177
            $ret[] = $row->l->getValue();
1178
        }
1179
        return $ret;
1180
    }
1181
1182
    /**
1183
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1184
     * @param string $lang language
1185
     * @return array array of characters
1186
     */
1187
    public function queryFirstCharacters($lang, $classes = null) {
1188
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1189
        $result = $this->client->query($query);
1190
        return $this->transformFirstCharactersResults($result);
1191
    }
1192
1193
    /**
1194
     * @param string $uri
1195
     * @param string $lang
1196
     * @return string sparql query string
1197
     */
1198
    private function generateLabelQuery($uri, $lang) {
1199
        $gcl = $this->graphClause;
1200
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1201
        $query = <<<EOQ
1202
SELECT ?label
1203
WHERE {
1204
  $gcl {
1205
    <$uri> a ?type .
1206
    OPTIONAL {
1207
      <$uri> skos:prefLabel ?label .
1208
      $labelcondLabel
1209
    }
1210
    OPTIONAL {
1211
      <$uri> rdfs:label ?label .
1212
      $labelcondLabel
1213
    }
1214
    OPTIONAL {
1215
      <$uri> dc:title ?label .
1216
      $labelcondLabel
1217
    }
1218
    OPTIONAL {
1219
      <$uri> dc11:title ?label .
1220
      $labelcondLabel
1221
    }
1222
  }
1223
}
1224
EOQ;
1225
        return $query;
1226
    }
1227
1228
    /**
1229
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1230
     * @param string $uri
1231
     * @param string $lang
1232
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1233
     */
1234
    public function queryLabel($uri, $lang) {
1235
        $query = $this->generateLabelQuery($uri, $lang);
1236
        $result = $this->client->query($query);
1237
        $ret = array();
1238
        foreach ($result as $row) {
1239
            if (!isset($row->label)) {
1240
                return array();
1241
            }
1242
            // existing concept but no labels
1243
            $ret[$row->label->getLang()] = $row->label;
1244
        }
1245
1246
        if (sizeof($ret) > 0) {
1247
            return $ret;
1248
        }
1249
        // existing concept, with label(s)
1250
        else {
1251
            return null;
1252
        }
1253
        // nonexistent concept
1254
    }
1255
1256
    /**
1257
     * Generates a sparql query for queryProperty.
1258
     * @param string $uri
1259
     * @param string $prop the name of the property eg. 'skos:broader'.
1260
     * @param string $lang
1261
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1262
     * @return string sparql query
1263
     */
1264 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...
1265
        $gcl = $this->graphClause;
1266
        $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1267
1268
        $query = <<<EOQ
1269
SELECT *
1270
WHERE {
1271
  $gcl {
1272
    <$uri> a skos:Concept .
1273
    OPTIONAL {
1274
      <$uri> $prop ?object .
1275
      OPTIONAL {
1276
        ?object skos:prefLabel ?label .
1277
        FILTER (langMatches(lang(?label), "$lang"))
1278
      }
1279
      OPTIONAL {
1280
        ?object skos:prefLabel ?label .
1281
        FILTER (lang(?label) = "")
1282
      }
1283
      $anylang
1284
    }
1285
  }
1286
}
1287
EOQ;
1288
        return $query;
1289
    }
1290
1291
    /**
1292
     * Transforms the sparql query result into an array or null if the concept doesn't exist.
1293
     * @param EasyRdf_Sparql_Result $result
1294
     * @param string $lang
1295
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1296
     */
1297
    private function transformPropertyQueryResults($result, $lang) {
1298
        $ret = array();
1299
        foreach ($result as $row) {
1300
            if (!isset($row->object)) {
1301
                return array();
1302
            }
1303
            // existing concept but no properties
1304
            if (isset($row->label)) {
1305
                if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1306
                    $ret[$row->object->getUri()]['label'] = $row->label->getValue();
1307
                }
1308
1309
            } else {
1310
                $ret[$row->object->getUri()]['label'] = null;
1311
            }
1312
        }
1313
        if (sizeof($ret) > 0) {
1314
            return $ret;
1315
        }
1316
        // existing concept, with properties
1317
        else {
1318
            return null;
1319
        }
1320
        // nonexistent concept
1321
    }
1322
1323
    /**
1324
     * Query a single property of a concept.
1325
     * @param string $uri
1326
     * @param string $prop the name of the property eg. 'skos:broader'.
1327
     * @param string $lang
1328
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1329
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1330
     */
1331
    public function queryProperty($uri, $prop, $lang, $anylang = false) {
1332
        $uri = is_array($uri) ? $uri[0] : $uri;
1333
        $query = $this->generatePropertyQuery($uri, $prop, $lang, $anylang);
1334
        $result = $this->client->query($query);
1335
        return $this->transformPropertyQueryResults($result, $lang);
1336
    }
1337
1338
    /**
1339
     * Query a single transitive property of a concept.
1340
     * @param string $uri
1341
     * @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...
1342
     * @param string $lang
1343
     * @param integer $limit
1344
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1345
     * @return string sparql query
1346
     */
1347
    private function generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang) {
1348
        $uri = is_array($uri) ? $uri[0] : $uri;
1349
        $gcl = $this->graphClause;
1350
        $propertyClause = implode('|', $props);
1351
        $filter = $anylang ? "" : "FILTER (langMatches(lang(?label), \"$lang\"))";
1352
        // need to do a SPARQL subquery because LIMIT needs to be applied /after/
1353
        // the direct relationships have been collapsed into one string
1354
        $query = <<<EOQ
1355
SELECT *
1356
WHERE {
1357
  SELECT ?object ?label (GROUP_CONCAT(?dir) as ?direct)
1358
  WHERE {
1359
    $gcl {
1360
      <$uri> a skos:Concept .
1361
      OPTIONAL {
1362
        <$uri> $propertyClause* ?object .
1363
        OPTIONAL {
1364
          ?object $propertyClause ?dir .
1365
        }
1366
      }
1367
      OPTIONAL {
1368
        ?object skos:prefLabel ?label .
1369
        $filter
1370
      }
1371
    }
1372
  }
1373
  GROUP BY ?object ?label
1374
}
1375
LIMIT $limit
1376
EOQ;
1377
        return $query;
1378
    }
1379
1380
    /**
1381
     * Transforms the sparql query result object into an array.
1382
     * @param EasyRdf_Sparql_Result $result
1383
     * @param string $lang
1384
     * @param string $fallbacklang language to use if label is not available in the preferred language
1385
     * @return array of property values (key: URI, val: label), or null if concept doesn't exist
1386
     */
1387
    private function transformTransitivePropertyResults($result, $lang, $fallbacklang) {
1388
        $ret = array();
1389
        foreach ($result as $row) {
1390
            if (!isset($row->object)) {
1391
                return array();
1392
            }
1393
            // existing concept but no properties
1394
            if (isset($row->label)) {
1395
                $val = array('label' => $row->label->getValue());
1396
            } else {
1397
                $val = array('label' => null);
1398
            }
1399 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...
1400
                $val['direct'] = explode(' ', $row->direct->getValue());
1401
            }
1402
            // Preventing labels in a non preferred language overriding the preferred language.
1403
            if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1404
                if (!isset($row->label) || $row->label->getLang() === $lang) {
1405
                    $ret[$row->object->getUri()] = $val;
1406
                } elseif ($row->label->getLang() === $fallbacklang) {
1407
                    $val['label'] .= ' (' . $row->label->getLang() . ')';
1408
                    $ret[$row->object->getUri()] = $val;
1409
                }
1410
            }
1411
        }
1412
1413
        // second iteration of results to find labels for the ones that didn't have one in the preferred languages
1414
        foreach ($result as $row) {
1415
            if (isset($row->object) && array_key_exists($row->object->getUri(), $ret) === false) {
1416
                $val = array('label' => $row->label->getValue());
1417 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...
1418
                    $val['direct'] = explode(' ', $row->direct->getValue());
1419
                }
1420
                $ret[$row->object->getUri()] = $val;
1421
            }
1422
        }
1423
1424
        if (sizeof($ret) > 0) {
1425
            return $ret;
1426
        }
1427
        // existing concept, with properties
1428
        else {
1429
            return null;
1430
        }
1431
        // nonexistent concept
1432
    }
1433
1434
    /**
1435
     * Query a single transitive property of a concept.
1436
     * @param string $uri
1437
     * @param array $props the property/properties.
1438
     * @param string $lang
1439
     * @param string $fallbacklang language to use if label is not available in the preferred language
1440
     * @param integer $limit
1441
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1442
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1443
     */
1444
    public function queryTransitiveProperty($uri, $props, $lang, $limit, $anylang = false, $fallbacklang = '') {
1445
        $query = $this->generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang);
1446
        $result = $this->client->query($query);
1447
        return $this->transformTransitivePropertyResults($result, $lang, $fallbacklang);
1448
    }
1449
1450
    /**
1451
     * Generates the query for a concepts skos:narrowers.
1452
     * @param string $uri
1453
     * @param string $lang
1454
     * @param string $fallback
1455
     * @return string sparql query
1456
     */
1457
    private function generateChildQuery($uri, $lang, $fallback, $props) {
1458
        $uri = is_array($uri) ? $uri[0] : $uri;
1459
        $gcl = $this->graphClause;
1460
        $propertyClause = implode('|', $props);
1461
        $query = <<<EOQ
1462
SELECT ?child ?label ?child ?grandchildren ?notation WHERE {
1463
  $gcl {
1464
    <$uri> a skos:Concept .
1465
    OPTIONAL {
1466
      ?child $propertyClause <$uri> .
1467
      OPTIONAL {
1468
        ?child skos:prefLabel ?label .
1469
        FILTER (langMatches(lang(?label), "$lang"))
1470
      }
1471
      OPTIONAL {
1472
        ?child skos:prefLabel ?label .
1473
        FILTER (langMatches(lang(?label), "$fallback"))
1474
      }
1475
      OPTIONAL { # other language case
1476
        ?child skos:prefLabel ?label .
1477
      }
1478
      OPTIONAL {
1479
        ?child skos:notation ?notation .
1480
      }
1481
      BIND ( EXISTS { ?a $propertyClause ?child . } AS ?grandchildren )
1482
    }
1483
  }
1484
}
1485
EOQ;
1486
        return $query;
1487
    }
1488
1489
    /**
1490
     * Transforms the sparql result object into an array.
1491
     * @param EasyRdf_Sparql_Result $result
1492
     * @param string $lang
1493
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1494
     */
1495
    private function transformNarrowerResults($result, $lang) {
1496
        $ret = array();
1497
        foreach ($result as $row) {
1498
            if (!isset($row->child)) {
1499
                return array();
1500
            }
1501
            // existing concept but no children
1502
1503
            $label = null;
1504 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...
1505
                if ($row->label->getLang() == $lang) {
1506
                    $label = $row->label->getValue();
1507
                } else {
1508
                    $label = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1509
                }
1510
1511
            }
1512
            $childArray = array(
1513
                'uri' => $row->child->getUri(),
1514
                'prefLabel' => $label,
1515
                'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1516
            );
1517
            if (isset($row->notation)) {
1518
                $childArray['notation'] = $row->notation->getValue();
1519
            }
1520
1521
            $ret[] = $childArray;
1522
        }
1523
        if (sizeof($ret) > 0) {
1524
            return $ret;
1525
        }
1526
        // existing concept, with children
1527
        else {
1528
            return null;
1529
        }
1530
        // nonexistent concept
1531
    }
1532
1533
    /**
1534
     * Query the narrower concepts of a concept.
1535
     * @param string $uri
1536
     * @param string $lang
1537
     * @param string $fallback
1538
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1539
     */
1540
    public function queryChildren($uri, $lang, $fallback, $props) {
1541
        $query = $this->generateChildQuery($uri, $lang, $fallback, $props);
1542
        $result = $this->client->query($query);
1543
        return $this->transformNarrowerResults($result, $lang);
1544
    }
1545
1546
    /**
1547
     * Query the top concepts of a vocabulary.
1548
     * @param string $conceptSchemes concept schemes whose top concepts to query for
1549
     * @param string $lang language of labels
1550
     */
1551
    public function queryTopConcepts($conceptSchemes, $lang) {
1552
        if (!is_array($conceptSchemes)) {
1553
            $conceptSchemes = array($conceptSchemes);
1554
        }
1555
1556
        $values = $this->formatValues('?topuri', $conceptSchemes, 'uri');
1557
1558
        $gcl = $this->graphClause;
1559
        $query = <<<EOQ
1560
SELECT DISTINCT ?top ?topuri ?label ?notation ?children WHERE {
1561
  $gcl {
1562
  ?top skos:topConceptOf ?topuri .
1563
  ?top skos:prefLabel ?label .
1564
  FILTER (langMatches(lang(?label), "$lang"))
1565
  OPTIONAL { ?top skos:notation ?notation . }
1566
  BIND ( EXISTS { ?top skos:narrower ?a . } AS ?children )
1567
  }
1568
  $values
1569
}
1570
EOQ;
1571
        $result = $this->client->query($query);
1572
        $ret = array();
1573
        foreach ($result as $row) {
1574
            if (isset($row->top) && isset($row->label)) {
1575
                $top = array('uri' => $row->top->getUri(), 'topConceptOf' => $row->topuri->getUri(), 'label' => $row->label->getValue(), 'hasChildren' => filter_var($row->children->getValue(), FILTER_VALIDATE_BOOLEAN));
1576
                if (isset($row->notation)) {
1577
                    $top['notation'] = $row->notation->getValue();
1578
                }
1579
1580
                $ret[] = $top;
1581
            }
1582
        }
1583
1584
        return $ret;
1585
    }
1586
1587
    /**
1588
     * Generates a sparql query for finding the hierarchy for a concept.
1589
     * @param string $uri concept uri.
1590
     * @param string $lang
1591
     * @param string $fallback language to use if label is not available in the preferred language
1592
     * @return string sparql query
1593
     */
1594
    private function generateParentListQuery($uri, $lang, $fallback, $props) {
1595
        $gcl = $this->graphClause;
1596
        $propertyClause = implode('|', $props);
1597
        $query = <<<EOQ
1598
SELECT ?broad ?parent ?member ?children ?grandchildren
1599
(SAMPLE(?lab) as ?label) (SAMPLE(?childlab) as ?childlabel) (SAMPLE(?topcs) AS ?top) (SAMPLE(?nota) as ?notation) (SAMPLE(?childnota) as ?childnotation)
1600
WHERE {
1601
    $gcl {
1602
      <$uri> a skos:Concept .
1603
      OPTIONAL {
1604
      <$uri> $propertyClause* ?broad .
1605
      OPTIONAL {
1606
        ?broad skos:prefLabel ?lab .
1607
        FILTER (langMatches(lang(?lab), "$lang"))
1608
      }
1609
      OPTIONAL {
1610
        ?broad skos:prefLabel ?lab .
1611
        FILTER (langMatches(lang(?lab), "$fallback"))
1612
      }
1613
      OPTIONAL { # fallback - other language case
1614
        ?broad skos:prefLabel ?lab .
1615
      }
1616
      OPTIONAL { ?broad skos:notation ?nota . }
1617
      OPTIONAL { ?broad $propertyClause ?parent . }
1618
      OPTIONAL { ?broad skos:narrower ?children .
1619
        OPTIONAL {
1620
          ?children skos:prefLabel ?childlab .
1621
          FILTER (langMatches(lang(?childlab), "$lang"))
1622
        }
1623
        OPTIONAL {
1624
          ?children skos:prefLabel ?childlab .
1625
          FILTER (langMatches(lang(?childlab), "$fallback"))
1626
        }
1627
        OPTIONAL { # fallback - other language case
1628
          ?children skos:prefLabel ?childlab .
1629
        }
1630
        OPTIONAL {
1631
          ?children skos:notation ?childnota .
1632
        }
1633
      }
1634
      BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren )
1635
      OPTIONAL { ?broad skos:topConceptOf ?topcs . }
1636
    }
1637
}
1638
}
1639
GROUP BY ?broad ?parent ?member ?children ?grandchildren
1640
EOQ;
1641
        return $query;
1642
    }
1643
1644
    /**
1645
     * Transforms the result into an array.
1646
     * @param EasyRdf_Sparql_Result
1647
     * @param string $lang
1648
     * @return an array for the REST controller to encode.
1649
     */
1650
    private function transformParentListResults($result, $lang)
1651
    {
1652
        $ret = array();
1653
        foreach ($result as $row) {
1654
            if (!isset($row->broad)) {
1655
                // existing concept but no broaders
1656
                return array();
1657
            }
1658
            $uri = $row->broad->getUri();
1659
            if (!isset($ret[$uri])) {
1660
                $ret[$uri] = array('uri' => $uri);
1661
            }
1662
            if (isset($row->exact)) {
1663
                $ret[$uri]['exact'] = $row->exact->getUri();
1664
            }
1665
            if (isset($row->top)) {
1666
                $ret[$uri]['top'] = $row->top->getUri();
1667
            }
1668
            if (isset($row->children)) {
1669
                if (!isset($ret[$uri]['narrower'])) {
1670
                    $ret[$uri]['narrower'] = array();
1671
                }
1672
1673
                $label = null;
1674 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...
1675
                    $label = $row->childlabel->getValue();
1676
                    if ($row->childlabel->getLang() !== $lang) {
1677
                        $label .= " (" . $row->childlabel->getLang() . ")";
1678
                    }
1679
1680
                }
1681
1682
                $childArr = array(
1683
                    'uri' => $row->children->getUri(),
1684
                    'label' => $label,
1685
                    'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1686
                );
1687
                if (isset($row->childnotation)) {
1688
                    $childArr['notation'] = $row->childnotation->getValue();
1689
                }
1690
1691
                if (!in_array($childArr, $ret[$uri]['narrower'])) {
1692
                    $ret[$uri]['narrower'][] = $childArr;
1693
                }
1694
1695
            }
1696
            if (isset($row->label)) {
1697
                $preflabel = $row->label->getValue();
1698
                if ($row->label->getLang() !== $lang) {
1699
                    $preflabel .= ' (' . $row->label->getLang() . ')';
1700
                }
1701
1702
                $ret[$uri]['prefLabel'] = $preflabel;
1703
            }
1704
            if (isset($row->notation)) {
1705
                $ret[$uri]['notation'] = $row->notation->getValue();
1706
            }
1707
1708
            if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) {
1709
                $ret[$uri]['broader'][] = $row->parent->getUri();
1710
            } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) {
1711
                $ret[$uri]['broader'][] = $row->parent->getUri();
1712
            }
1713
        }
1714
        if (sizeof($ret) > 0) {
1715
            // existing concept, with children
1716
            return $ret;
1717
        }
1718
        else {
1719
            // nonexistent concept
1720
            return null;
1721
        }
1722
    }
1723
1724
    /**
1725
     * Query for finding the hierarchy for a concept.
1726
     * @param string $uri concept uri.
1727
     * @param string $lang
1728
     * @param string $fallback language to use if label is not available in the preferred language
1729
     * @param array $props the hierarchy property/properties to use
1730
     * @return an array for the REST controller to encode.
1731
     */
1732
    public function queryParentList($uri, $lang, $fallback, $props) {
1733
        $query = $this->generateParentListQuery($uri, $lang, $fallback, $props);
1734
        $result = $this->client->query($query);
1735
        return $this->transformParentListResults($result, $lang);
1736
    }
1737
1738
    /**
1739
     * return a list of concept group instances, sorted by label
1740
     * @param string $groupClass URI of concept group class
1741
     * @param string $lang language of labels to return
1742
     * @return string sparql query
1743
     */
1744
    private function generateConceptGroupsQuery($groupClass, $lang) {
1745
        $gcl = $this->graphClause;
1746
        $query = <<<EOQ
1747
SELECT ?group (GROUP_CONCAT(DISTINCT STR(?child)) as ?children) ?label ?members ?notation
1748
WHERE {
1749
 $gcl {
1750
   ?group a <$groupClass> .
1751
   OPTIONAL { ?group skos:member|isothes:subGroup ?child .
1752
              ?child a <$groupClass> }
1753
   BIND(EXISTS{?group skos:member ?submembers} as ?members)
1754
   OPTIONAL { ?group skos:prefLabel ?label }
1755
   OPTIONAL { ?group rdfs:label ?label }
1756
   FILTER (langMatches(lang(?label), '$lang'))
1757
   OPTIONAL { ?group skos:notation ?notation }
1758
 }
1759
}
1760
GROUP BY ?group ?label ?members ?notation
1761
ORDER BY lcase(?label)
1762
EOQ;
1763
        return $query;
1764
    }
1765
1766
    /**
1767
     * Transforms the sparql query result into an array.
1768
     * @param EasyRdf_Sparql_Result $result
1769
     * @return array
1770
     */
1771
    private function transformConceptGroupsResults($result) {
1772
        $ret = array();
1773
        foreach ($result as $row) {
1774
            if (!isset($row->group)) {
1775
                # no groups found, see issue #357
1776
                continue;
1777
            }
1778
            $group = array('uri' => $row->group->getURI());
1779
            if (isset($row->label)) {
1780
                $group['prefLabel'] = $row->label->getValue();
1781
            }
1782
1783
            if (isset($row->children)) {
1784
                $group['childGroups'] = explode(' ', $row->children->getValue());
1785
            }
1786
1787
            if (isset($row->members)) {
1788
                $group['hasMembers'] = $row->members->getValue();
1789
            }
1790
1791
            if (isset($row->notation)) {
1792
                $group['notation'] = $row->notation->getValue();
1793
            }
1794
1795
            $ret[] = $group;
1796
        }
1797
        return $ret;
1798
    }
1799
1800
    /**
1801
     * return a list of concept group instances, sorted by label
1802
     * @param string $groupClass URI of concept group class
1803
     * @param string $lang language of labels to return
1804
     * @return array Result array with group URI as key and group label as value
1805
     */
1806
    public function listConceptGroups($groupClass, $lang) {
1807
        $query = $this->generateConceptGroupsQuery($groupClass, $lang);
1808
        $result = $this->client->query($query);
1809
        return $this->transformConceptGroupsResults($result);
1810
    }
1811
1812
    /**
1813
     * Generates the sparql query for listConceptGroupContents
1814
     * @param string $groupClass URI of concept group class
1815
     * @param string $group URI of the concept group instance
1816
     * @param string $lang language of labels to return
1817
     * @return string sparql query
1818
     */
1819
    private function generateConceptGroupContentsQuery($groupClass, $group, $lang) {
1820
        $gcl = $this->graphClause;
1821
        $query = <<<EOQ
1822
SELECT ?conc ?super ?label ?members ?type ?notation
1823
WHERE {
1824
 $gcl {
1825
   <$group> a <$groupClass> .
1826
   { <$group> skos:member ?conc . } UNION { ?conc isothes:superGroup <$group> }
1827
   FILTER NOT EXISTS { ?conc owl:deprecated true }
1828
   ?conc a ?type .
1829
   OPTIONAL { ?conc skos:prefLabel ?label .
1830
    FILTER (langMatches(lang(?label), '$lang'))
1831
   }
1832
   OPTIONAL { ?conc skos:prefLabel ?label . }
1833
   OPTIONAL { ?conc skos:notation ?notation }
1834
 }
1835
 BIND(EXISTS{?submembers isothes:superGroup ?conc} as ?super)
1836
 BIND(EXISTS{?conc skos:member ?submembers} as ?members)
1837
} ORDER BY lcase(?label)
1838
EOQ;
1839
        return $query;
1840
    }
1841
1842
    /**
1843
     * Transforms the sparql query result into an array.
1844
     * @param EasyRdf_Sparql_Result $result
1845
     * @param string $lang language of labels to return
1846
     * @return array
1847
     */
1848
    private function transformConceptGroupContentsResults($result, $lang) {
1849
        $ret = array();
1850
        $values = array();
1851
        foreach ($result as $row) {
1852
            if (!array_key_exists($row->conc->getURI(), $values)) {
1853
                $values[$row->conc->getURI()] = array(
1854
                    'uri' => $row->conc->getURI(),
1855
                    'isSuper' => $row->super->getValue(),
1856
                    'hasMembers' => $row->members->getValue(),
1857
                    'type' => array($row->type->shorten()),
1858
                );
1859
                if (isset($row->label)) {
1860
                    if ($row->label->getLang() == $lang) {
1861
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue();
1862
                    } else {
1863
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1864
                    }
1865
1866
                }
1867
                if (isset($row->notation)) {
1868
                    $values[$row->conc->getURI()]['notation'] = $row->notation->getValue();
1869
                }
1870
1871
            } else {
1872
                $values[$row->conc->getURI()]['type'][] = $row->type->shorten();
1873
            }
1874
        }
1875
1876
        foreach ($values as $val) {
1877
            $ret[] = $val;
1878
        }
1879
1880
        return $ret;
1881
    }
1882
1883
    /**
1884
     * return a list of concepts in a concept group
1885
     * @param string $groupClass URI of concept group class
1886
     * @param string $group URI of the concept group instance
1887
     * @param string $lang language of labels to return
1888
     * @return array Result array with concept URI as key and concept label as value
1889
     */
1890
    public function listConceptGroupContents($groupClass, $group, $lang) {
1891
        $query = $this->generateConceptGroupContentsQuery($groupClass, $group, $lang);
1892
        $result = $this->client->query($query);
1893
        return $this->transformConceptGroupContentsResults($result, $lang);
1894
    }
1895
1896
    /**
1897
     * Generates the sparql query for queryChangeList.
1898
     * @param string $lang language of labels to return.
1899
     * @param int $offset offset of results to retrieve; 0 for beginning of list
1900
     * @return string sparql query
1901
     */
1902 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...
1903
        $gcl = $this->graphClause;
1904
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
1905
1906
        $query = <<<EOQ
1907
SELECT DISTINCT ?concept ?date ?label
1908
WHERE {
1909
  $gcl {
1910
    ?concept a skos:Concept .
1911
    ?concept $prop ?date .
1912
    ?concept skos:prefLabel ?label .
1913
    FILTER (langMatches(lang(?label), '$lang'))
1914
  }
1915
}
1916
ORDER BY DESC(YEAR(?date)) DESC(MONTH(?date)) LCASE(?label)
1917
LIMIT 200 $offset
1918
EOQ;
1919
        return $query;
1920
    }
1921
1922
    /**
1923
     * Transforms the sparql query result into an array.
1924
     * @param EasyRdf_Sparql_Result $result
1925
     * @return array
1926
     */
1927 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...
1928
        $ret = array();
1929
        foreach ($result as $row) {
1930
            $concept = array('uri' => $row->concept->getURI());
1931
            if (isset($row->label)) {
1932
                $concept['prefLabel'] = $row->label->getValue();
1933
            }
1934
1935
            if (isset($row->date)) {
1936
                $concept['date'] = $row->date->getValue();
1937
            }
1938
1939
            $ret[] = $concept;
1940
        }
1941
        return $ret;
1942
    }
1943
1944
    /**
1945
     * return a list of recently changed or entirely new concepts
1946
     * @param string $lang language of labels to return
1947
     * @param int $offset offset of results to retrieve; 0 for beginning of list
1948
     * @return array Result array
1949
     */
1950
    public function queryChangeList($lang, $offset, $prop) {
1951
        $query = $this->generateChangeListQuery($lang, $offset, $prop);
1952
        $result = $this->client->query($query);
1953
        return $this->transformChangeListResults($result);
1954
    }
1955
}
1956