Completed
Push — master ( 574a1e...c86379 )
by Henri
03:52
created

GenericSparql::generateConceptSearchQuery()   C

Complexity

Conditions 8
Paths 64

Size

Total Lines 67
Code Lines 36

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 67
rs 6.6523
cc 8
eloc 36
nc 64
nop 3

How to fix   Long Method   

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
 * 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
     * Requires the following three parameters.
35
     * @param string $endpoint SPARQL endpoint address.
36
     * @param object $graph an EasyRDF SPARQL graph instance.
37
     * @param object $model a Model instance.
38
     */
39
    public function __construct($endpoint, $graph, $model) {
40
        // if special cache control (typically no-cache) was requested by the
41
        // client, set the same type of cache control headers also in subsequent
42
        // in the SPARQL requests (this is useful for performance testing)
43
        $cache_control = filter_input(INPUT_SERVER, 'HTTP_CACHE_CONTROL', FILTER_SANITIZE_STRING);
44
        $pragma = filter_input(INPUT_SERVER, 'HTTP_PRAGMA', FILTER_SANITIZE_STRING);
45
        if ($cache_control !== null || $pragma !== null) {
46
            $val = $pragma !== null ? $pragma : $cache_control;
47
            // configure the HTTP client used by EasyRdf_Sparql_Client
48
            $httpclient = EasyRdf_Http::getDefaultHttpClient();
49
            $httpclient->setHeaders('Cache-Control', $val);
50
            EasyRdf_Http::setDefaultHttpClient($httpclient); // actually redundant..
51
        }
52
53
        // create the EasyRDF SPARQL client instance to use
54
        $this->client = new EasyRdf_Sparql_Client($endpoint);
55
        $this->graph = $graph;
56
        $this->model = $model;
57
58
        // set graphClause so that it can be used by all queries
59
        if ($this->isDefaultEndpoint()) // default endpoint; query any graph (and catch it in a variable)
60
        {
61
            $this->graphClause = "GRAPH $graph";
62
        } elseif ($graph) // query a specific graph
63
        {
64
            $this->graphClause = "GRAPH <$graph>";
65
        } else // query the default graph
66
        {
67
            $this->graphClause = "";
68
        }
69
70
    }
71
72
    /**
73
     * Return true if this is the default SPARQL endpoint, used as the facade to query
74
     * all vocabularies.
75
     */
76
77
    protected function isDefaultEndpoint() {
78
        return $this->graph[0] == '?';
79
    }
80
81
    /**
82
     * Returns the graph instance
83
     * @return object EasyRDF graph instance.
84
     */
85
    public function getGraph() {
86
        return $this->graph;
87
    }
88
89
    /**
90
     * Generates the sparql query for retrieving concept and collection counts in a vocabulary.
91
     * @return string sparql query
92
     */
93 View Code Duplication
    private function generateCountConceptsQuery($array, $group) {
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...
94
        $gc = $this->graphClause;
95
        $optional = $array ? "UNION { ?type rdfs:subClassOf* <$array> }" : '';
96
        $optional .= $group ? "UNION { ?type rdfs:subClassOf* <$group> }" : '';
97
        $query = <<<EOQ
98
      SELECT (COUNT(?conc) as ?c) ?type ?typelabel WHERE {
99
        $gc {
100
          { ?conc a ?type .
101
          { ?type rdfs:subClassOf* skos:Concept . } UNION { ?type rdfs:subClassOf* skos:Collection . } $optional }
102
          OPTIONAL { ?type rdfs:label ?typelabel . }
103
        }
104
      }
105
GROUP BY ?type ?typelabel
106
EOQ;
107
        return $query;
108
    }
109
110
    /**
111
     * Used for transforming the concept count query results.
112
     * @param EasyRdf_Sparql_Result $result query results to be transformed
113
     * @param string $lang language of labels
114
     * @return Array containing the label counts
115
     */
116
    private function transformCountConceptsResults($result, $lang) {
117
        $ret = array();
118
        foreach ($result as $row) {
119
            if (!isset($row->type)) {
120
                continue;
121
            }
122
            $ret[$row->type->getUri()]['type'] = $row->type->getUri();
123
            $ret[$row->type->getUri()]['count'] = $row->c->getValue();
124
            if (isset($row->typelabel) && $row->typelabel->getLang() === $lang) {
125
                $ret[$row->type->getUri()]['label'] = $row->typelabel->getValue();
126
            }
127
128
        }
129
        return $ret;
130
    }
131
132
    /**
133
     * Used for counting number of concepts and collections in a vocabulary.
134
     * @param string $lang language of labels
135
     * @return int number of concepts in this vocabulary
136
     */
137
    public function countConcepts($lang = null, $array = null, $group = null) {
138
        $query = $this->generateCountConceptsQuery($array, $group);
139
        $result = $this->client->query($query);
140
        return $this->transformCountConceptsResults($result, $lang);
141
    }
142
143
    /**
144
     * @param array $langs Languages to query for
145
     * @param string[] $props property names
146
     * @return string sparql query
147
     */
148
    private function generateCountLangConceptsQuery($langs, $classes, $props) {
149
        $gc = $this->graphClause;
150
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
151
152
        $values = $this->formatValues('?type', $classes, 'uri');
153
        $values_lang = $this->formatValues('?lang', $langs, 'literal');
154
        $values_prop = $this->formatValues('?prop', $props, null);
155
156
        $query = <<<EOQ
157
SELECT ?lang ?prop
158
  (COUNT(?label) as ?count)
159
WHERE {
160
  $gc {
161
    ?conc a ?type .
162
    ?conc ?prop ?label .
163
    FILTER (langMatches(lang(?label), ?lang))
164
    $values_lang
165
    $values_prop
166
  }
167
  $values
168
}
169
GROUP BY ?lang ?prop ?type
170
EOQ;
171
        return $query;
172
    }
173
174
    /**
175
     * Transforms the CountLangConcepts results into an array of label counts.
176
     * @param EasyRdf_Sparql_Result $result query results to be transformed
177
     * @param array $langs Languages to query for
178
     * @param string[] $props property names
179
     */
180
    private function transformCountLangConceptsResults($result, $langs, $props) {
181
        $ret = array();
182
        // set default count to zero; overridden below if query found labels
183
        foreach ($langs as $lang) {
184
            foreach ($props as $prop) {
185
                $ret[$lang][$prop] = 0;
186
            }
187
        }
188
        foreach ($result as $row) {
189
            if (isset($row->lang) && isset($row->prop) && isset($row->count)) {
190
                $ret[$row->lang->getValue()][$row->prop->shorten()] =
191
                $row->count->getValue();
192
            }
193
194
        }
195
        ksort($ret);
196
        return $ret;
197
    }
198
199
    /**
200
     * Counts the number of concepts in a easyRDF graph with a specific language.
201
     * @param array $langs Languages to query for
202
     * @return Array containing count of concepts for each language and property.
203
     */
204
    public function countLangConcepts($langs, $classes = null) {
205
        $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel');
206
        $query = $this->generateCountLangConceptsQuery($langs, $classes, $props);
207
        // Count the number of terms in each language
208
        $result = $this->client->query($query);
209
        return $this->transformCountLangConceptsResults($result, $langs, $props);
210
    }
211
212
    /**
213
     * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one
214
     * of the constants given.
215
     * @param string $varname variable name, e.g. "?uri"
216
     * @param array $values the values
217
     * @param string $type type of values: "uri", "literal" or null (determines quoting style)
218
     */
219
    protected function formatValues($varname, $values, $type = null) {
220
        $constants = array();
221
        foreach ($values as $val) {
222
            if ($type == 'uri') {
223
                $val = "<$val>";
224
            }
225
226
            if ($type == 'literal') {
227
                $val = "'$val'";
228
            }
229
230
            $constants[] = "($val)";
231
        }
232
        $values = implode(" ", $constants);
233
234
        return "VALUES ($varname) { $values }";
235
    }
236
237
    /**
238
     * Filters multiple instances of the same vocabulary from the input array.
239
     * @param \Vocabulary[] $vocabs array of Vocabulary objects
240
     * @return \Vocabulary[]
241
     */
242
    private function filterDuplicateVocabs($vocabs) {
243
        // filtering duplicates
244
        $unique_vocabs = array();
245
        if (sizeof($vocabs) > 0) {
246
            foreach ($vocabs as $voc) {
247
                $unique_vocabs[$voc->getId()] = $voc;
248
            }
249
        }
250
251
        return $unique_vocabs;
252
    }
253
254
    /**
255
     * Generates a sparql query for one or more concept URIs
256
     * @param mixed $uris concept URI (string) or array of URIs
257
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
258
     * @param \Vocabulary[] $vocabs array of Vocabulary objects
259
     * @return string sparql query
260
     */
