Completed
Push — master ( a56b99...4d505a )
by
unknown
02:02 queued 11s
created

GenericSparql::generateLangClause()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Generates SPARQL queries and provides access to the SPARQL endpoint.
5
 */
6
class GenericSparql {
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 string|null $graph Which graph to query: Either an URI, the special value "?graph"
38
     *                           to use the default graph, or NULL to not use a GRAPH clause.
39
     * @param object $model a Model instance.
40
     */
41
    public function __construct($endpoint, $graph, $model) {
42
        $this->graph = $graph;
43
        $this->model = $model;
44
45
        // create the EasyRDF SPARQL client instance to use
46
        $this->initializeHttpClient();
47
        $this->client = new EasyRdf\Sparql\Client($endpoint);
48
49
        // set graphClause so that it can be used by all queries
50
        if ($this->isDefaultEndpoint()) // default endpoint; query any graph (and catch it in a variable)
51
        {
52
            $this->graphClause = "GRAPH $graph";
53
        } elseif ($graph !== null) // query a specific graph
54
        {
55
            $this->graphClause = "GRAPH <$graph>";
56
        } else // query the default graph
57
        {
58
            $this->graphClause = "";
59
        }
60
61
    }
62
63
    /**
64
     * Returns prefix-definitions for a query
65
     *
66
     * @param string $query
67
     * @return string
68
    */
69
    protected function generateQueryPrefixes($query)
70
    {
71
        // Check for undefined prefixes
72
        $prefixes = '';
73
        foreach (EasyRdf\RdfNamespace::namespaces() as $prefix => $uri) {
74
            if (strpos($query, "{$prefix}:") !== false and
75
                strpos($query, "PREFIX {$prefix}:") === false
76
            ) {
77
                $prefixes .= "PREFIX {$prefix}: <{$uri}>\n";
78
            }
79
        }
80
        return $prefixes;
81
    }
82
83
    /**
84
     * Execute the SPARQL query using the SPARQL client, logging it as well.
85
     * @param string $query SPARQL query to perform
86
     * @return Result|\EasyRdf\Graph query result
87
     */
88
    protected function query($query) {
89
        $queryId = sprintf("%05d", rand(0, 99999));
90
        $logger = $this->model->getLogger();
91
        $logger->info("[qid $queryId] SPARQL query:\n" . $this->generateQueryPrefixes($query) . "\n$query\n");
92
        $starttime = microtime(true);
93
        $result = $this->client->query($query);
94
        $elapsed = intval(round((microtime(true) - $starttime) * 1000));
95
        if(method_exists($result, 'numRows')) {
96
            $numRows = $result->numRows();
0 ignored issues
show
Bug introduced by
The method numRows does only exist in EasyRdf\Sparql\Result, but not in EasyRdf\Graph and EasyRdf\Http\Response.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
97
            $logger->info("[qid $queryId] result: $numRows rows returned in $elapsed ms");
98
        } else { // graph result
99
            $numTriples = $result->countTriples();
0 ignored issues
show
Bug introduced by
The method countTriples does only exist in EasyRdf\Graph, but not in EasyRdf\Http\Response and EasyRdf\Sparql\Result.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
100
            $logger->info("[qid $queryId] result: $numTriples triples returned in $elapsed ms");
101
        }
102
        return $result;
103
    }
104
105
106
    /**
107
     * Generates FROM clauses for the queries
108
     * @param Vocabulary[]|null $vocabs
109
     * @return string
110
     */
111
    protected function generateFromClause($vocabs=null) {
112
        $clause = '';
113
        if (!$vocabs) {
114
            return $this->graph !== '?graph' && $this->graph !== NULL ? "FROM <$this->graph>" : '';
115
        }
116
        $graphs = $this->getVocabGraphs($vocabs);
117
        foreach ($graphs as $graph) {
118
            $clause .= "FROM NAMED <$graph> ";
119
        }
120
        return $clause;
121
    }
122
123
    protected function initializeHttpClient() {
124
        // configure the HTTP client used by EasyRdf\Sparql\Client
125
        $httpclient = EasyRdf\Http::getDefaultHttpClient();
126
        $httpclient->setConfig(array('timeout' => $this->model->getConfig()->getSparqlTimeout()));
127
128
        // if special cache control (typically no-cache) was requested by the
129
        // client, set the same type of cache control headers also in subsequent
130
        // in the SPARQL requests (this is useful for performance testing)
131
        // @codeCoverageIgnoreStart
132
        $cacheControl = filter_input(INPUT_SERVER, 'HTTP_CACHE_CONTROL', FILTER_SANITIZE_STRING);
133
        $pragma = filter_input(INPUT_SERVER, 'HTTP_PRAGMA', FILTER_SANITIZE_STRING);
134
        if ($cacheControl !== null || $pragma !== null) {
135
            $val = $pragma !== null ? $pragma : $cacheControl;
136
            $httpclient->setHeaders('Cache-Control', $val);
137
        }
138
        // @codeCoverageIgnoreEnd
139
140
        EasyRdf\Http::setDefaultHttpClient($httpclient); // actually redundant..
141
    }
142
143
    /**
144
     * Return true if this is the default SPARQL endpoint, used as the facade to query
145
     * all vocabularies.
146
     */
147
148
    protected function isDefaultEndpoint() {
149
        return !is_null($this->graph) && $this->graph[0] == '?';
150
    }
151
152
    /**
153
     * Returns the graph instance
154
     * @return object EasyRDF graph instance.
155
     */
156
    public function getGraph() {
157
        return $this->graph;
158
    }
159
160
    /**
161
     * Shorten a URI
162
     * @param string $uri URI to shorten
163
     * @return string shortened URI, or original URI if it cannot be shortened
164
     */
165
    private function shortenUri($uri) {
166
        if (!array_key_exists($uri, $this->qnamecache)) {
167
            $res = new EasyRdf\Resource($uri);
168
            $qname = $res->shorten(); // returns null on failure
169
            $this->qnamecache[$uri] = ($qname !== null) ? $qname : $uri;
170
        }
171
        return $this->qnamecache[$uri];
172
    }
173
174
175
    /**
176
     * Generates the sparql query for retrieving concept and collection counts in a vocabulary.
177
     * @return string sparql query
178
     */
179
    private function generateCountConceptsQuery($array, $group) {
180
        $fcl = $this->generateFromClause();
181
        $optional = $array ? "UNION { ?type rdfs:subClassOf* <$array> }" : '';
182
        $optional .= $group ? "UNION { ?type rdfs:subClassOf* <$group> }" : '';
183
        $query = <<<EOQ
184
      SELECT (COUNT(?conc) as ?c) ?type ?typelabel $fcl WHERE {
185
        { ?conc a ?type .
186
        { ?type rdfs:subClassOf* skos:Concept . } UNION { ?type rdfs:subClassOf* skos:Collection . } $optional }
187
        OPTIONAL { ?type rdfs:label ?typelabel . }
188
      }
189
GROUP BY ?type ?typelabel
190
EOQ;
191
        return $query;
192
    }
193
194
    /**
195
     * Used for transforming the concept count query results.
196
     * @param EasyRdf\Sparql\Result $result query results to be transformed
197
     * @param string $lang language of labels
198
     * @return Array containing the label counts
199
     */
200
    private function transformCountConceptsResults($result, $lang) {
201
        $ret = array();
202
        foreach ($result as $row) {
203
            if (!isset($row->type)) {
204
                continue;
205
            }
206
            $ret[$row->type->getUri()]['type'] = $row->type->getUri();
207
            $ret[$row->type->getUri()]['count'] = $row->c->getValue();
208
            if (isset($row->typelabel) && $row->typelabel->getLang() === $lang) {
209
                $ret[$row->type->getUri()]['label'] = $row->typelabel->getValue();
210
            }
211
212
        }
213
        return $ret;
214
    }
215
216
    /**
217
     * Used for counting number of concepts and collections in a vocabulary.
218
     * @param string $lang language of labels
219
     * @return int number of concepts in this vocabulary
220
     */
221
    public function countConcepts($lang = null, $array = null, $group = null) {
222
        $query = $this->generateCountConceptsQuery($array, $group);
223
        $result = $this->query($query);
224
        return $this->transformCountConceptsResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 223 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformCountConceptsResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
225
    }
226
227
    /**
228
     * @param array $langs Languages to query for
229
     * @param string[] $props property names
230
     * @return string sparql query
231
     */
232
    private function generateCountLangConceptsQuery($langs, $classes, $props) {
233
        $gcl = $this->graphClause;
234
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
235
236
	$quote_string = function($val) { return "'$val'"; };
237
	$quoted_values = array_map($quote_string, $langs);
238
	$langFilter = "FILTER(?lang IN (" . implode(',', $quoted_values) . "))";
239
240
        $values = $this->formatValues('?type', $classes, 'uri');
241
        $valuesProp = $this->formatValues('?prop', $props, null);
242
243
        $query = <<<EOQ
244
SELECT ?lang ?prop
245
  (COUNT(?label) as ?count)
246
WHERE {
247
  $gcl {
248
    $values
249
    $valuesProp
250
    ?conc a ?type .
251
    ?conc ?prop ?label .
252
    BIND(LANG(?label) AS ?lang)
253
    $langFilter
254
  }
255
}
256
GROUP BY ?lang ?prop ?type
257
EOQ;
258
        return $query;
259
    }
260
261
    /**
262
     * Transforms the CountLangConcepts results into an array of label counts.
263
     * @param EasyRdf\Sparql\Result $result query results to be transformed
264
     * @param array $langs Languages to query for
265
     * @param string[] $props property names
266
     */
267
    private function transformCountLangConceptsResults($result, $langs, $props) {
268
        $ret = array();
269
        // set default count to zero; overridden below if query found labels
270
        foreach ($langs as $lang) {
271
            foreach ($props as $prop) {
272
                $ret[$lang][$prop] = 0;
273
            }
274
        }
275
        foreach ($result as $row) {
276
            if (isset($row->lang) && isset($row->prop) && isset($row->count)) {
277
                $ret[$row->lang->getValue()][$row->prop->shorten()] +=
278
                $row->count->getValue();
279
            }
280
281
        }
282
        ksort($ret);
283
        return $ret;
284
    }
285
286
    /**
287
     * Counts the number of concepts in a easyRDF graph with a specific language.
288
     * @param array $langs Languages to query for
289
     * @return Array containing count of concepts for each language and property.
290
     */
291
    public function countLangConcepts($langs, $classes = null) {
292
        $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel');
293
        $query = $this->generateCountLangConceptsQuery($langs, $classes, $props);
294
        // Count the number of terms in each language
295
        $result = $this->query($query);
296
        return $this->transformCountLangConceptsResults($result, $langs, $props);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 295 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformCountLangConceptsResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
297
    }
298
299
    /**
300
     * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one
301
     * of the constants given.
302
     * @param string $varname variable name, e.g. "?uri"
303
     * @param array $values the values
304
     * @param string $type type of values: "uri", "literal" or null (determines quoting style)
305
     */
306
    protected function formatValues($varname, $values, $type = null) {
307
        $constants = array();
308
        foreach ($values as $val) {
309
            if ($type == 'uri') {
310
                $val = "<$val>";
311
            }
312
313
            if ($type == 'literal') {
314
                $val = "'$val'";
315
            }
316
317
            $constants[] = "($val)";
318
        }
319
        $values = implode(" ", $constants);
320
321
        return "VALUES ($varname) { $values }";
322
    }
323
324
    /**
325
     * Filters multiple instances of the same vocabulary from the input array.
326
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
327
     * @return \Vocabulary[]
328
     */
329
    private function filterDuplicateVocabs($vocabs) {
330
        // filtering duplicates
331
        $uniqueVocabs = array();
332
        if ($vocabs !== null && sizeof($vocabs) > 0) {
333
            foreach ($vocabs as $voc) {
334
                $uniqueVocabs[$voc->getId()] = $voc;
335
            }
336
        }
337
338
        return $uniqueVocabs;
339
    }
340
341
    /**
342
     * Generates a sparql query for one or more concept URIs
343
     * @param mixed $uris concept URI (string) or array of URIs
344
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
345
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
346
     * @return string sparql query
347
     */
348
    private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) {
349
        $gcl = $this->graphClause;
350
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
351
        $values = $this->formatValues('?uri', $uris, 'uri');
352
        $uniqueVocabs = $this->filterDuplicateVocabs($vocabs);
353
        $valuesGraph = empty($vocabs) ? $this->formatValuesGraph($uniqueVocabs) : '';
354
355
        if ($arrayClass === null) {
356
            $construct = $optional = "";
357
        } else {
358
            // add information that can be used to format narrower concepts by
359
            // the array they belong to ("milk by source animal" use case)
360
            $construct = "\n ?x skos:member ?o . ?x skos:prefLabel ?xl . ?x a <$arrayClass> .";
361
            $optional = "\n OPTIONAL {
362
                      ?x skos:member ?o .
363
                      ?x a <$arrayClass> .
364
                      ?x skos:prefLabel ?xl .
365
                      FILTER NOT EXISTS {
366
                        ?x skos:member ?other .
367
                        MINUS { ?other skos:broader ?uri }
368
                      }
369
                    }";
370
        }
