Completed
Branch master (74f537)
by Osma
14:13 queued 07:27
created

GenericSparql::generateConceptSearchQueryInner()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 63
Code Lines 22

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 63
rs 8.8946
cc 4
eloc 22
nc 8
nop 5

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