261
    private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) {
262
        $gc = $this->graphClause;
263
        $values = $this->formatValues('?uri', $uris, 'uri');
264
        $unique_vocabs = $this->filterDuplicateVocabs($vocabs);
265
        $values_graph = $this->formatValuesGraph($unique_vocabs);
266
267
        if ($arrayClass === null) {
268
            $construct = $optional = "";
269
        } else {
270
            // add information that can be used to format narrower concepts by
271
            // the array they belong to ("milk by source animal" use case)
272
            $construct = "\n ?x skos:member ?o . ?x skos:prefLabel ?xl . ?x a <$arrayClass> .";
273
            $optional = "\n OPTIONAL {
274
                      ?x skos:member ?o .
275
                      ?x a <$arrayClass> .
276
                      ?x skos:prefLabel ?xl .
277
                    }";
278
        }
279
        $query = <<<EOQ
280
CONSTRUCT {
281
 ?s ?p ?uri .
282
 ?sp ?uri ?op .
283
 ?uri ?p ?o .
284
 ?p rdfs:label ?proplabel .
285
 ?p rdfs:subPropertyOf ?pp .
286
 ?pp rdfs:label ?plabel .
287
 ?o a ?ot .
288
 ?o skos:prefLabel ?opl .
289
 ?o rdfs:label ?ol .
290
 ?o rdf:value ?ov .
291
 ?o skos:notation ?on .
292
 ?directgroup skos:member ?uri .
293
 ?parent skos:member ?group .
294
 ?group skos:prefLabel ?grouplabel .
295
 ?b1 rdf:first ?item .
296
 ?b1 rdf:rest ?b2 .
297
 ?item a ?it .
298
 ?item skos:prefLabel ?il .
299
 ?group a ?grouptype . $construct
300
} WHERE {
301
 $gc {
302
  {
303
    ?s ?p ?uri .
304
    FILTER(!isBlank(?s))
305
  }
306
  UNION
307
  { ?sp ?uri ?op . }
308
  UNION
309
  {
310
    ?directgroup skos:member ?uri .
311
    ?group skos:member+ ?uri .
312
    ?group skos:prefLabel ?grouplabel .
313
    ?group a ?grouptype .
314
    OPTIONAL { ?parent skos:member ?group }
315
  }
316
  UNION
317
  {
318
   ?uri ?p ?o .
319
   OPTIONAL {
320
     ?o rdf:rest* ?b1 .
321
     ?b1 rdf:first ?item .
322
     ?b1 rdf:rest ?b2 .
323
     OPTIONAL { ?item a ?it . }
324
     OPTIONAL { ?item skos:prefLabel ?il . }
325
   }
326
   OPTIONAL {
327
     { ?p rdfs:label ?proplabel . }
328
     UNION
329
     { ?p rdfs:subPropertyOf ?pp . }
330
     UNION
331
     { ?o a ?ot . }
332
     UNION
333
     { ?o skos:prefLabel ?opl . }
334
     UNION
335
     { ?o rdfs:label ?ol . }
336
     UNION
337
     { ?o rdf:value ?ov . }
338
     UNION
339
     { ?o skos:notation ?on . }
340
   } $optional
341
  }
342
 }
343
 $values
344
}
345
$values_graph
346
EOQ;
347
        return $query;
348
    }
349
350
    /**
351
     * Transforms ConceptInfo query results into an array of Concept objects
352
     * @param EasyRdf_Graph $result query results to be transformed
353
     * @param mixed $uris concept URI (string) or array of URIs
354
     * @param \Vocabulary[] $vocabs array of Vocabulary object
355
     * @param string|null $clang content language
356
     * @return mixed query result graph (EasyRdf_Graph), or array of Concept objects
357
     */
358
    private function transformConceptInfoResults($result, $uris, $vocabs, $clang) {
359
        $conceptArray = array();
360
        foreach ($uris as $index => $uri) {
361
            $conc = $result->resource($uri);
362
            $vocab = sizeof($vocabs) == 1 ? $vocabs[0] : $vocabs[$index];
363
            $conceptArray[] = new Concept($this->model, $vocab, $conc, $result, $clang);
364
        }
365
        return $conceptArray;
366
    }
367
368
    /**
369
     * Returns information (as a graph) for one or more concept URIs
370
     * @param mixed $uris concept URI (string) or array of URIs
371
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
372
     * @param \Vocabulary[]|null $vocabs array of Vocabulary object
373
     * @param boolean $as_graph whether to return a graph (true) or array of Concepts (false)
374
     * @param string|null $clang content language
375
     * @return mixed query result graph (EasyRdf_Graph), or array of Concept objects
376
     */
377
    public function queryConceptInfo($uris, $arrayClass = null, $vocabs = array(), $as_graph = false, $clang = null) {
378
        // if just a single URI is given, put it in an array regardless
379
        if (!is_array($uris)) {
380
            $uris = array($uris);
381
        }
382
383
        $query = $this->generateConceptInfoQuery($uris, $arrayClass, $vocabs);
0 ignored issues
show
Bug introduced by
It seems like $vocabs defined by parameter $vocabs on line 377 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...
384
        $result = $this->client->query($query);
385
        if ($as_graph) {
386
            return $result;
387
        }
388
389
        if ($result->isEmpty()) {
390
            return;
391
        }
392
393
        return $this->transformConceptInfoResults($result, $uris, $vocabs, $clang);
0 ignored issues
show
Bug introduced by
It seems like $vocabs defined by parameter $vocabs on line 377 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...
394
    }
395
396
    /**
397
     * Generates the sparql query for queryTypes
398
     * @param string $lang
399
     * @return string sparql query
400
     */
401
    private function generateQueryTypesQuery($lang) {
402
        $gc = $this->graphClause;
403
        $query = <<<EOQ
404
SELECT DISTINCT ?type ?label ?superclass
405
WHERE {
406
  $gc {
407
    {
408
      { BIND( skos:Concept as ?type ) }
409
      UNION
410
      { BIND( skos:Collection as ?type ) }
411
      UNION
412
      { BIND( isothes:ConceptGroup as ?type ) }
413
      UNION
414
      { BIND( isothes:ThesaurusArray as ?type ) }
415
      UNION
416
      { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Concept . }
417
      UNION
418
      { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Collection . }
419
    }
420
    OPTIONAL {
421
      ?type rdfs:label ?label .
422
      FILTER(langMatches(lang(?label), '$lang'))
423
    }
424
    OPTIONAL {
425
      ?type rdfs:subClassOf ?superclass .
426
    }
427
    FILTER EXISTS {
428
      ?s a ?type .
429
      ?s skos:prefLabel ?prefLabel .
430
    }
431
  }
432
}
433
EOQ;
434
        return $query;
435
    }
436
437
    /**
438
     * Transforms the results into an array format.
439
     * @param EasyRdf_Sparql_Result $result
440
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
441
     */
442 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...
443
        $ret = array();
444
        foreach ($result as $row) {
445
            $type = array();
446
            if (isset($row->label)) {
447
                $type['label'] = $row->label->getValue();
448
            }
449
450
            if (isset($row->superclass)) {
451
                $type['superclass'] = $row->superclass->getUri();
452
            }
453
454
            $ret[$row->type->getURI()] = $type;
455
        }
456
        return $ret;
457
    }
458
459
    /**
460
     * Retrieve information about types from the endpoint
461
     * @param string $lang
462
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
463
     */
464
    public function queryTypes($lang) {
465
        $query = $this->generateQueryTypesQuery($lang);
466
        $result = $this->client->query($query);
467
        return $this->transformQueryTypesResults($result);
468
    }
469
470
    /**
471
     * Generates the concept scheme query.
472
     * @param string $conceptscheme concept scheme URI
473
     * @return string sparql query
474
     */
475
    private function generateQueryConceptSchemeQuery($conceptscheme) {
476
        $gc = $this->graphClause;
477
        $query = <<<EOQ
478
CONSTRUCT {
479
  <$conceptscheme> ?property ?value .
480
} WHERE {
481
  $gc {
482
    <$conceptscheme> ?property ?value .
483
    FILTER (?property != skos:hasTopConcept)
484
  }
485
}
486
EOQ;
487
        return $query;
488
    }
489
490
    /**
491
     * Retrieves conceptScheme information from the endpoint.
492
     * @param string $conceptscheme concept scheme URI
493
     * @return EasyRDF_Graph query result graph
494
     */
495
    public function queryConceptScheme($conceptscheme) {
496
        $query = $this->generateQueryConceptSchemeQuery($conceptscheme);
497
        return $this->client->query($query);
498
    }