371
        $query = <<<EOQ
372
CONSTRUCT {
373
 ?s ?p ?uri .
374
 ?sp ?uri ?op .
375
 ?uri ?p ?o .
376
 ?p rdfs:label ?proplabel .
377
 ?p rdfs:subPropertyOf ?pp .
378
 ?pp rdfs:label ?plabel .
379
 ?o a ?ot .
380
 ?o skos:prefLabel ?opl .
381
 ?o rdfs:label ?ol .
382
 ?o rdf:value ?ov .
383
 ?o skos:notation ?on .
384
 ?o ?oprop ?oval .
385
 ?o ?xlprop ?xlval .
386
 ?directgroup skos:member ?uri .
387
 ?parent skos:member ?group .
388
 ?group skos:prefLabel ?grouplabel .
389
 ?b1 rdf:first ?item .
390
 ?b1 rdf:rest ?b2 .
391
 ?item a ?it .
392
 ?item skos:prefLabel ?il .
393
 ?group a ?grouptype . $construct
394
} $fcl WHERE {
395
 $values
396
 $gcl {
397
  {
398
    ?s ?p ?uri .
399
    FILTER(!isBlank(?s))
400
    FILTER(?p != skos:inScheme)
401
  }
402
  UNION
403
  { ?sp ?uri ?op . }
404
  UNION
405
  {
406
    ?directgroup skos:member ?uri .
407
    ?group skos:member+ ?uri .
408
    ?group skos:prefLabel ?grouplabel .
409
    ?group a ?grouptype .
410
    OPTIONAL { ?parent skos:member ?group }
411
  }
412
  UNION
413
  {
414
   ?uri ?p ?o .
415
   OPTIONAL {
416
     ?o rdf:rest* ?b1 .
417
     ?b1 rdf:first ?item .
418
     ?b1 rdf:rest ?b2 .
419
     OPTIONAL { ?item a ?it . }
420
     OPTIONAL { ?item skos:prefLabel ?il . }
421
   }
422
   OPTIONAL {
423
     { ?p rdfs:label ?proplabel . }
424
     UNION
425
     { ?p rdfs:subPropertyOf ?pp . }
426
   }
427
   OPTIONAL {
428
     { ?o a ?ot . }
429
     UNION
430
     { ?o skos:prefLabel ?opl . }
431
     UNION
432
     { ?o rdfs:label ?ol . }
433
     UNION
434
     { ?o rdf:value ?ov . 
435
       OPTIONAL { ?o ?oprop ?oval . }
436
     }
437
     UNION
438
     { ?o skos:notation ?on . }
439
     UNION
440
     { ?o a skosxl:Label .
441
       ?o ?xlprop ?xlval }
442
   } $optional
443
  }
444
 }
445
}
446
$valuesGraph
447
EOQ;
448
        return $query;
449
    }
450
451
    /**
452
     * Transforms ConceptInfo query results into an array of Concept objects
453
     * @param EasyRdf\Graph $result query results to be transformed
454
     * @param array $uris concept URIs
455
     * @param \Vocabulary[] $vocabs array of Vocabulary object
456
     * @param string|null $clang content language
457
     * @return Concept[] array of Concept objects
458
     */
459
    private function transformConceptInfoResults($result, $uris, $vocabs, $clang) {
460
        $conceptArray = array();
461
        foreach ($uris as $index => $uri) {
462
            $conc = $result->resource($uri);
463
            $vocab = (isset($vocabs) && sizeof($vocabs) == 1) ? $vocabs[0] : $vocabs[$index];
464
            $conceptArray[] = new Concept($this->model, $vocab, $conc, $result, $clang);
465
        }
466
        return $conceptArray;
467
    }
468
469
    /**
470
     * Returns information (as a graph) for one or more concept URIs
471
     * @param mixed $uris concept URI (string) or array of URIs
472
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
473
     * @param \Vocabulary[]|null $vocabs vocabularies to target
474
     * @return \EasyRdf\Graph
475
     */
476
    public function queryConceptInfoGraph($uris, $arrayClass = null, $vocabs = array()) {
477
        // if just a single URI is given, put it in an array regardless
478
        if (!is_array($uris)) {
479
            $uris = array($uris);
480
        }
481
482
        $query = $this->generateConceptInfoQuery($uris, $arrayClass, $vocabs);
483
        $result = $this->query($query);
484
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $result; (EasyRdf\Http\Response|Ze...ql\Result|EasyRdf\Graph) is incompatible with the return type documented by GenericSparql::queryConceptInfoGraph of type EasyRdf\Graph.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
485
    }
486
487
    /**
488
     * Returns information (as an array of Concept objects) for one or more concept URIs
489
     * @param mixed $uris concept URI (string) or array of URIs
490
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
491
     * @param \Vocabulary[] $vocabs vocabularies to target
492
     * @param string|null $clang content language
493
     * @return Concept[]
494
     */
495
    public function queryConceptInfo($uris, $arrayClass = null, $vocabs = array(), $clang = null) {
496
        // if just a single URI is given, put it in an array regardless
497
        if (!is_array($uris)) {
498
            $uris = array($uris);
499
        }
500
        $result = $this->queryConceptInfoGraph($uris, $arrayClass, $vocabs);
501
        if ($result->isEmpty()) {
502
            return [];
503
        }
504
        return $this->transformConceptInfoResults($result, $uris, $vocabs, $clang);
505
    }
506
507
    /**
508
     * Generates the sparql query for queryTypes
509
     * @param string $lang
510
     * @return string sparql query
511
     */
512
    private function generateQueryTypesQuery($lang) {
513
        $fcl = $this->generateFromClause();
514
        $query = <<<EOQ
515
SELECT DISTINCT ?type ?label ?superclass $fcl
516
WHERE {
517
  {
518
    { BIND( skos:Concept as ?type ) }
519
    UNION
520
    { BIND( skos:Collection as ?type ) }
521
    UNION
522
    { BIND( isothes:ConceptGroup as ?type ) }
523
    UNION
524
    { BIND( isothes:ThesaurusArray as ?type ) }
525
    UNION
526
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Concept . }
527
    UNION
528
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Collection . }
529
  }
530
  OPTIONAL {
531
    ?type rdfs:label ?label .
532
    FILTER(langMatches(lang(?label), '$lang'))
533
  }
534
  OPTIONAL {
535
    ?type rdfs:subClassOf ?superclass .
536
  }
537
  FILTER EXISTS {
538
    ?s a ?type .
539
    ?s skos:prefLabel ?prefLabel .
540
  }
541
}
542
EOQ;
543
        return $query;
544
    }
545
546
    /**
547
     * Transforms the results into an array format.
548
     * @param EasyRdf\Sparql\Result $result
549
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
550
     */
551 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...
552
        $ret = array();
553
        foreach ($result as $row) {
554
            $type = array();
555
            if (isset($row->label)) {
556
                $type['label'] = $row->label->getValue();
557
            }
558
559
            if (isset($row->superclass)) {
560
                $type['superclass'] = $row->superclass->getUri();
561
            }
562
563
            $ret[$row->type->getURI()] = $type;
564
        }
565
        return $ret;
566
    }
567
568
    /**
569
     * Retrieve information about types from the endpoint
570
     * @param string $lang
571
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
572
     */
573
    public function queryTypes($lang) {
574
        $query = $this->generateQueryTypesQuery($lang);
575
        $result = $this->query($query);
576
        return $this->transformQueryTypesResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 575 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformQueryTypesResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
577
    }
578
579
    /**
580
     * Generates the concept scheme query.
581
     * @param string $conceptscheme concept scheme URI
582
     * @return string sparql query
583
     */
584
    private function generateQueryConceptSchemeQuery($conceptscheme) {
585
        $fcl = $this->generateFromClause();
586
        $query = <<<EOQ
587
CONSTRUCT {
588
  <$conceptscheme> ?property ?value .
589
} $fcl WHERE {
590
  <$conceptscheme> ?property ?value .
591
  FILTER (?property != skos:hasTopConcept)
592
}
593
EOQ;
594
        return $query;
595
    }
