Completed
Push — master ( 60724f...42b01e )
by Henri
02:30
created

GenericSparql::generateConceptGroupContentsQuery()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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

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

namespace YourVendor;

class YourClass { }

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

Loading history...
7
    /**
8
     * A SPARQL Client eg. an EasyRDF instance.
9
     * @property EasyRdf\Sparql\Client $client
10
     */
11
    protected $client;
12
    /**
13
     * Graph uri.
14
     * @property string $graph
15
     */
16
    protected $graph;
17
    /**
18
     * A SPARQL query graph part template.
19
     * @property string $graph
20
     */
21
    protected $graphClause;
22
    /**
23
     * Model instance.
24
     * @property Model $model
25
     */
26
    protected $model;
27
28
    /**
29
     * Cache used to avoid expensive shorten() calls
30
     * @property array $qnamecache
31
     */
32
    private $qnamecache = array();
33
34
    /**
35
     * Requires the following three parameters.
36
     * @param string $endpoint SPARQL endpoint address.
37
     * @param object $graph an EasyRDF SPARQL graph instance.
38
     * @param object $model a Model instance.
39
     */
40
    public function __construct($endpoint, $graph, $model) {
41
        $this->graph = $graph;
42
        $this->model = $model;
43
44
        // create the EasyRDF SPARQL client instance to use
45
        $this->initializeHttpClient();
46
        $this->client = new EasyRdf\Sparql\Client($endpoint);
47
48
        // set graphClause so that it can be used by all queries
49
        if ($this->isDefaultEndpoint()) // default endpoint; query any graph (and catch it in a variable)
50
        {
51
            $this->graphClause = "GRAPH $graph";
52
        } elseif ($graph) // query a specific graph
53
        {
54
            $this->graphClause = "GRAPH <$graph>";
55
        } else // query the default graph
56
        {
57
            $this->graphClause = "";
58
        }
59
60
    }
61
    
62
    /**
63
     * Execute the SPARQL query using the SPARQL client, logging it as well.
64
     * @param string $query SPARQL query to perform
65
     * @return object query result
66
     */
67
    protected function query($query) {
68
        $queryId = sprintf("%05d", rand(0, 99999));
69
        $logger = $this->model->getLogger();
70
        $logger->info("[qid $queryId] SPARQL query:\n$query\n");
71
        $starttime = microtime(true);
72
        $result = $this->client->query($query);
73
        $elapsed = intval(round((microtime(true) - $starttime) * 1000));
74
        if(method_exists($result, 'numRows')) {
75
            $numRows = $result->numRows();
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...
76
            $logger->info("[qid $queryId] result: $numRows rows returned in $elapsed ms");
77
        } else { // graph result
78
            $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...
79
            $logger->info("[qid $queryId] result: $numTriples triples returned in $elapsed ms");
80
        }
81
        return $result;
82
    }
83
    
84
    
85
    /**
86
     * Generates FROM clauses for the queries 
87
     * @param Vocabulary[]|null $vocabs
88
     * @return string
89
     */
90
    protected function generateFromClause($vocabs=null) {
91
        $graphs = array();
92
        $clause = '';
93
        if (!$vocabs) {
94
            return $this->graph !== '?graph' && $this->graph !== NULL ? "FROM <$this->graph>" : '';
95
        }
96
        foreach($vocabs as $vocab) {
97
            $graph = $vocab->getGraph();
98
            if (!in_array($graph, $graphs)) {
99
                array_push($graphs, $graph);
100
            }
101
        }
102
        foreach ($graphs as $graph) {
103
            if($graph !== NULL)
104
                $clause .= "FROM NAMED <$graph> "; 
105
        }
106
        return $clause;
107
    }
108
109
    protected function initializeHttpClient() {
110
        // configure the HTTP client used by EasyRdf\Sparql\Client
111
        $httpclient = EasyRdf\Http::getDefaultHttpClient();
112
        $httpclient->setConfig(array('timeout' => $this->model->getConfig()->getSparqlTimeout()));
113
114
        // if special cache control (typically no-cache) was requested by the
115
        // client, set the same type of cache control headers also in subsequent
116
        // in the SPARQL requests (this is useful for performance testing)
117
        // @codeCoverageIgnoreStart
118
        $cacheControl = filter_input(INPUT_SERVER, 'HTTP_CACHE_CONTROL', FILTER_SANITIZE_STRING);
119
        $pragma = filter_input(INPUT_SERVER, 'HTTP_PRAGMA', FILTER_SANITIZE_STRING);
120
        if ($cacheControl !== null || $pragma !== null) {
121
            $val = $pragma !== null ? $pragma : $cacheControl;
122
            $httpclient->setHeaders('Cache-Control', $val);
123
        }
124
        // @codeCoverageIgnoreEnd
125
126
        EasyRdf\Http::setDefaultHttpClient($httpclient); // actually redundant..
127
    }
128
129
    /**
130
     * Return true if this is the default SPARQL endpoint, used as the facade to query
131
     * all vocabularies.
132
     */
133
134
    protected function isDefaultEndpoint() {
135
        return $this->graph[0] == '?';
136
    }
137
138
    /**
139
     * Returns the graph instance
140
     * @return object EasyRDF graph instance.
141
     */
142
    public function getGraph() {
143
        return $this->graph;
144
    }
145
    
146
    /**
147
     * Shorten a URI
148
     * @param string $uri URI to shorten
149
     * @return string shortened URI, or original URI if it cannot be shortened
150
     */
151
    private function shortenUri($uri) {
152
        if (!array_key_exists($uri, $this->qnamecache)) {
153
            $res = new EasyRdf\Resource($uri);
154
            $qname = $res->shorten(); // returns null on failure
155
            $this->qnamecache[$uri] = ($qname !== null) ? $qname : $uri;
156
        }
157
        return $this->qnamecache[$uri];
158
    }
159
160
161
    /**
162
     * Generates the sparql query for retrieving concept and collection counts in a vocabulary.
163
     * @return string sparql query
164
     */
165
    private function generateCountConceptsQuery($array, $group) {
166
        $fcl = $this->generateFromClause();
167
        $optional = $array ? "UNION { ?type rdfs:subClassOf* <$array> }" : '';
168
        $optional .= $group ? "UNION { ?type rdfs:subClassOf* <$group> }" : '';
169
        $query = <<<EOQ
170
      SELECT (COUNT(?conc) as ?c) ?type ?typelabel $fcl WHERE {
171
        { ?conc a ?type .
172
        { ?type rdfs:subClassOf* skos:Concept . } UNION { ?type rdfs:subClassOf* skos:Collection . } $optional }
173
        OPTIONAL { ?type rdfs:label ?typelabel . }
174
      }
175
GROUP BY ?type ?typelabel
176
EOQ;
177
        return $query;
178
    }
179
180
    /**
181
     * Used for transforming the concept count query results.
182
     * @param EasyRdf\Sparql\Result $result query results to be transformed
183
     * @param string $lang language of labels
184
     * @return Array containing the label counts
185
     */
186
    private function transformCountConceptsResults($result, $lang) {
187
        $ret = array();
188
        foreach ($result as $row) {
189
            if (!isset($row->type)) {
190
                continue;
191
            }
192
            $ret[$row->type->getUri()]['type'] = $row->type->getUri();
193
            $ret[$row->type->getUri()]['count'] = $row->c->getValue();
194
            if (isset($row->typelabel) && $row->typelabel->getLang() === $lang) {
195
                $ret[$row->type->getUri()]['label'] = $row->typelabel->getValue();
196
            }
197
198
        }
199
        return $ret;
200
    }
201
202
    /**
203
     * Used for counting number of concepts and collections in a vocabulary.
204
     * @param string $lang language of labels
205
     * @return int number of concepts in this vocabulary
206
     */
207
    public function countConcepts($lang = null, $array = null, $group = null) {
208
        $query = $this->generateCountConceptsQuery($array, $group);
209
        $result = $this->query($query);
210
        return $this->transformCountConceptsResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 209 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...
211
    }
212
213
    /**
214
     * @param array $langs Languages to query for
215
     * @param string[] $props property names
216
     * @return string sparql query
217
     */
218
    private function generateCountLangConceptsQuery($langs, $classes, $props) {
219
        $gcl = $this->graphClause;
220
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
221
222
        $values = $this->formatValues('?type', $classes, 'uri');
223
        $valuesLang = $this->formatValues('?lang', $langs, 'literal');
224
        $valuesProp = $this->formatValues('?prop', $props, null);
225
226
        $query = <<<EOQ
227
SELECT ?lang ?prop
228
  (COUNT(?label) as ?count)
229
WHERE {
230
  $gcl {
231
    ?conc a ?type .
232
    ?conc ?prop ?label .
233
    FILTER (langMatches(lang(?label), ?lang))
234
    $valuesLang
235
    $valuesProp
236
  }
237
  $values
238
}
239
GROUP BY ?lang ?prop ?type
240
EOQ;
241
        return $query;
242
    }
243
244
    /**
245
     * Transforms the CountLangConcepts results into an array of label counts.
246
     * @param EasyRdf\Sparql\Result $result query results to be transformed
247
     * @param array $langs Languages to query for
248
     * @param string[] $props property names
249
     */
250
    private function transformCountLangConceptsResults($result, $langs, $props) {
251
        $ret = array();
252
        // set default count to zero; overridden below if query found labels
253
        foreach ($langs as $lang) {
254
            foreach ($props as $prop) {
255
                $ret[$lang][$prop] = 0;
256
            }
257
        }
258
        foreach ($result as $row) {
259
            if (isset($row->lang) && isset($row->prop) && isset($row->count)) {
260
                $ret[$row->lang->getValue()][$row->prop->shorten()] += 
261
                $row->count->getValue();
262
            }
263
264
        }
265
        ksort($ret);
266
        return $ret;
267
    }
268
269
    /**
270
     * Counts the number of concepts in a easyRDF graph with a specific language.
271
     * @param array $langs Languages to query for
272
     * @return Array containing count of concepts for each language and property.
273
     */