499
500
    /**
501
     * Generates the queryConceptSchemes sparql query.
502
     * @param string $lang language of labels
503
     * @return string sparql query
504
     */
505
    private function generateQueryConceptSchemesQuery($lang) {
506
        $gc = $this->graphClause;
507
        $query = <<<EOQ
508
SELECT ?cs ?label ?preflabel ?title
509
WHERE {
510
 $gc {
511
   ?cs a skos:ConceptScheme .
512
   OPTIONAL {
513
     ?cs rdfs:label ?label .
514
     FILTER(langMatches(lang(?label), '$lang'))
515
   }
516
   OPTIONAL {
517
     ?cs skos:prefLabel ?preflabel .
518
     FILTER(langMatches(lang(?preflabel), '$lang'))
519
   }
520
   OPTIONAL {
521
     { ?cs dc11:title ?title }
522
     UNION
523
     { ?cs dc:title ?title }
524
     FILTER(langMatches(lang(?title), '$lang'))
525
   }
526
 }
527
} ORDER BY ?cs
528
EOQ;
529
        return $query;
530
    }
531
532
    /**
533
     * Transforms the queryConceptScheme results into an array format.
534
     * @param EasyRdf_Sparql_Result $result
535
     * @return array
536
     */
537
    private function transformQueryConceptSchemesResults($result) {
538
        $ret = array();
539
        foreach ($result as $row) {
540
            $conceptscheme = array();
541
            if (isset($row->label)) {
542
                $conceptscheme['label'] = $row->label->getValue();
543
            }
544
545
            if (isset($row->preflabel)) {
546
                $conceptscheme['prefLabel'] = $row->preflabel->getValue();
547
            }
548
549
            if (isset($row->title)) {
550
                $conceptscheme['title'] = $row->title->getValue();
551
            }
552
553
            $ret[$row->cs->getURI()] = $conceptscheme;
554
        }
555
        return $ret;
556
    }
557
558
    /**
559
     * return a list of skos:ConceptScheme instances in the given graph
560
     * @param string $lang language of labels
561
     * @return array Array with concept scheme URIs (string) as keys and labels (string) as values
562
     */
563
    public function queryConceptSchemes($lang) {
564
        $query = $this->generateQueryConceptSchemesQuery($lang);
565
        $result = $this->client->query($query);
566
        return $this->transformQueryConceptSchemesResults($result);
567
    }
568
569
    /**
570
     * Generate a VALUES clause for limiting the targeted graphs.
571
     * @param Vocabulary[] $vocabs the vocabularies to target 
572
     * @return string[] array of graph URIs
573
     */
574
    protected function getVocabGraphs($vocabs) {
575
        if ($vocabs === null || sizeof($vocabs) == 0) {
576
            // searching from all vocabularies - limit to known graphs
577
            $vocabs = $this->model->getVocabularies();
578
        }
579
        $graphs = array();
580
        foreach ($vocabs as $voc) {
581
            $graphs[] = $voc->getGraph();
582
        }
583
        return $graphs;
584
    }
585
586
    /**
587
     * Generate a VALUES clause for limiting the targeted graphs.
588
     * @param array $vocabs array of Vocabulary objects to target
589
     * @return string VALUES clause, or "" if not necessary to limit
590
     */
591
    protected function formatValuesGraph($vocabs) {
592
        if (!$this->isDefaultEndpoint()) {
593
            return "";
594
        }
595
        $graphs = $this->getVocabGraphs($vocabs);
596
        return $this->formatValues('?graph', $graphs, 'uri');
597
    }
598
599
    /**
600
     * Generate a FILTER clause for limiting the targeted graphs.
601
     * @param array $vocabs array of Vocabulary objects to target
602
     * @return string FILTER clause, or "" if not necessary to limit
603
     */
604
    protected function formatFilterGraph($vocabs) {
605
        if (!$this->isDefaultEndpoint()) {
606
            return "";
607
        }
608
        $graphs = $this->getVocabGraphs($vocabs);
609
        $conditions = array();
610
        foreach ($graphs as $graph) {
611
          $conditions[] = "?graph=<$graph>";
612
        }
613
        return "FILTER (" . implode('||', $conditions) . ")";
614
    }
615
616
    /**
617
     * Formats combined limit and offset clauses for the sparql query
618
     * @param int $limit maximum number of hits to retrieve; 0 for unlimited
619
     * @param int $offset offset of results to retrieve; 0 for beginning of list
620
     * @return string sparql query clauses
621
     */
622
    protected function formatLimitAndOffset($limit, $offset) {
623
        $limit = ($limit) ? 'LIMIT ' . $limit : '';
624
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
625
        // eliminating whitespace and line changes when the conditions aren't needed.
626
        $limitandoffset = '';
627
        if ($limit && $offset) {
628
            $limitandoffset = "\n" . $limit . "\n" . $offset;
629
        } elseif ($limit) {
630
            $limitandoffset = "\n" . $limit;
631
        } elseif ($offset) {
632
            $limitandoffset = "\n" . $offset;
633
        }
634
635
        return $limitandoffset;
636
    }
637
638
    /**
639
     * Formats a sparql query clause for limiting the search to specific concept types.
640
     * @param array $types limit search to concepts of the given type(s)
641
     * @return string sparql query clause
642
     */
643
    protected function formatTypes($types) {
644
        $type_patterns = array();
645
        if (!empty($types)) {
646
            foreach ($types as $type) {
647
                $unprefixed = EasyRdf_Namespace::expand($type);
648
                $type_patterns[] = "{ ?s a <$unprefixed> }";
649
            }
650
        }
651
652
        return implode(' UNION ', $type_patterns);;
653
    }
654
655
    /**
656
     * @param string $lang language code of the returned labels
657
     * @param array $fields extra fields to include in the result (array of strings). (default: null = none)
658
     * @return string sparql query clause
659
     */
660
    protected function formatBroader($lang, $fields) {
661
        // extra variable expressions to request
662
        $extravars = '';
663
        // extra fields to query for
664
        $extrafields = '';
665
666
        if ($fields !== null && in_array('broader', $fields)) {
667
            # This expression creates a CSV row containing pairs of (uri,prefLabel) values.
668
            # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
669
            $extravars = <<<EOV
670
(GROUP_CONCAT(DISTINCT CONCAT(
671
 '"', STR(?broad), '"', ',',
672
 '"', REPLACE(IF(BOUND(?broadlab),?broadlab,''), '"', '""'), '"'
673
); separator='\\n') as ?broaders)
674
EOV;
675
            $extrafields = <<<EOF
676
OPTIONAL {
677
  ?s skos:broader ?broad .
678
  OPTIONAL { ?broad skos:prefLabel ?broadlab . FILTER(langMatches(lang(?broadlab), '$lang')) }
679
}
680
EOF;
681
        }
682
        return array('extravars' => $extravars, 'extrafields' => $extrafields);
683
    }
684
685
    /**
686
     * Generate condition for matching labels in SPARQL
687
     * @param string $term search term
688
     * @param string $search_lang language code used for matching labels (null means any language)
689
     * @return string sparql query snippet
690
     */
691
    protected function generateConceptSearchQueryCondition($term, $search_lang)
692
    {
693
        # use appropriate matching function depending on query type: =, strstarts, strends or full regex
694
        if (preg_match('/^[^\*]+$/', $term)) { // exact query
695
            $term = str_replace('\\', '\\\\', $term); // quote slashes
696
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
697
            $filtercond = "LCASE(STR(?match)) = '$term'";
698
        } elseif (preg_match('/^[^\*]+\*$/', $term)) { // prefix query
699
            $term = substr($term, 0, -1); // remove the final asterisk
700
            $term = str_replace('\\', '\\\\', $term); // quote slashes
701
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
702
            $filtercond = "STRSTARTS(LCASE(STR(?match)), '$term')";
703
        } elseif (preg_match('/^\*[^\*]+$/', $term)) { // suffix query
704
            $term = substr($term, 1); // remove the preceding asterisk
705
            $term = str_replace('\\', '\\\\', $term); // quote slashes
706
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
707
            $filtercond = "STRENDS(LCASE(STR(?match)), '$term')";
708
        } else { // too complicated - have to use a regex
709
            # make sure regex metacharacters are not passed through
710
            $term = str_replace('\\', '\\\\', preg_quote($term));
711
            $term = str_replace('\\\\*', '.*', $term); // convert asterisk to regex syntax
712
            $term = str_replace('\'', '\\\'', $term); // ensure single quotes are quoted
713
            $filtercond = "REGEX(STR(?match), '^$term$', 'i')";
714
        }
715
716
        $labelcond_match = ($search_lang) ? "&& LANGMATCHES(lang(?match), '$search_lang')" : "";
717
        
718
        return "?s ?prop ?match . FILTER ($filtercond $labelcond_match)";
719
    }