596
597
    /**
598
     * Retrieves conceptScheme information from the endpoint.
599
     * @param string $conceptscheme concept scheme URI
600
     * @return EasyRDF_Graph query result graph
601
     */
602
    public function queryConceptScheme($conceptscheme) {
603
        $query = $this->generateQueryConceptSchemeQuery($conceptscheme);
604
        return $this->query($query);
605
    }
606
607
    /**
608
     * Generates the queryConceptSchemes sparql query.
609
     * @param string $lang language of labels
610
     * @return string sparql query
611
     */
612
    private function generateQueryConceptSchemesQuery($lang) {
613
        $fcl = $this->generateFromClause();
614
        $query = <<<EOQ
615
SELECT ?cs ?label ?preflabel ?title ?domain ?domainLabel $fcl
616
WHERE {
617
 ?cs a skos:ConceptScheme .
618
 OPTIONAL{
619
    ?cs dcterms:subject ?domain.
620
    ?domain skos:prefLabel ?domainLabel.
621
    FILTER(langMatches(lang(?domainLabel), '$lang'))
622
}
623
 OPTIONAL {
624
   ?cs rdfs:label ?label .
625
   FILTER(langMatches(lang(?label), '$lang'))
626
 }
627
 OPTIONAL {
628
   ?cs skos:prefLabel ?preflabel .
629
   FILTER(langMatches(lang(?preflabel), '$lang'))
630
 }
631
 OPTIONAL {
632
   { ?cs dc11:title ?title }
633
   UNION
634
   { ?cs dc:title ?title }
635
   FILTER(langMatches(lang(?title), '$lang'))
636
 }
637
} 
638
ORDER BY ?cs
639
EOQ;
640
        return $query;
641
    }
642
643
    /**
644
     * Transforms the queryConceptScheme results into an array format.
645
     * @param EasyRdf\Sparql\Result $result
646
     * @return array
647
     */
648
    private function transformQueryConceptSchemesResults($result) {
649
        $ret = array();
650
        foreach ($result as $row) {
651
            $conceptscheme = array();
652
            if (isset($row->label)) {
653
                $conceptscheme['label'] = $row->label->getValue();
654
            }
655
656
            if (isset($row->preflabel)) {
657
                $conceptscheme['prefLabel'] = $row->preflabel->getValue();
658
            }
659
660
            if (isset($row->title)) {
661
                $conceptscheme['title'] = $row->title->getValue();
662
            }
663
            // add dct:subject and their labels in the result
664
            if(isset($row->domain) && isset($row->domainLabel)){
665
                $conceptscheme['subject']['uri']=$row->domain->getURI();
666
                $conceptscheme['subject']['prefLabel']=$row->domainLabel->getValue();
667
            }
668
669
            $ret[$row->cs->getURI()] = $conceptscheme;
670
        }
671
        return $ret;
672
    }
673
674
    /**
675
     * return a list of skos:ConceptScheme instances in the given graph
676
     * @param string $lang language of labels
677
     * @return array Array with concept scheme URIs (string) as keys and labels (string) as values
678
     */
679
    public function queryConceptSchemes($lang) {
680
        $query = $this->generateQueryConceptSchemesQuery($lang);
681
        $result = $this->query($query);
682
        return $this->transformQueryConceptSchemesResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 681 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transform...ConceptSchemesResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
683
    }
684
685
    /**
686
     * Generate a VALUES clause for limiting the targeted graphs.
687
     * @param Vocabulary[]|null $vocabs the vocabularies to target
688
     * @return string[] array of graph URIs
689
     */
690
    protected function getVocabGraphs($vocabs) {
691
        if ($vocabs === null || sizeof($vocabs) == 0) {
692
            // searching from all vocabularies - limit to known graphs
693
            $vocabs = $this->model->getVocabularies();
694
        }
695
        $graphs = array();
696
        foreach ($vocabs as $voc) {
697
            $graph = $voc->getGraph();
698
            if (!is_null($graph) && !in_array($graph, $graphs)) {
699
                $graphs[] = $graph;
700
            }
701
        }
702
        return $graphs;
703
    }
704
705
    /**
706
     * Generate a VALUES clause for limiting the targeted graphs.
707
     * @param Vocabulary[]|null $vocabs array of Vocabulary objects to target
708
     * @return string VALUES clause, or "" if not necessary to limit
709
     */
710
    protected function formatValuesGraph($vocabs) {
711
        if (!$this->isDefaultEndpoint()) {
712
            return "";
713
        }
714
        $graphs = $this->getVocabGraphs($vocabs);
715
        return $this->formatValues('?graph', $graphs, 'uri');
716
    }
717
718
    /**
719
     * Generate a FILTER clause for limiting the targeted graphs.
720
     * @param array $vocabs array of Vocabulary objects to target
721
     * @return string FILTER clause, or "" if not necessary to limit
722
     */
723
    protected function formatFilterGraph($vocabs) {
724
        if (!$this->isDefaultEndpoint()) {
725
            return "";
726
        }
727
        $graphs = $this->getVocabGraphs($vocabs);
728
        $values = array();
729
        foreach ($graphs as $graph) {
730
          $values[] = "<$graph>";
731
        }
732
        if (count($values)) {
733
          return "FILTER (?graph IN (" . implode(',', $values) . "))";
734
        }
735
    }
736
737
    /**
738
     * Formats combined limit and offset clauses for the sparql query
739
     * @param int $limit maximum number of hits to retrieve; 0 for unlimited
740
     * @param int $offset offset of results to retrieve; 0 for beginning of list
741
     * @return string sparql query clauses
742
     */
743
    protected function formatLimitAndOffset($limit, $offset) {
744
        $limit = ($limit) ? 'LIMIT ' . $limit : '';
745
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
746
        // eliminating whitespace and line changes when the conditions aren't needed.
747
        $limitandoffset = '';
748
        if ($limit && $offset) {
749
            $limitandoffset = "\n" . $limit . "\n" . $offset;
750
        } elseif ($limit) {
751
            $limitandoffset = "\n" . $limit;
752
        } elseif ($offset) {
753
            $limitandoffset = "\n" . $offset;
754
        }
755
756
        return $limitandoffset;
757
    }
758
759
    /**
760
     * Formats a sparql query clause for limiting the search to specific concept types.
761
     * @param array $types limit search to concepts of the given type(s)
762
     * @return string sparql query clause
763
     */
764
    protected function formatTypes($types) {
765
        $typePatterns = array();
766
        if (!empty($types)) {
767
            foreach ($types as $type) {
768
                $unprefixed = EasyRdf\RdfNamespace::expand($type);
769
                $typePatterns[] = "{ ?s a <$unprefixed> }";
770
            }
771
        }
772
773
        return implode(' UNION ', $typePatterns);
774
    }
775
776
    /**
777
     * @param string $prop property to include in the result eg. 'broader' or 'narrower'
778
     * @return string sparql query clause
779
     */
780
    private function formatPropertyCsvClause($prop) {
781
        # This expression creates a CSV row containing pairs of (uri,prefLabel) values.
782
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
783
        $clause = <<<EOV
784
(GROUP_CONCAT(DISTINCT CONCAT(
785
 '"', IF(isIRI(?$prop),STR(?$prop),''), '"', ',',
786
 '"', REPLACE(IF(BOUND(?{$prop}lab),?{$prop}lab,''), '"', '""'), '"', ',',
787
 '"', REPLACE(IF(isLiteral(?{$prop}),?{$prop},''), '"', '""'), '"'
788
); separator='\\n') as ?{$prop}s)
789
EOV;
790
        return $clause;
791
    }
792
793
    /**
794
     * @return string sparql query clause
795
     */
796
    private function formatPrefLabelCsvClause() {
797
        # This expression creates a CSV row containing pairs of (prefLabel, lang) values.
798
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
799
        $clause = <<<EOV
800
(GROUP_CONCAT(DISTINCT CONCAT(
801
 '"', STR(?pref), '"', ',', '"', lang(?pref), '"'
802
); separator='\\n') as ?preflabels)
803
EOV;
804
        return $clause;
805
    }
806
807
    /**
808
     * @param string $lang language code of the returned labels
809
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
810
     * @return string sparql query clause
811
     */
812
    protected function formatExtraFields($lang, $fields) {
813
        // extra variable expressions to request and extra fields to query for
814
        $ret = array('extravars' => '', 'extrafields' => '');
815
816
        if ($fields === null) {
817
            return $ret;
818
        }
819
820
        if (in_array('prefLabel', $fields)) {
821
            $ret['extravars'] .= $this->formatPreflabelCsvClause();
822
            $ret['extrafields'] .= <<<EOF
823
OPTIONAL {
824
  ?s skos:prefLabel ?pref .
825
}
826
EOF;
827
            // removing the prefLabel from the fields since it has been handled separately
828
            $fields = array_diff($fields, array('prefLabel'));
829
        }
830
831
        foreach ($fields as $field) {
832
            $ret['extravars'] .= $this->formatPropertyCsvClause($field);
833
            $ret['extrafields'] .= <<<EOF
834
OPTIONAL {
835
  ?s skos:$field ?$field .
836
  FILTER(!isLiteral(?$field)||langMatches(lang(?{$field}), '$lang'))
837
  OPTIONAL { ?$field skos:prefLabel ?{$field}lab . FILTER(langMatches(lang(?{$field}lab), '$lang')) }
838
}
839
EOF;
840
        }
841
842
        return $ret;
843
    }
844
845
    /**
846
     * Generate condition for matching labels in SPARQL
847
     * @param string $term search term
848
     * @param string $searchLang language code used for matching labels (null means any language)
849
     * @return string sparql query snippet
850
     */
851
    protected function generateConceptSearchQueryCondition($term, $searchLang)
