Completed
Push — master ( 2c8400...fa2a9a )
by Henri
02:11
created

GenericSparql::generateNotationQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 7
nc 1
nop 1
1
<?php
2
3
/**
4
 * Generates SPARQL queries and provides access to the SPARQL endpoint.
5
 */
6
class GenericSparql {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
7
    /**
8
     * A SPARQL Client eg. an EasyRDF instance.
9
     * @property EasyRdf_Sparql_Client $client
10
     */
11
    protected $client;
12
    /**
13
     * Graph uri.
14
     * @property string $graph
15
     */
16
    protected $graph;
17
    /**
18
     * A SPARQL query graph part template.
19
     * @property string $graph
20
     */
21
    protected $graphClause;
22
    /**
23
     * Model instance.
24
     * @property Model $model
25
     */
26
    protected $model;
27
28
    /**
29
     * Cache used to avoid expensive shorten() calls
30
     * @property array $qnamecache
31
     */
32
    private $qnamecache = array();
33
34
    /**
35
     * Requires the following three parameters.
36
     * @param string $endpoint SPARQL endpoint address.
37
     * @param object $graph an EasyRDF SPARQL graph instance.
38
     * @param object $model a Model instance.
39
     */
40
    public function __construct($endpoint, $graph, $model) {
41
        $this->graph = $graph;
42
        $this->model = $model;
43
44
        // create the EasyRDF SPARQL client instance to use
45
        $this->initializeHttpClient();
46
        $this->client = new EasyRdf_Sparql_Client($endpoint);
47
48
        // set graphClause so that it can be used by all queries
49
        if ($this->isDefaultEndpoint()) // default endpoint; query any graph (and catch it in a variable)
50
        {
51
            $this->graphClause = "GRAPH $graph";
52
        } elseif ($graph) // query a specific graph
53
        {
54
            $this->graphClause = "GRAPH <$graph>";
55
        } else // query the default graph
56
        {
57
            $this->graphClause = "";
58
        }
59
60
    }
61
    
62
    /**
63
     * Execute the SPARQL query using the SPARQL client, logging it as well.
64
     * @param string $query SPARQL query to perform
65
     * @return object query result
66
     */
67
    protected function query($query) {
68
        $queryId = sprintf("%05d", rand(0, 99999));
69
        $logger = $this->model->getLogger();
70
        $logger->info("[qid $queryId] SPARQL query:\n$query\n");
71
        $starttime = microtime(true);
72
        $result = $this->client->query($query);
73
        $elapsed = intval(round((microtime(true) - $starttime) * 1000));
74
        if(method_exists($result, 'numRows')) {
75
            $numRows = $result->numRows();
76
            $logger->info("[qid $queryId] result: $numRows rows returned in $elapsed ms");
77
        } else { // graph result
78
            $numTriples = $result->countTriples();
79
            $logger->info("[qid $queryId] result: $numTriples triples returned in $elapsed ms");
80
        }
81
        return $result;
82
    }
83
    
84
    
85
    /**
86
     * Generates FROM clauses for the queries 
87
     * @param Vocabulary[]|null $vocabs
88
     * @return string
89
     */
90
    protected function generateFromClause($vocabs=null) {
91
        $graphs = array();
92
        $clause = '';
93
        if (!$vocabs) {
94
            return $this->graph !== '?graph' && $this->graph !== NULL ? "FROM <$this->graph>" : '';
95
        }
96
        foreach($vocabs as $vocab) {
97
            $graph = $vocab->getGraph();
98
            if (!in_array($graph, $graphs)) {
99
                array_push($graphs, $graph);
100
            }
101
        }
102
        foreach ($graphs as $graph) {
103
            if($graph !== NULL)
104
                $clause .= "FROM NAMED <$graph> "; 
105
        }
106
        return $clause;
107
    }
108
109
    protected function initializeHttpClient() {
110
        // configure the HTTP client used by EasyRdf_Sparql_Client
111
        $httpclient = EasyRdf_Http::getDefaultHttpClient();
112
        $httpclient->setConfig(array('timeout' => $this->model->getConfig()->getSparqlTimeout()));
113
114
        // if special cache control (typically no-cache) was requested by the
115
        // client, set the same type of cache control headers also in subsequent
116
        // in the SPARQL requests (this is useful for performance testing)
117
        // @codeCoverageIgnoreStart
118
        $cacheControl = filter_input(INPUT_SERVER, 'HTTP_CACHE_CONTROL', FILTER_SANITIZE_STRING);
119
        $pragma = filter_input(INPUT_SERVER, 'HTTP_PRAGMA', FILTER_SANITIZE_STRING);
120
        if ($cacheControl !== null || $pragma !== null) {
121
            $val = $pragma !== null ? $pragma : $cacheControl;
122
            $httpclient->setHeaders('Cache-Control', $val);
123
        }
124
        // @codeCoverageIgnoreEnd
125
126
        EasyRdf_Http::setDefaultHttpClient($httpclient); // actually redundant..
127
    }
128
129
    /**
130
     * Return true if this is the default SPARQL endpoint, used as the facade to query
131
     * all vocabularies.
132
     */
133
134
    protected function isDefaultEndpoint() {
135
        return $this->graph[0] == '?';
136
    }
137
138
    /**
139
     * Returns the graph instance
140
     * @return object EasyRDF graph instance.
141
     */
142
    public function getGraph() {
143
        return $this->graph;
144
    }
145
    
146
    /**
147
     * Shorten a URI
148
     * @param string $uri URI to shorten
149
     * @return string shortened URI, or original URI if it cannot be shortened
150
     */
151
    private function shortenUri($uri) {
152
        if (!array_key_exists($uri, $this->qnamecache)) {
153
            $res = new EasyRdf_Resource($uri);
154
            $qname = $res->shorten(); // returns null on failure
155
            $this->qnamecache[$uri] = ($qname !== null) ? $qname : $uri;
156
        }
157
        return $this->qnamecache[$uri];
158
    }
159
160
161
    /**
162
     * Generates the sparql query for retrieving concept and collection counts in a vocabulary.
163
     * @return string sparql query
164
     */
165
    private function generateCountConceptsQuery($array, $group) {
166
        $fcl = $this->generateFromClause();
167
        $optional = $array ? "UNION { ?type rdfs:subClassOf* <$array> }" : '';
168
        $optional .= $group ? "UNION { ?type rdfs:subClassOf* <$group> }" : '';
169
        $query = <<<EOQ
170
      SELECT (COUNT(?conc) as ?c) ?type ?typelabel $fcl WHERE {
171
        { ?conc a ?type .
172
        { ?type rdfs:subClassOf* skos:Concept . } UNION { ?type rdfs:subClassOf* skos:Collection . } $optional }
173
        OPTIONAL { ?type rdfs:label ?typelabel . }
174
      }
175
GROUP BY ?type ?typelabel
176
EOQ;
177
        return $query;
178
    }
179
180
    /**
181
     * Used for transforming the concept count query results.
182
     * @param EasyRdf_Sparql_Result $result query results to be transformed
183
     * @param string $lang language of labels
184
     * @return Array containing the label counts
185
     */
186
    private function transformCountConceptsResults($result, $lang) {
187
        $ret = array();
188
        foreach ($result as $row) {
189
            if (!isset($row->type)) {
190
                continue;
191
            }
192
            $ret[$row->type->getUri()]['type'] = $row->type->getUri();
193
            $ret[$row->type->getUri()]['count'] = $row->c->getValue();
194
            if (isset($row->typelabel) && $row->typelabel->getLang() === $lang) {
195
                $ret[$row->type->getUri()]['label'] = $row->typelabel->getValue();
196
            }
197
198
        }
199
        return $ret;
200
    }
201
202
    /**
203
     * Used for counting number of concepts and collections in a vocabulary.
204
     * @param string $lang language of labels
205
     * @return int number of concepts in this vocabulary
206
     */
207
    public function countConcepts($lang = null, $array = null, $group = null) {
208
        $query = $this->generateCountConceptsQuery($array, $group);
209
        $result = $this->query($query);
210
        return $this->transformCountConceptsResults($result, $lang);
211
    }
212
213
    /**
214
     * @param array $langs Languages to query for
215
     * @param string[] $props property names
216
     * @return string sparql query
217
     */
218
    private function generateCountLangConceptsQuery($langs, $classes, $props) {
219
        $gcl = $this->graphClause;
220
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
221
222
        $values = $this->formatValues('?type', $classes, 'uri');
223
        $valuesLang = $this->formatValues('?lang', $langs, 'literal');
224
        $valuesProp = $this->formatValues('?prop', $props, null);
225
226
        $query = <<<EOQ
227
SELECT ?lang ?prop
228
  (COUNT(?label) as ?count)
229
WHERE {
230
  $gcl {
231
    ?conc a ?type .
232
    ?conc ?prop ?label .
233
    FILTER (langMatches(lang(?label), ?lang))
234
    $valuesLang
235
    $valuesProp
236
  }
237
  $values
238
}
239
GROUP BY ?lang ?prop ?type
240
EOQ;
241
        return $query;
242
    }
243
244
    /**
245
     * Transforms the CountLangConcepts results into an array of label counts.
246
     * @param EasyRdf_Sparql_Result $result query results to be transformed
247
     * @param array $langs Languages to query for
248
     * @param string[] $props property names
249
     */
250
    private function transformCountLangConceptsResults($result, $langs, $props) {
251
        $ret = array();
252
        // set default count to zero; overridden below if query found labels
253
        foreach ($langs as $lang) {
254
            foreach ($props as $prop) {
255
                $ret[$lang][$prop] = 0;
256
            }
257
        }
258
        foreach ($result as $row) {
259
            if (isset($row->lang) && isset($row->prop) && isset($row->count)) {
260
                $ret[$row->lang->getValue()][$row->prop->shorten()] += 
261
                $row->count->getValue();
262
            }
263
264
        }
265
        ksort($ret);
266
        return $ret;
267
    }
268
269
    /**
270
     * Counts the number of concepts in a easyRDF graph with a specific language.
271
     * @param array $langs Languages to query for
272
     * @return Array containing count of concepts for each language and property.
273
     */
274
    public function countLangConcepts($langs, $classes = null) {
275
        $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel');
276
        $query = $this->generateCountLangConceptsQuery($langs, $classes, $props);
277
        // Count the number of terms in each language
278
        $result = $this->query($query);
279
        return $this->transformCountLangConceptsResults($result, $langs, $props);
280
    }
281
282
    /**
283
     * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one
284
     * of the constants given.
285
     * @param string $varname variable name, e.g. "?uri"
286
     * @param array $values the values
287
     * @param string $type type of values: "uri", "literal" or null (determines quoting style)
288
     */
289
    protected function formatValues($varname, $values, $type = null) {
290
        $constants = array();
291
        foreach ($values as $val) {
292
            if ($type == 'uri') {
293
                $val = "<$val>";
294
            }
295
296
            if ($type == 'literal') {
297
                $val = "'$val'";
298
            }
299
300
            $constants[] = "($val)";
301
        }
302
        $values = implode(" ", $constants);
303
304
        return "VALUES ($varname) { $values }";
305
    }
306
307
    /**
308
     * Filters multiple instances of the same vocabulary from the input array.
309
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
310
     * @return \Vocabulary[]
311
     */
312
    private function filterDuplicateVocabs($vocabs) {
313
        // filtering duplicates
314
        $uniqueVocabs = array();
315
        if ($vocabs !== null && sizeof($vocabs) > 0) {
316
            foreach ($vocabs as $voc) {
317
                $uniqueVocabs[$voc->getId()] = $voc;
318
            }
319
        }
320
321
        return $uniqueVocabs;
322
    }
323
324
    /**
325
     * Generates a sparql query for one or more concept URIs
326
     * @param mixed $uris concept URI (string) or array of URIs
327
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
328
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
329
     * @return string sparql query
330
     */
331
    private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) {
332
        $gcl = $this->graphClause;
333
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
334
        $values = $this->formatValues('?uri', $uris, 'uri');
335
        $uniqueVocabs = $this->filterDuplicateVocabs($vocabs);
336
        $valuesGraph = empty($vocabs) ? $this->formatValuesGraph($uniqueVocabs) : '';
337
338
        if ($arrayClass === null) {
339
            $construct = $optional = "";
340
        } else {
341
            // add information that can be used to format narrower concepts by
342
            // the array they belong to ("milk by source animal" use case)
343
            $construct = "\n ?x skos:member ?o . ?x skos:prefLabel ?xl . ?x a <$arrayClass> .";
344
            $optional = "\n OPTIONAL {
345
                      ?x skos:member ?o .
346
                      ?x a <$arrayClass> .
347
                      ?x skos:prefLabel ?xl .
348
                      FILTER NOT EXISTS {
349
                        ?x skos:member ?other .
350
                        FILTER NOT EXISTS { ?other skos:broader ?uri }
351
                      }
352
                    }";
353
        }