720
721
722
    /**
723
     * Inner query for concepts using a search term.
724
     * @param string $term search term
725
     * @param string $lang language code of the returned labels
726
     * @param string $search_lang language code used for matching labels (null means any language)
727
     * @param string[] $props properties to target e.g. array('skos:prefLabel','skos:altLabel')
728
     * @param boolean $unique restrict results to unique concepts (default: false)
729
     * @return string sparql query
730
     */
731
    protected function generateConceptSearchQueryInner($term, $lang, $search_lang, $props, $unique)
732
    {
733
        $values_prop = $this->formatValues('?prop', $props);
734
        $textcond = $this->generateConceptSearchQueryCondition($term, $search_lang);
735
736
        // extra conditions for label language, if specified
737
        $labelcond_label = ($lang) ? "LANGMATCHES(lang(?label), '$lang')" : "LANGMATCHES(lang(?label), lang(?match))";
738
        // if search language and UI/display language differ, must also consider case where there is no prefLabel in
739
        // the display language; in that case, should use the label with the same language as the matched label
740
        $labelcond_fallback = ($search_lang != $lang) ?
741
          "OPTIONAL { # in case previous OPTIONAL block gives no labels\n" .
742
          "?s skos:prefLabel ?label . FILTER (LANGMATCHES(LANG(?label), LANG(?match))) }" : "";
743
744
        /*
745
         * This query does some tricks to obtain a list of unique concepts.
746
         * From each match generated by the text index, a string such as
747
         * "1en@example" is generated, where the first character is a number
748
         * encoding the property and priority, then comes the language tag and
749
         * finally the original literal after an @ sign. Of these, the MIN
750
         * function is used to pick the best match for each concept. Finally,
751
         * the structure is unpacked to get back the original string. Phew!
752
         */
753
        $hitvar = $unique ? '(MIN(?matchstr) AS ?hit)' : '(?matchstr AS ?hit)';
754
        $hitgroup = $unique ? 'GROUP BY ?s ?label' : '';
755
         
756
        $query = <<<EOQ
757
   SELECT DISTINCT ?s ?label $hitvar
758
   WHERE {
759
    $values_prop
760
    VALUES (?prop ?pri) { (skos:prefLabel 1) (skos:altLabel 3) (skos:hiddenLabel 5)}
761
    $textcond
762
    ?s ?prop ?match
763
    OPTIONAL {
764
     ?s skos:prefLabel ?label .
765
     FILTER ($labelcond_label)
766
    } $labelcond_fallback
767
    BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri)
768
    BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr)
769
   }
770
   $hitgroup
771
EOQ;
772
773
        return $query;
774
    }
775
776
    /**
777
     * Query for concepts using a search term.
778
     * @param array $fields extra fields to include in the result (array of strings). (default: null = none)
779
     * @param boolean $unique restrict results to unique concepts (default: false)
780
     * @param ConceptSearchParameters $params 
781
     * @return string sparql query
782
     */
783
    protected function generateConceptSearchQuery($fields, $unique, $params) {
784
        $gc = $this->graphClause;
785
        $limitandoffset = $this->formatLimitAndOffset($params->getSearchLimit(), $params->getOffset());
786
        $formattedtype = $this->formatTypes($params->getTypeLimit());
787
        $formattedbroader = $this->formatBroader($params->getLang(), $fields);
788
        $extravars = $formattedbroader['extravars'];
789
        $extrafields = $formattedbroader['extrafields'];
790
        $schemes = $params->getSchemeLimit();
791
792
        $schemecond = '';
793
        if (!empty($schemes)) {
794
            foreach($schemes as $scheme) {
795
                $schemecond .= "?s skos:inScheme <$scheme> . ";
796
            }
797
        }
798
799
        // extra conditions for parent and group, if specified
800
        $parentcond = ($params->getParentLimit()) ? "?s skos:broader+ <" . $params->getParentLimit() . "> ." : "";
801
        $groupcond = ($params->getGroupLimit()) ? "<$params->getGroupLimit()> skos:member ?s ." : "";
0 ignored issues
show
Bug introduced by
The property getGroupLimit does not seem to exist in ConceptSearchParameters.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
802
        $pgcond = $parentcond . $groupcond;
803
804
        $orderextra = $this->isDefaultEndpoint() ? $this->graph : '';
805
806
        # make VALUES clauses
807
        $props = array('skos:prefLabel', 'skos:altLabel');
808
        if ($params->getHidden()) {
809
            $props[] = 'skos:hiddenLabel';
810
        }
811
812
        $filter_graph = $this->formatFilterGraph($params->getVocabs());
813
814
        // remove futile asterisks from the search term
815
        $term = $params->getSearchTerm();
816
        while (strpos($term, '**') !== false) {
817
            $term = str_replace('**', '*', $term);
818
        }
819
        
820
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique);
821
822
        $query = <<<EOQ
823
SELECT DISTINCT ?s ?label ?plabel ?alabel ?hlabel ?graph (GROUP_CONCAT(DISTINCT ?type) as ?types)
824
$extravars
825
WHERE {
826
 $gc {
827
  {
828
$innerquery
829
  }
830
  FILTER(BOUND(?s))
831
  BIND(STR(SUBSTR(?hit,1,1)) AS ?pri)
832
  BIND(STRLANG(STRAFTER(?hit, '@'), SUBSTR(STRBEFORE(?hit, '@'),2)) AS ?match)
833
  BIND(IF((?pri = "1" || ?pri = "2") && ?match != ?label, ?match, ?unbound) as ?plabel)
834
  BIND(IF((?pri = "3" || ?pri = "4"), ?match, ?unbound) as ?alabel)
835
  BIND(IF((?pri = "5" || ?pri = "6"), ?match, ?unbound) as ?hlabel)
836
  $formattedtype
837
  { $pgcond 
838
   ?s a ?type .
839
   $extrafields $schemecond
840
  }
841
  FILTER NOT EXISTS { ?s owl:deprecated true }
842
 }
843
 $filter_graph
844
}
845
GROUP BY ?s ?match ?label ?plabel ?alabel ?hlabel ?graph
846
ORDER BY LCASE(STR(?match)) LANG(?match) $orderextra $limitandoffset
847
EOQ;
848
        return $query;
849
    }
850
851
    /**
852
     * Transform the concept search query results into the skosmos desired return format.
853
     * @param EasyRdf_Sparql_Result $results
854
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
855
     * @return array query result object
856
     */
857
    private function transformConceptSearchResults($results, $vocabs) {
858
        $ret = array();
859
        $qnamecache = array(); // optimization to avoid expensive shorten() calls
860
861
        foreach ($results as $row) {
862
            if (!isset($row->s)) {
863
                continue;
864
            }
865
            // don't break if query returns a single dummy result
866
867
            $hit = array();
868
            $hit['uri'] = $row->s->getUri();
869
870
            if (isset($row->graph)) {
871
                $hit['graph'] = $row->graph->getUri();
872
            }
873
874
            foreach (explode(" ", $row->types->getValue()) as $typeuri) {
875
                if (!array_key_exists($typeuri, $qnamecache)) {
876
                    $res = new EasyRdf_Resource($typeuri);
877
                    $qname = $res->shorten(); // returns null on failure
878
                    $qnamecache[$typeuri] = ($qname !== null) ? $qname : $typeuri;
879
                }
880
                $hit['type'][] = $qnamecache[$typeuri];
881
            }
882
883
            if (isset($row->broaders)) {
884
                foreach (explode("\n", $row->broaders->getValue()) as $line) {
885
                    $brdata = str_getcsv($line, ',', '"', '"');
886
                    $broader = array('uri' => $brdata[0]);
887
                    if ($brdata[1] != '') {
888
                        $broader['prefLabel'] = $brdata[1];
889
                    }
890
891
                    $hit['broader'][] = $broader;
892
                }
893
            }
894
895
            foreach ($vocabs as $vocab) { // looping the vocabulary objects and asking these for a localname for the concept.
896
                $localname = $vocab->getLocalName($hit['uri']);
897
                if ($localname !== $hit['uri']) { // only passing the result forward if the uri didn't boomerang right back.
898
                    $hit['localname'] = $localname;
899
                    break; // stopping the search when we find one that returns something valid.
900
                }
901
            }
902
903
            if (isset($row->label)) {
904
                $hit['prefLabel'] = $row->label->getValue();
905
            }
906
907
            if (isset($row->label)) {
908
                $hit['lang'] = $row->label->getLang();
909
            }
910
911
            if (isset($row->plabel)) {
912
                $hit['matchedPrefLabel'] = $row->plabel->getValue();
913
                $hit['lang'] = $row->plabel->getLang();
914 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...
915
                $hit['altLabel'] = $row->alabel->getValue();
916
                $hit['lang'] = $row->alabel->getLang();
917
            } elseif (isset($row->hlabel)) {
918
                $hit['hiddenLabel'] = $row->hlabel->getValue();
919
                $hit['lang'] = $row->hlabel->getLang();
920
            }
921
922
            $ret[] = $hit;
923
        }
924
        return $ret;
925
    }