852
    {
853
        # use appropriate matching function depending on query type: =, strstarts, strends or full regex
854
        if (preg_match('/^[^\*]+$/', $term)) { // exact query
855
            $term = str_replace('\\', '\\\\', $term); // quote slashes
856
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
857
            $filtercond = "LCASE(STR(?match)) = '$term'";
858
        } elseif (preg_match('/^[^\*]+\*$/', $term)) { // prefix query
859
            $term = substr($term, 0, -1); // remove the final asterisk
860
            $term = str_replace('\\', '\\\\', $term); // quote slashes
861
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
862
            $filtercond = "STRSTARTS(LCASE(STR(?match)), '$term')";
863
        } elseif (preg_match('/^\*[^\*]+$/', $term)) { // suffix query
864
            $term = substr($term, 1); // remove the preceding asterisk
865
            $term = str_replace('\\', '\\\\', $term); // quote slashes
866
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
867
            $filtercond = "STRENDS(LCASE(STR(?match)), '$term')";
868
        } else { // too complicated - have to use a regex
869
            # make sure regex metacharacters are not passed through
870
            $term = str_replace('\\', '\\\\', preg_quote($term));
871
            $term = str_replace('\\\\*', '.*', $term); // convert asterisk to regex syntax
872
            $term = str_replace('\'', '\\\'', $term); // ensure single quotes are quoted
873
            $filtercond = "REGEX(STR(?match), '^$term$', 'i')";
874
        }
875
876
        $labelcondMatch = ($searchLang) ? "&& (?prop = skos:notation || LANGMATCHES(lang(?match), ?langParam))" : "";
877
878
        return "?s ?prop ?match . FILTER ($filtercond $labelcondMatch)";
879
    }
880
881
882
    /**
883
     * Inner query for concepts using a search term.
884
     * @param string $term search term
885
     * @param string $lang language code of the returned labels
886
     * @param string $searchLang language code used for matching labels (null means any language)
887
     * @param string[] $props properties to target e.g. array('skos:prefLabel','skos:altLabel')
888
     * @param boolean $unique restrict results to unique concepts (default: false)
889
     * @return string sparql query
890
     */
891
    protected function generateConceptSearchQueryInner($term, $lang, $searchLang, $props, $unique, $filterGraph)
892
    {
893
        $valuesProp = $this->formatValues('?prop', $props);
894
        $textcond = $this->generateConceptSearchQueryCondition($term, $searchLang);
895
896
        $rawterm = str_replace(array('\\', '*', '"'), array('\\\\', '', '\"'), $term);
897
        // graph clause, if necessary
898
        $graphClause = $filterGraph != '' ? 'GRAPH ?graph' : '';
899
900
        // extra conditions for label language, if specified
901
        $labelcondLabel = ($lang) ? "LANGMATCHES(lang(?label), '$lang')" : "lang(?match) = '' || LANGMATCHES(lang(?label), lang(?match))";
902
        // if search language and UI/display language differ, must also consider case where there is no prefLabel in
903
        // the display language; in that case, should use the label with the same language as the matched label
904
        $labelcondFallback = ($searchLang != $lang) ?
905
          "OPTIONAL { # in case previous OPTIONAL block gives no labels\n" .
906
          "?s skos:prefLabel ?label . FILTER (LANGMATCHES(LANG(?label), LANG(?match))) }" : "";
907
908
        //  Including the labels if there is no query term given.
909
        if ($rawterm === '') {
910
          $labelClause = "?s skos:prefLabel ?label .";
911
          $labelClause = ($lang) ? $labelClause . " FILTER (LANGMATCHES(LANG(?label), '$lang'))" : $labelClause . "";
912
          return $labelClause . " BIND(?label AS ?match)";
913
        }
914
915
        /*
916
         * This query does some tricks to obtain a list of unique concepts.
917
         * From each match generated by the text index, a string such as
918
         * "1en@example" is generated, where the first character is a number
919
         * encoding the property and priority, then comes the language tag and
920
         * finally the original literal after an @ sign. Of these, the MIN
921
         * function is used to pick the best match for each concept. Finally,
922
         * the structure is unpacked to get back the original string. Phew!
923
         */
924
        $hitvar = $unique ? '(MIN(?matchstr) AS ?hit)' : '(?matchstr AS ?hit)';
925
        $hitgroup = $unique ? 'GROUP BY ?s ?label ?notation' : '';
926
927
        $langClause = $this->generateLangClause($lang);
928
929
        $query = <<<EOQ
930
   SELECT DISTINCT ?s ?label ?notation $hitvar
931
   WHERE {
932
    $graphClause {
933
     { 
934
     $valuesProp
935
     VALUES (?prop ?pri ?langParam) { (skos:prefLabel 1 $langClause) (skos:altLabel 3 $langClause) (skos:notation 5 '') (skos:hiddenLabel 7 $langClause)}
936
     $textcond
937
     ?s ?prop ?match }
938
     OPTIONAL {
939
      ?s skos:prefLabel ?label .
940
      FILTER ($labelcondLabel)
941
     } $labelcondFallback
942
     BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri)
943
     BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr)
944
     OPTIONAL { ?s skos:notation ?notation }
945
    }
946
    $filterGraph
947
   }
948
   $hitgroup
949
EOQ;
950
951
        return $query;
952
    }
953
    /**
954
    *  This function can be overwritten in other SPARQL dialects for the possibility of handling the differenc language clauses
955
     * @param string $lang
956
     * @return string formatted language clause
957
     */
958
    protected function generateLangClause($lang) {
959
        return "'$lang'";
960
    }
961
962
    /**
963
     * Query for concepts using a search term.
964
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
965
     * @param boolean $unique restrict results to unique concepts (default: false)
966
     * @param boolean $showDeprecated whether to include deprecated concepts in search results (default: false)
967
     * @param ConceptSearchParameters $params
968
     * @return string sparql query
969
     */
970
    protected function generateConceptSearchQuery($fields, $unique, $params, $showDeprecated = false) {
971
        $vocabs = $params->getVocabs();
972
        $gcl = $this->graphClause;
973
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
974
        $formattedtype = $this->formatTypes($params->getTypeLimit());
975
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);
976
        $extravars = $formattedfields['extravars'];
977
        $extrafields = $formattedfields['extrafields'];
978
        $schemes = $params->getSchemeLimit();
979
980
        // limit the search to only requested concept schemes
981
        $schemecond = '';
982
        if (!empty($schemes)) {
983
            $conditions = array();
984
            foreach($schemes as $scheme) {
985
                $conditions[] = "{?s skos:inScheme <$scheme>}";
986
            }
987
            $schemecond = '{'.implode(" UNION ",$conditions).'}';
988
        }
989
        $filterDeprecated="";
990
        //show or hide deprecated concepts
991
        if(!$showDeprecated){
992
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
993
        }
994
        // extra conditions for parent and group, if specified
995
        $parentcond = ($params->getParentLimit()) ? "?s skos:broader+ <" . $params->getParentLimit() . "> ." : "";
996
        $groupcond = ($params->getGroupLimit()) ? "<" . $params->getGroupLimit() . "> skos:member ?s ." : "";
997
        $pgcond = $parentcond . $groupcond;
998
999
        $orderextra = $this->isDefaultEndpoint() ? $this->graph : '';
1000
1001
        # make VALUES clauses
1002
        $props = array('skos:prefLabel', 'skos:altLabel');
1003
1004
        //add notation into searchable data for the vocabularies which have been configured for it
1005
        if ($vocabs) {
1006
            $searchByNotation = false;
1007
            foreach ($vocabs as $vocab) {
1008
                if ($vocab->getConfig()->searchByNotation()) {
1009
                    $searchByNotation = true;
1010
                }
1011
            }
1012
            if ($searchByNotation) {
1013
                $props[] = 'skos:notation';
1014
            }
1015
        }
1016
1017
        if ($params->getHidden()) {
1018
            $props[] = 'skos:hiddenLabel';
1019
        }
1020
        $filterGraph = empty($vocabs) ? $this->formatFilterGraph($vocabs) : '';
1021
1022
        // remove futile asterisks from the search term
1023
        $term = $params->getSearchTerm();
1024
        while (strpos($term, '**') !== false) {
1025
            $term = str_replace('**', '*', $term);
1026
        }
1027
1028
        $labelpriority = <<<EOQ
1029
  FILTER(BOUND(?s))
1030
  BIND(STR(SUBSTR(?hit,1,1)) AS ?pri)
1031
  BIND(IF((SUBSTR(STRBEFORE(?hit, '@'),1) != ?pri), STRLANG(STRAFTER(?hit, '@'), SUBSTR(STRBEFORE(?hit, '@'),2)), STRAFTER(?hit, '@')) AS ?match)
1032
  BIND(IF((?pri = "1" || ?pri = "2") && ?match != ?label, ?match, ?unbound) as ?plabel)
1033
  BIND(IF((?pri = "3" || ?pri = "4"), ?match, ?unbound) as ?alabel)
1034
  BIND(IF((?pri = "7" || ?pri = "8"), ?match, ?unbound) as ?hlabel)
1035
EOQ;
1036
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique, $filterGraph);
1037
        if ($params->getSearchTerm() === '*' || $params->getSearchTerm() === '') {
1038
          $labelpriority = '';
1039
        }
1040
        $query = <<<EOQ
1041
SELECT DISTINCT ?s ?label ?plabel ?alabel ?hlabel ?graph ?notation (GROUP_CONCAT(DISTINCT STR(?type);separator=' ') as ?types) $extravars 
1042
$fcl
1043
WHERE {
1044
 $gcl {
1045
  {
1046
  $innerquery
1047
  }
1048
  $labelpriority
1049
  $formattedtype
1050
  { $pgcond 
1051
   ?s a ?type .
1052
   $extrafields $schemecond
1053
  }
1054
  $filterDeprecated
1055
 }
1056
 $filterGraph
1057
}
1058
GROUP BY ?s ?match ?label ?plabel ?alabel ?hlabel ?notation ?graph
1059
ORDER BY LCASE(STR(?match)) LANG(?match) $orderextra
1060
EOQ;
1061
        return $query;
1062
    }
1063
1064
    /**
1065
     * Transform a single concept search query results into the skosmos desired return format.
1066
     * @param $row SPARQL query result row
1067
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1068
     * @return array query result object
1069
     */
1070
    private function transformConceptSearchResult($row, $vocabs, $fields)