354
        $query = <<<EOQ
355
CONSTRUCT {
356
 ?s ?p ?uri .
357
 ?sp ?uri ?op .
358
 ?uri ?p ?o .
359
 ?p rdfs:label ?proplabel .
360
 ?p rdfs:subPropertyOf ?pp .
361
 ?pp rdfs:label ?plabel .
362
 ?o a ?ot .
363
 ?o skos:prefLabel ?opl .
364
 ?o rdfs:label ?ol .
365
 ?o rdf:value ?ov .
366
 ?o skos:notation ?on .
367
 ?o ?oprop ?oval .
368
 ?o ?xlprop ?xlval .
369
 ?directgroup skos:member ?uri .
370
 ?parent skos:member ?group .
371
 ?group skos:prefLabel ?grouplabel .
372
 ?b1 rdf:first ?item .
373
 ?b1 rdf:rest ?b2 .
374
 ?item a ?it .
375
 ?item skos:prefLabel ?il .
376
 ?group a ?grouptype . $construct
377
} $fcl WHERE {
378
 $gcl {
379
  {
380
    ?s ?p ?uri .
381
    FILTER(!isBlank(?s))
382
    FILTER(?p != skos:inScheme)
383
  }
384
  UNION
385
  { ?sp ?uri ?op . }
386
  UNION
387
  {
388
    ?directgroup skos:member ?uri .
389
    ?group skos:member+ ?uri .
390
    ?group skos:prefLabel ?grouplabel .
391
    ?group a ?grouptype .
392
    OPTIONAL { ?parent skos:member ?group }
393
  }
394
  UNION
395
  {
396
   ?uri ?p ?o .
397
   OPTIONAL {
398
     ?o rdf:rest* ?b1 .
399
     ?b1 rdf:first ?item .
400
     ?b1 rdf:rest ?b2 .
401
     OPTIONAL { ?item a ?it . }
402
     OPTIONAL { ?item skos:prefLabel ?il . }
403
   }
404
   OPTIONAL {
405
     { ?p rdfs:label ?proplabel . }
406
     UNION
407
     { ?p rdfs:subPropertyOf ?pp . }
408
   }
409
   OPTIONAL {
410
     { ?o a ?ot . }
411
     UNION
412
     { ?o skos:prefLabel ?opl . }
413
     UNION
414
     { ?o rdfs:label ?ol . }
415
     UNION
416
     { ?o rdf:value ?ov . 
417
       OPTIONAL { ?o ?oprop ?oval . }
418
     }
419
     UNION
420
     { ?o skos:notation ?on . }
421
     UNION
422
     { ?o a skosxl:Label .
423
       ?o ?xlprop ?xlval }
424
   } $optional
425
  }
426
 }
427
 $values
428
}
429
$valuesGraph
430
EOQ;
431
        return $query;
432
    }
433
434
    /**
435
     * Transforms ConceptInfo query results into an array of Concept objects
436
     * @param EasyRdf_Graph $result query results to be transformed
437
     * @param array $uris concept URIs
438
     * @param \Vocabulary[] $vocabs array of Vocabulary object
439
     * @param string|null $clang content language
440
     * @return mixed query result graph (EasyRdf_Graph), or array of Concept objects
441
     */
442
    private function transformConceptInfoResults($result, $uris, $vocabs, $clang) {
443
        $conceptArray = array();
444
        foreach ($uris as $index => $uri) {
445
            $conc = $result->resource($uri);
446
            $vocab = sizeof($vocabs) == 1 ? $vocabs[0] : $vocabs[$index];
447
            $conceptArray[] = new Concept($this->model, $vocab, $conc, $result, $clang);
448
        }
449
        return $conceptArray;
450
    }
451
452
    /**
453
     * Returns information (as a graph) for one or more concept URIs
454
     * @param mixed $uris concept URI (string) or array of URIs
455
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
456
     * @param \Vocabulary[]|null $vocabs vocabularies to target
457
     * @return \Concept[]
458
     */
459
    public function queryConceptInfoGraph($uris, $arrayClass = null, $vocabs = array()) {
460
        // if just a single URI is given, put it in an array regardless
461
        if (!is_array($uris)) {
462
            $uris = array($uris);
463
        }
464
465
        $query = $this->generateConceptInfoQuery($uris, $arrayClass, $vocabs);
466
        $result = $this->query($query);
467
        return $result;
468
    }
469
470
    /**
471
     * Returns information (as an array of Concept objects) for one or more concept URIs
472
     * @param mixed $uris concept URI (string) or array of URIs
473
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
474
     * @param \Vocabulary[] $vocabs vocabularies to target
475
     * @param string|null $clang content language
476
     * @return EasyRdf_Graph
477
     */
478
    public function queryConceptInfo($uris, $arrayClass = null, $vocabs = array(), $clang = null) {
479
        // if just a single URI is given, put it in an array regardless
480
        if (!is_array($uris)) {
481
            $uris = array($uris);
482
        }
483
        $result = $this->queryConceptInfoGraph($uris, $arrayClass, $vocabs);
484
        if ($result->isEmpty()) {
485
            return;
486
        }
487
        return $this->transformConceptInfoResults($result, $uris, $vocabs, $clang);
488
    }
489
490
    /**
491
     * Generates the sparql query for queryTypes
492
     * @param string $lang
493
     * @return string sparql query
494
     */
495
    private function generateQueryTypesQuery($lang) {
496
        $fcl = $this->generateFromClause();
497
        $query = <<<EOQ
498
SELECT DISTINCT ?type ?label ?superclass $fcl
499
WHERE {
500
  {
501
    { BIND( skos:Concept as ?type ) }
502
    UNION
503
    { BIND( skos:Collection as ?type ) }
504
    UNION
505
    { BIND( isothes:ConceptGroup as ?type ) }
506
    UNION
507
    { BIND( isothes:ThesaurusArray as ?type ) }
508
    UNION
509
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Concept . }
510
    UNION
511
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Collection . }
512
  }
513
  OPTIONAL {
514
    ?type rdfs:label ?label .
515
    FILTER(langMatches(lang(?label), '$lang'))
516
  }
517
  OPTIONAL {
518
    ?type rdfs:subClassOf ?superclass .
519
  }
520
  FILTER EXISTS {
521
    ?s a ?type .
522
    ?s skos:prefLabel ?prefLabel .
523
  }
524
}
525
EOQ;
526
        return $query;
527
    }
528
529
    /**
530
     * Transforms the results into an array format.
531
     * @param EasyRdf_Sparql_Result $result
532
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
533
     */
534 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...
535
        $ret = array();