926
927
    /**
928
     * Query for concepts using a search term.
929
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
930
     * @param array $fields extra fields to include in the result (array of strings). (default: null = none)
931
     * @param boolean $unique restrict results to unique concepts (default: false)
932
     * @param ConceptSearchParameters $params 
933
     * @return array query result object
934
     */
935
    public function queryConcepts($vocabs, $fields = null, $unique = false, $params) {
936
        $query = $this->generateConceptSearchQuery($fields, $unique, $params);
0 ignored issues
show
Bug introduced by
It seems like $fields defined by parameter $fields on line 935 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...
937
        $results = $this->client->query($query);
938
        return $this->transformConceptSearchResults($results, $vocabs);
939
    }
940
941
    /**
942
     * Generates sparql query clauses used for creating the alphabetical index.
943
     * @param string $letter the letter (or special class) to search for
944
     * @return array of sparql query clause strings
945
     */
946
    private function formatFilterConditions($letter) {
947
        $use_regex = false;
948
949
        if ($letter == '*') {
950
            $letter = '.*';
951
            $use_regex = true;
952
        } elseif ($letter == '0-9') {
953
            $letter = '[0-9].*';
954
            $use_regex = true;
955
        } elseif ($letter == '!*') {
956
            $letter = '[^\\\\p{L}\\\\p{N}].*';
957
            $use_regex = true;
958
        }
959
960
        # make text query clause
961
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
962
        if ($use_regex) {
963
            $filtercond_label = "regex(str(?label), '^$letter$', 'i')";
964
            $filtercond_alabel = "regex(str(?alabel), '^$letter$', 'i')";
965
        } else {
966
            $filtercond_label = "strstarts(lcase(str(?label)), '$lcletter')";
967
            $filtercond_alabel = "strstarts(lcase(str(?alabel)), '$lcletter')";
968
        }
969
        return array('filterpref' => $filtercond_label, 'filteralt' => $filtercond_alabel);
970
    }
971
972
    /**
973
     * Generates the sparql query used for rendering the alphabetical index.
974
     * @param string $letter the letter (or special class) to search for
975
     * @param string $lang language of labels
976
     * @param integer $limit limits the amount of results
977
     * @param integer $offset offsets the result set
978
     * @param array $classes
979
     * @return string sparql query
980
     */
981
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes) {
982
        $gc = $this->graphClause;
983
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
984
        $values = $this->formatValues('?type', $classes, 'uri');
985
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
986
        $conditions = $this->formatFilterConditions($letter);
987
        $filtercond_label = $conditions['filterpref'];
988
        $filtercond_alabel = $conditions['filteralt'];
989
990
        $query = <<<EOQ
991
SELECT DISTINCT ?s ?label ?alabel
992
WHERE {
993
  $gc {
994
    {
995
      ?s skos:prefLabel ?label .
996
      FILTER (
997
        $filtercond_label
998
        && langMatches(lang(?label), '$lang')
999
      )
1000
    }
1001
    UNION
1002
    {
1003
      {
1004
        ?s skos:altLabel ?alabel .
1005
        FILTER (
1006
          $filtercond_alabel
1007
          && langMatches(lang(?alabel), '$lang')
1008
        )
1009
      }
1010
      {
1011
        ?s skos:prefLabel ?label .
1012
        FILTER (langMatches(lang(?label), '$lang'))
1013
      }
1014
    }
1015
    ?s a ?type .
1016
    FILTER NOT EXISTS { ?s owl:deprecated true }
1017
  } $values
1018
}
1019
ORDER BY LCASE(IF(BOUND(?alabel), STR(?alabel), STR(?label))) $limitandoffset
1020
EOQ;
1021
        return $query;
1022
    }
1023
1024
    /**
1025
     * Transforms the alphabetical list query results into an array format.
1026
     * @param EasyRdf_Sparql_Result $results
1027
     * @return array
1028
     */
1029
    private function transformAlphabeticalListResults($results) {
1030
        $ret = array();
1031
1032
        foreach ($results as $row) {
1033
            if (!isset($row->s)) {
1034
                continue;
1035
            }
1036
            // don't break if query returns a single dummy result
1037
1038
            $hit = array();
1039
            $hit['uri'] = $row->s->getUri();
1040
1041
            $hit['localname'] = $row->s->localName();
1042
1043
            $hit['prefLabel'] = $row->label->getValue();
1044
            $hit['lang'] = $row->label->getLang();
1045
1046 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...
1047
                $hit['altLabel'] = $row->alabel->getValue();
1048
                $hit['lang'] = $row->alabel->getLang();
1049
            }
1050
1051
            $ret[] = $hit;
1052
        }
1053
1054
        return $ret;
1055
    }
1056
1057
    /**
1058
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1059
     * '*!' (special characters) and '*' (everything) are accepted.
1060
     * @param string $letter the letter (or special class) to search for
1061
     * @param string $lang language of labels
1062
     * @param integer $limit limits the amount of results
1063
     * @param integer $offset offsets the result set
1064
     * @param array $classes
1065
     */
1066
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null) {
1067
        $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 1066 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...
1068
        $results = $this->client->query($query);
1069
        return $this->transformAlphabeticalListResults($results);
1070
    }
1071
1072
    /**
1073
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1074
     * @param string $lang language
1075
     * @return string sparql query
1076
     */
1077
    private function generateFirstCharactersQuery($lang, $classes) {
1078
        $gc = $this->graphClause;
1079
        $classes = (sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1080
        $values = $this->formatValues('?type', $classes, 'uri');
1081
        $query = <<<EOQ
1082
SELECT DISTINCT (substr(ucase(str(?label)), 1, 1) as ?l) WHERE {
1083
  $gc {
1084
    ?c skos:prefLabel ?label .
1085
    ?c a ?type
1086
    FILTER(langMatches(lang(?label), '$lang'))
1087
  }
1088
  $values
1089
}
1090
EOQ;
1091
        return $query;
1092
    }
1093
1094
    /**
1095
     * Transforms the first characters query results into an array format.
1096
     * @param EasyRdf_Sparql_Result $result
1097
     * @return array
1098
     */
1099
    private function transformFirstCharactersResults($result) {
1100
        $ret = array();
1101
        foreach ($result as $row) {
1102
            $ret[] = $row->l->getValue();
1103
        }
1104
        return $ret;
1105
    }
1106
1107
    /**
1108
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1109
     * @param string $lang language
1110
     * @return array array of characters
1111
     */
1112
    public function queryFirstCharacters($lang, $classes = null) {
1113
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1114
        $result = $this->client->query($query);
1115
        return $this->transformFirstCharactersResults($result);
1116
    }
1117
1118
    /**
1119
     * @param string $uri
1120
     * @param string $lang
1121
     * @return string sparql query string
1122
     */
1123
    private function generateLabelQuery($uri, $lang) {
1124
        $gc = $this->graphClause;
1125
        $labelcond_label = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1126
        $query = <<<EOQ
1127
SELECT ?label
1128
WHERE {
1129
  $gc {
1130
    <$uri> a ?type .
1131
    OPTIONAL {
1132
      <$uri> skos:prefLabel ?label .
1133
      $labelcond_label
1134
    }
1135
    OPTIONAL {
1136
      <$uri> rdfs:label ?label .
1137
      $labelcond_label
1138
    }
1139
    OPTIONAL {
1140
      <$uri> dc:title ?label .
1141
      $labelcond_label
1142
    }
1143
    OPTIONAL {
1144
      <$uri> dc11:title ?label .
1145
      $labelcond_label
1146
    }
1147
  }
1148
}
1149
EOQ;
1150
        return $query;
1151
    }
1152
1153
    /**
1154
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1155
     * @param string $uri
1156
     * @param string $lang
1157
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1158
     */
1159
    public function queryLabel($uri, $lang) {
1160
        $query = $this->generateLabelQuery($uri, $lang);
1161
        $result = $this->client->query($query);
1162
        $ret = array();
1163
        foreach ($result as $row) {
1164
            if (!isset($row->label)) {
1165
                return array();
1166
            }
1167
            // existing concept but no labels
1168
            $ret[$row->label->getLang()] = $row->label;
1169
        }
1170
1171
        if (sizeof($ret) > 0) {
1172
            return $ret;
1173
        }
1174
        // existing concept, with label(s)
1175
        else {
1176
            return null;
1177
        }
1178
        // nonexistent concept
1179
    }
1180
1181
    /**
1182
     * Generates a sparql query for queryProperty.
1183
     * @param string $uri
1184
     * @param string $prop the name of the property eg. 'skos:broader'.
1185
     * @param string $lang
1186
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1187
     * @return string sparql query
1188
     */
1189 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...
1190
        $gc = $this->graphClause;
1191
        $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1192
1193
        $query = <<<EOQ
1194
SELECT *
1195
WHERE {
1196
  $gc {
1197
    <$uri> a skos:Concept .
1198
    OPTIONAL {
1199
      <$uri> $prop ?object .
1200
      OPTIONAL {
1201
        ?object skos:prefLabel ?label .
1202
        FILTER (langMatches(lang(?label), "$lang"))
1203
      }
1204
      OPTIONAL {
1205
        ?object skos:prefLabel ?label .
1206
        FILTER (lang(?label) = "")
1207
      }
1208
      $anylang
1209
    }
1210
  }
1211
}
1212
EOQ;
1213
        return $query;
1214
    }