1071
    {
1072
        $hit = array();
1073
        $hit['uri'] = $row->s->getUri();
1074
1075
        if (isset($row->graph)) {
1076
            $hit['graph'] = $row->graph->getUri();
1077
        }
1078
1079
        foreach (explode(" ", $row->types->getValue()) as $typeuri) {
1080
            $hit['type'][] = $this->shortenUri($typeuri);
1081
        }
1082
1083
        if(!empty($fields)) {
1084
            foreach ($fields as $prop) {
1085
                $propname = $prop . 's';
1086
                if (isset($row->$propname)) {
1087
                    foreach (explode("\n", $row->$propname->getValue()) as $line) {
1088
                        $rdata = str_getcsv($line, ',', '"', '"');
1089
                        $propvals = array();
1090
                        if ($rdata[0] != '') {
1091
                            $propvals['uri'] = $rdata[0];
1092
                        }
1093
                        if ($rdata[1] != '') {
1094
                            $propvals['prefLabel'] = $rdata[1];
1095
                        }
1096
                        if ($rdata[2] != '') {
1097
                            $propvals = $rdata[2];
1098
                        }
1099
1100
                        $hit['skos:' . $prop][] = $propvals;
1101
                    }
1102
                }
1103
            }
1104
        }
1105
1106
1107
        if (isset($row->preflabels)) {
1108
            foreach (explode("\n", $row->preflabels->getValue()) as $line) {
1109
                $pref = str_getcsv($line, ',', '"', '"');
1110
                $hit['prefLabels'][$pref[1]] = $pref[0];
1111
            }
1112
        }
1113
1114
        foreach ($vocabs as $vocab) { // looping the vocabulary objects and asking these for a localname for the concept.
1115
            $localname = $vocab->getLocalName($hit['uri']);
1116
            if ($localname !== $hit['uri']) { // only passing the result forward if the uri didn't boomerang right back.
1117
                $hit['localname'] = $localname;
1118
                break; // stopping the search when we find one that returns something valid.
1119
            }
1120
        }
1121
1122
        if (isset($row->label)) {
1123
            $hit['prefLabel'] = $row->label->getValue();
1124
        }
1125
1126
        if (isset($row->label)) {
1127
            $hit['lang'] = $row->label->getLang();
1128
        }
1129
1130
        if (isset($row->notation)) {
1131
            $hit['notation'] = $row->notation->getValue();
1132
        }
1133
1134
        if (isset($row->plabel)) {
1135
            $hit['matchedPrefLabel'] = $row->plabel->getValue();
1136
            $hit['lang'] = $row->plabel->getLang();
1137 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...
1138
            $hit['altLabel'] = $row->alabel->getValue();
1139
            $hit['lang'] = $row->alabel->getLang();
1140
        } elseif (isset($row->hlabel)) {
1141
            $hit['hiddenLabel'] = $row->hlabel->getValue();
1142
            $hit['lang'] = $row->hlabel->getLang();
1143
        }
1144
        return $hit;
1145
    }
1146
1147
    /**
1148
     * Transform the concept search query results into the skosmos desired return format.
1149
     * @param EasyRdf\Sparql\Result $results
1150
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1151
     * @return array query result object
1152
     */
1153
    private function transformConceptSearchResults($results, $vocabs, $fields) {
1154
        $ret = array();
1155
1156
        foreach ($results as $row) {
1157
            if (!isset($row->s)) {
1158
                // don't break if query returns a single dummy result
1159
                continue;
1160
            }
1161
            $ret[] = $this->transformConceptSearchResult($row, $vocabs, $fields);
1162
        }
1163
        return $ret;
1164
    }
1165
1166
    /**
1167
     * Query for concepts using a search term.
1168
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1169
     * @param array $fields extra fields to include in the result (array of strings). (default: null = none)
1170
     * @param boolean $unique restrict results to unique concepts (default: false)
1171
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1172
     * @param ConceptSearchParameters $params
1173
     * @return array query result object
1174
     */
1175
    public function queryConcepts($vocabs, $fields = null, $unique = false, $params, $showDeprecated = false) {
1176
        $query = $this->generateConceptSearchQuery($fields, $unique, $params,$showDeprecated);
1177
        $results = $this->query($query);
1178
        return $this->transformConceptSearchResults($results, $vocabs, $fields);
0 ignored issues
show
Bug introduced by
It seems like $results defined by $this->query($query) on line 1177 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformConceptSearchResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1179
    }
1180
1181
    /**
1182
     * Generates sparql query clauses used for creating the alphabetical index.
1183
     * @param string $letter the letter (or special class) to search for
1184
     * @return array of sparql query clause strings
1185
     */
1186
    private function formatFilterConditions($letter, $lang) {
1187
        $useRegex = false;
1188
1189
        if ($letter == '*') {
1190
            $letter = '.*';
1191
            $useRegex = true;
1192
        } elseif ($letter == '0-9') {
1193
            $letter = '[0-9].*';
1194
            $useRegex = true;
1195
        } elseif ($letter == '!*') {
1196
            $letter = '[^\\\\p{L}\\\\p{N}].*';
1197
            $useRegex = true;
1198
        }
1199
1200
        # make text query clause
1201
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
1202
        if ($useRegex) {
1203
            $filtercondLabel = $lang ? "regex(str(?label), '^$letter$', 'i') && langMatches(lang(?label), '$lang')" : "regex(str(?label), '^$letter$', 'i')";
1204
            $filtercondALabel = $lang ? "regex(str(?alabel), '^$letter$', 'i') && langMatches(lang(?alabel), '$lang')" : "regex(str(?alabel), '^$letter$', 'i')";
1205
        } else {
1206
            $filtercondLabel = $lang ? "strstarts(lcase(str(?label)), '$lcletter') && langMatches(lang(?label), '$lang')" : "strstarts(lcase(str(?label)), '$lcletter')";
1207
            $filtercondALabel = $lang ? "strstarts(lcase(str(?alabel)), '$lcletter') && langMatches(lang(?alabel), '$lang')" : "strstarts(lcase(str(?alabel)), '$lcletter')";
1208
        }
1209
        return array('filterpref' => $filtercondLabel, 'filteralt' => $filtercondALabel);
1210
    }
1211
1212
    /**
1213
     * Generates the sparql query used for rendering the alphabetical index.
1214
     * @param string $letter the letter (or special class) to search for
1215
     * @param string $lang language of labels
1216
     * @param integer $limit limits the amount of results
1217
     * @param integer $offset offsets the result set
1218
     * @param array|null $classes
1219
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1220
     * @param \EasyRdf\Resource|null $qualifier alphabetical list qualifier resource or null (default: null)
1221
     * @return string sparql query
1222
     */
1223
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated = false, $qualifier = null) {
1224
        $fcl = $this->generateFromClause();
1225
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1226
        $values = $this->formatValues('?type', $classes, 'uri');
1227
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
1228
        $conditions = $this->formatFilterConditions($letter, $lang);
1229
        $filtercondLabel = $conditions['filterpref'];
1230
        $filtercondALabel = $conditions['filteralt'];
1231
        $qualifierClause = $qualifier ? "OPTIONAL { ?s <" . $qualifier->getURI() . "> ?qualifier }" : "";
1232
        $filterDeprecated="";
1233
        if(!$showDeprecated){
1234
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
1235
        }
1236
        $query = <<<EOQ
1237
SELECT DISTINCT ?s ?label ?alabel ?qualifier $fcl
1238
WHERE {
1239
  {
1240
    ?s skos:prefLabel ?label .
1241
    FILTER (
1242
      $filtercondLabel
1243
    )
1244
  }
1245
  UNION
1246
  {
1247
    {
1248
      ?s skos:altLabel ?alabel .
1249
      FILTER (
1250
        $filtercondALabel
1251
      )
1252
    }
1253
    {
1254
      ?s skos:prefLabel ?label .
1255
      FILTER (langMatches(lang(?label), '$lang'))
1256
    }
1257
  }
1258
  ?s a ?type .
1259
  $qualifierClause
1260
  $filterDeprecated
1261
  $values
1262
}
1263
ORDER BY LCASE(STR(COALESCE(?alabel, ?label))) STR(?s) LCASE(STR(?qualifier)) $limitandoffset
1264
EOQ;
1265
        return $query;
1266
    }
1267
1268
    /**
1269
     * Transforms the alphabetical list query results into an array format.
1270
     * @param EasyRdf\Sparql\Result $results
1271
     * @return array
1272
     */
1273
    private function transformAlphabeticalListResults($results) {
1274
        $ret = array();
1275
1276
        foreach ($results as $row) {
1277
            if (!isset($row->s)) {
1278
                continue;
1279
            }
1280
            // don't break if query returns a single dummy result
1281
1282
            $hit = array();
1283
            $hit['uri'] = $row->s->getUri();
1284
1285
            $hit['localname'] = $row->s->localName();
1286
1287
            $hit['prefLabel'] = $row->label->getValue();
1288
            $hit['lang'] = $row->label->getLang();
1289
1290 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...
1291
                $hit['altLabel'] = $row->alabel->getValue();
1292
                $hit['lang'] = $row->alabel->getLang();
1293
            }
1294
1295
            if (isset($row->qualifier)) {
1296
                if ($row->qualifier instanceof EasyRdf\Literal) {
1297
                    $hit['qualifier'] = $row->qualifier->getValue();
1298
                }
1299
                else {
1300
                    $hit['qualifier'] = $row->qualifier->localName();
1301
                }
1302
            }
1303
1304
            $ret[] = $hit;
1305
        }
1306
1307
        return $ret;
1308
    }
1309
1310
    /**
1311
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1312
     * '*!' (special characters) and '*' (everything) are accepted.
1313
     * @param string $letter the letter (or special class) to search for
1314
     * @param string $lang language of labels
1315
     * @param integer $limit limits the amount of results
1316
     * @param integer $offset offsets the result set
1317
     * @param array $classes
1318
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1319
     * @param \EasyRdf\Resource|null $qualifier alphabetical list qualifier resource or null (default: null)
1320
     */
1321
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null, $showDeprecated = false, $qualifier = null) {
1322
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated, $qualifier);
1323
        $results = $this->query($query);
1324
        return $this->transformAlphabeticalListResults($results);
0 ignored issues
show
Bug introduced by
It seems like $results defined by $this->query($query) on line 1323 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformAlphabeticalListResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1325
    }
1326
1327
    /**
1328
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1329
     * Note that we force the datatype of the result variable otherwise Virtuoso does not properly interpret the DISTINCT and we have duplicated results
1330
     * @param string $lang language
1331
     * @return string sparql query
1332
     */