536
        foreach ($result as $row) {
537
            $type = array();
538
            if (isset($row->label)) {
539
                $type['label'] = $row->label->getValue();
540
            }
541
542
            if (isset($row->superclass)) {
543
                $type['superclass'] = $row->superclass->getUri();
544
            }
545
546
            $ret[$row->type->getURI()] = $type;
547
        }
548
        return $ret;
549
    }
550
551
    /**
552
     * Retrieve information about types from the endpoint
553
     * @param string $lang
554
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
555
     */
556
    public function queryTypes($lang) {
557
        $query = $this->generateQueryTypesQuery($lang);
558
        $result = $this->query($query);
559
        return $this->transformQueryTypesResults($result);
560
    }
561
562
    /**
563
     * Generates the concept scheme query.
564
     * @param string $conceptscheme concept scheme URI
565
     * @return string sparql query
566
     */
567
    private function generateQueryConceptSchemeQuery($conceptscheme) {
568
        $fcl = $this->generateFromClause();
569
        $query = <<<EOQ
570
CONSTRUCT {
571
  <$conceptscheme> ?property ?value .
572
} $fcl WHERE {
573
  <$conceptscheme> ?property ?value .
574
  FILTER (?property != skos:hasTopConcept)
575
}
576
EOQ;
577
        return $query;
578
    }
579
580
    /**
581
     * Retrieves conceptScheme information from the endpoint.
582
     * @param string $conceptscheme concept scheme URI
583
     * @return EasyRDF_Graph query result graph
584
     */
585
    public function queryConceptScheme($conceptscheme) {
586
        $query = $this->generateQueryConceptSchemeQuery($conceptscheme);
587
        return $this->query($query);
588
    }
589
590
    /**
591
     * Generates the queryConceptSchemes sparql query.
592
     * @param string $lang language of labels
593
     * @return string sparql query
594
     */
595
    private function generateQueryConceptSchemesQuery($lang) {
596
        $fcl = $this->generateFromClause();
597
        $query = <<<EOQ
598
SELECT ?cs ?label ?preflabel ?title $fcl
599
WHERE {
600
 ?cs a skos:ConceptScheme .
601
 OPTIONAL {
602
   ?cs rdfs:label ?label .
603
   FILTER(langMatches(lang(?label), '$lang'))
604
 }
605
 OPTIONAL {
606
   ?cs skos:prefLabel ?preflabel .
607
   FILTER(langMatches(lang(?preflabel), '$lang'))
608
 }
609
 OPTIONAL {
610
   { ?cs dc11:title ?title }
611
   UNION
612
   { ?cs dc:title ?title }
613
   FILTER(langMatches(lang(?title), '$lang'))
614
 }
615
} ORDER BY ?cs
616
EOQ;
617
        return $query;
618
    }
619
620
    /**
621
     * Transforms the queryConceptScheme results into an array format.
622
     * @param EasyRdf_Sparql_Result $result
623
     * @return array
624
     */
625
    private function transformQueryConceptSchemesResults($result) {
626
        $ret = array();
627
        foreach ($result as $row) {
628
            $conceptscheme = array();
629
            if (isset($row->label)) {
630
                $conceptscheme['label'] = $row->label->getValue();
631
            }
632
633
            if (isset($row->preflabel)) {
634
                $conceptscheme['prefLabel'] = $row->preflabel->getValue();
635
            }
636
637
            if (isset($row->title)) {
638
                $conceptscheme['title'] = $row->title->getValue();
639
            }
640
641
            $ret[$row->cs->getURI()] = $conceptscheme;
642
        }
643
        return $ret;
644
    }
645
646
    /**
647
     * return a list of skos:ConceptScheme instances in the given graph
648
     * @param string $lang language of labels
649
     * @return array Array with concept scheme URIs (string) as keys and labels (string) as values
650
     */
651
    public function queryConceptSchemes($lang) {
652
        $query = $this->generateQueryConceptSchemesQuery($lang);
653
        $result = $this->query($query);
654
        return $this->transformQueryConceptSchemesResults($result);
655
    }
656
657
    /**
658
     * Generate a VALUES clause for limiting the targeted graphs.
659
     * @param Vocabulary[]|null $vocabs the vocabularies to target 
660
     * @return string[] array of graph URIs
661
     */
662
    protected function getVocabGraphs($vocabs) {
663
        if ($vocabs === null || sizeof($vocabs) == 0) {
664
            // searching from all vocabularies - limit to known graphs
665
            $vocabs = $this->model->getVocabularies();
666
        }
667
        $graphs = array();
668
        foreach ($vocabs as $voc) {
669
            $graphs[] = $voc->getGraph();
670
        }
671
        return $graphs;
672
    }
673
674
    /**
675
     * Generate a VALUES clause for limiting the targeted graphs.
676
     * @param Vocabulary[]|null $vocabs array of Vocabulary objects to target
677
     * @return string VALUES clause, or "" if not necessary to limit
678
     */
679
    protected function formatValuesGraph($vocabs) {
680
        if (!$this->isDefaultEndpoint()) {
681
            return "";
682
        }
683
        $graphs = $this->getVocabGraphs($vocabs);
684
        return $this->formatValues('?graph', $graphs, 'uri');
685
    }
686
687
    /**
688
     * Generate a FILTER clause for limiting the targeted graphs.
689
     * @param array $vocabs array of Vocabulary objects to target
690
     * @return string FILTER clause, or "" if not necessary to limit
691
     */
692
    protected function formatFilterGraph($vocabs) {
693
        if (!$this->isDefaultEndpoint()) {
694
            return "";
695
        }
696
        $graphs = $this->getVocabGraphs($vocabs);
697
        $values = array();
698
        foreach ($graphs as $graph) {
699
          $values[] = "<$graph>";
700
        }
701
        return "FILTER (?graph IN (" . implode(',', $values) . "))";
702
    }
703
704
    /**
705
     * Formats combined limit and offset clauses for the sparql query
706
     * @param int $limit maximum number of hits to retrieve; 0 for unlimited
707
     * @param int $offset offset of results to retrieve; 0 for beginning of list
708
     * @return string sparql query clauses
709
     */
710
    protected function formatLimitAndOffset($limit, $offset) {
711
        $limit = ($limit) ? 'LIMIT ' . $limit : '';
712
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
713
        // eliminating whitespace and line changes when the conditions aren't needed.
714
        $limitandoffset = '';
715
        if ($limit && $offset) {
716
            $limitandoffset = "\n" . $limit . "\n" . $offset;
717
        } elseif ($limit) {
718
            $limitandoffset = "\n" . $limit;
719
        } elseif ($offset) {
720
            $limitandoffset = "\n" . $offset;
721
        }
722
723
        return $limitandoffset;
724
    }
725
726
    /**
727
     * Formats a sparql query clause for limiting the search to specific concept types.
728
     * @param array $types limit search to concepts of the given type(s)
729
     * @return string sparql query clause
730
     */
731
    protected function formatTypes($types) {
732
        $typePatterns = array();
733
        if (!empty($types)) {
734
            foreach ($types as $type) {
735
                $unprefixed = EasyRdf_Namespace::expand($type);
736
                $typePatterns[] = "{ ?s a <$unprefixed> }";
737
            }
738
        }
739
740
        return implode(' UNION ', $typePatterns);;
741
    }
742
743
    /**
744
     * @param string $prop property to include in the result eg. 'broader' or 'narrower'
745
     * @return string sparql query clause
746
     */
747
    private function formatPropertyCsvClause($prop) {
748
        # This expression creates a CSV row containing pairs of (uri,prefLabel) values.
749
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
750
        $clause = <<<EOV
751
(GROUP_CONCAT(DISTINCT CONCAT(
752
 '"', IF(isIRI(?$prop),STR(?$prop),''), '"', ',',
753
 '"', REPLACE(IF(BOUND(?{$prop}lab),?{$prop}lab,''), '"', '""'), '"', ',',
754
 '"', REPLACE(IF(isLiteral(?{$prop}),?{$prop},''), '"', '""'), '"'
755
); separator='\\n') as ?{$prop}s)
756
EOV;
757
        return $clause;
758
    }
759
    
760
    /**
761
     * @return string sparql query clause
762
     */
763
    private function formatPrefLabelCsvClause() {
764
        # This expression creates a CSV row containing pairs of (prefLabel, lang) values.
765
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
766
        $clause = <<<EOV
767
(GROUP_CONCAT(DISTINCT CONCAT(
768
 '"', STR(?pref), '"', ',', '"', lang(?pref), '"'
769
); separator='\\n') as ?preflabels)
770
EOV;
771
        return $clause;
772
    }
773
774
    /**
775
     * @param string $lang language code of the returned labels
776
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
777
     * @return string sparql query clause
778
     */
779
    protected function formatExtraFields($lang, $fields) {
780
        // extra variable expressions to request and extra fields to query for
781
        $ret = array('extravars' => '', 'extrafields' => '');
782
783
        if ($fields === null) {
784
            return $ret; 
785
        }
786
787
        if (in_array('prefLabel', $fields)) {
788
            $ret['extravars'] .= $this->formatPreflabelCsvClause();
789
            $ret['extrafields'] .= <<<EOF
790
OPTIONAL {
791
  ?s skos:prefLabel ?pref .
792
}
793
EOF;
794
            // removing the prefLabel from the fields since it has been handled separately
795
            $fields = array_diff($fields, array('prefLabel'));
796
        }
797
798
        foreach ($fields as $field) {
799
            $ret['extravars'] .= $this->formatPropertyCsvClause($field);
800
            $ret['extrafields'] .= <<<EOF
801
OPTIONAL {
802
  ?s skos:$field ?$field .
803
  FILTER(!isLiteral(?$field)||langMatches(lang(?{$field}), '$lang'))
804
  OPTIONAL { ?$field skos:prefLabel ?{$field}lab . FILTER(langMatches(lang(?{$field}lab), '$lang')) }
805
}
806
EOF;
807
        }
808
809
        return $ret;
810
    }