1215
1216
    /**
1217
     * Transforms the sparql query result into an array or null if the concept doesn't exist.
1218
     * @param EasyRdf_Sparql_Result $result
1219
     * @param string $lang
1220
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1221
     */
1222
    private function transformPropertyQueryResults($result, $lang) {
1223
        $ret = array();
1224
        foreach ($result as $row) {
1225
            if (!isset($row->object)) {
1226
                return array();
1227
            }
1228
            // existing concept but no properties
1229
            if (isset($row->label)) {
1230
                if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1231
                    $ret[$row->object->getUri()]['label'] = $row->label->getValue();
1232
                }
1233
1234
            } else {
1235
                $ret[$row->object->getUri()]['label'] = null;
1236
            }
1237
        }
1238
        if (sizeof($ret) > 0) {
1239
            return $ret;
1240
        }
1241
        // existing concept, with properties
1242
        else {
1243
            return null;
1244
        }
1245
        // nonexistent concept
1246
    }
1247
1248
    /**
1249
     * Query a single property of a concept.
1250
     * @param string $uri
1251
     * @param string $prop the name of the property eg. 'skos:broader'.
1252
     * @param string $lang
1253
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1254
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1255
     */
1256
    public function queryProperty($uri, $prop, $lang, $anylang = false) {
1257
        $uri = is_array($uri) ? $uri[0] : $uri;
1258
        $query = $this->generatePropertyQuery($uri, $prop, $lang, $anylang);
1259
        $result = $this->client->query($query);
1260
        return $this->transformPropertyQueryResults($result, $lang);
1261
    }
1262
1263
    /**
1264
     * Query a single transitive property of a concept.
1265
     * @param string $uri
1266
     * @param string $prop the name of the property eg. 'skos:broader'.
1267
     * @param string $lang
1268
     * @param integer $limit
1269
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1270
     * @return string sparql query
1271
     */
1272
    private function generateTransitivePropertyQuery($uri, $prop, $lang, $limit, $anylang) {
1273
        $uri = is_array($uri) ? $uri[0] : $uri;
1274
        $gc = $this->graphClause;
1275
        $filter = $anylang ? "" : "FILTER (langMatches(lang(?label), \"$lang\"))";
1276
        // need to do a SPARQL subquery because LIMIT needs to be applied /after/
1277
        // the direct relationships have been collapsed into one string
1278
        $query = <<<EOQ
1279
SELECT *
1280
WHERE {
1281
  SELECT ?object ?label (GROUP_CONCAT(?dir) as ?direct)
1282
  WHERE {
1283
    $gc {
1284
      <$uri> a skos:Concept .
1285
      OPTIONAL {
1286
        <$uri> $prop* ?object .
1287
        OPTIONAL {
1288
          ?object $prop ?dir .
1289
        }
1290
      }
1291
      OPTIONAL {
1292
        ?object skos:prefLabel ?label .
1293
        $filter
1294
      }
1295
    }
1296
  }
1297
  GROUP BY ?object ?label
1298
}
1299
LIMIT $limit
1300
EOQ;
1301
        return $query;
1302
    }
1303
1304
    /**
1305
     * Transforms the sparql query result object into an array.
1306
     * @param EasyRdf_Sparql_Result $result
1307
     * @param string $lang
1308
     * @param string $fallbacklang language to use if label is not available in the preferred language
1309
     * @return array of property values (key: URI, val: label), or null if concept doesn't exist
1310
     */
1311
    private function transformTransitivePropertyResults($result, $lang, $fallbacklang) {
1312
        $ret = array();
1313
        foreach ($result as $row) {
1314
            if (!isset($row->object)) {
1315
                return array();
1316
            }
1317
            // existing concept but no properties
1318
            if (isset($row->label)) {
1319
                $val = array('label' => $row->label->getValue());
1320
            } else {
1321
                $val = array('label' => null);
1322
            }
1323 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...
1324
                $val['direct'] = explode(' ', $row->direct->getValue());
1325
            }
1326
            // Preventing labels in a non preferred language overriding the preferred language.
1327
            if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1328
                if (!isset($row->label) || $row->label->getLang() === $lang) {
1329
                    $ret[$row->object->getUri()] = $val;
1330
                } elseif ($row->label->getLang() === $fallbacklang) {
1331
                    $val['label'] .= ' (' . $row->label->getLang() . ')';
1332
                    $ret[$row->object->getUri()] = $val;
1333
                }
1334
            }
1335
        }
1336
1337
        // second iteration of results to find labels for the ones that didn't have one in the preferred languages
1338
        foreach ($result as $row) {
1339
            if (isset($row->object) && array_key_exists($row->object->getUri(), $ret) === false) {
1340
                $val = array('label' => $row->label->getValue());
1341 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...
1342
                    $val['direct'] = explode(' ', $row->direct->getValue());
1343
                }
1344
                $ret[$row->object->getUri()] = $val;
1345
            }
1346
        }
1347
1348
        if (sizeof($ret) > 0) {
1349
            return $ret;
1350
        }
1351
        // existing concept, with properties
1352
        else {
1353
            return null;
1354
        }
1355
        // nonexistent concept
1356
    }
1357
1358
    /**
1359
     * Query a single transitive property of a concept.
1360
     * @param string $uri
1361
     * @param string $prop the name of the property eg. 'skos:broader'.
1362
     * @param string $lang
1363
     * @param string $fallbacklang language to use if label is not available in the preferred language
1364
     * @param integer $limit
1365
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1366
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1367
     */
1368
    public function queryTransitiveProperty($uri, $prop, $lang, $limit, $anylang = false, $fallbacklang = '') {
1369
        $query = $this->generateTransitivePropertyQuery($uri, $prop, $lang, $limit, $anylang);
1370
        $result = $this->client->query($query);
1371
        return $this->transformTransitivePropertyResults($result, $lang, $fallbacklang);
1372
    }
1373
1374
    /**
1375
     * Generates the query for a concepts skos:narrowers.
1376
     * @param string $uri
1377
     * @param string $lang
1378
     * @param string $fallback
1379
     * @return string sparql query
1380
     */
1381 View Code Duplication
    private function generateNarrowerQuery($uri, $lang, $fallback) {
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...
1382
        $uri = is_array($uri) ? $uri[0] : $uri;
1383
        $gc = $this->graphClause;
1384
        $query = <<<EOQ
1385
SELECT ?child ?label ?child ?grandchildren ?notation WHERE {
1386
  $gc {
1387
    <$uri> a skos:Concept .
1388
    OPTIONAL {
1389
      <$uri> skos:narrower ?child .
1390
      OPTIONAL {
1391
        ?child skos:prefLabel ?label .
1392
        FILTER (langMatches(lang(?label), "$lang"))
1393
      }
1394
      OPTIONAL {
1395
        ?child skos:prefLabel ?label .
1396
        FILTER (langMatches(lang(?label), "$fallback"))
1397
      }
1398
      OPTIONAL { # other language case
1399
        ?child skos:prefLabel ?label .
1400
      }
1401
      OPTIONAL {
1402
        ?child skos:notation ?notation .
1403
      }
1404
      BIND ( EXISTS { ?child skos:narrower ?a . } AS ?grandchildren )
1405
    }
1406
  }
1407
}
1408
EOQ;
1409
        return $query;
1410
    }