274
    public function countLangConcepts($langs, $classes = null) {
275
        $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel');
276
        $query = $this->generateCountLangConceptsQuery($langs, $classes, $props);
277
        // Count the number of terms in each language
278
        $result = $this->query($query);
279
        return $this->transformCountLangConceptsResults($result, $langs, $props);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 278 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...
280
    }
281
282
    /**
283
     * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one
284
     * of the constants given.
285
     * @param string $varname variable name, e.g. "?uri"
286
     * @param array $values the values
287
     * @param string $type type of values: "uri", "literal" or null (determines quoting style)
288
     */
289
    protected function formatValues($varname, $values, $type = null) {
290
        $constants = array();
291
        foreach ($values as $val) {
292
            if ($type == 'uri') {
293
                $val = "<$val>";
294
            }
295
296
            if ($type == 'literal') {
297
                $val = "'$val'";
298
            }
299
300
            $constants[] = "($val)";
301
        }
302
        $values = implode(" ", $constants);
303
304
        return "VALUES ($varname) { $values }";
305
    }
306
307
    /**
308
     * Filters multiple instances of the same vocabulary from the input array.
309
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
310
     * @return \Vocabulary[]
311
     */
312
    private function filterDuplicateVocabs($vocabs) {
313
        // filtering duplicates
314
        $uniqueVocabs = array();
315
        if ($vocabs !== null && sizeof($vocabs) > 0) {
316
            foreach ($vocabs as $voc) {
317
                $uniqueVocabs[$voc->getId()] = $voc;
318
            }
319
        }
320
321
        return $uniqueVocabs;
322
    }
323
324
    /**
325
     * Generates a sparql query for one or more concept URIs
326
     * @param mixed $uris concept URI (string) or array of URIs
327
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
328
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
329
     * @return string sparql query
330
     */
331
    private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) {
332
        $gcl = $this->graphClause;
333
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
334
        $values = $this->formatValues('?uri', $uris, 'uri');
335
        $uniqueVocabs = $this->filterDuplicateVocabs($vocabs);
336
        $valuesGraph = empty($vocabs) ? $this->formatValuesGraph($uniqueVocabs) : '';
337
338
        if ($arrayClass === null) {
339
            $construct = $optional = "";
340
        } else {
341
            // add information that can be used to format narrower concepts by
342
            // the array they belong to ("milk by source animal" use case)
343
            $construct = "\n ?x skos:member ?o . ?x skos:prefLabel ?xl . ?x a <$arrayClass> .";
344
            $optional = "\n OPTIONAL {
345
                      ?x skos:member ?o .
346
                      ?x a <$arrayClass> .
347
                      ?x skos:prefLabel ?xl .
348
                      FILTER NOT EXISTS {
349
                        ?x skos:member ?other .
350
                        FILTER NOT EXISTS { ?other skos:broader ?uri }
351
                      }
352
                    }";
353
        }
354
        $query = <<<EOQ
355
CONSTRUCT {
356
 ?s ?p ?uri .
357
 ?sp ?uri ?op .
358
 ?uri ?p ?o .
359
 ?p rdfs:label ?proplabel .
360
 ?p rdfs:subPropertyOf ?pp .
361
 ?pp rdfs:label ?plabel .
362
 ?o a ?ot .
363
 ?o skos:prefLabel ?opl .
364
 ?o rdfs:label ?ol .
365
 ?o rdf:value ?ov .
366
 ?o skos:notation ?on .
367
 ?o ?oprop ?oval .
368
 ?o ?xlprop ?xlval .
369
 ?directgroup skos:member ?uri .
370
 ?parent skos:member ?group .
371
 ?group skos:prefLabel ?grouplabel .
372
 ?b1 rdf:first ?item .
373
 ?b1 rdf:rest ?b2 .
374
 ?item a ?it .
375
 ?item skos:prefLabel ?il .
376
 ?group a ?grouptype . $construct
377
} $fcl WHERE {
378
 $gcl {
379
  {
380
    ?s ?p ?uri .
381
    FILTER(!isBlank(?s))
382
    FILTER(?p != skos:inScheme)
383
  }
384
  UNION
385
  { ?sp ?uri ?op . }
386
  UNION
387
  {
388
    ?directgroup skos:member ?uri .
389
    ?group skos:member+ ?uri .
390
    ?group skos:prefLabel ?grouplabel .
391
    ?group a ?grouptype .
392
    OPTIONAL { ?parent skos:member ?group }
393
  }
394
  UNION
395
  {
396
   ?uri ?p ?o .
397
   OPTIONAL {
398
     ?o rdf:rest* ?b1 .
399
     ?b1 rdf:first ?item .
400
     ?b1 rdf:rest ?b2 .
401
     OPTIONAL { ?item a ?it . }
402
     OPTIONAL { ?item skos:prefLabel ?il . }
403
   }
404
   OPTIONAL {
405
     { ?p rdfs:label ?proplabel . }
406
     UNION
407
     { ?p rdfs:subPropertyOf ?pp . }
408
   }
409
   OPTIONAL {
410
     { ?o a ?ot . }
411
     UNION
412
     { ?o skos:prefLabel ?opl . }
413
     UNION
414
     { ?o rdfs:label ?ol . }
415
     UNION
416
     { ?o rdf:value ?ov . 
417
       OPTIONAL { ?o ?oprop ?oval . }
418
     }
419
     UNION
420
     { ?o skos:notation ?on . }
421
     UNION
422
     { ?o a skosxl:Label .
423
       ?o ?xlprop ?xlval }
424
   } $optional
425
  }
426
 }
427
 $values
428
}
429
$valuesGraph
430
EOQ;
431
        return $query;
432
    }
433
434
    /**
435
     * Transforms ConceptInfo query results into an array of Concept objects
436
     * @param EasyRdf\Graph $result query results to be transformed
437
     * @param array $uris concept URIs
438
     * @param \Vocabulary[] $vocabs array of Vocabulary object
439
     * @param string|null $clang content language
440
     * @return mixed query result graph (EasyRdf\Graph), or array of Concept objects
441
     */
442
    private function transformConceptInfoResults($result, $uris, $vocabs, $clang) {
443
        $conceptArray = array();
444
        foreach ($uris as $index => $uri) {
445
            $conc = $result->resource($uri);
446
            $vocab = sizeof($vocabs) == 1 ? $vocabs[0] : $vocabs[$index];
447
            $conceptArray[] = new Concept($this->model, $vocab, $conc, $result, $clang);
448
        }
449
        return $conceptArray;
450
    }
451
452
    /**
453
     * Returns information (as a graph) for one or more concept URIs
454
     * @param mixed $uris concept URI (string) or array of URIs
455
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
456
     * @param \Vocabulary[]|null $vocabs vocabularies to target
457
     * @return \Concept[]
458
     */
459
    public function queryConceptInfoGraph($uris, $arrayClass = null, $vocabs = array()) {
460
        // if just a single URI is given, put it in an array regardless
461
        if (!is_array($uris)) {
462
            $uris = array($uris);
463
        }
464
465
        $query = $this->generateConceptInfoQuery($uris, $arrayClass, $vocabs);
466
        $result = $this->query($query);
467
        return $result;
468
    }
469
470
    /**
471
     * Returns information (as an array of Concept objects) for one or more concept URIs
472
     * @param mixed $uris concept URI (string) or array of URIs
473
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
474
     * @param \Vocabulary[] $vocabs vocabularies to target
475
     * @param string|null $clang content language
476
     * @return EasyRdf\Graph
477
     */
478
    public function queryConceptInfo($uris, $arrayClass = null, $vocabs = array(), $clang = null) {
479
        // if just a single URI is given, put it in an array regardless
480
        if (!is_array($uris)) {
481
            $uris = array($uris);
482
        }
483
        $result = $this->queryConceptInfoGraph($uris, $arrayClass, $vocabs);
484
        if ($result->isEmpty()) {
0 ignored issues
show
Bug introduced by
The method isEmpty 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...
485
            return;
486
        }
487
        return $this->transformConceptInfoResults($result, $uris, $vocabs, $clang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->queryConceptInfoG..., $arrayClass, $vocabs) on line 483 can also be of type object<EasyRdf\Http\Response> or object<EasyRdf\Sparql\Result>; however, GenericSparql::transformConceptInfoResults() does only seem to accept object<EasyRdf\Graph>, 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...
488
    }
489
490
    /**
491
     * Generates the sparql query for queryTypes
492
     * @param string $lang
493
     * @return string sparql query
494
     */
495
    private function generateQueryTypesQuery($lang) {
496
        $fcl = $this->generateFromClause();
497
        $query = <<<EOQ
498
SELECT DISTINCT ?type ?label ?superclass $fcl
499
WHERE {
500
  {
501
    { BIND( skos:Concept as ?type ) }
502
    UNION
503
    { BIND( skos:Collection as ?type ) }
504
    UNION
505
    { BIND( isothes:ConceptGroup as ?type ) }
506
    UNION
507
    { BIND( isothes:ThesaurusArray as ?type ) }
508
    UNION
509
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Concept . }
510
    UNION
511
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Collection . }
512
  }
513
  OPTIONAL {
514
    ?type rdfs:label ?label .
515
    FILTER(langMatches(lang(?label), '$lang'))
516
  }
517
  OPTIONAL {
518
    ?type rdfs:subClassOf ?superclass .
519
  }
520
  FILTER EXISTS {
521
    ?s a ?type .
522
    ?s skos:prefLabel ?prefLabel .
523
  }
524
}
525
EOQ;
526
        return $query;
527
    }
528
529
    /**
530
     * Transforms the results into an array format.
531
     * @param EasyRdf\Sparql\Result $result
532
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
533
     */
534 View Code Duplication
    private function transformQueryTypesResults($result) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
535
        $ret = array();