811
812
    /**
813
     * Generate condition for matching labels in SPARQL
814
     * @param string $term search term
815
     * @param string $searchLang language code used for matching labels (null means any language)
816
     * @return string sparql query snippet
817
     */
818
    protected function generateConceptSearchQueryCondition($term, $searchLang)
819
    {
820
        # use appropriate matching function depending on query type: =, strstarts, strends or full regex
821
        if (preg_match('/^[^\*]+$/', $term)) { // exact query
822
            $term = str_replace('\\', '\\\\', $term); // quote slashes
823
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
824
            $filtercond = "LCASE(STR(?match)) = '$term'";
825
        } elseif (preg_match('/^[^\*]+\*$/', $term)) { // prefix query
826
            $term = substr($term, 0, -1); // remove the final asterisk
827
            $term = str_replace('\\', '\\\\', $term); // quote slashes
828
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
829
            $filtercond = "STRSTARTS(LCASE(STR(?match)), '$term')";
830
        } elseif (preg_match('/^\*[^\*]+$/', $term)) { // suffix query
831
            $term = substr($term, 1); // remove the preceding asterisk
832
            $term = str_replace('\\', '\\\\', $term); // quote slashes
833
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
834
            $filtercond = "STRENDS(LCASE(STR(?match)), '$term')";
835
        } else { // too complicated - have to use a regex
836
            # make sure regex metacharacters are not passed through
837
            $term = str_replace('\\', '\\\\', preg_quote($term));
838
            $term = str_replace('\\\\*', '.*', $term); // convert asterisk to regex syntax
839
            $term = str_replace('\'', '\\\'', $term); // ensure single quotes are quoted
840
            $filtercond = "REGEX(STR(?match), '^$term$', 'i')";
841
        }
842
843
        $labelcondMatch = ($searchLang) ? "&& LANGMATCHES(lang(?match), '$searchLang')" : "";
844
        
845
        return "?s ?prop ?match . FILTER ($filtercond $labelcondMatch)";
846
    }
847
848
849
    /**
850
     * Inner query for concepts using a search term.
851
     * @param string $term search term
852
     * @param string $lang language code of the returned labels
853
     * @param string $searchLang language code used for matching labels (null means any language)
854
     * @param string[] $props properties to target e.g. array('skos:prefLabel','skos:altLabel')
855
     * @param boolean $unique restrict results to unique concepts (default: false)
856
     * @return string sparql query
857
     */
858
    protected function generateConceptSearchQueryInner($term, $lang, $searchLang, $props, $unique, $filterGraph)
859
    {
860
        $valuesProp = $this->formatValues('?prop', $props);
861
        $textcond = $this->generateConceptSearchQueryCondition($term, $searchLang);
862
        $rawterm = str_replace('\\', '\\\\', str_replace('*', '', $term));
863
        
864
        // graph clause, if necessary
865
        $graphClause = $filterGraph != '' ? 'GRAPH ?graph' : '';
866
867
        // extra conditions for label language, if specified
868
        $labelcondLabel = ($lang) ? "LANGMATCHES(lang(?label), '$lang')" : "lang(?match) = '' || LANGMATCHES(lang(?label), lang(?match))";
869
        // if search language and UI/display language differ, must also consider case where there is no prefLabel in
870
        // the display language; in that case, should use the label with the same language as the matched label
871
        $labelcondFallback = ($searchLang != $lang) ?
872
          "OPTIONAL { # in case previous OPTIONAL block gives no labels\n" .
873
          "?s skos:prefLabel ?label . FILTER (LANGMATCHES(LANG(?label), LANG(?match))) }" : "";
874
          
875
        //  Including the labels if there is no query term given.
876
        if ($rawterm === '') {
877
          $labelClause = "?s skos:prefLabel ?label .";
878
          $labelClause = ($lang) ? $labelClause . " FILTER (LANGMATCHES(LANG(?label), '$lang'))" : $labelClause . "";
879
          return $labelClause . " BIND(?label AS ?match)";
880
        }
881
882
        /*
883
         * This query does some tricks to obtain a list of unique concepts.
884
         * From each match generated by the text index, a string such as
885
         * "1en@example" is generated, where the first character is a number
886
         * encoding the property and priority, then comes the language tag and
887
         * finally the original literal after an @ sign. Of these, the MIN
888
         * function is used to pick the best match for each concept. Finally,
889
         * the structure is unpacked to get back the original string. Phew!
890
         */
891
        $hitvar = $unique ? '(MIN(?matchstr) AS ?hit)' : '(?matchstr AS ?hit)';
892
        $hitgroup = $unique ? 'GROUP BY ?s ?label ?notation' : '';
893
         
894
        $query = <<<EOQ
895
   SELECT DISTINCT ?s ?label ?notation $hitvar
896
   WHERE {
897
    $graphClause {
898
     { 
899
     $valuesProp
900
     VALUES (?prop ?pri) { (skos:prefLabel 1) (skos:altLabel 3) (skos:hiddenLabel 5)}
901
     $textcond
902
     ?s ?prop ?match }
903
     UNION
904
     { ?s skos:notation "$rawterm" }
905
     OPTIONAL {
906
      ?s skos:prefLabel ?label .
907
      FILTER ($labelcondLabel)
908
     } $labelcondFallback
909
     BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri)
910
     BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr)
911
     OPTIONAL { ?s skos:notation ?notation }
912
    }
913
    $filterGraph
914
   }
915
   $hitgroup
916
EOQ;
917
918
        return $query;
919
    }
920
921
    /**
922
     * Query for concepts using a search term.
923
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
924
     * @param boolean $unique restrict results to unique concepts (default: false)
925
     * @param ConceptSearchParameters $params 
926
     * @return string sparql query
927
     */
928
    protected function generateConceptSearchQuery($fields, $unique, $params) {
929
        $vocabs = $params->getVocabs();
930
        $gcl = $this->graphClause;
931
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
932
        $formattedtype = $this->formatTypes($params->getTypeLimit());
933
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);
934
        $extravars = $formattedfields['extravars'];
935
        $extrafields = $formattedfields['extrafields'];
936
        $schemes = $params->getSchemeLimit();
937
938
        $schemecond = '';
939
        if (!empty($schemes)) {
940
            foreach($schemes as $scheme) {
941
                $schemecond .= "?s skos:inScheme <$scheme> . ";
942
            }
943
        }
944
945
        // extra conditions for parent and group, if specified
946
        $parentcond = ($params->getParentLimit()) ? "?s skos:broader+ <" . $params->getParentLimit() . "> ." : "";
947
        $groupcond = ($params->getGroupLimit()) ? "<" . $params->getGroupLimit() . "> skos:member ?s ." : "";
948
        $pgcond = $parentcond . $groupcond;
949
950
        $orderextra = $this->isDefaultEndpoint() ? $this->graph : '';
951
952
        # make VALUES clauses
953
        $props = array('skos:prefLabel', 'skos:altLabel');
954
        if ($params->getHidden()) {
955
            $props[] = 'skos:hiddenLabel';
956
        }
957
958
        $filterGraph = empty($vocabs) ? $this->formatFilterGraph($vocabs) : '';
959
960
        // remove futile asterisks from the search term
961
        $term = $params->getSearchTerm();
962
        while (strpos($term, '**') !== false) {
963
            $term = str_replace('**', '*', $term);
964
        }
965
966
        $labelpriority = <<<EOQ
967
  FILTER(BOUND(?s))
968
  BIND(STR(SUBSTR(?hit,1,1)) AS ?pri)
969
  BIND(IF((SUBSTR(STRBEFORE(?hit, '@'),1) != ?pri), STRLANG(STRAFTER(?hit, '@'), SUBSTR(STRBEFORE(?hit, '@'),2)), STRAFTER(?hit, '@')) AS ?match)
970
  BIND(IF((?pri = "1" || ?pri = "2") && ?match != ?label, ?match, ?unbound) as ?plabel)
971
  BIND(IF((?pri = "3" || ?pri = "4"), ?match, ?unbound) as ?alabel)
972
  BIND(IF((?pri = "5" || ?pri = "6"), ?match, ?unbound) as ?hlabel)
973
EOQ;
974
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique, $filterGraph);
975
        if ($params->getSearchTerm() === '*' || $params->getSearchTerm() === '') { 
976
          $labelpriority = ''; 
977
        }
978
        $query = <<<EOQ
979
SELECT DISTINCT ?s ?label ?plabel ?alabel ?hlabel ?graph ?notation (GROUP_CONCAT(DISTINCT ?type) as ?types) $extravars 
980
$fcl
981
WHERE {
982
 $gcl {
983
  {
984
  $innerquery
985
  }
986
  $labelpriority
987
  $formattedtype
988
  { $pgcond 
989
   ?s a ?type .
990
   $extrafields $schemecond
991
  }
992
  FILTER NOT EXISTS { ?s owl:deprecated true }
993
 }
994
 $filterGraph
995
}
996
GROUP BY ?s ?match ?label ?plabel ?alabel ?hlabel ?notation ?graph
997
ORDER BY LCASE(STR(?match)) LANG(?match) $orderextra
998
EOQ;
999
        return $query;
1000
    }
1001
1002
    /**
1003
     * Transform a single concept search query results into the skosmos desired return format.
1004
     * @param $row SPARQL query result row
1005
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1006
     * @return array query result object
1007
     */
1008
    private function transformConceptSearchResult($row, $vocabs, $fields)