1333
    private function generateFirstCharactersQuery($lang, $classes) {
1334
        $fcl = $this->generateFromClause();
1335
        $classes = (isset($classes) && sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1336
        $values = $this->formatValues('?type', $classes, 'uri');
1337
        $query = <<<EOQ
1338
SELECT DISTINCT (ucase(str(substr(?label, 1, 1))) as ?l) $fcl WHERE {
1339
  ?c skos:prefLabel ?label .
1340
  ?c a ?type
1341
  FILTER(langMatches(lang(?label), '$lang'))
1342
  $values
1343
}
1344
EOQ;
1345
        return $query;
1346
    }
1347
1348
    /**
1349
     * Transforms the first characters query results into an array format.
1350
     * @param EasyRdf\Sparql\Result $result
1351
     * @return array
1352
     */
1353
    private function transformFirstCharactersResults($result) {
1354
        $ret = array();
1355
        foreach ($result as $row) {
1356
            $ret[] = $row->l->getValue();
1357
        }
1358
        return $ret;
1359
    }
1360
1361
    /**
1362
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1363
     * @param string $lang language
1364
     * @return array array of characters
1365
     */
1366
    public function queryFirstCharacters($lang, $classes = null) {
1367
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1368
        $result = $this->query($query);
1369
        return $this->transformFirstCharactersResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1368 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformFirstCharactersResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1370
    }
1371
1372
    /**
1373
     * @param string $uri
1374
     * @param string $lang
1375
     * @return string sparql query string
1376
     */
1377
    private function generateLabelQuery($uri, $lang) {
1378
        $fcl = $this->generateFromClause();
1379
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1380
        $query = <<<EOQ
1381
SELECT ?label $fcl
1382
WHERE {
1383
  <$uri> a ?type .
1384
  OPTIONAL {
1385
    <$uri> skos:prefLabel ?label .
1386
    $labelcondLabel
1387
  }
1388
  OPTIONAL {
1389
    <$uri> rdfs:label ?label .
1390
    $labelcondLabel
1391
  }
1392
  OPTIONAL {
1393
    <$uri> dc:title ?label .
1394
    $labelcondLabel
1395
  }
1396
  OPTIONAL {
1397
    <$uri> dc11:title ?label .
1398
    $labelcondLabel
1399
  }
1400
}
1401
EOQ;
1402
        return $query;
1403
    }
1404
1405
    /**
1406
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1407
     * @param string $uri
1408
     * @param string $lang
1409
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1410
     */
1411 View Code Duplication
    public function queryLabel($uri, $lang) {
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...
1412
        $query = $this->generateLabelQuery($uri, $lang);
1413
        $result = $this->query($query);
1414
        $ret = array();
1415
        foreach ($result as $row) {
0 ignored issues
show
Bug introduced by
The expression $result of type object<EasyRdf\Http\Resp...>|object<EasyRdf\Graph> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1416
            if (!isset($row->label)) {
1417
                // existing concept but no labels
1418
                return array();
1419
            }
1420
            $ret[$row->label->getLang()] = $row->label;
1421
        }
1422
1423
        if (sizeof($ret) > 0) {
1424
            // existing concept, with label(s)
1425
            return $ret;
1426
        } else {
1427
            // nonexistent concept
1428
            return null;
1429
        }
1430
    }
1431
1432
    /**
1433
     * Generates a SPARQL query to retrieve the super properties of a given property URI.
1434
     * Note this must be executed in the graph where this information is available.
1435
     * @param string $uri
1436
     * @return string sparql query string
1437
     */
1438
    private function generateSubPropertyOfQuery($uri) {
1439
        $fcl = $this->generateFromClause();
1440
        $query = <<<EOQ
1441
SELECT ?superProperty $fcl
1442
WHERE {
1443
  <$uri> rdfs:subPropertyOf ?superProperty
1444
}
1445
EOQ;
1446
        return $query;
1447
    }
1448
1449
    /**
1450
     * Query the super properties of a provided property URI.
1451
     * @param string $uri URI of a propertyes
1452
     * @return array array super properties, or null if none exist
1453
     */
1454 View Code Duplication
    public function querySuperProperties($uri) {
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...
1455
        $query = $this->generateSubPropertyOfQuery($uri);
1456
        $result = $this->query($query);
1457
        $ret = array();
1458
        foreach ($result as $row) {
0 ignored issues
show
Bug introduced by
The expression $result of type object<EasyRdf\Http\Resp...>|object<EasyRdf\Graph> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1459
            if (isset($row->superProperty)) {
1460
                $ret[] = $row->superProperty->getUri();
1461
            }
1462
1463
        }
1464
1465
        if (sizeof($ret) > 0) {
1466
            // return result
1467
            return $ret;
1468
        } else {
1469
            // no result, return null
1470
            return null;
1471
        }
1472
    }
1473
1474
1475
    /**
1476
     * Generates a sparql query for queryNotation.
1477
     * @param string $uri
1478
     * @return string sparql query
1479
     */
1480
    private function generateNotationQuery($uri) {
1481
        $fcl = $this->generateFromClause();
1482
1483
        $query = <<<EOQ
1484
SELECT * $fcl
1485
WHERE {
1486
  <$uri> skos:notation ?notation .
1487
}
1488
EOQ;
1489
        return $query;
1490
    }
1491
1492
    /**
1493
     * Query for the notation of the concept (skos:notation) of a resource.
1494
     * @param string $uri
1495
     * @return string notation or null if it doesn't exist
1496
     */
1497
    public function queryNotation($uri) {
1498
        $query = $this->generateNotationQuery($uri);
1499
        $result = $this->query($query);
1500
        foreach ($result as $row) {
0 ignored issues
show
Bug introduced by
The expression $result of type object<EasyRdf\Http\Resp...>|object<EasyRdf\Graph> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1501
            if (isset($row->notation)) {
1502
                return $row->notation->getValue();
1503
            }
1504
        }
1505
        return null;
1506
    }
1507
1508
    /**
1509
     * Generates a sparql query for queryProperty.
1510
     * @param string $uri
1511
     * @param string $prop the name of the property eg. 'skos:broader'.
1512
     * @param string $lang
1513
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1514
     * @return string sparql query
1515
     */
1516 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...
1517
        $fcl = $this->generateFromClause();
1518
        $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1519
1520
        $query = <<<EOQ
1521
SELECT * $fcl
1522
WHERE {
1523
  <$uri> a skos:Concept .
1524
  OPTIONAL {
1525
    <$uri> $prop ?object .
1526
    OPTIONAL {
1527
      ?object skos:prefLabel ?label .
1528
      FILTER (langMatches(lang(?label), "$lang"))
1529
    }
1530
    OPTIONAL {
1531
      ?object skos:prefLabel ?label .
1532
      FILTER (lang(?label) = "")
1533
    }
1534
    $anylang
1535
  }
1536
}
1537
EOQ;
1538
        return $query;
1539
    }
1540
1541
    /**
1542
     * Transforms the sparql query result into an array or null if the concept doesn't exist.
1543
     * @param EasyRdf\Sparql\Result $result
1544
     * @param string $lang
1545
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1546
     */
1547
    private function transformPropertyQueryResults($result, $lang) {
1548
        $ret = array();
1549
        foreach ($result as $row) {
1550
            if (!isset($row->object)) {
1551
                return array();
1552
            }
1553
            // existing concept but no properties
1554
            if (isset($row->label)) {
1555
                if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1556
                    $ret[$row->object->getUri()]['label'] = $row->label->getValue();
1557
                }
1558
1559
            } else {
1560
                $ret[$row->object->getUri()]['label'] = null;
1561
            }
1562
        }
1563
        if (sizeof($ret) > 0) {
1564
            return $ret;
1565
        }
1566
        // existing concept, with properties
1567
        else {
1568
            return null;
1569
        }
1570
        // nonexistent concept
1571
    }
1572
1573
    /**
1574
     * Query a single property of a concept.
1575
     * @param string $uri
1576
     * @param string $prop the name of the property eg. 'skos:broader'.
1577
     * @param string $lang
1578
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1579
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1580
     */
1581
    public function queryProperty($uri, $prop, $lang, $anylang = false) {
1582
        $uri = is_array($uri) ? $uri[0] : $uri;
1583
        $query = $this->generatePropertyQuery($uri, $prop, $lang, $anylang);
1584
        $result = $this->query($query);
1585
        return $this->transformPropertyQueryResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1584 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformPropertyQueryResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1586
    }
1587
1588
    /**
1589
     * Query a single transitive property of a concept.
1590
     * @param string $uri
1591
     * @param array $props the name of the property eg. 'skos:broader'.
1592
     * @param string $lang
1593
     * @param integer $limit
1594
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1595
     * @return string sparql query
1596
     */
1597
    private function generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang) {
1598
        $uri = is_array($uri) ? $uri[0] : $uri;
1599
        $fcl = $this->generateFromClause();
1600
        $propertyClause = implode('|', $props);
1601
        $otherlang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1602
        // need to do a SPARQL subquery because LIMIT needs to be applied /after/
1603
        // the direct relationships have been collapsed into one string
1604
        $query = <<<EOQ
1605
SELECT * $fcl
1606
WHERE {
1607
  SELECT ?object ?label (GROUP_CONCAT(STR(?dir);separator=' ') as ?direct)
1608
  WHERE {
1609
    <$uri> a skos:Concept .
1610
    OPTIONAL {
1611
      <$uri> $propertyClause* ?object .
1612
      OPTIONAL {
1613
        ?object $propertyClause ?dir .
1614
      }
1615
    }
1616
    OPTIONAL {
1617
      ?object skos:prefLabel ?label .
1618
      FILTER (langMatches(lang(?label), "$lang"))
1619
    }
1620
    $otherlang
1621
  }
1622
  GROUP BY ?object ?label
1623
}
1624
LIMIT $limit
1625
EOQ;
1626
        return $query;
1627
    }
1628
1629
    /**
1630
     * Transforms the sparql query result object into an array.
1631
     * @param EasyRdf\Sparql\Result $result
1632
     * @param string $lang
1633
     * @param string $fallbacklang language to use if label is not available in the preferred language
1634
     * @return array of property values (key: URI, val: label), or null if concept doesn't exist
1635
     */
1636
    private function transformTransitivePropertyResults($result, $lang, $fallbacklang) {
1637
        $ret = array();
1638
        foreach ($result as $row) {
1639
            if (!isset($row->object)) {
1640
                return array();
1641
            }
1642
            // existing concept but no properties
1643
            if (isset($row->label)) {
1644
                $val = array('label' => $row->label->getValue());
1645
            } else {
1646
                $val = array('label' => null);
1647
            }
1648 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...
1649
                $val['direct'] = explode(' ', $row->direct->getValue());
1650
            }
1651
            // Preventing labels in a non preferred language overriding the preferred language.
1652
            if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1653
                if (!isset($row->label) || $row->label->getLang() === $lang) {
1654
                    $ret[$row->object->getUri()] = $val;
1655
                } elseif ($row->label->getLang() === $fallbacklang) {
1656
                    $val['label'] .= ' (' . $row->label->getLang() . ')';
1657
                    $ret[$row->object->getUri()] = $val;
1658
                }
1659
            }
1660
        }
1661
1662
        // second iteration of results to find labels for the ones that didn't have one in the preferred languages
1663
        foreach ($result as $row) {
1664
            if (isset($row->object) && array_key_exists($row->object->getUri(), $ret) === false) {
1665
                $val = array('label' => $row->label->getValue());
1666 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...
1667
                    $val['direct'] = explode(' ', $row->direct->getValue());
1668
                }
1669
                $ret[$row->object->getUri()] = $val;
1670
            }