536
        foreach ($result as $row) {
537
            $type = array();
538
            if (isset($row->label)) {
539
                $type['label'] = $row->label->getValue();
540
            }
541
542
            if (isset($row->superclass)) {
543
                $type['superclass'] = $row->superclass->getUri();
544
            }
545
546
            $ret[$row->type->getURI()] = $type;
547
        }
548
        return $ret;
549
    }
550
551
    /**
552
     * Retrieve information about types from the endpoint
553
     * @param string $lang
554
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
555
     */
556
    public function queryTypes($lang) {
557
        $query = $this->generateQueryTypesQuery($lang);
558
        $result = $this->query($query);
559
        return $this->transformQueryTypesResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 558 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...
560
    }
561
562
    /**
563
     * Generates the concept scheme query.
564
     * @param string $conceptscheme concept scheme URI
565
     * @return string sparql query
566
     */
567
    private function generateQueryConceptSchemeQuery($conceptscheme) {
568
        $fcl = $this->generateFromClause();
569
        $query = <<<EOQ
570
CONSTRUCT {
571
  <$conceptscheme> ?property ?value .
572
} $fcl WHERE {
573
  <$conceptscheme> ?property ?value .
574
  FILTER (?property != skos:hasTopConcept)
575
}
576
EOQ;
577
        return $query;
578
    }
579
580
    /**
581
     * Retrieves conceptScheme information from the endpoint.
582
     * @param string $conceptscheme concept scheme URI
583
     * @return EasyRDF_Graph query result graph
584
     */
585
    public function queryConceptScheme($conceptscheme) {
586
        $query = $this->generateQueryConceptSchemeQuery($conceptscheme);
587
        return $this->query($query);
588
    }
589
590
    /**
591
     * Generates the queryConceptSchemes sparql query.
592
     * @param string $lang language of labels
593
     * @return string sparql query
594
     */
595
    private function generateQueryConceptSchemesQuery($lang) {
596
        $fcl = $this->generateFromClause();
597
        $query = <<<EOQ
598
SELECT ?cs ?label ?preflabel ?title $fcl
599
WHERE {
600
 ?cs a skos:ConceptScheme .
601
 OPTIONAL {
602
   ?cs rdfs:label ?label .
603
   FILTER(langMatches(lang(?label), '$lang'))
604
 }
605
 OPTIONAL {
606
   ?cs skos:prefLabel ?preflabel .
607
   FILTER(langMatches(lang(?preflabel), '$lang'))
608
 }
609
 OPTIONAL {
610
   { ?cs dc11:title ?title }
611
   UNION
612
   { ?cs dc:title ?title }
613
   FILTER(langMatches(lang(?title), '$lang'))
614
 }
615
} ORDER BY ?cs
616
EOQ;
617
        return $query;
618
    }
619
620
    /**
621
     * Transforms the queryConceptScheme results into an array format.
622
     * @param EasyRdf\Sparql\Result $result
623
     * @return array
624
     */
625
    private function transformQueryConceptSchemesResults($result) {
626
        $ret = array();
627
        foreach ($result as $row) {
628
            $conceptscheme = array();
629
            if (isset($row->label)) {
630
                $conceptscheme['label'] = $row->label->getValue();
631
            }
632
633
            if (isset($row->preflabel)) {
634
                $conceptscheme['prefLabel'] = $row->preflabel->getValue();
635
            }
636
637
            if (isset($row->title)) {
638
                $conceptscheme['title'] = $row->title->getValue();
639
            }
640
641
            $ret[$row->cs->getURI()] = $conceptscheme;
642
        }
643
        return $ret;
644
    }
645
646
    /**
647
     * return a list of skos:ConceptScheme instances in the given graph
648
     * @param string $lang language of labels
649
     * @return array Array with concept scheme URIs (string) as keys and labels (string) as values
650
     */
651
    public function queryConceptSchemes($lang) {
652
        $query = $this->generateQueryConceptSchemesQuery($lang);
653
        $result = $this->query($query);
654
        return $this->transformQueryConceptSchemesResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 653 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...
655
    }
656
657
    /**
658
     * Generate a VALUES clause for limiting the targeted graphs.
659
     * @param Vocabulary[]|null $vocabs the vocabularies to target 
660
     * @return string[] array of graph URIs
661
     */
662
    protected function getVocabGraphs($vocabs) {
663
        if ($vocabs === null || sizeof($vocabs) == 0) {
664
            // searching from all vocabularies - limit to known graphs
665
            $vocabs = $this->model->getVocabularies();
666
        }
667
        $graphs = array();
668
        foreach ($vocabs as $voc) {
669
            $graphs[] = $voc->getGraph();
670
        }
671
        return $graphs;
672
    }
673
674
    /**
675
     * Generate a VALUES clause for limiting the targeted graphs.
676
     * @param Vocabulary[]|null $vocabs array of Vocabulary objects to target
677
     * @return string VALUES clause, or "" if not necessary to limit
678
     */
679
    protected function formatValuesGraph($vocabs) {
680
        if (!$this->isDefaultEndpoint()) {
681
            return "";
682
        }
683
        $graphs = $this->getVocabGraphs($vocabs);
684
        return $this->formatValues('?graph', $graphs, 'uri');
685
    }
686
687
    /**
688
     * Generate a FILTER clause for limiting the targeted graphs.
689
     * @param array $vocabs array of Vocabulary objects to target
690
     * @return string FILTER clause, or "" if not necessary to limit
691
     */
692
    protected function formatFilterGraph($vocabs) {
693
        if (!$this->isDefaultEndpoint()) {
694
            return "";
695
        }
696
        $graphs = $this->getVocabGraphs($vocabs);
697
        $values = array();
698
        foreach ($graphs as $graph) {
699
          $values[] = "<$graph>";
700
        }
701
        return "FILTER (?graph IN (" . implode(',', $values) . "))";
702
    }
703
704
    /**
705
     * Formats combined limit and offset clauses for the sparql query
706
     * @param int $limit maximum number of hits to retrieve; 0 for unlimited
707
     * @param int $offset offset of results to retrieve; 0 for beginning of list
708
     * @return string sparql query clauses
709
     */
710
    protected function formatLimitAndOffset($limit, $offset) {
711
        $limit = ($limit) ? 'LIMIT ' . $limit : '';
712
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
713
        // eliminating whitespace and line changes when the conditions aren't needed.
714
        $limitandoffset = '';
715
        if ($limit && $offset) {
716
            $limitandoffset = "\n" . $limit . "\n" . $offset;
717
        } elseif ($limit) {
718
            $limitandoffset = "\n" . $limit;
719
        } elseif ($offset) {
720
            $limitandoffset = "\n" . $offset;
721
        }
722
723
        return $limitandoffset;
724
    }
725
726
    /**
727
     * Formats a sparql query clause for limiting the search to specific concept types.
728
     * @param array $types limit search to concepts of the given type(s)
729
     * @return string sparql query clause
730
     */
731
    protected function formatTypes($types) {
732
        $typePatterns = array();
733
        if (!empty($types)) {
734
            foreach ($types as $type) {
735
                $unprefixed = EasyRdf\RdfNamespace::expand($type);
736
                $typePatterns[] = "{ ?s a <$unprefixed> }";
737
            }
738
        }
739
740
        return implode(' UNION ', $typePatterns);;
741
    }
742
743
    /**
744
     * @param string $prop property to include in the result eg. 'broader' or 'narrower'
745
     * @return string sparql query clause
746
     */
747
    private function formatPropertyCsvClause($prop) {
748
        # This expression creates a CSV row containing pairs of (uri,prefLabel) values.
749
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
750
        $clause = <<<EOV
751
(GROUP_CONCAT(DISTINCT CONCAT(
752
 '"', IF(isIRI(?$prop),STR(?$prop),''), '"', ',',
753
 '"', REPLACE(IF(BOUND(?{$prop}lab),?{$prop}lab,''), '"', '""'), '"', ',',
754
 '"', REPLACE(IF(isLiteral(?{$prop}),?{$prop},''), '"', '""'), '"'
755
); separator='\\n') as ?{$prop}s)
756
EOV;
757
        return $clause;
758
    }
759
    
760
    /**
761
     * @return string sparql query clause
762
     */
763
    private function formatPrefLabelCsvClause() {
764
        # This expression creates a CSV row containing pairs of (prefLabel, lang) values.
765
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
766
        $clause = <<<EOV
767
(GROUP_CONCAT(DISTINCT CONCAT(
768
 '"', STR(?pref), '"', ',', '"', lang(?pref), '"'
769
); separator='\\n') as ?preflabels)
770
EOV;
771
        return $clause;
772
    }
773
774
    /**
775
     * @param string $lang language code of the returned labels
776
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
777
     * @return string sparql query clause
778
     */
779
    protected function formatExtraFields($lang, $fields) {
780
        // extra variable expressions to request and extra fields to query for
781
        $ret = array('extravars' => '', 'extrafields' => '');
782
783
        if ($fields === null) {
784
            return $ret; 
785
        }
786
787
        if (in_array('prefLabel', $fields)) {
788
            $ret['extravars'] .= $this->formatPreflabelCsvClause();
789
            $ret['extrafields'] .= <<<EOF
790
OPTIONAL {
791
  ?s skos:prefLabel ?pref .
792
}
793
EOF;
794
            // removing the prefLabel from the fields since it has been handled separately
795
            $fields = array_diff($fields, array('prefLabel'));
796
        }
797
798
        foreach ($fields as $field) {
799
            $ret['extravars'] .= $this->formatPropertyCsvClause($field);
800
            $ret['extrafields'] .= <<<EOF
801
OPTIONAL {
802
  ?s skos:$field ?$field .
803
  FILTER(!isLiteral(?$field)||langMatches(lang(?{$field}), '$lang'))
804
  OPTIONAL { ?$field skos:prefLabel ?{$field}lab . FILTER(langMatches(lang(?{$field}lab), '$lang')) }
805
}
806
EOF;
807
        }
808
809
        return $ret;
810
    }