1009
    {
1010
        $hit = array();
1011
        $hit['uri'] = $row->s->getUri();
1012
1013
        if (isset($row->graph)) {
1014
            $hit['graph'] = $row->graph->getUri();
1015
        }
1016
1017
        foreach (explode(" ", $row->types->getValue()) as $typeuri) {
1018
            $hit['type'][] = $this->shortenUri($typeuri);
1019
        }
1020
1021
        if(!empty($fields)) {
1022
            foreach ($fields as $prop) {
1023
                $propname = $prop . 's';
1024
                if (isset($row->$propname)) {
1025
                    foreach (explode("\n", $row->$propname->getValue()) as $line) {
1026
                        $rdata = str_getcsv($line, ',', '"', '"');
1027
                        $propvals = array();
1028
                        if ($rdata[0] != '') {
1029
                            $propvals['uri'] = $rdata[0];
1030
                        }
1031
                        if ($rdata[1] != '') {
1032
                            $propvals['prefLabel'] = $rdata[1];
1033
                        }
1034
                        if ($rdata[2] != '') {
1035
                            $propvals = $rdata[2];
1036
                        }
1037
1038
                        $hit['skos:' . $prop][] = $propvals;
1039
                    }
1040
                }
1041
            }
1042
        }
1043
1044
        
1045
        if (isset($row->preflabels)) {
1046
            foreach (explode("\n", $row->preflabels->getValue()) as $line) {
1047
                $pref = str_getcsv($line, ',', '"', '"');
1048
                $hit['prefLabels'][$pref[1]] = $pref[0];
1049
            }
1050
        }
1051
1052
        foreach ($vocabs as $vocab) { // looping the vocabulary objects and asking these for a localname for the concept.
1053
            $localname = $vocab->getLocalName($hit['uri']);
1054
            if ($localname !== $hit['uri']) { // only passing the result forward if the uri didn't boomerang right back.
1055
                $hit['localname'] = $localname;
1056
                break; // stopping the search when we find one that returns something valid.
1057
            }
1058
        }
1059
1060
        if (isset($row->label)) {
1061
            $hit['prefLabel'] = $row->label->getValue();
1062
        }
1063
1064
        if (isset($row->label)) {
1065
            $hit['lang'] = $row->label->getLang();
1066
        }
1067
1068
        if (isset($row->notation)) {
1069
            $hit['notation'] = $row->notation->getValue();
1070
        }
1071
1072
        if (isset($row->plabel)) {
1073
            $hit['matchedPrefLabel'] = $row->plabel->getValue();
1074
            $hit['lang'] = $row->plabel->getLang();
1075 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...
1076
            $hit['altLabel'] = $row->alabel->getValue();
1077
            $hit['lang'] = $row->alabel->getLang();
1078
        } elseif (isset($row->hlabel)) {
1079
            $hit['hiddenLabel'] = $row->hlabel->getValue();
1080
            $hit['lang'] = $row->hlabel->getLang();
1081
        }
1082
        return $hit;
1083
    }
1084
1085
    /**
1086
     * Transform the concept search query results into the skosmos desired return format.
1087
     * @param EasyRdf_Sparql_Result $results
1088
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1089
     * @return array query result object
1090
     */
1091
    private function transformConceptSearchResults($results, $vocabs, $fields) {
1092
        $ret = array();
1093
1094
        foreach ($results as $row) {
1095
            if (!isset($row->s)) {
1096
                // don't break if query returns a single dummy result
1097
                continue;
1098
            }
1099
            $ret[] = $this->transformConceptSearchResult($row, $vocabs, $fields);
1100
        }
1101
        return $ret;
1102
    }
1103
1104
    /**
1105
     * Query for concepts using a search term.
1106
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1107
     * @param array $fields extra fields to include in the result (array of strings). (default: null = none)
1108
     * @param boolean $unique restrict results to unique concepts (default: false)
1109
     * @param ConceptSearchParameters $params 
1110
     * @return array query result object
1111
     */
1112
    public function queryConcepts($vocabs, $fields = null, $unique = false, $params) {
1113
        $query = $this->generateConceptSearchQuery($fields, $unique, $params);
1114
        $results = $this->query($query);
1115
        return $this->transformConceptSearchResults($results, $vocabs, $fields);
1116
    }
1117
1118
    /**
1119
     * Generates sparql query clauses used for creating the alphabetical index.
1120
     * @param string $letter the letter (or special class) to search for
1121
     * @return array of sparql query clause strings
1122
     */
1123
    private function formatFilterConditions($letter, $lang) {
1124
        $useRegex = false;
1125
1126
        if ($letter == '*') {
1127
            $letter = '.*';
1128
            $useRegex = true;
1129
        } elseif ($letter == '0-9') {
1130
            $letter = '[0-9].*';
1131
            $useRegex = true;
1132
        } elseif ($letter == '!*') {
1133
            $letter = '[^\\\\p{L}\\\\p{N}].*';
1134
            $useRegex = true;
1135
        }
1136
1137
        # make text query clause
1138
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
1139
        if ($useRegex) {
1140
            $filtercondLabel = $lang ? "regex(str(?label), '^$letter$', 'i') && langMatches(lang(?label), '$lang')" : "regex(str(?label), '^$letter$', 'i')";
1141
            $filtercondALabel = $lang ? "regex(str(?alabel), '^$letter$', 'i') && langMatches(lang(?alabel), '$lang')" : "regex(str(?alabel), '^$letter$', 'i')";
1142
        } else {
1143
            $filtercondLabel = $lang ? "strstarts(lcase(str(?label)), '$lcletter') && langMatches(lang(?label), '$lang')" : "strstarts(lcase(str(?label)), '$lcletter')";
1144
            $filtercondALabel = $lang ? "strstarts(lcase(str(?alabel)), '$lcletter') && langMatches(lang(?alabel), '$lang')" : "strstarts(lcase(str(?alabel)), '$lcletter')";
1145
        }
1146
        return array('filterpref' => $filtercondLabel, 'filteralt' => $filtercondALabel);
1147
    }
1148
1149
    /**
1150
     * Generates the sparql query used for rendering the alphabetical index.
1151
     * @param string $letter the letter (or special class) to search for
1152
     * @param string $lang language of labels
1153
     * @param integer $limit limits the amount of results
1154
     * @param integer $offset offsets the result set
1155
     * @param array|null $classes
1156
     * @return string sparql query
1157
     */
1158
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes) {
1159
        $fcl = $this->generateFromClause();
1160
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1161
        $values = $this->formatValues('?type', $classes, 'uri');
1162
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
1163
        $conditions = $this->formatFilterConditions($letter, $lang);
1164
        $filtercondLabel = $conditions['filterpref'];
1165
        $filtercondALabel = $conditions['filteralt'];
1166
1167
        $query = <<<EOQ
1168
SELECT DISTINCT ?s ?label ?alabel $fcl
1169
WHERE {
1170
  {
1171
    ?s skos:prefLabel ?label .
1172
    FILTER (
1173
      $filtercondLabel
1174
    )
1175
  }
1176
  UNION
1177
  {
1178
    {
1179
      ?s skos:altLabel ?alabel .
1180
      FILTER (
1181
        $filtercondALabel
1182
      )
1183
    }
1184
    {
1185
      ?s skos:prefLabel ?label .
1186
      FILTER (langMatches(lang(?label), '$lang'))
1187
    }
1188
  }
1189
  ?s a ?type .
1190
  FILTER NOT EXISTS { ?s owl:deprecated true }
1191
  $values
1192
}
1193
ORDER BY LCASE(IF(BOUND(?alabel), STR(?alabel), STR(?label))) $limitandoffset
1194
EOQ;
1195
        return $query;
1196
    }
1197
1198
    /**
1199
     * Transforms the alphabetical list query results into an array format.
1200
     * @param EasyRdf_Sparql_Result $results
1201
     * @return array
1202
     */
1203
    private function transformAlphabeticalListResults($results) {
1204
        $ret = array();
1205
1206
        foreach ($results as $row) {
1207
            if (!isset($row->s)) {
1208
                continue;
1209
            }
1210
            // don't break if query returns a single dummy result
1211
1212
            $hit = array();
1213
            $hit['uri'] = $row->s->getUri();
1214
1215
            $hit['localname'] = $row->s->localName();
1216
1217
            $hit['prefLabel'] = $row->label->getValue();
1218
            $hit['lang'] = $row->label->getLang();
1219
1220 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...
1221
                $hit['altLabel'] = $row->alabel->getValue();
1222
                $hit['lang'] = $row->alabel->getLang();
1223
            }
1224
1225
            $ret[] = $hit;
1226
        }
1227
1228
        return $ret;
1229
    }
1230
1231
    /**
1232
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1233
     * '*!' (special characters) and '*' (everything) are accepted.
1234
     * @param string $letter the letter (or special class) to search for
1235
     * @param string $lang language of labels
1236
     * @param integer $limit limits the amount of results
1237
     * @param integer $offset offsets the result set
1238
     * @param array $classes
1239
     */
1240
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null) {
1241
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes);
1242
        $results = $this->query($query);
1243
        return $this->transformAlphabeticalListResults($results);
1244
    }
1245
1246
    /**
1247
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1248
     * @param string $lang language
1249
     * @return string sparql query
1250
     */