1411
1412
    /**
1413
     * Transforms the sparql result object into an array.
1414
     * @param EasyRdf_Sparql_Result $result
1415
     * @param string $lang
1416
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1417
     */
1418
    private function transformNarrowerResults($result, $lang) {
1419
        $ret = array();
1420
        foreach ($result as $row) {
1421
            if (!isset($row->child)) {
1422
                return array();
1423
            }
1424
            // existing concept but no children
1425
1426
            $label = null;
1427 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...
1428
                if ($row->label->getLang() == $lang) {
1429
                    $label = $row->label->getValue();
1430
                } else {
1431
                    $label = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1432
                }
1433
1434
            }
1435
            $child_array = array(
1436
                'uri' => $row->child->getUri(),
1437
                'prefLabel' => $label,
1438
                'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1439
            );
1440
            if (isset($row->notation)) {
1441
                $child_array['notation'] = $row->notation->getValue();
1442
            }
1443
1444
            $ret[] = $child_array;
1445
        }
1446
        if (sizeof($ret) > 0) {
1447
            return $ret;
1448
        }
1449
        // existing concept, with children
1450
        else {
1451
            return null;
1452
        }
1453
        // nonexistent concept
1454
    }
1455
1456
    /**
1457
     * Query the narrower concepts of a concept.
1458
     * @param string $uri
1459
     * @param string $lang
1460
     * @param string $fallback
1461
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1462
     */
1463
    public function queryChildren($uri, $lang, $fallback) {
1464
        $query = $this->generateNarrowerQuery($uri, $lang, $fallback);
1465
        $result = $this->client->query($query);
1466
        return $this->transformNarrowerResults($result, $lang);
1467
    }
1468
1469
    /**
1470
     * Query the top concepts of a vocabulary.
1471
     * @param string $conceptSchemes concept schemes whose top concepts to query for
1472
     * @param string $lang language of labels
1473
     */
1474
    public function queryTopConcepts($conceptSchemes, $lang) {
1475
        if (!is_array($conceptSchemes)) {
1476
            $conceptSchemes = array($conceptSchemes);
1477
        }
1478
1479
        $values = $this->formatValues('?topuri', $conceptSchemes, 'uri');
1480
1481
        $gc = $this->graphClause;
1482
        $query = <<<EOQ
1483
SELECT DISTINCT ?top ?topuri ?label ?notation ?children WHERE {
1484
  $gc {
1485
  ?top skos:topConceptOf ?topuri .
1486
  ?top skos:prefLabel ?label .
1487
  FILTER (langMatches(lang(?label), "$lang"))
1488
  OPTIONAL { ?top skos:notation ?notation . }
1489
  BIND ( EXISTS { ?top skos:narrower ?a . } AS ?children )
1490
  }
1491
  $values
1492
}
1493
EOQ;
1494
        $result = $this->client->query($query);
1495
        $ret = array();
1496
        foreach ($result as $row) {
1497
            if (isset($row->top) && isset($row->label)) {
1498
                $top = array('uri' => $row->top->getUri(), 'topConceptOf' => $row->topuri->getUri(), 'label' => $row->label->getValue(), 'hasChildren' => filter_var($row->children->getValue(), FILTER_VALIDATE_BOOLEAN));
1499
                if (isset($row->notation)) {
1500
                    $top['notation'] = $row->notation->getValue();
1501
                }
1502
1503
                $ret[] = $top;
1504
            }
1505
        }
1506
1507
        return $ret;
1508
    }
1509
1510
    /**
1511
     * Generates a sparql query for finding the hierarchy for a concept.
1512
     * @param string $uri concept uri.
1513
     * @param string $lang
1514
     * @param string $fallback language to use if label is not available in the preferred language
1515
     * @return string sparql query
1516
     */
1517
    private function generateParentListQuery($uri, $lang, $fallback) {
1518
        $gc = $this->graphClause;
1519
        $query = <<<EOQ
1520
SELECT ?broad ?parent ?member ?children ?grandchildren
1521
(SAMPLE(?lab) as ?label) (SAMPLE(?childlab) as ?childlabel) (SAMPLE(?topcs) AS ?top) (SAMPLE(?nota) as ?notation) (SAMPLE(?childnota) as ?childnotation)
1522
WHERE {
1523
    $gc {
1524
      <$uri> a skos:Concept .
1525
      OPTIONAL {
1526
      <$uri> skos:broader* ?broad .
1527
      OPTIONAL {
1528
        ?broad skos:prefLabel ?lab .
1529
        FILTER (langMatches(lang(?lab), "$lang"))
1530
      }
1531
      OPTIONAL {
1532
        ?broad skos:prefLabel ?lab .
1533
        FILTER (langMatches(lang(?lab), "$fallback"))
1534
      }
1535
      OPTIONAL { # fallback - other language case
1536
        ?broad skos:prefLabel ?lab .
1537
      }
1538
      OPTIONAL { ?broad skos:notation ?nota . }
1539
      OPTIONAL { ?broad skos:broader ?parent . }
1540
      OPTIONAL { ?broad skos:narrower ?children .
1541
        OPTIONAL {
1542
          ?children skos:prefLabel ?childlab .
1543
          FILTER (langMatches(lang(?childlab), "$lang"))
1544
        }
1545
        OPTIONAL {
1546
          ?children skos:prefLabel ?childlab .
1547
          FILTER (langMatches(lang(?childlab), "$fallback"))
1548
        }
1549
        OPTIONAL { # fallback - other language case
1550
          ?children skos:prefLabel ?childlab .
1551
        }
1552
        OPTIONAL {
1553
          ?children skos:notation ?childnota .
1554
        }
1555
      }
1556
      BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren )
1557
      OPTIONAL { ?broad skos:topConceptOf ?topcs . }
1558
    }
1559
}
1560
}
1561
GROUP BY ?broad ?parent ?member ?children ?grandchildren
1562
EOQ;
1563
        return $query;
1564
    }
1565
1566
    /**
1567
     * Transforms the result into an array.
1568
     * @param EasyRdf_Sparql_Result
1569
     * @param string $lang
1570
     * @return an array for the REST controller to encode.
1571
     */
1572
    private function transformParentListResults($result, $lang) {
1573
        $ret = array();
1574
        foreach ($result as $row) {
1575
            if (!isset($row->broad)) {
1576
                return array();
1577
            }
1578
            // existing concept but no broaders
1579
            $uri = $row->broad->getUri();
1580
            if (!isset($ret[$uri])) {
1581
                $ret[$uri] = array('uri' => $uri);
1582
            }
1583
            if (isset($row->exact)) {
1584
                $ret[$uri]['exact'] = $row->exact->getUri();
1585
            }
1586
            if (isset($row->top)) {
1587
                $ret[$uri]['top'] = $row->top->getUri();
1588
            }
1589
            if (isset($row->children)) {
1590
                if (!isset($ret[$uri]['narrower'])) {
1591
                    $ret[$uri]['narrower'] = array();
1592
                }
1593
1594
                $label = null;
1595 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...
1596
                    $label = $row->childlabel->getValue();
1597
                    if ($row->childlabel->getLang() !== $lang) {
1598
                        $label .= " (" . $row->childlabel->getLang() . ")";
1599
                    }
1600
1601
                }
1602
1603
                $child_arr = array(
1604
                    'uri' => $row->children->getUri(),
1605
                    'label' => $label,
1606
                    'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1607
                );
1608
                if (isset($row->childnotation)) {
1609
                    $child_arr['notation'] = $row->childnotation->getValue();
1610
                }
1611
1612
                if (!in_array($child_arr, $ret[$uri]['narrower'])) {
1613
                    $ret[$uri]['narrower'][] = $child_arr;
1614
                }
1615
1616
            }
1617
            if (isset($row->label)) {
1618
                $preflabel = $row->label->getValue();
1619
                if ($row->label->getLang() !== $lang) {
1620
                    $preflabel .= ' (' . $row->label->getLang() . ')';
1621
                }
1622
1623
                $ret[$uri]['prefLabel'] = $preflabel;
1624
            }
1625
            if (isset($row->notation)) {
1626
                $ret[$uri]['notation'] = $row->notation->getValue();
1627
            }
1628
1629
            if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) {
1630
                $ret[$uri]['broader'][] = $row->parent->getUri();
1631
            } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) {
1632
                $ret[$uri]['broader'][] = $row->parent->getUri();
1633
            }
1634
        }
1635
        if (sizeof($ret) > 0) {
1636
            return $ret;
1637
        }
1638
        // existing concept, with children
1639
        else {
1640
            return null;
1641
        }
1642
        // nonexistent concept
1643
    }