811
812
    /**
813
     * Generate condition for matching labels in SPARQL
814
     * @param string $term search term
815
     * @param string $searchLang language code used for matching labels (null means any language)
816
     * @return string sparql query snippet
817
     */
818
    protected function generateConceptSearchQueryCondition($term, $searchLang)
819
    {
820
        # use appropriate matching function depending on query type: =, strstarts, strends or full regex
821
        if (preg_match('/^[^\*]+$/', $term)) { // exact query
822
            $term = str_replace('\\', '\\\\', $term); // quote slashes
823
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
824
            $filtercond = "LCASE(STR(?match)) = '$term'";
825
        } elseif (preg_match('/^[^\*]+\*$/', $term)) { // prefix query
826
            $term = substr($term, 0, -1); // remove the final asterisk
827
            $term = str_replace('\\', '\\\\', $term); // quote slashes
828
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
829
            $filtercond = "STRSTARTS(LCASE(STR(?match)), '$term')";
830
        } elseif (preg_match('/^\*[^\*]+$/', $term)) { // suffix query
831
            $term = substr($term, 1); // remove the preceding asterisk
832
            $term = str_replace('\\', '\\\\', $term); // quote slashes
833
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
834
            $filtercond = "STRENDS(LCASE(STR(?match)), '$term')";
835
        } else { // too complicated - have to use a regex
836
            # make sure regex metacharacters are not passed through
837
            $term = str_replace('\\', '\\\\', preg_quote($term));
838
            $term = str_replace('\\\\*', '.*', $term); // convert asterisk to regex syntax
839
            $term = str_replace('\'', '\\\'', $term); // ensure single quotes are quoted
840
            $filtercond = "REGEX(STR(?match), '^$term$', 'i')";
841
        }
842
843
        $labelcondMatch = ($searchLang) ? "&& LANGMATCHES(lang(?match), '$searchLang')" : "";
844
        
845
        return "?s ?prop ?match . FILTER ($filtercond $labelcondMatch)";
846
    }
847
848
849
    /**
850
     * Inner query for concepts using a search term.
851
     * @param string $term search term
852
     * @param string $lang language code of the returned labels
853
     * @param string $searchLang language code used for matching labels (null means any language)
854
     * @param string[] $props properties to target e.g. array('skos:prefLabel','skos:altLabel')
855
     * @param boolean $unique restrict results to unique concepts (default: false)
856
     * @return string sparql query
857
     */
858
    protected function generateConceptSearchQueryInner($term, $lang, $searchLang, $props, $unique, $filterGraph)
859
    {
860
        $valuesProp = $this->formatValues('?prop', $props);
861
        $textcond = $this->generateConceptSearchQueryCondition($term, $searchLang);
862
        $rawterm = str_replace('\\', '\\\\', str_replace('*', '', $term));
863
        
864
        // graph clause, if necessary
865
        $graphClause = $filterGraph != '' ? 'GRAPH ?graph' : '';
866
867
        // extra conditions for label language, if specified
868
        $labelcondLabel = ($lang) ? "LANGMATCHES(lang(?label), '$lang')" : "lang(?match) = '' || LANGMATCHES(lang(?label), lang(?match))";
869
        // if search language and UI/display language differ, must also consider case where there is no prefLabel in
870
        // the display language; in that case, should use the label with the same language as the matched label
871
        $labelcondFallback = ($searchLang != $lang) ?
872
          "OPTIONAL { # in case previous OPTIONAL block gives no labels\n" .
873
          "?s skos:prefLabel ?label . FILTER (LANGMATCHES(LANG(?label), LANG(?match))) }" : "";
874
          
875
        //  Including the labels if there is no query term given.
876
        if ($rawterm === '') {
877
          $labelClause = "?s skos:prefLabel ?label .";
878
          $labelClause = ($lang) ? $labelClause . " FILTER (LANGMATCHES(LANG(?label), '$lang'))" : $labelClause . "";
879
          return $labelClause . " BIND(?label AS ?match)";
880
        }
881
882
        /*
883
         * This query does some tricks to obtain a list of unique concepts.
884
         * From each match generated by the text index, a string such as
885
         * "1en@example" is generated, where the first character is a number
886
         * encoding the property and priority, then comes the language tag and
887
         * finally the original literal after an @ sign. Of these, the MIN
888
         * function is used to pick the best match for each concept. Finally,
889
         * the structure is unpacked to get back the original string. Phew!
890
         */
891
        $hitvar = $unique ? '(MIN(?matchstr) AS ?hit)' : '(?matchstr AS ?hit)';
892
        $hitgroup = $unique ? 'GROUP BY ?s ?label ?notation' : '';
893
         
894
        $query = <<<EOQ
895
   SELECT DISTINCT ?s ?label ?notation $hitvar
896
   WHERE {
897
    $graphClause {
898
     { 
899
     $valuesProp
900
     VALUES (?prop ?pri) { (skos:prefLabel 1) (skos:altLabel 3) (skos:hiddenLabel 5)}
901
     $textcond
902
     ?s ?prop ?match }
903
     UNION
904
     { ?s skos:notation "$rawterm" }
905
     OPTIONAL {
906
      ?s skos:prefLabel ?label .
907
      FILTER ($labelcondLabel)
908
     } $labelcondFallback
909
     BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri)
910
     BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr)
911
     OPTIONAL { ?s skos:notation ?notation }
912
    }
913
    $filterGraph
914
   }
915
   $hitgroup
916
EOQ;
917
918
        return $query;
919
    }
920
921
    /**
922
     * Query for concepts using a search term.
923
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
924
     * @param boolean $unique restrict results to unique concepts (default: false)
925
     * @param boolean $showDeprecated whether to include deprecated concepts in search results (default: false)
926
     * @param ConceptSearchParameters $params 
927
     * @return string sparql query
928
     */
929
    protected function generateConceptSearchQuery($fields, $unique, $params, $showDeprecated = false) {
930
        $vocabs = $params->getVocabs();
931
        $gcl = $this->graphClause;
932
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
933
        $formattedtype = $this->formatTypes($params->getTypeLimit());
934
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);
935
        $extravars = $formattedfields['extravars'];
936
        $extrafields = $formattedfields['extrafields'];
937
        $schemes = $params->getSchemeLimit();
938
939
        $schemecond = '';
940
        if (!empty($schemes)) {
941
            foreach($schemes as $scheme) {
942
                $schemecond .= "?s skos:inScheme <$scheme> . ";
943
            }
944
        }
945
        $filterDeprecated="";
946
        //show or hide deprecated concepts
947
        if(!$showDeprecated){
948
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
949
        }
950
        // extra conditions for parent and group, if specified
951
        $parentcond = ($params->getParentLimit()) ? "?s skos:broader+ <" . $params->getParentLimit() . "> ." : "";
952
        $groupcond = ($params->getGroupLimit()) ? "<" . $params->getGroupLimit() . "> skos:member ?s ." : "";
953
        $pgcond = $parentcond . $groupcond;
954
955
        $orderextra = $this->isDefaultEndpoint() ? $this->graph : '';
956
957
        # make VALUES clauses
958
        $props = array('skos:prefLabel', 'skos:altLabel');
959
        if ($params->getHidden()) {
960
            $props[] = 'skos:hiddenLabel';
961
        }
962
963
        $filterGraph = empty($vocabs) ? $this->formatFilterGraph($vocabs) : '';
964
965
        // remove futile asterisks from the search term
966
        $term = $params->getSearchTerm();
967
        while (strpos($term, '**') !== false) {
968
            $term = str_replace('**', '*', $term);
969
        }
970
971
        $labelpriority = <<<EOQ
972
  FILTER(BOUND(?s))
973
  BIND(STR(SUBSTR(?hit,1,1)) AS ?pri)
974
  BIND(IF((SUBSTR(STRBEFORE(?hit, '@'),1) != ?pri), STRLANG(STRAFTER(?hit, '@'), SUBSTR(STRBEFORE(?hit, '@'),2)), STRAFTER(?hit, '@')) AS ?match)
975
  BIND(IF((?pri = "1" || ?pri = "2") && ?match != ?label, ?match, ?unbound) as ?plabel)
976
  BIND(IF((?pri = "3" || ?pri = "4"), ?match, ?unbound) as ?alabel)
977
  BIND(IF((?pri = "5" || ?pri = "6"), ?match, ?unbound) as ?hlabel)
978
EOQ;
979
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique, $filterGraph);
980
        if ($params->getSearchTerm() === '*' || $params->getSearchTerm() === '') { 
981
          $labelpriority = ''; 
982
        }
983
        $query = <<<EOQ
984
SELECT DISTINCT ?s ?label ?plabel ?alabel ?hlabel ?graph ?notation (GROUP_CONCAT(DISTINCT STR(?type);separator=' ') as ?types) $extravars 
985
$fcl
986
WHERE {
987
 $gcl {
988
  {
989
  $innerquery
990
  }
991
  $labelpriority
992
  $formattedtype
993
  { $pgcond 
994
   ?s a ?type .
995
   $extrafields $schemecond
996
  }
997
  $filterDeprecated
998
 }
999
 $filterGraph
1000
}
1001
GROUP BY ?s ?match ?label ?plabel ?alabel ?hlabel ?notation ?graph
1002
ORDER BY LCASE(STR(?match)) LANG(?match) $orderextra
1003
EOQ;
1004
        return $query;
1005
    }
1006
1007
    /**
1008
     * Transform a single concept search query results into the skosmos desired return format.
1009
     * @param $row SPARQL query result row
1010
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1011
     * @return array query result object
1012
     */
1013
    private function transformConceptSearchResult($row, $vocabs, $fields)