1251
    private function generateFirstCharactersQuery($lang, $classes) {
1252
        $fcl = $this->generateFromClause();
1253
        $classes = (sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1254
        $values = $this->formatValues('?type', $classes, 'uri');
1255
        $query = <<<EOQ
1256
SELECT DISTINCT (substr(ucase(str(?label)), 1, 1) as ?l) $fcl WHERE {
1257
  ?c skos:prefLabel ?label .
1258
  ?c a ?type
1259
  FILTER(langMatches(lang(?label), '$lang'))
1260
  $values
1261
}
1262
EOQ;
1263
        return $query;
1264
    }
1265
1266
    /**
1267
     * Transforms the first characters query results into an array format.
1268
     * @param EasyRdf_Sparql_Result $result
1269
     * @return array
1270
     */
1271
    private function transformFirstCharactersResults($result) {
1272
        $ret = array();
1273
        foreach ($result as $row) {
1274
            $ret[] = $row->l->getValue();
1275
        }
1276
        return $ret;
1277
    }
1278
1279
    /**
1280
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1281
     * @param string $lang language
1282
     * @return array array of characters
1283
     */
1284
    public function queryFirstCharacters($lang, $classes = null) {
1285
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1286
        $result = $this->query($query);
1287
        return $this->transformFirstCharactersResults($result);
1288
    }
1289
1290
    /**
1291
     * @param string $uri
1292
     * @param string $lang
1293
     * @return string sparql query string
1294
     */
1295
    private function generateLabelQuery($uri, $lang) {
1296
        $fcl = $this->generateFromClause();
1297
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1298
        $query = <<<EOQ
1299
SELECT ?label $fcl
1300
WHERE {
1301
  <$uri> a ?type .
1302
  OPTIONAL {
1303
    <$uri> skos:prefLabel ?label .
1304
    $labelcondLabel
1305
  }
1306
  OPTIONAL {
1307
    <$uri> rdfs:label ?label .
1308
    $labelcondLabel
1309
  }
1310
  OPTIONAL {
1311
    <$uri> dc:title ?label .
1312
    $labelcondLabel
1313
  }
1314
  OPTIONAL {
1315
    <$uri> dc11:title ?label .
1316
    $labelcondLabel
1317
  }
1318
}
1319
EOQ;
1320
        return $query;
1321
    }
1322
1323
    /**
1324
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1325
     * @param string $uri
1326
     * @param string $lang
1327
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1328
     */
1329
    public function queryLabel($uri, $lang) {
1330
        $query = $this->generateLabelQuery($uri, $lang);
1331
        $result = $this->query($query);
1332
        $ret = array();
1333
        foreach ($result as $row) {
1334
            if (!isset($row->label)) {
1335
                // existing concept but no labels
1336
                return array();
1337
            }
1338
            $ret[$row->label->getLang()] = $row->label;
1339
        }
1340
1341
        if (sizeof($ret) > 0) {
1342
            // existing concept, with label(s)
1343
            return $ret;
1344
        } else {
1345
            // nonexistent concept
1346
            return null;
1347
        }
1348
    }
1349
1350
1351
    /**
1352
     * Generates a sparql query for queryNotation.
1353
     * @param string $uri
1354
     * @return string sparql query
1355
     */
1356
    private function generateNotationQuery($uri) {
1357
        $fcl = $this->generateFromClause();
1358
1359
        $query = <<<EOQ
1360
SELECT * $fcl
1361
WHERE {
1362
  <$uri> skos:notation ?notation .
1363
}
1364
EOQ;
1365
        return $query;
1366
    }
1367
1368
    /**
1369
     * Query for the notation of the concept (skos:notation) of a resource.
1370
     * @param string $uri
1371
     * @return string notation or null if it doesn't exist
1372
     */
1373
    public function queryNotation($uri) {
1374
        $query = $this->generateNotationQuery($uri);
1375
        $result = $this->query($query);
1376
        $ret = array();
0 ignored issues
show
Unused Code introduced by
$ret is not used, you could remove the assignment.

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

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

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

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

Loading history...
1377
        foreach ($result as $row) {
1378
            if (isset($row->notation)) {
1379
                return $row->notation->getValue();
1380
            }
1381
        }
1382
        return null;
1383
    }
1384
1385
    /**
1386
     * Generates a sparql query for queryProperty.
1387
     * @param string $uri
1388
     * @param string $prop the name of the property eg. 'skos:broader'.
1389
     * @param string $lang
1390
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1391
     * @return string sparql query
1392
     */
1393 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...
1394
        $fcl = $this->generateFromClause();
1395
        $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1396
1397
        $query = <<<EOQ
1398
SELECT * $fcl
1399
WHERE {
1400
  <$uri> a skos:Concept .
1401
  OPTIONAL {
1402
    <$uri> $prop ?object .
1403
    OPTIONAL {
1404
      ?object skos:prefLabel ?label .
1405
      FILTER (langMatches(lang(?label), "$lang"))
1406
    }
1407
    OPTIONAL {
1408
      ?object skos:prefLabel ?label .
1409
      FILTER (lang(?label) = "")
1410
    }
1411
    $anylang
1412
  }
1413
}
1414
EOQ;
1415
        return $query;
1416
    }
1417
1418
    /**
1419
     * Transforms the sparql query result into an array or null if the concept doesn't exist.
1420
     * @param EasyRdf_Sparql_Result $result
1421
     * @param string $lang
1422
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1423
     */
1424
    private function transformPropertyQueryResults($result, $lang) {
1425
        $ret = array();
1426
        foreach ($result as $row) {
1427
            if (!isset($row->object)) {
1428
                return array();
1429
            }
1430
            // existing concept but no properties
1431
            if (isset($row->label)) {
1432
                if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1433
                    $ret[$row->object->getUri()]['label'] = $row->label->getValue();
1434
                }
1435
1436
            } else {
1437
                $ret[$row->object->getUri()]['label'] = null;
1438
            }
1439
        }
1440
        if (sizeof($ret) > 0) {
1441
            return $ret;
1442
        }
1443
        // existing concept, with properties
1444
        else {
1445
            return null;
1446
        }
1447
        // nonexistent concept
1448
    }
1449
1450
    /**
1451
     * Query a single property of a concept.
1452
     * @param string $uri
1453
     * @param string $prop the name of the property eg. 'skos:broader'.
1454
     * @param string $lang
1455
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1456
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1457
     */
1458
    public function queryProperty($uri, $prop, $lang, $anylang = false) {
1459
        $uri = is_array($uri) ? $uri[0] : $uri;
1460
        $query = $this->generatePropertyQuery($uri, $prop, $lang, $anylang);
1461
        $result = $this->query($query);
1462
        return $this->transformPropertyQueryResults($result, $lang);
1463
    }
1464
1465
    /**
1466
     * Query a single transitive property of a concept.
1467
     * @param string $uri
1468
     * @param array $props the name of the property eg. 'skos:broader'.
1469
     * @param string $lang
1470
     * @param integer $limit
1471
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1472
     * @return string sparql query
1473
     */
1474
    private function generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang) {
1475
        $uri = is_array($uri) ? $uri[0] : $uri;
1476
        $fcl = $this->generateFromClause();
1477
        $propertyClause = implode('|', $props);
1478
        $otherlang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1479
        // need to do a SPARQL subquery because LIMIT needs to be applied /after/
1480
        // the direct relationships have been collapsed into one string
1481
        $query = <<<EOQ
1482
SELECT * $fcl
1483
WHERE {
1484
  SELECT ?object ?label (GROUP_CONCAT(?dir) as ?direct)
1485
  WHERE {
1486
    <$uri> a skos:Concept .
1487
    OPTIONAL {
1488
      <$uri> $propertyClause* ?object .
1489
      OPTIONAL {
1490
        ?object $propertyClause ?dir .
1491
      }
1492
    }
1493
    OPTIONAL {
1494
      ?object skos:prefLabel ?label .
1495
      FILTER (langMatches(lang(?label), "$lang"))
1496
    }
1497
    $otherlang
1498
  }
1499
  GROUP BY ?object ?label
1500
}
1501
LIMIT $limit
1502
EOQ;
1503
        return $query;
1504
    }
1505
1506
    /**
1507
     * Transforms the sparql query result object into an array.
1508
     * @param EasyRdf_Sparql_Result $result
1509
     * @param string $lang
1510
     * @param string $fallbacklang language to use if label is not available in the preferred language
1511
     * @return array of property values (key: URI, val: label), or null if concept doesn't exist
1512
     */
1513
    private function transformTransitivePropertyResults($result, $lang, $fallbacklang) {
1514
        $ret = array();
1515
        foreach ($result as $row) {
1516
            if (!isset($row->object)) {
1517
                return array();
1518
            }
1519
            // existing concept but no properties
1520
            if (isset($row->label)) {
1521
                $val = array('label' => $row->label->getValue());
1522
            } else {
1523
                $val = array('label' => null);
1524
            }
1525 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...
1526
                $val['direct'] = explode(' ', $row->direct->getValue());
1527
            }
1528
            // Preventing labels in a non preferred language overriding the preferred language.
1529
            if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1530
                if (!isset($row->label) || $row->label->getLang() === $lang) {
1531
                    $ret[$row->object->getUri()] = $val;
1532
                } elseif ($row->label->getLang() === $fallbacklang) {
1533
                    $val['label'] .= ' (' . $row->label->getLang() . ')';
1534
                    $ret[$row->object->getUri()] = $val;
1535
                }
1536
            }
1537
        }
1538
1539
        // second iteration of results to find labels for the ones that didn't have one in the preferred languages
1540
        foreach ($result as $row) {
1541
            if (isset($row->object) && array_key_exists($row->object->getUri(), $ret) === false) {
1542
                $val = array('label' => $row->label->getValue());
1543 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...
1544
                    $val['direct'] = explode(' ', $row->direct->getValue());
1545
                }
1546
                $ret[$row->object->getUri()] = $val;
1547
            }
1548
        }
1549
1550
        if (sizeof($ret) > 0) {
1551
            return $ret;
1552
        }
1553
        // existing concept, with properties
1554
        else {
1555
            return null;
1556
        }
1557
        // nonexistent concept
1558
    }
1559
1560
    /**
1561
     * Query a single transitive property of a concept.
1562
     * @param string $uri
1563
     * @param array $props the property/properties.
1564
     * @param string $lang
1565
     * @param string $fallbacklang language to use if label is not available in the preferred language
1566
     * @param integer $limit
1567
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1568
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1569
     */
1570
    public function queryTransitiveProperty($uri, $props, $lang, $limit, $anylang = false, $fallbacklang = '') {
1571
        $query = $this->generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang);
1572
        $result = $this->query($query);