1671
        }
1672
1673
        if (sizeof($ret) > 0) {
1674
            return $ret;
1675
        }
1676
        // existing concept, with properties
1677
        else {
1678
            return null;
1679
        }
1680
        // nonexistent concept
1681
    }
1682
1683
    /**
1684
     * Query a single transitive property of a concept.
1685
     * @param string $uri
1686
     * @param array $props the property/properties.
1687
     * @param string $lang
1688
     * @param string $fallbacklang language to use if label is not available in the preferred language
1689
     * @param integer $limit
1690
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1691
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1692
     */
1693
    public function queryTransitiveProperty($uri, $props, $lang, $limit, $anylang = false, $fallbacklang = '') {
1694
        $query = $this->generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang);
1695
        $result = $this->query($query);
1696
        return $this->transformTransitivePropertyResults($result, $lang, $fallbacklang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1695 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transform...sitivePropertyResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1697
    }
1698
1699
    /**
1700
     * Generates the query for a concepts skos:narrowers.
1701
     * @param string $uri
1702
     * @param string $lang
1703
     * @param string $fallback
1704
     * @return string sparql query
1705
     */
1706
    private function generateChildQuery($uri, $lang, $fallback, $props) {
1707
        $uri = is_array($uri) ? $uri[0] : $uri;
1708
        $fcl = $this->generateFromClause();
1709
        $propertyClause = implode('|', $props);
1710
        $query = <<<EOQ
1711
SELECT ?child ?label ?child ?grandchildren ?notation $fcl WHERE {
1712
  <$uri> a skos:Concept .
1713
  OPTIONAL {
1714
    ?child $propertyClause <$uri> .
1715
    OPTIONAL {
1716
      ?child skos:prefLabel ?label .
1717
      FILTER (langMatches(lang(?label), "$lang"))
1718
    }
1719
    OPTIONAL {
1720
      ?child skos:prefLabel ?label .
1721
      FILTER (langMatches(lang(?label), "$fallback"))
1722
    }
1723
    OPTIONAL { # other language case
1724
      ?child skos:prefLabel ?label .
1725
    }
1726
    OPTIONAL {
1727
      ?child skos:notation ?notation .
1728
    }
1729
    BIND ( EXISTS { ?a $propertyClause ?child . } AS ?grandchildren )
1730
  }
1731
}
1732
EOQ;
1733
        return $query;
1734
    }
1735
1736
    /**
1737
     * Transforms the sparql result object into an array.
1738
     * @param EasyRdf\Sparql\Result $result
1739
     * @param string $lang
1740
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1741
     */
1742
    private function transformNarrowerResults($result, $lang) {
1743
        $ret = array();
1744
        foreach ($result as $row) {
1745
            if (!isset($row->child)) {
1746
                return array();
1747
            }
1748
            // existing concept but no children
1749
1750
            $label = null;
1751
            if (isset($row->label)) {
1752
                if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
1753
                    $label = $row->label->getValue();
1754
                } else {
1755
                    $label = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1756
                }
1757
1758
            }
1759
            $childArray = array(
1760
                'uri' => $row->child->getUri(),
1761
                'prefLabel' => $label,
1762
                'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1763
            );
1764
            if (isset($row->notation)) {
1765
                $childArray['notation'] = $row->notation->getValue();
1766
            }
1767
1768
            $ret[] = $childArray;
1769
        }
1770
        if (sizeof($ret) > 0) {
1771
            return $ret;
1772
        }
1773
        // existing concept, with children
1774
        else {
1775
            return null;
1776
        }
1777
        // nonexistent concept
1778
    }
1779
1780
    /**
1781
     * Query the narrower concepts of a concept.
1782
     * @param string $uri
1783
     * @param string $lang
1784
     * @param string $fallback
1785
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1786
     */
1787
    public function queryChildren($uri, $lang, $fallback, $props) {
1788
        $query = $this->generateChildQuery($uri, $lang, $fallback, $props);
1789
        $result = $this->query($query);
1790
        return $this->transformNarrowerResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1789 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformNarrowerResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1791
    }
1792
1793
    /**
1794
     * Query the top concepts of a vocabulary.
1795
     * @param string $conceptSchemes concept schemes whose top concepts to query for
1796
     * @param string $lang language of labels
1797
     * @param string $fallback language to use if label is not available in the preferred language
1798
     */
1799
    public function queryTopConcepts($conceptSchemes, $lang, $fallback) {
1800
        if (!is_array($conceptSchemes)) {
1801
            $conceptSchemes = array($conceptSchemes);
1802
        }
1803
1804
        $values = $this->formatValues('?topuri', $conceptSchemes, 'uri');
1805
1806
        $fcl = $this->generateFromClause();
1807
        $query = <<<EOQ
1808
SELECT DISTINCT ?top ?topuri ?label ?notation ?children $fcl WHERE {
1809
  ?top skos:topConceptOf ?topuri .
1810
  OPTIONAL {
1811
    ?top skos:prefLabel ?label .
1812
    FILTER (langMatches(lang(?label), "$lang"))
1813
  }
1814
  OPTIONAL {
1815
    ?top skos:prefLabel ?label .
1816
    FILTER (langMatches(lang(?label), "$fallback"))
1817
  }
1818
  OPTIONAL { # fallback - other language case
1819
    ?top skos:prefLabel ?label .
1820
  }
1821
  OPTIONAL { ?top skos:notation ?notation . }
1822
  BIND ( EXISTS { ?top skos:narrower ?a . } AS ?children )
1823
  $values
1824
}
1825
EOQ;
1826
        $result = $this->query($query);
1827
        $ret = array();
1828
        foreach ($result as $row) {
0 ignored issues
show
Bug introduced by
The expression $result of type object<EasyRdf\Http\Resp...>|object<EasyRdf\Graph> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1829
            if (isset($row->top) && isset($row->label)) {
1830
                $label = $row->label->getValue();
1831 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...
1832
                    $label .= ' (' . $row->label->getLang() . ')';
1833
                }
1834
                $top = array('uri' => $row->top->getUri(), 'topConceptOf' => $row->topuri->getUri(), 'label' => $label, 'hasChildren' => filter_var($row->children->getValue(), FILTER_VALIDATE_BOOLEAN));
1835
                if (isset($row->notation)) {
1836
                    $top['notation'] = $row->notation->getValue();
1837
                }
1838
1839
                $ret[] = $top;
1840
            }
1841
        }
1842
1843
        return $ret;
1844
    }
1845
1846
    /**
1847
     * Generates a sparql query for finding the hierarchy for a concept.
1848
	 * A concept may be a top concept in multiple schemes, returned as a single whitespace-separated literal.
1849
     * @param string $uri concept uri.
1850
     * @param string $lang
1851
     * @param string $fallback language to use if label is not available in the preferred language
1852
     * @return string sparql query
1853
     */
1854
    private function generateParentListQuery($uri, $lang, $fallback, $props) {
1855
        $fcl = $this->generateFromClause();
1856
        $propertyClause = implode('|', $props);
1857
        $query = <<<EOQ
1858
SELECT ?broad ?parent ?children ?grandchildren
1859
(SAMPLE(?lab) as ?label) (SAMPLE(?childlab) as ?childlabel) (GROUP_CONCAT(?topcs; separator=" ") as ?tops) 
1860
(SAMPLE(?nota) as ?notation) (SAMPLE(?childnota) as ?childnotation) $fcl
1861
WHERE {
1862
  <$uri> a skos:Concept .
1863
  OPTIONAL {
1864
    <$uri> $propertyClause* ?broad .
1865
    OPTIONAL {
1866
      ?broad skos:prefLabel ?lab .
1867
      FILTER (langMatches(lang(?lab), "$lang"))
1868
    }
1869
    OPTIONAL {
1870
      ?broad skos:prefLabel ?lab .
1871
      FILTER (langMatches(lang(?lab), "$fallback"))
1872
    }
1873
    OPTIONAL { # fallback - other language case
1874
      ?broad skos:prefLabel ?lab .
1875
    }
1876
    OPTIONAL { ?broad skos:notation ?nota . }
1877
    OPTIONAL { ?broad $propertyClause ?parent . }
1878
    OPTIONAL { ?broad skos:narrower ?children .
1879
      OPTIONAL {
1880
        ?children skos:prefLabel ?childlab .
1881
        FILTER (langMatches(lang(?childlab), "$lang"))
1882
      }
1883
      OPTIONAL {
1884
        ?children skos:prefLabel ?childlab .
1885
        FILTER (langMatches(lang(?childlab), "$fallback"))
1886
      }
1887
      OPTIONAL { # fallback - other language case
1888
        ?children skos:prefLabel ?childlab .
1889
      }
1890
      OPTIONAL {
1891
        ?children skos:notation ?childnota .
1892
      }
1893
    }
1894
    BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren )
1895
    OPTIONAL { ?broad skos:topConceptOf ?topcs . }
1896
  }
1897
}
1898
GROUP BY ?broad ?parent ?member ?children ?grandchildren
1899
EOQ;
1900
        return $query;
1901
    }
1902
1903
    /**
1904
     * Transforms the result into an array.
1905
     * @param EasyRdf\Sparql\Result
1906
     * @param string $lang
1907
     * @return an array for the REST controller to encode.
1908
     */
1909
    private function transformParentListResults($result, $lang)