1014
    {
1015
        $hit = array();
1016
        $hit['uri'] = $row->s->getUri();
1017
1018
        if (isset($row->graph)) {
1019
            $hit['graph'] = $row->graph->getUri();
1020
        }
1021
1022
        foreach (explode(" ", $row->types->getValue()) as $typeuri) {
1023
            $hit['type'][] = $this->shortenUri($typeuri);
1024
        }
1025
1026
        if(!empty($fields)) {
1027
            foreach ($fields as $prop) {
1028
                $propname = $prop . 's';
1029
                if (isset($row->$propname)) {
1030
                    foreach (explode("\n", $row->$propname->getValue()) as $line) {
1031
                        $rdata = str_getcsv($line, ',', '"', '"');
1032
                        $propvals = array();
1033
                        if ($rdata[0] != '') {
1034
                            $propvals['uri'] = $rdata[0];
1035
                        }
1036
                        if ($rdata[1] != '') {
1037
                            $propvals['prefLabel'] = $rdata[1];
1038
                        }
1039
                        if ($rdata[2] != '') {
1040
                            $propvals = $rdata[2];
1041
                        }
1042
1043
                        $hit['skos:' . $prop][] = $propvals;
1044
                    }
1045
                }
1046
            }
1047
        }
1048
1049
        
1050
        if (isset($row->preflabels)) {
1051
            foreach (explode("\n", $row->preflabels->getValue()) as $line) {
1052
                $pref = str_getcsv($line, ',', '"', '"');
1053
                $hit['prefLabels'][$pref[1]] = $pref[0];
1054
            }
1055
        }
1056
1057
        foreach ($vocabs as $vocab) { // looping the vocabulary objects and asking these for a localname for the concept.
1058
            $localname = $vocab->getLocalName($hit['uri']);
1059
            if ($localname !== $hit['uri']) { // only passing the result forward if the uri didn't boomerang right back.
1060
                $hit['localname'] = $localname;
1061
                break; // stopping the search when we find one that returns something valid.
1062
            }
1063
        }
1064
1065
        if (isset($row->label)) {
1066
            $hit['prefLabel'] = $row->label->getValue();
1067
        }
1068
1069
        if (isset($row->label)) {
1070
            $hit['lang'] = $row->label->getLang();
1071
        }
1072
1073
        if (isset($row->notation)) {
1074
            $hit['notation'] = $row->notation->getValue();
1075
        }
1076
1077
        if (isset($row->plabel)) {
1078
            $hit['matchedPrefLabel'] = $row->plabel->getValue();
1079
            $hit['lang'] = $row->plabel->getLang();
1080 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...
1081
            $hit['altLabel'] = $row->alabel->getValue();
1082
            $hit['lang'] = $row->alabel->getLang();
1083
        } elseif (isset($row->hlabel)) {
1084
            $hit['hiddenLabel'] = $row->hlabel->getValue();
1085
            $hit['lang'] = $row->hlabel->getLang();
1086
        }
1087
        return $hit;
1088
    }
1089
1090
    /**
1091
     * Transform the concept search query results into the skosmos desired return format.
1092
     * @param EasyRdf\Sparql\Result $results
1093
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1094
     * @return array query result object
1095
     */
1096
    private function transformConceptSearchResults($results, $vocabs, $fields) {
1097
        $ret = array();
1098
1099
        foreach ($results as $row) {
1100
            if (!isset($row->s)) {
1101
                // don't break if query returns a single dummy result
1102
                continue;
1103
            }
1104
            $ret[] = $this->transformConceptSearchResult($row, $vocabs, $fields);
1105
        }
1106
        return $ret;
1107
    }
1108
1109
    /**
1110
     * Query for concepts using a search term.
1111
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1112
     * @param array $fields extra fields to include in the result (array of strings). (default: null = none)
1113
     * @param boolean $unique restrict results to unique concepts (default: false)
1114
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1115
     * @param ConceptSearchParameters $params 
1116
     * @return array query result object
1117
     */
1118
    public function queryConcepts($vocabs, $fields = null, $unique = false, $params, $showDeprecated = false) {
1119
        $query = $this->generateConceptSearchQuery($fields, $unique, $params,$showDeprecated);
1120
        $results = $this->query($query);
1121
        return $this->transformConceptSearchResults($results, $vocabs, $fields);
0 ignored issues
show
Bug introduced by
It seems like $results defined by $this->query($query) on line 1120 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...
1122
    }
1123
1124
    /**
1125
     * Generates sparql query clauses used for creating the alphabetical index.
1126
     * @param string $letter the letter (or special class) to search for
1127
     * @return array of sparql query clause strings
1128
     */
1129
    private function formatFilterConditions($letter, $lang) {
1130
        $useRegex = false;
1131
1132
        if ($letter == '*') {
1133
            $letter = '.*';
1134
            $useRegex = true;
1135
        } elseif ($letter == '0-9') {
1136
            $letter = '[0-9].*';
1137
            $useRegex = true;
1138
        } elseif ($letter == '!*') {
1139
            $letter = '[^\\\\p{L}\\\\p{N}].*';
1140
            $useRegex = true;
1141
        }
1142
1143
        # make text query clause
1144
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
1145
        if ($useRegex) {
1146
            $filtercondLabel = $lang ? "regex(str(?label), '^$letter$', 'i') && langMatches(lang(?label), '$lang')" : "regex(str(?label), '^$letter$', 'i')";
1147
            $filtercondALabel = $lang ? "regex(str(?alabel), '^$letter$', 'i') && langMatches(lang(?alabel), '$lang')" : "regex(str(?alabel), '^$letter$', 'i')";
1148
        } else {
1149
            $filtercondLabel = $lang ? "strstarts(lcase(str(?label)), '$lcletter') && langMatches(lang(?label), '$lang')" : "strstarts(lcase(str(?label)), '$lcletter')";
1150
            $filtercondALabel = $lang ? "strstarts(lcase(str(?alabel)), '$lcletter') && langMatches(lang(?alabel), '$lang')" : "strstarts(lcase(str(?alabel)), '$lcletter')";
1151
        }
1152
        return array('filterpref' => $filtercondLabel, 'filteralt' => $filtercondALabel);
1153
    }
1154
1155
    /**
1156
     * Generates the sparql query used for rendering the alphabetical index.
1157
     * @param string $letter the letter (or special class) to search for
1158
     * @param string $lang language of labels
1159
     * @param integer $limit limits the amount of results
1160
     * @param integer $offset offsets the result set
1161
     * @param array|null $classes
1162
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1163
     * @return string sparql query
1164
     */
1165
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated = false) {
1166
        $fcl = $this->generateFromClause();
1167
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1168
        $values = $this->formatValues('?type', $classes, 'uri');
1169
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
1170
        $conditions = $this->formatFilterConditions($letter, $lang);
1171
        $filtercondLabel = $conditions['filterpref'];
1172
        $filtercondALabel = $conditions['filteralt'];
1173
        $filterDeprecated="";
1174
        if(!$showDeprecated){
1175
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
1176
        }
1177
        $query = <<<EOQ
1178
SELECT DISTINCT ?s ?label ?alabel $fcl
1179
WHERE {
1180
  {
1181
    ?s skos:prefLabel ?label .
1182
    FILTER (
1183
      $filtercondLabel
1184
    )
1185
  }
1186
  UNION
1187
  {
1188
    {
1189
      ?s skos:altLabel ?alabel .
1190
      FILTER (
1191
        $filtercondALabel
1192
      )
1193
    }
1194
    {
1195
      ?s skos:prefLabel ?label .
1196
      FILTER (langMatches(lang(?label), '$lang'))
1197
    }
1198
  }
1199
  ?s a ?type .
1200
  $filterDeprecated
1201
  $values
1202
}
1203
ORDER BY STR(LCASE(COALESCE(?alabel, ?label))) $limitandoffset
1204
EOQ;
1205
        return $query;
1206
    }
1207
1208
    /**
1209
     * Transforms the alphabetical list query results into an array format.
1210
     * @param EasyRdf\Sparql\Result $results
1211
     * @return array
1212
     */
1213
    private function transformAlphabeticalListResults($results) {
1214
        $ret = array();
1215
1216
        foreach ($results as $row) {
1217
            if (!isset($row->s)) {
1218
                continue;
1219
            }
1220
            // don't break if query returns a single dummy result
1221
1222
            $hit = array();
1223
            $hit['uri'] = $row->s->getUri();
1224
1225
            $hit['localname'] = $row->s->localName();
1226
1227
            $hit['prefLabel'] = $row->label->getValue();
1228
            $hit['lang'] = $row->label->getLang();
1229
1230 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...
1231
                $hit['altLabel'] = $row->alabel->getValue();
1232
                $hit['lang'] = $row->alabel->getLang();
1233
            }
1234
1235
            $ret[] = $hit;
1236
        }
1237
1238
        return $ret;
1239
    }
1240
1241
    /**
1242
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1243
     * '*!' (special characters) and '*' (everything) are accepted.
1244
     * @param string $letter the letter (or special class) to search for
1245
     * @param string $lang language of labels
1246
     * @param integer $limit limits the amount of results
1247
     * @param integer $offset offsets the result set
1248
     * @param array $classes
1249
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1250
     */
1251
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null,$showDeprecated = false) {
1252
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes,$showDeprecated);
1253
        $results = $this->query($query);
1254
        return $this->transformAlphabeticalListResults($results);
0 ignored issues
show
Bug introduced by
It seems like $results defined by $this->query($query) on line 1253 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...
1255
    }
1256
1257
    /**
1258
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1259
     * Note that we force the datatype of the result variable otherwise Virtuoso does not properly interpret the DISTINCT and we have duplicated results
1260
     * @param string $lang language
1261
     * @return string sparql query
1262
     */