1573
        return $this->transformTransitivePropertyResults($result, $lang, $fallbacklang);
1574
    }
1575
1576
    /**
1577
     * Generates the query for a concepts skos:narrowers.
1578
     * @param string $uri
1579
     * @param string $lang
1580
     * @param string $fallback
1581
     * @return string sparql query
1582
     */
1583
    private function generateChildQuery($uri, $lang, $fallback, $props) {
1584
        $uri = is_array($uri) ? $uri[0] : $uri;
1585
        $fcl = $this->generateFromClause();
1586
        $propertyClause = implode('|', $props);
1587
        $query = <<<EOQ
1588
SELECT ?child ?label ?child ?grandchildren ?notation $fcl WHERE {
1589
  <$uri> a skos:Concept .
1590
  OPTIONAL {
1591
    ?child $propertyClause <$uri> .
1592
    OPTIONAL {
1593
      ?child skos:prefLabel ?label .
1594
      FILTER (langMatches(lang(?label), "$lang"))
1595
    }
1596
    OPTIONAL {
1597
      ?child skos:prefLabel ?label .
1598
      FILTER (langMatches(lang(?label), "$fallback"))
1599
    }
1600
    OPTIONAL { # other language case
1601
      ?child skos:prefLabel ?label .
1602
    }
1603
    OPTIONAL {
1604
      ?child skos:notation ?notation .
1605
    }
1606
    BIND ( EXISTS { ?a $propertyClause ?child . } AS ?grandchildren )
1607
  }
1608
}
1609
EOQ;
1610
        return $query;
1611
    }
1612
1613
    /**
1614
     * Transforms the sparql result object into an array.
1615
     * @param EasyRdf_Sparql_Result $result
1616
     * @param string $lang
1617
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1618
     */
1619
    private function transformNarrowerResults($result, $lang) {
1620
        $ret = array();
1621
        foreach ($result as $row) {
1622
            if (!isset($row->child)) {
1623
                return array();
1624
            }
1625
            // existing concept but no children
1626
1627
            $label = null;
1628
            if (isset($row->label)) {
1629
                if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
1630
                    $label = $row->label->getValue();
1631
                } else {
1632
                    $label = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1633
                }
1634
1635
            }
1636
            $childArray = array(
1637
                'uri' => $row->child->getUri(),
1638
                'prefLabel' => $label,
1639
                'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1640
            );
1641
            if (isset($row->notation)) {
1642
                $childArray['notation'] = $row->notation->getValue();
1643
            }
1644
1645
            $ret[] = $childArray;
1646
        }
1647
        if (sizeof($ret) > 0) {
1648
            return $ret;
1649
        }
1650
        // existing concept, with children
1651
        else {
1652
            return null;
1653
        }
1654
        // nonexistent concept
1655
    }
1656
1657
    /**
1658
     * Query the narrower concepts of a concept.
1659
     * @param string $uri
1660
     * @param string $lang
1661
     * @param string $fallback
1662
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1663
     */
1664
    public function queryChildren($uri, $lang, $fallback, $props) {
1665
        $query = $this->generateChildQuery($uri, $lang, $fallback, $props);
1666
        $result = $this->query($query);
1667
        return $this->transformNarrowerResults($result, $lang);
1668
    }
1669
1670
    /**
1671
     * Query the top concepts of a vocabulary.
1672
     * @param string $conceptSchemes concept schemes whose top concepts to query for
1673
     * @param string $lang language of labels
1674
     * @param string $fallback language to use if label is not available in the preferred language
1675
     */
1676
    public function queryTopConcepts($conceptSchemes, $lang, $fallback) {
1677
        if (!is_array($conceptSchemes)) {
1678
            $conceptSchemes = array($conceptSchemes);
1679
        }
1680
1681
        $values = $this->formatValues('?topuri', $conceptSchemes, 'uri');
1682
1683
        $fcl = $this->generateFromClause();
1684
        $query = <<<EOQ
1685
SELECT DISTINCT ?top ?topuri ?label ?notation ?children $fcl WHERE {
1686
  ?top skos:topConceptOf ?topuri .
1687
  OPTIONAL {
1688
    ?top skos:prefLabel ?label .
1689
    FILTER (langMatches(lang(?label), "$lang"))
1690
  }
1691
  OPTIONAL {
1692
    ?top skos:prefLabel ?label .
1693
    FILTER (langMatches(lang(?label), "$fallback"))
1694
  }
1695
  OPTIONAL { # fallback - other language case
1696
    ?top skos:prefLabel ?label .
1697
  }
1698
  OPTIONAL { ?top skos:notation ?notation . }
1699
  BIND ( EXISTS { ?top skos:narrower ?a . } AS ?children )
1700
  $values
1701
}
1702
EOQ;
1703
        $result = $this->query($query);
1704
        $ret = array();
1705
        foreach ($result as $row) {
1706
            if (isset($row->top) && isset($row->label)) {
1707
                $label = $row->label->getValue();
1708 View Code Duplication
                if ($row->label->getLang() && $row->label->getLang() !== $lang && strpos($row->label->getLang(), $lang . "-") !== 0) {
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...
1709
                    $label .= ' (' . $row->label->getLang() . ')';
1710
                }
1711
                $top = array('uri' => $row->top->getUri(), 'topConceptOf' => $row->topuri->getUri(), 'label' => $label, 'hasChildren' => filter_var($row->children->getValue(), FILTER_VALIDATE_BOOLEAN));
1712
                if (isset($row->notation)) {
1713
                    $top['notation'] = $row->notation->getValue();
1714
                }
1715
1716
                $ret[] = $top;
1717
            }
1718
        }
1719
1720
        return $ret;
1721
    }
1722
1723
    /**
1724
     * Generates a sparql query for finding the hierarchy for a concept.
1725
     * @param string $uri concept uri.
1726
     * @param string $lang
1727
     * @param string $fallback language to use if label is not available in the preferred language
1728
     * @return string sparql query
1729
     */
1730
    private function generateParentListQuery($uri, $lang, $fallback, $props) {
1731
        $fcl = $this->generateFromClause();
1732
        $propertyClause = implode('|', $props);
1733
        $query = <<<EOQ
1734
SELECT ?broad ?parent ?member ?children ?grandchildren
1735
(SAMPLE(?lab) as ?label) (SAMPLE(?childlab) as ?childlabel) (SAMPLE(?topcs) AS ?top) (SAMPLE(?nota) as ?notation) (SAMPLE(?childnota) as ?childnotation) $fcl
1736
WHERE {
1737
  <$uri> a skos:Concept .
1738
  OPTIONAL {
1739
    <$uri> $propertyClause* ?broad .
1740
    OPTIONAL {
1741
      ?broad skos:prefLabel ?lab .
1742
      FILTER (langMatches(lang(?lab), "$lang"))
1743
    }
1744
    OPTIONAL {
1745
      ?broad skos:prefLabel ?lab .
1746
      FILTER (langMatches(lang(?lab), "$fallback"))
1747
    }
1748
    OPTIONAL { # fallback - other language case
1749
      ?broad skos:prefLabel ?lab .
1750
    }
1751
    OPTIONAL { ?broad skos:notation ?nota . }
1752
    OPTIONAL { ?broad $propertyClause ?parent . }
1753
    OPTIONAL { ?broad skos:narrower ?children .
1754
      OPTIONAL {
1755
        ?children skos:prefLabel ?childlab .
1756
        FILTER (langMatches(lang(?childlab), "$lang"))
1757
      }
1758
      OPTIONAL {
1759
        ?children skos:prefLabel ?childlab .
1760
        FILTER (langMatches(lang(?childlab), "$fallback"))
1761
      }
1762
      OPTIONAL { # fallback - other language case
1763
        ?children skos:prefLabel ?childlab .
1764
      }
1765
      OPTIONAL {
1766
        ?children skos:notation ?childnota .
1767
      }
1768
    }
1769
    BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren )
1770
    OPTIONAL { ?broad skos:topConceptOf ?topcs . }
1771
  }
1772
}
1773
GROUP BY ?broad ?parent ?member ?children ?grandchildren
1774
EOQ;
1775
        return $query;
1776
    }
1777
1778
    /**
1779
     * Transforms the result into an array.
1780
     * @param EasyRdf_Sparql_Result
1781
     * @param string $lang
1782
     * @return an array for the REST controller to encode.
1783
     */
1784
    private function transformParentListResults($result, $lang)
1785
    {
1786
        $ret = array();
1787
        foreach ($result as $row) {
1788
            if (!isset($row->broad)) {
1789
                // existing concept but no broaders
1790
                return array();
1791
            }
1792
            $uri = $row->broad->getUri();
1793
            if (!isset($ret[$uri])) {
1794
                $ret[$uri] = array('uri' => $uri);
1795
            }
1796
            if (isset($row->exact)) {
1797
                $ret[$uri]['exact'] = $row->exact->getUri();
1798
            }
1799
            if (isset($row->top)) {
1800
                $ret[$uri]['top'] = $row->top->getUri();
1801
            }
1802
            if (isset($row->children)) {
1803
                if (!isset($ret[$uri]['narrower'])) {
1804
                    $ret[$uri]['narrower'] = array();
1805
                }
1806
1807
                $label = null;
1808
                if (isset($row->childlabel)) {
1809
                    $label = $row->childlabel->getValue();
1810
                    if ($row->childlabel->getLang() !== $lang && strpos($row->childlabel->getLang(), $lang . "-") !== 0) {
1811
                        $label .= " (" . $row->childlabel->getLang() . ")";
1812
                    }
1813
1814
                }
1815
1816
                $childArr = array(
1817
                    'uri' => $row->children->getUri(),
1818
                    'label' => $label,
1819
                    'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1820
                );
1821
                if (isset($row->childnotation)) {
1822
                    $childArr['notation'] = $row->childnotation->getValue();
1823
                }
1824
1825
                if (!in_array($childArr, $ret[$uri]['narrower'])) {
1826
                    $ret[$uri]['narrower'][] = $childArr;
1827
                }
1828
1829
            }
1830
            if (isset($row->label)) {
1831
                $preflabel = $row->label->getValue();
1832 View Code Duplication
                if ($row->label->getLang() && $row->label->getLang() !== $lang && strpos($row->label->getLang(), $lang . "-") !== 0) {
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...
1833
                    $preflabel .= ' (' . $row->label->getLang() . ')';
1834
                }
1835
1836
                $ret[$uri]['prefLabel'] = $preflabel;
1837
            }
1838
            if (isset($row->notation)) {
1839
                $ret[$uri]['notation'] = $row->notation->getValue();
1840
            }
1841
1842
            if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) {
1843
                $ret[$uri]['broader'][] = $row->parent->getUri();
1844
            } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) {
1845
                $ret[$uri]['broader'][] = $row->parent->getUri();
1846
            }