1910
    {
1911
        $ret = array();
1912
        foreach ($result as $row) {
1913
            if (!isset($row->broad)) {
1914
                // existing concept but no broaders
1915
                return array();
1916
            }
1917
            $uri = $row->broad->getUri();
1918
            if (!isset($ret[$uri])) {
1919
                $ret[$uri] = array('uri' => $uri);
1920
            }
1921
            if (isset($row->exact)) {
1922
                $ret[$uri]['exact'] = $row->exact->getUri();
1923
            }
1924
            if (isset($row->tops)) {
1925
               $topConceptsList=explode(" ", $row->tops->getValue());
1926
               // sort to garantee an alphabetical ordering of the URI
1927
               sort($topConceptsList);
1928
               $ret[$uri]['tops'] = $topConceptsList;
1929
            }
1930
            if (isset($row->children)) {
1931
                if (!isset($ret[$uri]['narrower'])) {
1932
                    $ret[$uri]['narrower'] = array();
1933
                }
1934
1935
                $label = null;
1936
                if (isset($row->childlabel)) {
1937
                    $label = $row->childlabel->getValue();
1938 View Code Duplication
                    if ($row->childlabel->getLang() !== $lang && strpos($row->childlabel->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...
1939
                        $label .= " (" . $row->childlabel->getLang() . ")";
1940
                    }
1941
1942
                }
1943
1944
                $childArr = array(
1945
                    'uri' => $row->children->getUri(),
1946
                    'label' => $label,
1947
                    'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1948
                );
1949
                if (isset($row->childnotation)) {
1950
                    $childArr['notation'] = $row->childnotation->getValue();
1951
                }
1952
1953
                if (!in_array($childArr, $ret[$uri]['narrower'])) {
1954
                    $ret[$uri]['narrower'][] = $childArr;
1955
                }
1956
1957
            }
1958
            if (isset($row->label)) {
1959
                $preflabel = $row->label->getValue();
1960 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...
1961
                    $preflabel .= ' (' . $row->label->getLang() . ')';
1962
                }
1963
1964
                $ret[$uri]['prefLabel'] = $preflabel;
1965
            }
1966
            if (isset($row->notation)) {
1967
                $ret[$uri]['notation'] = $row->notation->getValue();
1968
            }
1969
1970
            if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) {
1971
                $ret[$uri]['broader'][] = $row->parent->getUri();
1972
            } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) {
1973
                $ret[$uri]['broader'][] = $row->parent->getUri();
1974
            }
1975
        }
1976
        if (sizeof($ret) > 0) {
1977
            // existing concept, with children
1978
            return $ret;
1979
        }
1980
        else {
1981
            // nonexistent concept
1982
            return null;
1983
        }
1984
    }
1985
1986
    /**
1987
     * Query for finding the hierarchy for a concept.
1988
     * @param string $uri concept uri.
1989
     * @param string $lang
1990
     * @param string $fallback language to use if label is not available in the preferred language
1991
     * @param array $props the hierarchy property/properties to use
1992
     * @return an array for the REST controller to encode.
1993
     */
1994
    public function queryParentList($uri, $lang, $fallback, $props) {
1995
        $query = $this->generateParentListQuery($uri, $lang, $fallback, $props);
1996
        $result = $this->query($query);
1997
        return $this->transformParentListResults($result, $lang);
1998
    }
1999
2000
    /**
2001
     * return a list of concept group instances, sorted by label
2002
     * @param string $groupClass URI of concept group class
2003
     * @param string $lang language of labels to return
2004
     * @return string sparql query
2005
     */
2006
    private function generateConceptGroupsQuery($groupClass, $lang) {
2007
        $fcl = $this->generateFromClause();
2008
        $query = <<<EOQ
2009
SELECT ?group (GROUP_CONCAT(DISTINCT STR(?child);separator=' ') as ?children) ?label ?members ?notation $fcl
2010
WHERE {
2011
  ?group a <$groupClass> .
2012
  OPTIONAL { ?group skos:member|isothes:subGroup ?child .
2013
             ?child a <$groupClass> }
2014
  BIND(EXISTS{?group skos:member ?submembers} as ?members)
2015
  OPTIONAL { ?group skos:prefLabel ?label }
2016
  OPTIONAL { ?group rdfs:label ?label }
2017
  FILTER (langMatches(lang(?label), '$lang'))
2018
  OPTIONAL { ?group skos:notation ?notation }
2019
}
2020
GROUP BY ?group ?label ?members ?notation
2021
ORDER BY lcase(?label)
2022
EOQ;
2023
        return $query;
2024
    }
2025
2026
    /**
2027
     * Transforms the sparql query result into an array.
2028
     * @param EasyRdf\Sparql\Result $result
2029
     * @return array
2030
     */
2031
    private function transformConceptGroupsResults($result) {
2032
        $ret = array();
2033
        foreach ($result as $row) {
2034
            if (!isset($row->group)) {
2035
                # no groups found, see issue #357
2036
                continue;
2037
            }
2038
            $group = array('uri' => $row->group->getURI());
2039
            if (isset($row->label)) {
2040
                $group['prefLabel'] = $row->label->getValue();
2041
            }
2042
2043
            if (isset($row->children)) {
2044
                $group['childGroups'] = explode(' ', $row->children->getValue());
2045
            }
2046
2047
            if (isset($row->members)) {
2048
                $group['hasMembers'] = $row->members->getValue();
2049
            }
2050
2051
            if (isset($row->notation)) {
2052
                $group['notation'] = $row->notation->getValue();
2053
            }
2054
2055
            $ret[] = $group;
2056
        }
2057
        return $ret;
2058
    }
2059
2060
    /**
2061
     * return a list of concept group instances, sorted by label
2062
     * @param string $groupClass URI of concept group class
2063
     * @param string $lang language of labels to return
2064
     * @return array Result array with group URI as key and group label as value
2065
     */
2066
    public function listConceptGroups($groupClass, $lang) {
2067
        $query = $this->generateConceptGroupsQuery($groupClass, $lang);
2068
        $result = $this->query($query);
2069
        return $this->transformConceptGroupsResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2068 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformConceptGroupsResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2070
    }
2071
2072
    /**
2073
     * Generates the sparql query for listConceptGroupContents
2074
     * @param string $groupClass URI of concept group class
2075
     * @param string $group URI of the concept group instance
2076
     * @param string $lang language of labels to return
2077
     * @param boolean $showDeprecated whether to include deprecated in the result
2078
     * @return string sparql query
2079
     */
2080
    private function generateConceptGroupContentsQuery($groupClass, $group, $lang, $showDeprecated = false) {
2081
        $fcl = $this->generateFromClause();
2082
        $filterDeprecated="";
2083
        if(!$showDeprecated){
2084
            $filterDeprecated="  FILTER NOT EXISTS { ?conc owl:deprecated true }";
2085
        }
2086
        $query = <<<EOQ
2087
SELECT ?conc ?super ?label ?members ?type ?notation $fcl
2088
WHERE {
2089
 <$group> a <$groupClass> .
2090
 { <$group> skos:member ?conc . } UNION { ?conc isothes:superGroup <$group> }
2091
$filterDeprecated
2092
 ?conc a ?type .
2093
 OPTIONAL { ?conc skos:prefLabel ?label .
2094
  FILTER (langMatches(lang(?label), '$lang'))
2095
 }
2096
 OPTIONAL { ?conc skos:prefLabel ?label . }
2097
 OPTIONAL { ?conc skos:notation ?notation }
2098
 BIND(EXISTS{?submembers isothes:superGroup ?conc} as ?super)
2099
 BIND(EXISTS{?conc skos:member ?submembers} as ?members)
2100
} ORDER BY lcase(?label)
2101
EOQ;
2102
        return $query;
2103
    }
2104
2105
    /**
2106
     * Transforms the sparql query result into an array.
2107
     * @param EasyRdf\Sparql\Result $result
2108
     * @param string $lang language of labels to return
2109
     * @return array
2110
     */
2111
    private function transformConceptGroupContentsResults($result, $lang) {
2112
        $ret = array();
2113
        $values = array();
2114
        foreach ($result as $row) {
2115
            if (!array_key_exists($row->conc->getURI(), $values)) {
2116
                $values[$row->conc->getURI()] = array(
2117
                    'uri' => $row->conc->getURI(),
2118
                    'isSuper' => $row->super->getValue(),
2119
                    'hasMembers' => $row->members->getValue(),
2120
                    'type' => array($row->type->shorten()),
2121
                );
2122
                if (isset($row->label)) {
2123
                    if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
2124
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue();
2125
                    } else {
2126
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue() . " (" . $row->label->getLang() . ")";
2127
                    }
2128
2129
                }
2130
                if (isset($row->notation)) {
2131
                    $values[$row->conc->getURI()]['notation'] = $row->notation->getValue();
2132
                }
2133
2134
            } else {
2135
                $values[$row->conc->getURI()]['type'][] = $row->type->shorten();
2136
            }
2137
        }
2138
2139
        foreach ($values as $val) {
2140
            $ret[] = $val;
2141
        }
2142
2143
        return $ret;
2144
    }
2145
2146
    /**
2147
     * return a list of concepts in a concept group
2148
     * @param string $groupClass URI of concept group class
2149
     * @param string $group URI of the concept group instance
2150
     * @param string $lang language of labels to return
2151
     * @param boolean $showDeprecated whether to include deprecated concepts in search results
2152
     * @return array Result array with concept URI as key and concept label as value
2153
     */
2154
    public function listConceptGroupContents($groupClass, $group, $lang,$showDeprecated = false) {
2155
        $query = $this->generateConceptGroupContentsQuery($groupClass, $group, $lang,$showDeprecated);
2156
        $result = $this->query($query);
2157
        return $this->transformConceptGroupContentsResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2156 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transform...tGroupContentsResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2158
    }
2159
2160
    /**
2161
     * Generates the sparql query for queryChangeList.
2162
     * @param string $lang language of labels to return.
2163
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2164
     * @return string sparql query
2165
     */
2166 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...
2167
        $fcl = $this->generateFromClause();
2168
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
2169
2170
        $query = <<<EOQ
2171
SELECT DISTINCT ?concept ?date ?label $fcl
2172
WHERE {
2173
  ?concept a skos:Concept .
2174
  ?concept $prop ?date .
2175
  ?concept skos:prefLabel ?label .
2176
  FILTER (langMatches(lang(?label), '$lang'))
2177
}
2178
ORDER BY DESC(YEAR(?date)) DESC(MONTH(?date)) LCASE(?label)
2179
LIMIT 200 $offset
2180
EOQ;
2181
        return $query;
2182
    }
2183
2184
    /**
2185
     * Transforms the sparql query result into an array.
2186
     * @param EasyRdf\Sparql\Result $result
2187
     * @return array
2188
     */
2189 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...
2190
        $ret = array();
2191
        foreach ($result as $row) {
2192
            $concept = array('uri' => $row->concept->getURI());
2193
            if (isset($row->label)) {
2194
                $concept['prefLabel'] = $row->label->getValue();
2195
            }
2196
2197
            if (isset($row->date)) {
2198
                $concept['date'] = $row->date->getValue();
2199
            }
2200
2201
            $ret[] = $concept;
2202
        }
2203
        return $ret;
2204
    }
2205
2206
    /**
2207
     * return a list of recently changed or entirely new concepts
2208
     * @param string $lang language of labels to return
2209
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2210
     * @return array Result array
2211
     */
2212
    public function queryChangeList($lang, $offset, $prop) {
2213
        $query = $this->generateChangeListQuery($lang, $offset, $prop);
2214
        $result = $this->query($query);
2215
        return $this->transformChangeListResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2214 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformChangeListResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2216
    }
2217
}
2218