1263
    private function generateFirstCharactersQuery($lang, $classes) {
1264
        $fcl = $this->generateFromClause();
1265
        $classes = (sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1266
        $values = $this->formatValues('?type', $classes, 'uri');
1267
        $query = <<<EOQ
1268
SELECT DISTINCT (ucase(str(substr(?label, 1, 1))) as ?l) $fcl WHERE {
1269
  ?c skos:prefLabel ?label .
1270
  ?c a ?type
1271
  FILTER(langMatches(lang(?label), '$lang'))
1272
  $values
1273
}
1274
EOQ;
1275
        return $query;
1276
    }
1277
1278
    /**
1279
     * Transforms the first characters query results into an array format.
1280
     * @param EasyRdf\Sparql\Result $result
1281
     * @return array
1282
     */
1283
    private function transformFirstCharactersResults($result) {
1284
        $ret = array();
1285
        foreach ($result as $row) {
1286
            $ret[] = $row->l->getValue();
1287
        }
1288
        return $ret;
1289
    }
1290
1291
    /**
1292
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1293
     * @param string $lang language
1294
     * @return array array of characters
1295
     */
1296
    public function queryFirstCharacters($lang, $classes = null) {
1297
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1298
        $result = $this->query($query);
1299
        return $this->transformFirstCharactersResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1298 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...
1300
    }
1301
1302
    /**
1303
     * @param string $uri
1304
     * @param string $lang
1305
     * @return string sparql query string
1306
     */
1307
    private function generateLabelQuery($uri, $lang) {
1308
        $fcl = $this->generateFromClause();
1309
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1310
        $query = <<<EOQ
1311
SELECT ?label $fcl
1312
WHERE {
1313
  <$uri> a ?type .
1314
  OPTIONAL {
1315
    <$uri> skos:prefLabel ?label .
1316
    $labelcondLabel
1317
  }
1318
  OPTIONAL {
1319
    <$uri> rdfs:label ?label .
1320
    $labelcondLabel
1321
  }
1322
  OPTIONAL {
1323
    <$uri> dc:title ?label .
1324
    $labelcondLabel
1325
  }
1326
  OPTIONAL {
1327
    <$uri> dc11:title ?label .
1328
    $labelcondLabel
1329
  }
1330
}
1331
EOQ;
1332
        return $query;
1333
    }
1334
1335
    /**
1336
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1337
     * @param string $uri
1338
     * @param string $lang
1339
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1340
     */
1341
    public function queryLabel($uri, $lang) {
1342
        $query = $this->generateLabelQuery($uri, $lang);
1343
        $result = $this->query($query);
1344
        $ret = array();
1345
        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...
1346
            if (!isset($row->label)) {
1347
                // existing concept but no labels
1348
                return array();
1349
            }
1350
            $ret[$row->label->getLang()] = $row->label;
1351
        }
1352
1353
        if (sizeof($ret) > 0) {
1354
            // existing concept, with label(s)
1355
            return $ret;
1356
        } else {
1357
            // nonexistent concept
1358
            return null;
1359
        }
1360
    }
1361
1362
1363
    /**
1364
     * Generates a sparql query for queryNotation.
1365
     * @param string $uri
1366
     * @return string sparql query
1367
     */
1368
    private function generateNotationQuery($uri) {
1369
        $fcl = $this->generateFromClause();
1370
1371
        $query = <<<EOQ
1372
SELECT * $fcl
1373
WHERE {
1374
  <$uri> skos:notation ?notation .
1375
}
1376
EOQ;
1377
        return $query;
1378
    }
1379
1380
    /**
1381
     * Query for the notation of the concept (skos:notation) of a resource.
1382
     * @param string $uri
1383
     * @return string notation or null if it doesn't exist
1384
     */
1385
    public function queryNotation($uri) {
1386
        $query = $this->generateNotationQuery($uri);
1387
        $result = $this->query($query);
1388
        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...
1389
            if (isset($row->notation)) {
1390
                return $row->notation->getValue();
1391
            }
1392
        }
1393
        return null;
1394
    }
1395
1396
    /**
1397
     * Generates a sparql query for queryProperty.
1398
     * @param string $uri
1399
     * @param string $prop the name of the property eg. 'skos:broader'.
1400
     * @param string $lang
1401
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1402
     * @return string sparql query
1403
     */
1404 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...
1405
        $fcl = $this->generateFromClause();
1406
        $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1407
1408
        $query = <<<EOQ
1409
SELECT * $fcl
1410
WHERE {
1411
  <$uri> a skos:Concept .
1412
  OPTIONAL {
1413
    <$uri> $prop ?object .
1414
    OPTIONAL {
1415
      ?object skos:prefLabel ?label .
1416
      FILTER (langMatches(lang(?label), "$lang"))
1417
    }
1418
    OPTIONAL {
1419
      ?object skos:prefLabel ?label .
1420
      FILTER (lang(?label) = "")
1421
    }
1422
    $anylang
1423
  }
1424
}
1425
EOQ;
1426
        return $query;
1427
    }
1428
1429
    /**
1430
     * Transforms the sparql query result into an array or null if the concept doesn't exist.
1431
     * @param EasyRdf\Sparql\Result $result
1432
     * @param string $lang
1433
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1434
     */
1435
    private function transformPropertyQueryResults($result, $lang) {
1436
        $ret = array();
1437
        foreach ($result as $row) {
1438
            if (!isset($row->object)) {
1439
                return array();
1440
            }
1441
            // existing concept but no properties
1442
            if (isset($row->label)) {
1443
                if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1444
                    $ret[$row->object->getUri()]['label'] = $row->label->getValue();
1445
                }
1446
1447
            } else {
1448
                $ret[$row->object->getUri()]['label'] = null;
1449
            }
1450
        }
1451
        if (sizeof($ret) > 0) {
1452
            return $ret;
1453
        }
1454
        // existing concept, with properties
1455
        else {
1456
            return null;
1457
        }
1458
        // nonexistent concept
1459
    }
1460
1461
    /**
1462
     * Query a single property of a concept.
1463
     * @param string $uri
1464
     * @param string $prop the name of the property eg. 'skos:broader'.
1465
     * @param string $lang
1466
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1467
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1468
     */
1469
    public function queryProperty($uri, $prop, $lang, $anylang = false) {
1470
        $uri = is_array($uri) ? $uri[0] : $uri;
1471
        $query = $this->generatePropertyQuery($uri, $prop, $lang, $anylang);
1472
        $result = $this->query($query);
1473
        return $this->transformPropertyQueryResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1472 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...
1474
    }
1475
1476
    /**
1477
     * Query a single transitive property of a concept.
1478
     * @param string $uri
1479
     * @param array $props the name of the property eg. 'skos:broader'.
1480
     * @param string $lang
1481
     * @param integer $limit
1482
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1483
     * @return string sparql query
1484
     */
1485
    private function generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang) {
1486
        $uri = is_array($uri) ? $uri[0] : $uri;
1487
        $fcl = $this->generateFromClause();
1488
        $propertyClause = implode('|', $props);
1489
        $otherlang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1490
        // need to do a SPARQL subquery because LIMIT needs to be applied /after/
1491
        // the direct relationships have been collapsed into one string
1492
        $query = <<<EOQ
1493
SELECT * $fcl
1494
WHERE {
1495
  SELECT ?object ?label (GROUP_CONCAT(STR(?dir);separator=' ') as ?direct)
1496
  WHERE {
1497
    <$uri> a skos:Concept .
1498
    OPTIONAL {
1499
      <$uri> $propertyClause* ?object .
1500
      OPTIONAL {
1501
        ?object $propertyClause ?dir .
1502
      }
1503
    }
1504
    OPTIONAL {
1505
      ?object skos:prefLabel ?label .
1506
      FILTER (langMatches(lang(?label), "$lang"))
1507
    }
1508
    $otherlang
1509
  }
1510
  GROUP BY ?object ?label
1511
}
1512
LIMIT $limit
1513
EOQ;
1514
        return $query;
1515
    }
1516
1517
    /**
1518
     * Transforms the sparql query result object into an array.
1519
     * @param EasyRdf\Sparql\Result $result
1520
     * @param string $lang
1521
     * @param string $fallbacklang language to use if label is not available in the preferred language
1522
     * @return array of property values (key: URI, val: label), or null if concept doesn't exist
1523
     */
1524
    private function transformTransitivePropertyResults($result, $lang, $fallbacklang) {
1525
        $ret = array();
1526
        foreach ($result as $row) {
1527
            if (!isset($row->object)) {
1528
                return array();
1529
            }
1530
            // existing concept but no properties
1531
            if (isset($row->label)) {
1532
                $val = array('label' => $row->label->getValue());
1533
            } else {
1534
                $val = array('label' => null);
1535
            }
1536 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...
1537
                $val['direct'] = explode(' ', $row->direct->getValue());
1538
            }
1539
            // Preventing labels in a non preferred language overriding the preferred language.
1540
            if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1541
                if (!isset($row->label) || $row->label->getLang() === $lang) {
1542
                    $ret[$row->object->getUri()] = $val;
1543
                } elseif ($row->label->getLang() === $fallbacklang) {
1544
                    $val['label'] .= ' (' . $row->label->getLang() . ')';
1545
                    $ret[$row->object->getUri()] = $val;
1546
                }
1547
            }
1548
        }
1549
1550
        // second iteration of results to find labels for the ones that didn't have one in the preferred languages
1551
        foreach ($result as $row) {
1552
            if (isset($row->object) && array_key_exists($row->object->getUri(), $ret) === false) {
1553
                $val = array('label' => $row->label->getValue());
1554 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...
1555
                    $val['direct'] = explode(' ', $row->direct->getValue());
1556
                }
1557
                $ret[$row->object->getUri()] = $val;
1558
            }
1559
        }
1560
1561
        if (sizeof($ret) > 0) {
1562
            return $ret;
1563
        }
1564
        // existing concept, with properties
1565
        else {
1566
            return null;
1567
        }
1568
        // nonexistent concept
1569
    }