1644
1645
    /**
1646
     * Query for finding the hierarchy for a concept.
1647
     * @param string $uri concept uri.
1648
     * @param string $lang
1649
     * @param string $fallback language to use if label is not available in the preferred language
1650
     * @return an array for the REST controller to encode.
1651
     */
1652
    public function queryParentList($uri, $lang, $fallback) {
1653
        $query = $this->generateParentListQuery($uri, $lang, $fallback);
1654
        $result = $this->client->query($query);
1655
        return $this->transformParentListResults($result, $lang);
1656
    }
1657
1658
    /**
1659
     * return a list of concept group instances, sorted by label
1660
     * @param string $groupClass URI of concept group class
1661
     * @param string $lang language of labels to return
1662
     * @return string sparql query
1663
     */
1664
    private function generateConceptGroupsQuery($groupClass, $lang) {
1665
        $gc = $this->graphClause;
1666
        $query = <<<EOQ
1667
SELECT ?group (GROUP_CONCAT(DISTINCT STR(?child)) as ?children) ?label ?members ?notation
1668
WHERE {
1669
 $gc {
1670
   ?group a <$groupClass> .
1671
   OPTIONAL { ?group skos:member|isothes:subGroup ?child .
1672
              ?child a <$groupClass> }
1673
   BIND(EXISTS{?group skos:member ?submembers} as ?members)
1674
   OPTIONAL { ?group skos:prefLabel ?label }
1675
   OPTIONAL { ?group rdfs:label ?label }
1676
   FILTER (langMatches(lang(?label), '$lang'))
1677
   OPTIONAL { ?group skos:notation ?notation }
1678
 }
1679
}
1680
GROUP BY ?group ?label ?members ?notation
1681
ORDER BY lcase(?label)
1682
EOQ;
1683
        return $query;
1684
    }
1685
1686
    /**
1687
     * Transforms the sparql query result into an array.
1688
     * @param EasyRdf_Sparql_Result $result
1689
     * @return array
1690
     */
1691
    private function transformConceptGroupsResults($result) {
1692
        $ret = array();
1693
        foreach ($result as $row) {
1694
            if (!isset($row->group)) {
1695
                # no groups found, see issue #357
1696
                continue;
1697
            }
1698
            $group = array('uri' => $row->group->getURI());
1699
            if (isset($row->label)) {
1700
                $group['prefLabel'] = $row->label->getValue();
1701
            }
1702
1703
            if (isset($row->children)) {
1704
                $group['childGroups'] = explode(' ', $row->children->getValue());
1705
            }
1706
1707
            if (isset($row->members)) {
1708
                $group['hasMembers'] = $row->members->getValue();
1709
            }
1710
1711
            if (isset($row->notation)) {
1712
                $group['notation'] = $row->notation->getValue();
1713
            }
1714
1715
            $ret[] = $group;
1716
        }
1717
        return $ret;
1718
    }
1719
1720
    /**
1721
     * return a list of concept group instances, sorted by label
1722
     * @param string $groupClass URI of concept group class
1723
     * @param string $lang language of labels to return
1724
     * @return array Result array with group URI as key and group label as value
1725
     */
1726
    public function listConceptGroups($groupClass, $lang) {
1727
        $query = $this->generateConceptGroupsQuery($groupClass, $lang);
1728
        $result = $this->client->query($query);
1729
        return $this->transformConceptGroupsResults($result);
1730
    }
1731
1732
    /**
1733
     * Generates the sparql query for listConceptGroupContents
1734
     * @param string $groupClass URI of concept group class
1735
     * @param string $group URI of the concept group instance
1736
     * @param string $lang language of labels to return
1737
     * @return string sparql query
1738
     */
1739
    private function generateConceptGroupContentsQuery($groupClass, $group, $lang) {
1740
        $gc = $this->graphClause;
1741
        $query = <<<EOQ
1742
SELECT ?conc ?super ?label ?members ?type ?notation
1743
WHERE {
1744
 $gc {
1745
   <$group> a <$groupClass> .
1746
   { <$group> skos:member ?conc . } UNION { ?conc isothes:superGroup <$group> }
1747
   FILTER NOT EXISTS { ?conc owl:deprecated true }
1748
   ?conc a ?type .
1749
   OPTIONAL { ?conc skos:prefLabel ?label .
1750
    FILTER (langMatches(lang(?label), '$lang'))
1751
   }
1752
   OPTIONAL { ?conc skos:prefLabel ?label . }
1753
   OPTIONAL { ?conc skos:notation ?notation }
1754
 }
1755
 BIND(EXISTS{?submembers isothes:superGroup ?conc} as ?super)
1756
 BIND(EXISTS{?conc skos:member ?submembers} as ?members)
1757
} ORDER BY lcase(?label)
1758
EOQ;
1759
        return $query;
1760
    }
1761
1762
    /**
1763
     * Transforms the sparql query result into an array.
1764
     * @param EasyRdf_Sparql_Result $result
1765
     * @param string $lang language of labels to return
1766
     * @return array
1767
     */
1768
    private function transformConceptGroupContentsResults($result, $lang) {
1769
        $ret = array();
1770
        $values = array();
1771
        foreach ($result as $row) {
1772
            if (!array_key_exists($row->conc->getURI(), $values)) {
1773
                $values[$row->conc->getURI()] = array(
1774
                    'uri' => $row->conc->getURI(),
1775
                    'isSuper' => $row->super->getValue(),
1776
                    'hasMembers' => $row->members->getValue(),
1777
                    'type' => array($row->type->shorten()),
1778
                );
1779
                if (isset($row->label)) {
1780
                    if ($row->label->getLang() == $lang) {
1781
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue();
1782
                    } else {
1783
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1784
                    }
1785
1786
                }
1787
                if (isset($row->notation)) {
1788
                    $values[$row->conc->getURI()]['notation'] = $row->notation->getValue();
1789
                }
1790
1791
            } else {
1792
                $values[$row->conc->getURI()]['type'][] = $row->type->shorten();
1793
            }
1794
        }
1795
1796
        foreach ($values as $val) {
1797
            $ret[] = $val;
1798
        }
1799
1800
        return $ret;
1801
    }
1802
1803
    /**
1804
     * return a list of concepts in a concept group
1805
     * @param string $groupClass URI of concept group class
1806
     * @param string $group URI of the concept group instance
1807
     * @param string $lang language of labels to return
1808
     * @return array Result array with concept URI as key and concept label as value
1809
     */
1810
    public function listConceptGroupContents($groupClass, $group, $lang) {
1811
        $query = $this->generateConceptGroupContentsQuery($groupClass, $group, $lang);
1812
        $result = $this->client->query($query);
1813
        return $this->transformConceptGroupContentsResults($result, $lang);
1814
    }
1815
1816
    /**
1817
     * Generates the sparql query for queryChangeList.
1818
     * @param string $lang language of labels to return.
1819
     * @param int $offset offset of results to retrieve; 0 for beginning of list
1820
     * @return string sparql query
1821
     */
1822 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...
1823
        $gc = $this->graphClause;
1824
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
1825
1826
        $query = <<<EOQ
1827
SELECT DISTINCT ?concept ?date ?label
1828
WHERE {
1829
  $gc {
1830
    ?concept a skos:Concept .
1831
    ?concept $prop ?date .
1832
    ?concept skos:prefLabel ?label .
1833
    FILTER (langMatches(lang(?label), '$lang'))
1834
  }
1835
}
1836
ORDER BY DESC(YEAR(?date)) DESC(MONTH(?date)) LCASE(?label)
1837
LIMIT 200 $offset
1838
EOQ;
1839
        return $query;
1840
    }
1841
1842
    /**
1843
     * Transforms the sparql query result into an array.
1844
     * @param EasyRdf_Sparql_Result $result
1845
     * @return array
1846
     */
1847 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...
1848
        $ret = array();
1849
        foreach ($result as $row) {
1850
            $concept = array('uri' => $row->concept->getURI());
1851
            if (isset($row->label)) {
1852
                $concept['prefLabel'] = $row->label->getValue();
1853
            }
1854
1855
            if (isset($row->date)) {
1856
                $concept['date'] = $row->date->getValue();
1857
            }
1858
1859
            $ret[] = $concept;
1860
        }
1861
        return $ret;
1862
    }
1863
1864
    /**
1865
     * return a list of recently changed or entirely new concepts
1866
     * @param string $lang language of labels to return
1867
     * @param int $offset offset of results to retrieve; 0 for beginning of list
1868
     * @return array Result array
1869
     */
1870
    public function queryChangeList($lang, $offset, $prop) {
1871
        $query = $this->generateChangeListQuery($lang, $offset, $prop);
1872
        $result = $this->client->query($query);
1873
        return $this->transformChangeListResults($result);
1874
    }
1875
}
1876