1847
        }
1848
        if (sizeof($ret) > 0) {
1849
            // existing concept, with children
1850
            return $ret;
1851
        }
1852
        else {
1853
            // nonexistent concept
1854
            return null;
1855
        }
1856
    }
1857
1858
    /**
1859
     * Query for finding the hierarchy for a concept.
1860
     * @param string $uri concept uri.
1861
     * @param string $lang
1862
     * @param string $fallback language to use if label is not available in the preferred language
1863
     * @param array $props the hierarchy property/properties to use
1864
     * @return an array for the REST controller to encode.
1865
     */
1866
    public function queryParentList($uri, $lang, $fallback, $props) {
1867
        $query = $this->generateParentListQuery($uri, $lang, $fallback, $props);
1868
        $result = $this->query($query);
1869
        return $this->transformParentListResults($result, $lang);
1870
    }
1871
1872
    /**
1873
     * return a list of concept group instances, sorted by label
1874
     * @param string $groupClass URI of concept group class
1875
     * @param string $lang language of labels to return
1876
     * @return string sparql query
1877
     */
1878
    private function generateConceptGroupsQuery($groupClass, $lang) {
1879
        $fcl = $this->generateFromClause();
1880
        $query = <<<EOQ
1881
SELECT ?group (GROUP_CONCAT(DISTINCT STR(?child)) as ?children) ?label ?members ?notation $fcl
1882
WHERE {
1883
  ?group a <$groupClass> .
1884
  OPTIONAL { ?group skos:member|isothes:subGroup ?child .
1885
             ?child a <$groupClass> }
1886
  BIND(EXISTS{?group skos:member ?submembers} as ?members)
1887
  OPTIONAL { ?group skos:prefLabel ?label }
1888
  OPTIONAL { ?group rdfs:label ?label }
1889
  FILTER (langMatches(lang(?label), '$lang'))
1890
  OPTIONAL { ?group skos:notation ?notation }
1891
}
1892
GROUP BY ?group ?label ?members ?notation
1893
ORDER BY lcase(?label)
1894
EOQ;
1895
        return $query;
1896
    }
1897
1898
    /**
1899
     * Transforms the sparql query result into an array.
1900
     * @param EasyRdf_Sparql_Result $result
1901
     * @return array
1902
     */
1903
    private function transformConceptGroupsResults($result) {
1904
        $ret = array();
1905
        foreach ($result as $row) {
1906
            if (!isset($row->group)) {
1907
                # no groups found, see issue #357
1908
                continue;
1909
            }
1910
            $group = array('uri' => $row->group->getURI());
1911
            if (isset($row->label)) {
1912
                $group['prefLabel'] = $row->label->getValue();
1913
            }
1914
1915
            if (isset($row->children)) {
1916
                $group['childGroups'] = explode(' ', $row->children->getValue());
1917
            }
1918
1919
            if (isset($row->members)) {
1920
                $group['hasMembers'] = $row->members->getValue();
1921
            }
1922
1923
            if (isset($row->notation)) {
1924
                $group['notation'] = $row->notation->getValue();
1925
            }
1926
1927
            $ret[] = $group;
1928
        }
1929
        return $ret;
1930
    }
1931
1932
    /**
1933
     * return a list of concept group instances, sorted by label
1934
     * @param string $groupClass URI of concept group class
1935
     * @param string $lang language of labels to return
1936
     * @return array Result array with group URI as key and group label as value
1937
     */
1938
    public function listConceptGroups($groupClass, $lang) {
1939
        $query = $this->generateConceptGroupsQuery($groupClass, $lang);
1940
        $result = $this->query($query);
1941
        return $this->transformConceptGroupsResults($result);
1942
    }
1943
1944
    /**
1945
     * Generates the sparql query for listConceptGroupContents
1946
     * @param string $groupClass URI of concept group class
1947
     * @param string $group URI of the concept group instance
1948
     * @param string $lang language of labels to return
1949
     * @return string sparql query
1950
     */
1951
    private function generateConceptGroupContentsQuery($groupClass, $group, $lang) {
1952
        $fcl = $this->generateFromClause();
1953
        $query = <<<EOQ
1954
SELECT ?conc ?super ?label ?members ?type ?notation $fcl
1955
WHERE {
1956
 <$group> a <$groupClass> .
1957
 { <$group> skos:member ?conc . } UNION { ?conc isothes:superGroup <$group> }
1958
 FILTER NOT EXISTS { ?conc owl:deprecated true }
1959
 ?conc a ?type .
1960
 OPTIONAL { ?conc skos:prefLabel ?label .
1961
  FILTER (langMatches(lang(?label), '$lang'))
1962
 }
1963
 OPTIONAL { ?conc skos:prefLabel ?label . }
1964
 OPTIONAL { ?conc skos:notation ?notation }
1965
 BIND(EXISTS{?submembers isothes:superGroup ?conc} as ?super)
1966
 BIND(EXISTS{?conc skos:member ?submembers} as ?members)
1967
} ORDER BY lcase(?label)
1968
EOQ;
1969
        return $query;
1970
    }
1971
1972
    /**
1973
     * Transforms the sparql query result into an array.
1974
     * @param EasyRdf_Sparql_Result $result
1975
     * @param string $lang language of labels to return
1976
     * @return array
1977
     */
1978
    private function transformConceptGroupContentsResults($result, $lang) {
1979
        $ret = array();
1980
        $values = array();
1981
        foreach ($result as $row) {
1982
            if (!array_key_exists($row->conc->getURI(), $values)) {
1983
                $values[$row->conc->getURI()] = array(
1984
                    'uri' => $row->conc->getURI(),
1985
                    'isSuper' => $row->super->getValue(),
1986
                    'hasMembers' => $row->members->getValue(),
1987
                    'type' => array($row->type->shorten()),
1988
                );
1989
                if (isset($row->label)) {
1990
                    if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
1991
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue();
1992
                    } else {
1993
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1994
                    }
1995
1996
                }
1997
                if (isset($row->notation)) {
1998
                    $values[$row->conc->getURI()]['notation'] = $row->notation->getValue();
1999
                }
2000
2001
            } else {
2002
                $values[$row->conc->getURI()]['type'][] = $row->type->shorten();
2003
            }
2004
        }
2005
2006
        foreach ($values as $val) {
2007
            $ret[] = $val;
2008
        }
2009
2010
        return $ret;
2011
    }
2012
2013
    /**
2014
     * return a list of concepts in a concept group
2015
     * @param string $groupClass URI of concept group class
2016
     * @param string $group URI of the concept group instance
2017
     * @param string $lang language of labels to return
2018
     * @return array Result array with concept URI as key and concept label as value
2019
     */
2020
    public function listConceptGroupContents($groupClass, $group, $lang) {
2021
        $query = $this->generateConceptGroupContentsQuery($groupClass, $group, $lang);
2022
        $result = $this->query($query);
2023
        return $this->transformConceptGroupContentsResults($result, $lang);
2024
    }
2025
2026
    /**
2027
     * Generates the sparql query for queryChangeList.
2028
     * @param string $lang language of labels to return.
2029
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2030
     * @return string sparql query
2031
     */
2032 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...
2033
        $fcl = $this->generateFromClause();
2034
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
2035
2036
        $query = <<<EOQ
2037
SELECT DISTINCT ?concept ?date ?label $fcl
2038
WHERE {
2039
  ?concept a skos:Concept .
2040
  ?concept $prop ?date .
2041
  ?concept skos:prefLabel ?label .
2042
  FILTER (langMatches(lang(?label), '$lang'))
2043
}
2044
ORDER BY DESC(YEAR(?date)) DESC(MONTH(?date)) LCASE(?label)
2045
LIMIT 200 $offset
2046
EOQ;
2047
        return $query;
2048
    }
2049
2050
    /**
2051
     * Transforms the sparql query result into an array.
2052
     * @param EasyRdf_Sparql_Result $result
2053
     * @return array
2054
     */
2055 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...
2056
        $ret = array();
2057
        foreach ($result as $row) {
2058
            $concept = array('uri' => $row->concept->getURI());
2059
            if (isset($row->label)) {
2060
                $concept['prefLabel'] = $row->label->getValue();
2061
            }
2062
2063
            if (isset($row->date)) {
2064
                $concept['date'] = $row->date->getValue();
2065
            }
2066
2067
            $ret[] = $concept;
2068
        }
2069
        return $ret;
2070
    }
2071
2072
    /**
2073
     * return a list of recently changed or entirely new concepts
2074
     * @param string $lang language of labels to return
2075
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2076
     * @return array Result array
2077
     */
2078
    public function queryChangeList($lang, $offset, $prop) {
2079
        $query = $this->generateChangeListQuery($lang, $offset, $prop);
2080
        $result = $this->query($query);
2081
        return $this->transformChangeListResults($result);
2082
    }
2083
}
2084