1570
1571
    /**
1572
     * Query a single transitive property of a concept.
1573
     * @param string $uri
1574
     * @param array $props the property/properties.
1575
     * @param string $lang
1576
     * @param string $fallbacklang language to use if label is not available in the preferred language
1577
     * @param integer $limit
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 queryTransitiveProperty($uri, $props, $lang, $limit, $anylang = false, $fallbacklang = '') {
1582
        $query = $this->generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang);
1583
        $result = $this->query($query);
1584
        return $this->transformTransitivePropertyResults($result, $lang, $fallbacklang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1583 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...
1585
    }
1586
1587
    /**
1588
     * Generates the query for a concepts skos:narrowers.
1589
     * @param string $uri
1590
     * @param string $lang
1591
     * @param string $fallback
1592
     * @return string sparql query
1593
     */
1594
    private function generateChildQuery($uri, $lang, $fallback, $props) {
1595
        $uri = is_array($uri) ? $uri[0] : $uri;
1596
        $fcl = $this->generateFromClause();
1597
        $propertyClause = implode('|', $props);
1598
        $query = <<<EOQ
1599
SELECT ?child ?label ?child ?grandchildren ?notation $fcl WHERE {
1600
  <$uri> a skos:Concept .
1601
  OPTIONAL {
1602
    ?child $propertyClause <$uri> .
1603
    OPTIONAL {
1604
      ?child skos:prefLabel ?label .
1605
      FILTER (langMatches(lang(?label), "$lang"))
1606
    }
1607
    OPTIONAL {
1608
      ?child skos:prefLabel ?label .
1609
      FILTER (langMatches(lang(?label), "$fallback"))
1610
    }
1611
    OPTIONAL { # other language case
1612
      ?child skos:prefLabel ?label .
1613
    }
1614
    OPTIONAL {
1615
      ?child skos:notation ?notation .
1616
    }
1617
    BIND ( EXISTS { ?a $propertyClause ?child . } AS ?grandchildren )
1618
  }
1619
}
1620
EOQ;
1621
        return $query;
1622
    }
1623
1624
    /**
1625
     * Transforms the sparql result object into an array.
1626
     * @param EasyRdf\Sparql\Result $result
1627
     * @param string $lang
1628
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1629
     */
1630
    private function transformNarrowerResults($result, $lang) {
1631
        $ret = array();
1632
        foreach ($result as $row) {
1633
            if (!isset($row->child)) {
1634
                return array();
1635
            }
1636
            // existing concept but no children
1637
1638
            $label = null;
1639
            if (isset($row->label)) {
1640
                if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
1641
                    $label = $row->label->getValue();
1642
                } else {
1643
                    $label = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1644
                }
1645
1646
            }
1647
            $childArray = array(
1648
                'uri' => $row->child->getUri(),
1649
                'prefLabel' => $label,
1650
                'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1651
            );
1652
            if (isset($row->notation)) {
1653
                $childArray['notation'] = $row->notation->getValue();
1654
            }
1655
1656
            $ret[] = $childArray;
1657
        }
1658
        if (sizeof($ret) > 0) {
1659
            return $ret;
1660
        }
1661
        // existing concept, with children
1662
        else {
1663
            return null;
1664
        }
1665
        // nonexistent concept
1666
    }
1667
1668
    /**
1669
     * Query the narrower concepts of a concept.
1670
     * @param string $uri
1671
     * @param string $lang
1672
     * @param string $fallback
1673
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1674
     */
1675
    public function queryChildren($uri, $lang, $fallback, $props) {
1676
        $query = $this->generateChildQuery($uri, $lang, $fallback, $props);
1677
        $result = $this->query($query);
1678
        return $this->transformNarrowerResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1677 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...
1679
    }
1680
1681
    /**
1682
     * Query the top concepts of a vocabulary.
1683
     * @param string $conceptSchemes concept schemes whose top concepts to query for
1684
     * @param string $lang language of labels
1685
     * @param string $fallback language to use if label is not available in the preferred language
1686
     */
1687
    public function queryTopConcepts($conceptSchemes, $lang, $fallback) {
1688
        if (!is_array($conceptSchemes)) {
1689
            $conceptSchemes = array($conceptSchemes);
1690
        }
1691
1692
        $values = $this->formatValues('?topuri', $conceptSchemes, 'uri');
1693
1694
        $fcl = $this->generateFromClause();
1695
        $query = <<<EOQ
1696
SELECT DISTINCT ?top ?topuri ?label ?notation ?children $fcl WHERE {
1697
  ?top skos:topConceptOf ?topuri .
1698
  OPTIONAL {
1699
    ?top skos:prefLabel ?label .
1700
    FILTER (langMatches(lang(?label), "$lang"))
1701
  }
1702
  OPTIONAL {
1703
    ?top skos:prefLabel ?label .
1704
    FILTER (langMatches(lang(?label), "$fallback"))
1705
  }
1706
  OPTIONAL { # fallback - other language case
1707
    ?top skos:prefLabel ?label .
1708
  }
1709
  OPTIONAL { ?top skos:notation ?notation . }
1710
  BIND ( EXISTS { ?top skos:narrower ?a . } AS ?children )
1711
  $values
1712
}
1713
EOQ;
1714
        $result = $this->query($query);
1715
        $ret = array();
1716
        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...
1717
            if (isset($row->top) && isset($row->label)) {
1718
                $label = $row->label->getValue();
1719 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...
1720
                    $label .= ' (' . $row->label->getLang() . ')';
1721
                }
1722
                $top = array('uri' => $row->top->getUri(), 'topConceptOf' => $row->topuri->getUri(), 'label' => $label, 'hasChildren' => filter_var($row->children->getValue(), FILTER_VALIDATE_BOOLEAN));
1723
                if (isset($row->notation)) {
1724
                    $top['notation'] = $row->notation->getValue();
1725
                }
1726
1727
                $ret[] = $top;
1728
            }
1729
        }
1730
1731
        return $ret;
1732
    }
1733
1734
    /**
1735
     * Generates a sparql query for finding the hierarchy for a concept.
1736
     * @param string $uri concept uri.
1737
     * @param string $lang
1738
     * @param string $fallback language to use if label is not available in the preferred language
1739
     * @return string sparql query
1740
     */
1741
    private function generateParentListQuery($uri, $lang, $fallback, $props) {
1742
        $fcl = $this->generateFromClause();
1743
        $propertyClause = implode('|', $props);
1744
        $query = <<<EOQ
1745
SELECT ?broad ?parent ?member ?children ?grandchildren
1746
(SAMPLE(?lab) as ?label) (SAMPLE(?childlab) as ?childlabel) (SAMPLE(?topcs) AS ?top) (SAMPLE(?nota) as ?notation) (SAMPLE(?childnota) as ?childnotation) $fcl
1747
WHERE {
1748
  <$uri> a skos:Concept .
1749
  OPTIONAL {
1750
    <$uri> $propertyClause* ?broad .
1751
    OPTIONAL {
1752
      ?broad skos:prefLabel ?lab .
1753
      FILTER (langMatches(lang(?lab), "$lang"))
1754
    }
1755
    OPTIONAL {
1756
      ?broad skos:prefLabel ?lab .
1757
      FILTER (langMatches(lang(?lab), "$fallback"))
1758
    }
1759
    OPTIONAL { # fallback - other language case
1760
      ?broad skos:prefLabel ?lab .
1761
    }
1762
    OPTIONAL { ?broad skos:notation ?nota . }
1763
    OPTIONAL { ?broad $propertyClause ?parent . }
1764
    OPTIONAL { ?broad skos:narrower ?children .
1765
      OPTIONAL {
1766
        ?children skos:prefLabel ?childlab .
1767
        FILTER (langMatches(lang(?childlab), "$lang"))
1768
      }
1769
      OPTIONAL {
1770
        ?children skos:prefLabel ?childlab .
1771
        FILTER (langMatches(lang(?childlab), "$fallback"))
1772
      }
1773
      OPTIONAL { # fallback - other language case
1774
        ?children skos:prefLabel ?childlab .
1775
      }
1776
      OPTIONAL {
1777
        ?children skos:notation ?childnota .
1778
      }
1779
    }
1780
    BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren )
1781
    OPTIONAL { ?broad skos:topConceptOf ?topcs . }
1782
  }
1783
}
1784
GROUP BY ?broad ?parent ?member ?children ?grandchildren
1785
EOQ;
1786
        return $query;
1787
    }
1788
1789
    /**
1790
     * Transforms the result into an array.
1791
     * @param EasyRdf\Sparql\Result
1792
     * @param string $lang
1793
     * @return an array for the REST controller to encode.
1794
     */
1795
    private function transformParentListResults($result, $lang)
1796
    {
1797
        $ret = array();
1798
        foreach ($result as $row) {
1799
            if (!isset($row->broad)) {
1800
                // existing concept but no broaders
1801
                return array();
1802
            }
1803
            $uri = $row->broad->getUri();
1804
            if (!isset($ret[$uri])) {
1805
                $ret[$uri] = array('uri' => $uri);
1806
            }
1807
            if (isset($row->exact)) {
1808
                $ret[$uri]['exact'] = $row->exact->getUri();
1809
            }
1810
            if (isset($row->top)) {
1811
                $ret[$uri]['top'] = $row->top->getUri();
1812
            }
1813
            if (isset($row->children)) {
1814
                if (!isset($ret[$uri]['narrower'])) {
1815
                    $ret[$uri]['narrower'] = array();
1816
                }
1817
1818
                $label = null;
1819
                if (isset($row->childlabel)) {
1820
                    $label = $row->childlabel->getValue();
1821
                    if ($row->childlabel->getLang() !== $lang && strpos($row->childlabel->getLang(), $lang . "-") !== 0) {
1822
                        $label .= " (" . $row->childlabel->getLang() . ")";
1823
                    }
1824
1825
                }
1826
1827
                $childArr = array(
1828
                    'uri' => $row->children->getUri(),
1829
                    'label' => $label,
1830
                    'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1831
                );
1832
                if (isset($row->childnotation)) {
1833
                    $childArr['notation'] = $row->childnotation->getValue();
1834
                }
1835
1836
                if (!in_array($childArr, $ret[$uri]['narrower'])) {
1837
                    $ret[$uri]['narrower'][] = $childArr;
1838
                }
1839
1840
            }
1841
            if (isset($row->label)) {
1842
                $preflabel = $row->label->getValue();
1843 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...
1844
                    $preflabel .= ' (' . $row->label->getLang() . ')';
1845
                }
1846
1847
                $ret[$uri]['prefLabel'] = $preflabel;
1848
            }
1849
            if (isset($row->notation)) {
1850
                $ret[$uri]['notation'] = $row->notation->getValue();
1851
            }
1852
1853
            if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) {
1854
                $ret[$uri]['broader'][] = $row->parent->getUri();
1855
            } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) {
1856
                $ret[$uri]['broader'][] = $row->parent->getUri();
1857
            }
1858
        }
1859
        if (sizeof($ret) > 0) {
1860
            // existing concept, with children
1861
            return $ret;
1862
        }
1863
        else {
1864
            // nonexistent concept
1865
            return null;
1866
        }
1867
    }
1868
1869
    /**
1870
     * Query for finding the hierarchy for a concept.
1871
     * @param string $uri concept uri.
1872
     * @param string $lang
1873
     * @param string $fallback language to use if label is not available in the preferred language
1874
     * @param array $props the hierarchy property/properties to use
1875
     * @return an array for the REST controller to encode.
1876
     */
1877
    public function queryParentList($uri, $lang, $fallback, $props) {
1878
        $query = $this->generateParentListQuery($uri, $lang, $fallback, $props);
1879
        $result = $this->query($query);
1880
        return $this->transformParentListResults($result, $lang);
1881
    }
1882
1883
    /**
1884
     * return a list of concept group instances, sorted by label
1885
     * @param string $groupClass URI of concept group class
1886
     * @param string $lang language of labels to return
1887
     * @return string sparql query
1888
     */
1889
    private function generateConceptGroupsQuery($groupClass, $lang) {
1890
        $fcl = $this->generateFromClause();
1891
        $query = <<<EOQ
1892
SELECT ?group (GROUP_CONCAT(DISTINCT STR(?child);separator=' ') as ?children) ?label ?members ?notation $fcl
1893
WHERE {
1894
  ?group a <$groupClass> .
1895
  OPTIONAL { ?group skos:member|isothes:subGroup ?child .
1896
             ?child a <$groupClass> }
1897
  BIND(EXISTS{?group skos:member ?submembers} as ?members)
1898
  OPTIONAL { ?group skos:prefLabel ?label }
1899
  OPTIONAL { ?group rdfs:label ?label }
1900
  FILTER (langMatches(lang(?label), '$lang'))
1901
  OPTIONAL { ?group skos:notation ?notation }
1902
}
1903
GROUP BY ?group ?label ?members ?notation
1904
ORDER BY lcase(?label)
1905
EOQ;
1906
        return $query;
1907
    }
1908
1909
    /**
1910
     * Transforms the sparql query result into an array.
1911
     * @param EasyRdf\Sparql\Result $result
1912
     * @return array
1913
     */
1914
    private function transformConceptGroupsResults($result) {
1915
        $ret = array();
1916
        foreach ($result as $row) {
1917
            if (!isset($row->group)) {
1918
                # no groups found, see issue #357
1919
                continue;
1920
            }
1921
            $group = array('uri' => $row->group->getURI());
1922
            if (isset($row->label)) {
1923
                $group['prefLabel'] = $row->label->getValue();
1924
            }
1925
1926
            if (isset($row->children)) {
1927
                $group['childGroups'] = explode(' ', $row->children->getValue());
1928
            }
1929
1930
            if (isset($row->members)) {
1931
                $group['hasMembers'] = $row->members->getValue();
1932
            }
1933
1934
            if (isset($row->notation)) {
1935
                $group['notation'] = $row->notation->getValue();
1936
            }
1937
1938
            $ret[] = $group;
1939
        }
1940
        return $ret;
1941
    }
1942
1943
    /**
1944
     * return a list of concept group instances, sorted by label
1945
     * @param string $groupClass URI of concept group class
1946
     * @param string $lang language of labels to return
1947
     * @return array Result array with group URI as key and group label as value
1948
     */
1949
    public function listConceptGroups($groupClass, $lang) {
1950
        $query = $this->generateConceptGroupsQuery($groupClass, $lang);
1951
        $result = $this->query($query);
1952
        return $this->transformConceptGroupsResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1951 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...
1953
    }
1954
1955
    /**
1956
     * Generates the sparql query for listConceptGroupContents
1957
     * @param string $groupClass URI of concept group class
1958
     * @param string $group URI of the concept group instance
1959
     * @param string $lang language of labels to return
1960
     * @param boolean $showDeprecated whether to include deprecated in the result
1961
     * @return string sparql query
1962
     */
1963
    private function generateConceptGroupContentsQuery($groupClass, $group, $lang, $showDeprecated = false) {
1964
        $fcl = $this->generateFromClause();
1965
        $filterDeprecated="";
1966
        if(!$showDeprecated){
1967
            $filterDeprecated="  FILTER NOT EXISTS { ?conc owl:deprecated true }";
1968
        }
1969
        $query = <<<EOQ
1970
SELECT ?conc ?super ?label ?members ?type ?notation $fcl
1971
WHERE {
1972
 <$group> a <$groupClass> .
1973
 { <$group> skos:member ?conc . } UNION { ?conc isothes:superGroup <$group> }
1974
$filterDeprecated
1975
 ?conc a ?type .
1976
 OPTIONAL { ?conc skos:prefLabel ?label .
1977
  FILTER (langMatches(lang(?label), '$lang'))
1978
 }
1979
 OPTIONAL { ?conc skos:prefLabel ?label . }
1980
 OPTIONAL { ?conc skos:notation ?notation }
1981
 BIND(EXISTS{?submembers isothes:superGroup ?conc} as ?super)
1982
 BIND(EXISTS{?conc skos:member ?submembers} as ?members)
1983
} ORDER BY lcase(?label)
1984
EOQ;
1985
        return $query;
1986
    }
1987
1988
    /**
1989
     * Transforms the sparql query result into an array.
1990
     * @param EasyRdf\Sparql\Result $result
1991
     * @param string $lang language of labels to return
1992
     * @return array
1993
     */
1994
    private function transformConceptGroupContentsResults($result, $lang) {
1995
        $ret = array();
1996
        $values = array();
1997
        foreach ($result as $row) {
1998
            if (!array_key_exists($row->conc->getURI(), $values)) {
1999
                $values[$row->conc->getURI()] = array(
2000
                    'uri' => $row->conc->getURI(),
2001
                    'isSuper' => $row->super->getValue(),
2002
                    'hasMembers' => $row->members->getValue(),
2003
                    'type' => array($row->type->shorten()),
2004
                );
2005
                if (isset($row->label)) {
2006
                    if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
2007
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue();
2008
                    } else {
2009
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue() . " (" . $row->label->getLang() . ")";
2010
                    }
2011
2012
                }
2013
                if (isset($row->notation)) {
2014
                    $values[$row->conc->getURI()]['notation'] = $row->notation->getValue();
2015
                }
2016
2017
            } else {
2018
                $values[$row->conc->getURI()]['type'][] = $row->type->shorten();
2019
            }
2020
        }
2021
2022
        foreach ($values as $val) {
2023
            $ret[] = $val;
2024
        }
2025
2026
        return $ret;
2027
    }
2028
2029
    /**
2030
     * return a list of concepts in a concept group
2031
     * @param string $groupClass URI of concept group class
2032
     * @param string $group URI of the concept group instance
2033
     * @param string $lang language of labels to return
2034
     * @param boolean $showDeprecated whether to include deprecated concepts in search results
2035
     * @return array Result array with concept URI as key and concept label as value
2036
     */
2037
    public function listConceptGroupContents($groupClass, $group, $lang,$showDeprecated = false) {
2038
        $query = $this->generateConceptGroupContentsQuery($groupClass, $group, $lang,$showDeprecated);
2039
        $result = $this->query($query);
2040
        return $this->transformConceptGroupContentsResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2039 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...
2041
    }
2042
2043
    /**
2044
     * Generates the sparql query for queryChangeList.
2045
     * @param string $lang language of labels to return.
2046
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2047
     * @return string sparql query
2048
     */
2049 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...
2050
        $fcl = $this->generateFromClause();
2051
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
2052
2053
        $query = <<<EOQ
2054
SELECT DISTINCT ?concept ?date ?label $fcl
2055
WHERE {
2056
  ?concept a skos:Concept .
2057
  ?concept $prop ?date .
2058
  ?concept skos:prefLabel ?label .
2059
  FILTER (langMatches(lang(?label), '$lang'))
2060
}
2061
ORDER BY DESC(YEAR(?date)) DESC(MONTH(?date)) LCASE(?label)
2062
LIMIT 200 $offset
2063
EOQ;
2064
        return $query;
2065
    }
2066
2067
    /**
2068
     * Transforms the sparql query result into an array.
2069
     * @param EasyRdf\Sparql\Result $result
2070
     * @return array
2071
     */
2072 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...
2073
        $ret = array();
2074
        foreach ($result as $row) {
2075
            $concept = array('uri' => $row->concept->getURI());
2076
            if (isset($row->label)) {
2077
                $concept['prefLabel'] = $row->label->getValue();
2078
            }
2079
2080
            if (isset($row->date)) {
2081
                $concept['date'] = $row->date->getValue();
2082
            }
2083
2084
            $ret[] = $concept;
2085
        }
2086
        return $ret;
2087
    }
2088
2089
    /**
2090
     * return a list of recently changed or entirely new concepts
2091
     * @param string $lang language of labels to return
2092
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2093
     * @return array Result array
2094
     */
2095
    public function queryChangeList($lang, $offset, $prop) {
2096
        $query = $this->generateChangeListQuery($lang, $offset, $prop);
2097
        $result = $this->query($query);
2098
        return $this->transformChangeListResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2097 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...
2099
    }
2100
}
2101