Completed
Push — master ( c6c130...999f50 )
by Osma
02:17
created

GenericSparql::generateFromClause()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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

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

Let’s take a look at an example:

class A
{
    public function foo() { }
}

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

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

Available Fixes

  1. Add an additional type-check:

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

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

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

Let’s take a look at an example:

class A
{
    public function foo() { }
}

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

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

Available Fixes

  1. Add an additional type-check:

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

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

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

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

    return array();
}

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

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

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

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

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

    return array();
}

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

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

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

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
485
    }
486
487
    /**
488
     * Returns information (as an array of Concept objects) for one or more concept URIs
489
     * @param mixed $uris concept URI (string) or array of URIs
490
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
491
     * @param \Vocabulary[] $vocabs vocabularies to target
492
     * @param string|null $clang content language
493
     * @return Concept[]
494
     */
495
    public function queryConceptInfo($uris, $arrayClass = null, $vocabs = array(), $clang = null) {
496
        // if just a single URI is given, put it in an array regardless
497
        if (!is_array($uris)) {
498
            $uris = array($uris);
499
        }
500
        $result = $this->queryConceptInfoGraph($uris, $arrayClass, $vocabs);
501
        if ($result->isEmpty()) {
502
            return [];
503
        }
504
        return $this->transformConceptInfoResults($result, $uris, $vocabs, $clang);
505
    }
506
507
    /**
508
     * Generates the sparql query for queryTypes
509
     * @param string $lang
510
     * @return string sparql query
511
     */
512
    private function generateQueryTypesQuery($lang) {
513
        $fcl = $this->generateFromClause();
514
        $query = <<<EOQ
515
SELECT DISTINCT ?type ?label ?superclass $fcl
516
WHERE {
517
  {
518
    { BIND( skos:Concept as ?type ) }
519
    UNION
520
    { BIND( skos:Collection as ?type ) }
521
    UNION
522
    { BIND( isothes:ConceptGroup as ?type ) }
523
    UNION
524
    { BIND( isothes:ThesaurusArray as ?type ) }
525
    UNION
526
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Concept . }
527
    UNION
528
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Collection . }
529
  }
530
  OPTIONAL {
531
    ?type rdfs:label ?label .
532
    FILTER(langMatches(lang(?label), '$lang'))
533
  }
534
  OPTIONAL {
535
    ?type rdfs:subClassOf ?superclass .
536
  }
537
  FILTER EXISTS {
538
    ?s a ?type .
539
    ?s skos:prefLabel ?prefLabel .
540
  }
541
}
542
EOQ;
543
        return $query;
544
    }
545
546
    /**
547
     * Transforms the results into an array format.
548
     * @param EasyRdf\Sparql\Result $result
549
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
550
     */
551 View Code Duplication
    private function transformQueryTypesResults($result) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
552
        $ret = array();
553
        foreach ($result as $row) {
554
            $type = array();
555
            if (isset($row->label)) {
556
                $type['label'] = $row->label->getValue();
557
            }
558
559
            if (isset($row->superclass)) {
560
                $type['superclass'] = $row->superclass->getUri();
561
            }
562
563
            $ret[$row->type->getURI()] = $type;
564
        }
565
        return $ret;
566
    }
567
568
    /**
569
     * Retrieve information about types from the endpoint
570
     * @param string $lang
571
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
572
     */
573
    public function queryTypes($lang) {
574
        $query = $this->generateQueryTypesQuery($lang);
575
        $result = $this->query($query);
576
        return $this->transformQueryTypesResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 575 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformQueryTypesResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

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

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

    return array();
}

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

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

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

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

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

    return array();
}

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

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

Loading history...
683
    }
684
685
    /**
686
     * Generate a VALUES clause for limiting the targeted graphs.
687
     * @param Vocabulary[]|null $vocabs the vocabularies to target 
688
     * @return string[] array of graph URIs
689
     */
690
    protected function getVocabGraphs($vocabs) {
691
        if ($vocabs === null || sizeof($vocabs) == 0) {
692
            // searching from all vocabularies - limit to known graphs
693
            $vocabs = $this->model->getVocabularies();
694
        }
695
        $graphs = array();
696
        foreach ($vocabs as $voc) {
697
            $graph = $voc->getGraph();
698
            if (!is_null($graph) && !in_array($graph, $graphs)) {
699
                $graphs[] = $graph;
700
            }
701
        }
702
        return $graphs;
703
    }
704
705
    /**
706
     * Generate a VALUES clause for limiting the targeted graphs.
707
     * @param Vocabulary[]|null $vocabs array of Vocabulary objects to target
708
     * @return string VALUES clause, or "" if not necessary to limit
709
     */
710
    protected function formatValuesGraph($vocabs) {
711
        if (!$this->isDefaultEndpoint()) {
712
            return "";
713
        }
714
        $graphs = $this->getVocabGraphs($vocabs);
715
        return $this->formatValues('?graph', $graphs, 'uri');
716
    }
717
718
    /**
719
     * Generate a FILTER clause for limiting the targeted graphs.
720
     * @param array $vocabs array of Vocabulary objects to target
721
     * @return string FILTER clause, or "" if not necessary to limit
722
     */
723
    protected function formatFilterGraph($vocabs) {
724
        if (!$this->isDefaultEndpoint()) {
725
            return "";
726
        }
727
        $graphs = $this->getVocabGraphs($vocabs);
728
        $values = array();
729
        foreach ($graphs as $graph) {
730
          $values[] = "<$graph>";
731
        }
732
        if (count($values)) {
733
          return "FILTER (?graph IN (" . implode(',', $values) . "))";
734
        }
735
    }
736
737
    /**
738
     * Formats combined limit and offset clauses for the sparql query
739
     * @param int $limit maximum number of hits to retrieve; 0 for unlimited
740
     * @param int $offset offset of results to retrieve; 0 for beginning of list
741
     * @return string sparql query clauses
742
     */
743
    protected function formatLimitAndOffset($limit, $offset) {
744
        $limit = ($limit) ? 'LIMIT ' . $limit : '';
745
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
746
        // eliminating whitespace and line changes when the conditions aren't needed.
747
        $limitandoffset = '';
748
        if ($limit && $offset) {
749
            $limitandoffset = "\n" . $limit . "\n" . $offset;
750
        } elseif ($limit) {
751
            $limitandoffset = "\n" . $limit;
752
        } elseif ($offset) {
753
            $limitandoffset = "\n" . $offset;
754
        }
755
756
        return $limitandoffset;
757
    }
758
759
    /**
760
     * Formats a sparql query clause for limiting the search to specific concept types.
761
     * @param array $types limit search to concepts of the given type(s)
762
     * @return string sparql query clause
763
     */
764
    protected function formatTypes($types) {
765
        $typePatterns = array();
766
        if (!empty($types)) {
767
            foreach ($types as $type) {
768
                $unprefixed = EasyRdf\RdfNamespace::expand($type);
769
                $typePatterns[] = "{ ?s a <$unprefixed> }";
770
            }
771
        }
772
773
        return implode(' UNION ', $typePatterns);
774
    }
775
776
    /**
777
     * @param string $prop property to include in the result eg. 'broader' or 'narrower'
778
     * @return string sparql query clause
779
     */
780
    private function formatPropertyCsvClause($prop) {
781
        # This expression creates a CSV row containing pairs of (uri,prefLabel) values.
782
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
783
        $clause = <<<EOV
784
(GROUP_CONCAT(DISTINCT CONCAT(
785
 '"', IF(isIRI(?$prop),STR(?$prop),''), '"', ',',
786
 '"', REPLACE(IF(BOUND(?{$prop}lab),?{$prop}lab,''), '"', '""'), '"', ',',
787
 '"', REPLACE(IF(isLiteral(?{$prop}),?{$prop},''), '"', '""'), '"'
788
); separator='\\n') as ?{$prop}s)
789
EOV;
790
        return $clause;
791
    }
792
    
793
    /**
794
     * @return string sparql query clause
795
     */
796
    private function formatPrefLabelCsvClause() {
797
        # This expression creates a CSV row containing pairs of (prefLabel, lang) values.
798
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
799
        $clause = <<<EOV
800
(GROUP_CONCAT(DISTINCT CONCAT(
801
 '"', STR(?pref), '"', ',', '"', lang(?pref), '"'
802
); separator='\\n') as ?preflabels)
803
EOV;
804
        return $clause;
805
    }
806
807
    /**
808
     * @param string $lang language code of the returned labels
809
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
810
     * @return string sparql query clause
811
     */
812
    protected function formatExtraFields($lang, $fields) {
813
        // extra variable expressions to request and extra fields to query for
814
        $ret = array('extravars' => '', 'extrafields' => '');
815
816
        if ($fields === null) {
817
            return $ret; 
818
        }
819
820
        if (in_array('prefLabel', $fields)) {
821
            $ret['extravars'] .= $this->formatPreflabelCsvClause();
822
            $ret['extrafields'] .= <<<EOF
823
OPTIONAL {
824
  ?s skos:prefLabel ?pref .
825
}
826
EOF;
827
            // removing the prefLabel from the fields since it has been handled separately
828
            $fields = array_diff($fields, array('prefLabel'));
829
        }
830
831
        foreach ($fields as $field) {
832
            $ret['extravars'] .= $this->formatPropertyCsvClause($field);
833
            $ret['extrafields'] .= <<<EOF
834
OPTIONAL {
835
  ?s skos:$field ?$field .
836
  FILTER(!isLiteral(?$field)||langMatches(lang(?{$field}), '$lang'))
837
  OPTIONAL { ?$field skos:prefLabel ?{$field}lab . FILTER(langMatches(lang(?{$field}lab), '$lang')) }
838
}
839
EOF;
840
        }
841
842
        return $ret;
843
    }
844
845
    /**
846
     * Generate condition for matching labels in SPARQL
847
     * @param string $term search term
848
     * @param string $searchLang language code used for matching labels (null means any language)
849
     * @return string sparql query snippet
850
     */
851
    protected function generateConceptSearchQueryCondition($term, $searchLang)
852
    {
853
        # use appropriate matching function depending on query type: =, strstarts, strends or full regex
854
        if (preg_match('/^[^\*]+$/', $term)) { // exact query
855
            $term = str_replace('\\', '\\\\', $term); // quote slashes
856
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
857
            $filtercond = "LCASE(STR(?match)) = '$term'";
858
        } elseif (preg_match('/^[^\*]+\*$/', $term)) { // prefix query
859
            $term = substr($term, 0, -1); // remove the final asterisk
860
            $term = str_replace('\\', '\\\\', $term); // quote slashes
861
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
862
            $filtercond = "STRSTARTS(LCASE(STR(?match)), '$term')";
863
        } elseif (preg_match('/^\*[^\*]+$/', $term)) { // suffix query
864
            $term = substr($term, 1); // remove the preceding asterisk
865
            $term = str_replace('\\', '\\\\', $term); // quote slashes
866
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
867
            $filtercond = "STRENDS(LCASE(STR(?match)), '$term')";
868
        } else { // too complicated - have to use a regex
869
            # make sure regex metacharacters are not passed through
870
            $term = str_replace('\\', '\\\\', preg_quote($term));
871
            $term = str_replace('\\\\*', '.*', $term); // convert asterisk to regex syntax
872
            $term = str_replace('\'', '\\\'', $term); // ensure single quotes are quoted
873
            $filtercond = "REGEX(STR(?match), '^$term$', 'i')";
874
        }
875
876
        $labelcondMatch = ($searchLang) ? "&& LANGMATCHES(lang(?match), '$searchLang')" : "";
877
        
878
        return "?s ?prop ?match . FILTER ($filtercond $labelcondMatch)";
879
    }
880
881
882
    /**
883
     * Inner query for concepts using a search term.
884
     * @param string $term search term
885
     * @param string $lang language code of the returned labels
886
     * @param string $searchLang language code used for matching labels (null means any language)
887
     * @param string[] $props properties to target e.g. array('skos:prefLabel','skos:altLabel')
888
     * @param boolean $unique restrict results to unique concepts (default: false)
889
     * @return string sparql query
890
     */
891
    protected function generateConceptSearchQueryInner($term, $lang, $searchLang, $props, $unique, $filterGraph)
892
    {
893
        $valuesProp = $this->formatValues('?prop', $props);
894
        $textcond = $this->generateConceptSearchQueryCondition($term, $searchLang);
895
896
        $rawterm = str_replace(array('\\', '*', '"'), array('\\\\', '', '\"'), $term);
897
        // graph clause, if necessary
898
        $graphClause = $filterGraph != '' ? 'GRAPH ?graph' : '';
899
900
        // extra conditions for label language, if specified
901
        $labelcondLabel = ($lang) ? "LANGMATCHES(lang(?label), '$lang')" : "lang(?match) = '' || LANGMATCHES(lang(?label), lang(?match))";
902
        // if search language and UI/display language differ, must also consider case where there is no prefLabel in
903
        // the display language; in that case, should use the label with the same language as the matched label
904
        $labelcondFallback = ($searchLang != $lang) ?
905
          "OPTIONAL { # in case previous OPTIONAL block gives no labels\n" .
906
          "?s skos:prefLabel ?label . FILTER (LANGMATCHES(LANG(?label), LANG(?match))) }" : "";
907
          
908
        //  Including the labels if there is no query term given.
909
        if ($rawterm === '') {
910
          $labelClause = "?s skos:prefLabel ?label .";
911
          $labelClause = ($lang) ? $labelClause . " FILTER (LANGMATCHES(LANG(?label), '$lang'))" : $labelClause . "";
912
          return $labelClause . " BIND(?label AS ?match)";
913
        }
914
915
        /*
916
         * This query does some tricks to obtain a list of unique concepts.
917
         * From each match generated by the text index, a string such as
918
         * "1en@example" is generated, where the first character is a number
919
         * encoding the property and priority, then comes the language tag and
920
         * finally the original literal after an @ sign. Of these, the MIN
921
         * function is used to pick the best match for each concept. Finally,
922
         * the structure is unpacked to get back the original string. Phew!
923
         */
924
        $hitvar = $unique ? '(MIN(?matchstr) AS ?hit)' : '(?matchstr AS ?hit)';
925
        $hitgroup = $unique ? 'GROUP BY ?s ?label ?notation' : '';
926
         
927
        $query = <<<EOQ
928
   SELECT DISTINCT ?s ?label ?notation $hitvar
929
   WHERE {
930
    $graphClause {
931
     { 
932
     $valuesProp
933
     VALUES (?prop ?pri) { (skos:prefLabel 1) (skos:altLabel 3) (skos:hiddenLabel 5)}
934
     $textcond
935
     ?s ?prop ?match }
936
     UNION
937
     { ?s skos:notation "$rawterm" }
938
     OPTIONAL {
939
      ?s skos:prefLabel ?label .
940
      FILTER ($labelcondLabel)
941
     } $labelcondFallback
942
     BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri)
943
     BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr)
944
     OPTIONAL { ?s skos:notation ?notation }
945
    }
946
    $filterGraph
947
   }
948
   $hitgroup
949
EOQ;
950
951
        return $query;
952
    }
953
954
    /**
955
     * Query for concepts using a search term.
956
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
957
     * @param boolean $unique restrict results to unique concepts (default: false)
958
     * @param boolean $showDeprecated whether to include deprecated concepts in search results (default: false)
959
     * @param ConceptSearchParameters $params 
960
     * @return string sparql query
961
     */
962
    protected function generateConceptSearchQuery($fields, $unique, $params, $showDeprecated = false) {
963
        $vocabs = $params->getVocabs();
964
        $gcl = $this->graphClause;
965
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
966
        $formattedtype = $this->formatTypes($params->getTypeLimit());
967
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);
968
        $extravars = $formattedfields['extravars'];
969
        $extrafields = $formattedfields['extrafields'];
970
        $schemes = $params->getSchemeLimit();
971
972
        // limit the search to only requested concept schemes
973
        $schemecond = '';
974
        if (!empty($schemes)) {
975
            $conditions = array();
976
            foreach($schemes as $scheme) {
977
                $conditions[] = "{?s skos:inScheme <$scheme>}";
978
            }
979
            $schemecond = '{'.implode(" UNION ",$conditions).'}';
980
        }
981
        $filterDeprecated="";
982
        //show or hide deprecated concepts
983
        if(!$showDeprecated){
984
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
985
        }
986
        // extra conditions for parent and group, if specified
987
        $parentcond = ($params->getParentLimit()) ? "?s skos:broader+ <" . $params->getParentLimit() . "> ." : "";
988
        $groupcond = ($params->getGroupLimit()) ? "<" . $params->getGroupLimit() . "> skos:member ?s ." : "";
989
        $pgcond = $parentcond . $groupcond;
990
991
        $orderextra = $this->isDefaultEndpoint() ? $this->graph : '';
992
993
        # make VALUES clauses
994
        $props = array('skos:prefLabel', 'skos:altLabel');
995
        if ($params->getHidden()) {
996
            $props[] = 'skos:hiddenLabel';
997
        }
998
999
        $filterGraph = empty($vocabs) ? $this->formatFilterGraph($vocabs) : '';
1000
1001
        // remove futile asterisks from the search term
1002
        $term = $params->getSearchTerm();
1003
        while (strpos($term, '**') !== false) {
1004
            $term = str_replace('**', '*', $term);
1005
        }
1006
1007
        $labelpriority = <<<EOQ
1008
  FILTER(BOUND(?s))
1009
  BIND(STR(SUBSTR(?hit,1,1)) AS ?pri)
1010
  BIND(IF((SUBSTR(STRBEFORE(?hit, '@'),1) != ?pri), STRLANG(STRAFTER(?hit, '@'), SUBSTR(STRBEFORE(?hit, '@'),2)), STRAFTER(?hit, '@')) AS ?match)
1011
  BIND(IF((?pri = "1" || ?pri = "2") && ?match != ?label, ?match, ?unbound) as ?plabel)
1012
  BIND(IF((?pri = "3" || ?pri = "4"), ?match, ?unbound) as ?alabel)
1013
  BIND(IF((?pri = "5" || ?pri = "6"), ?match, ?unbound) as ?hlabel)
1014
EOQ;
1015
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique, $filterGraph);
1016
        if ($params->getSearchTerm() === '*' || $params->getSearchTerm() === '') { 
1017
          $labelpriority = ''; 
1018
        }
1019
        $query = <<<EOQ
1020
SELECT DISTINCT ?s ?label ?plabel ?alabel ?hlabel ?graph ?notation (GROUP_CONCAT(DISTINCT STR(?type);separator=' ') as ?types) $extravars 
1021
$fcl
1022
WHERE {
1023
 $gcl {
1024
  {
1025
  $innerquery
1026
  }
1027
  $labelpriority
1028
  $formattedtype
1029
  { $pgcond 
1030
   ?s a ?type .
1031
   $extrafields $schemecond
1032
  }
1033
  $filterDeprecated
1034
 }
1035
 $filterGraph
1036
}
1037
GROUP BY ?s ?match ?label ?plabel ?alabel ?hlabel ?notation ?graph
1038
ORDER BY LCASE(STR(?match)) LANG(?match) $orderextra
1039
EOQ;
1040
        return $query;
1041
    }
1042
1043
    /**
1044
     * Transform a single concept search query results into the skosmos desired return format.
1045
     * @param $row SPARQL query result row
1046
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1047
     * @return array query result object
1048
     */
1049
    private function transformConceptSearchResult($row, $vocabs, $fields)
1050
    {
1051
        $hit = array();
1052
        $hit['uri'] = $row->s->getUri();
1053
1054
        if (isset($row->graph)) {
1055
            $hit['graph'] = $row->graph->getUri();
1056
        }
1057
1058
        foreach (explode(" ", $row->types->getValue()) as $typeuri) {
1059
            $hit['type'][] = $this->shortenUri($typeuri);
1060
        }
1061
1062
        if(!empty($fields)) {
1063
            foreach ($fields as $prop) {
1064
                $propname = $prop . 's';
1065
                if (isset($row->$propname)) {
1066
                    foreach (explode("\n", $row->$propname->getValue()) as $line) {
1067
                        $rdata = str_getcsv($line, ',', '"', '"');
1068
                        $propvals = array();
1069
                        if ($rdata[0] != '') {
1070
                            $propvals['uri'] = $rdata[0];
1071
                        }
1072
                        if ($rdata[1] != '') {
1073
                            $propvals['prefLabel'] = $rdata[1];
1074
                        }
1075
                        if ($rdata[2] != '') {
1076
                            $propvals = $rdata[2];
1077
                        }
1078
1079
                        $hit['skos:' . $prop][] = $propvals;
1080
                    }
1081
                }
1082
            }
1083
        }
1084
1085
        
1086
        if (isset($row->preflabels)) {
1087
            foreach (explode("\n", $row->preflabels->getValue()) as $line) {
1088
                $pref = str_getcsv($line, ',', '"', '"');
1089
                $hit['prefLabels'][$pref[1]] = $pref[0];
1090
            }
1091
        }
1092
1093
        foreach ($vocabs as $vocab) { // looping the vocabulary objects and asking these for a localname for the concept.
1094
            $localname = $vocab->getLocalName($hit['uri']);
1095
            if ($localname !== $hit['uri']) { // only passing the result forward if the uri didn't boomerang right back.
1096
                $hit['localname'] = $localname;
1097
                break; // stopping the search when we find one that returns something valid.
1098
            }
1099
        }
1100
1101
        if (isset($row->label)) {
1102
            $hit['prefLabel'] = $row->label->getValue();
1103
        }
1104
1105
        if (isset($row->label)) {
1106
            $hit['lang'] = $row->label->getLang();
1107
        }
1108
1109
        if (isset($row->notation)) {
1110
            $hit['notation'] = $row->notation->getValue();
1111
        }
1112
1113
        if (isset($row->plabel)) {
1114
            $hit['matchedPrefLabel'] = $row->plabel->getValue();
1115
            $hit['lang'] = $row->plabel->getLang();
1116 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...
1117
            $hit['altLabel'] = $row->alabel->getValue();
1118
            $hit['lang'] = $row->alabel->getLang();
1119
        } elseif (isset($row->hlabel)) {
1120
            $hit['hiddenLabel'] = $row->hlabel->getValue();
1121
            $hit['lang'] = $row->hlabel->getLang();
1122
        }
1123
        return $hit;
1124
    }
1125
1126
    /**
1127
     * Transform the concept search query results into the skosmos desired return format.
1128
     * @param EasyRdf\Sparql\Result $results
1129
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1130
     * @return array query result object
1131
     */
1132
    private function transformConceptSearchResults($results, $vocabs, $fields) {
1133
        $ret = array();
1134
1135
        foreach ($results as $row) {
1136
            if (!isset($row->s)) {
1137
                // don't break if query returns a single dummy result
1138
                continue;
1139
            }
1140
            $ret[] = $this->transformConceptSearchResult($row, $vocabs, $fields);
1141
        }
1142
        return $ret;
1143
    }
1144
1145
    /**
1146
     * Query for concepts using a search term.
1147
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1148
     * @param array $fields extra fields to include in the result (array of strings). (default: null = none)
1149
     * @param boolean $unique restrict results to unique concepts (default: false)
1150
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1151
     * @param ConceptSearchParameters $params 
1152
     * @return array query result object
1153
     */
1154
    public function queryConcepts($vocabs, $fields = null, $unique = false, $params, $showDeprecated = false) {
1155
        $query = $this->generateConceptSearchQuery($fields, $unique, $params,$showDeprecated);
1156
        $results = $this->query($query);
1157
        return $this->transformConceptSearchResults($results, $vocabs, $fields);
0 ignored issues
show
Bug introduced by
It seems like $results defined by $this->query($query) on line 1156 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...
1158
    }
1159
1160
    /**
1161
     * Generates sparql query clauses used for creating the alphabetical index.
1162
     * @param string $letter the letter (or special class) to search for
1163
     * @return array of sparql query clause strings
1164
     */
1165
    private function formatFilterConditions($letter, $lang) {
1166
        $useRegex = false;
1167
1168
        if ($letter == '*') {
1169
            $letter = '.*';
1170
            $useRegex = true;
1171
        } elseif ($letter == '0-9') {
1172
            $letter = '[0-9].*';
1173
            $useRegex = true;
1174
        } elseif ($letter == '!*') {
1175
            $letter = '[^\\\\p{L}\\\\p{N}].*';
1176
            $useRegex = true;
1177
        }
1178
1179
        # make text query clause
1180
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
1181
        if ($useRegex) {
1182
            $filtercondLabel = $lang ? "regex(str(?label), '^$letter$', 'i') && langMatches(lang(?label), '$lang')" : "regex(str(?label), '^$letter$', 'i')";
1183
            $filtercondALabel = $lang ? "regex(str(?alabel), '^$letter$', 'i') && langMatches(lang(?alabel), '$lang')" : "regex(str(?alabel), '^$letter$', 'i')";
1184
        } else {
1185
            $filtercondLabel = $lang ? "strstarts(lcase(str(?label)), '$lcletter') && langMatches(lang(?label), '$lang')" : "strstarts(lcase(str(?label)), '$lcletter')";
1186
            $filtercondALabel = $lang ? "strstarts(lcase(str(?alabel)), '$lcletter') && langMatches(lang(?alabel), '$lang')" : "strstarts(lcase(str(?alabel)), '$lcletter')";
1187
        }
1188
        return array('filterpref' => $filtercondLabel, 'filteralt' => $filtercondALabel);
1189
    }
1190
1191
    /**
1192
     * Generates the sparql query used for rendering the alphabetical index.
1193
     * @param string $letter the letter (or special class) to search for
1194
     * @param string $lang language of labels
1195
     * @param integer $limit limits the amount of results
1196
     * @param integer $offset offsets the result set
1197
     * @param array|null $classes
1198
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1199
     * @return string sparql query
1200
     */
1201
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated = false) {
1202
        $fcl = $this->generateFromClause();
1203
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1204
        $values = $this->formatValues('?type', $classes, 'uri');
1205
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
1206
        $conditions = $this->formatFilterConditions($letter, $lang);
1207
        $filtercondLabel = $conditions['filterpref'];
1208
        $filtercondALabel = $conditions['filteralt'];
1209
        $filterDeprecated="";
1210
        if(!$showDeprecated){
1211
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
1212
        }
1213
        $query = <<<EOQ
1214
SELECT DISTINCT ?s ?label ?alabel $fcl
1215
WHERE {
1216
  {
1217
    ?s skos:prefLabel ?label .
1218
    FILTER (
1219
      $filtercondLabel
1220
    )
1221
  }
1222
  UNION
1223
  {
1224
    {
1225
      ?s skos:altLabel ?alabel .
1226
      FILTER (
1227
        $filtercondALabel
1228
      )
1229
    }
1230
    {
1231
      ?s skos:prefLabel ?label .
1232
      FILTER (langMatches(lang(?label), '$lang'))
1233
    }
1234
  }
1235
  ?s a ?type .
1236
  $filterDeprecated
1237
  $values
1238
}
1239
ORDER BY STR(LCASE(COALESCE(?alabel, ?label))) $limitandoffset
1240
EOQ;
1241
        return $query;
1242
    }
1243
1244
    /**
1245
     * Transforms the alphabetical list query results into an array format.
1246
     * @param EasyRdf\Sparql\Result $results
1247
     * @return array
1248
     */
1249
    private function transformAlphabeticalListResults($results) {
1250
        $ret = array();
1251
1252
        foreach ($results as $row) {
1253
            if (!isset($row->s)) {
1254
                continue;
1255
            }
1256
            // don't break if query returns a single dummy result
1257
1258
            $hit = array();
1259
            $hit['uri'] = $row->s->getUri();
1260
1261
            $hit['localname'] = $row->s->localName();
1262
1263
            $hit['prefLabel'] = $row->label->getValue();
1264
            $hit['lang'] = $row->label->getLang();
1265
1266 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...
1267
                $hit['altLabel'] = $row->alabel->getValue();
1268
                $hit['lang'] = $row->alabel->getLang();
1269
            }
1270
1271
            $ret[] = $hit;
1272
        }
1273
1274
        return $ret;
1275
    }
1276
1277
    /**
1278
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1279
     * '*!' (special characters) and '*' (everything) are accepted.
1280
     * @param string $letter the letter (or special class) to search for
1281
     * @param string $lang language of labels
1282
     * @param integer $limit limits the amount of results
1283
     * @param integer $offset offsets the result set
1284
     * @param array $classes
1285
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1286
     */
1287
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null,$showDeprecated = false) {
1288
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes,$showDeprecated);
1289
        $results = $this->query($query);
1290
        return $this->transformAlphabeticalListResults($results);
0 ignored issues
show
Bug introduced by
It seems like $results defined by $this->query($query) on line 1289 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...
1291
    }
1292
1293
    /**
1294
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1295
     * Note that we force the datatype of the result variable otherwise Virtuoso does not properly interpret the DISTINCT and we have duplicated results
1296
     * @param string $lang language
1297
     * @return string sparql query
1298
     */
1299
    private function generateFirstCharactersQuery($lang, $classes) {
1300
        $fcl = $this->generateFromClause();
1301
        $classes = (isset($classes) && sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1302
        $values = $this->formatValues('?type', $classes, 'uri');
1303
        $query = <<<EOQ
1304
SELECT DISTINCT (ucase(str(substr(?label, 1, 1))) as ?l) $fcl WHERE {
1305
  ?c skos:prefLabel ?label .
1306
  ?c a ?type
1307
  FILTER(langMatches(lang(?label), '$lang'))
1308
  $values
1309
}
1310
EOQ;
1311
        return $query;
1312
    }
1313
1314
    /**
1315
     * Transforms the first characters query results into an array format.
1316
     * @param EasyRdf\Sparql\Result $result
1317
     * @return array
1318
     */
1319
    private function transformFirstCharactersResults($result) {
1320
        $ret = array();
1321
        foreach ($result as $row) {
1322
            $ret[] = $row->l->getValue();
1323
        }
1324
        return $ret;
1325
    }
1326
1327
    /**
1328
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1329
     * @param string $lang language
1330
     * @return array array of characters
1331
     */
1332
    public function queryFirstCharacters($lang, $classes = null) {
1333
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1334
        $result = $this->query($query);
1335
        return $this->transformFirstCharactersResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1334 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...
1336
    }
1337
1338
    /**
1339
     * @param string $uri
1340
     * @param string $lang
1341
     * @return string sparql query string
1342
     */
1343
    private function generateLabelQuery($uri, $lang) {
1344
        $fcl = $this->generateFromClause();
1345
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1346
        $query = <<<EOQ
1347
SELECT ?label $fcl
1348
WHERE {
1349
  <$uri> a ?type .
1350
  OPTIONAL {
1351
    <$uri> skos:prefLabel ?label .
1352
    $labelcondLabel
1353
  }
1354
  OPTIONAL {
1355
    <$uri> rdfs:label ?label .
1356
    $labelcondLabel
1357
  }
1358
  OPTIONAL {
1359
    <$uri> dc:title ?label .
1360
    $labelcondLabel
1361
  }
1362
  OPTIONAL {
1363
    <$uri> dc11:title ?label .
1364
    $labelcondLabel
1365
  }
1366
}
1367
EOQ;
1368
        return $query;
1369
    }
1370
1371
    /**
1372
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1373
     * @param string $uri
1374
     * @param string $lang
1375
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1376
     */
1377 View Code Duplication
    public function queryLabel($uri, $lang) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1378
        $query = $this->generateLabelQuery($uri, $lang);
1379
        $result = $this->query($query);
1380
        $ret = array();
1381
        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...
1382
            if (!isset($row->label)) {
1383
                // existing concept but no labels
1384
                return array();
1385
            }
1386
            $ret[$row->label->getLang()] = $row->label;
1387
        }
1388
1389
        if (sizeof($ret) > 0) {
1390
            // existing concept, with label(s)
1391
            return $ret;
1392
        } else {
1393
            // nonexistent concept
1394
            return null;
1395
        }
1396
    }
1397
    
1398
    /**
1399
     * Generates a SPARQL query to retrieve the super properties of a given property URI.
1400
     * Note this must be executed in the graph where this information is available.
1401
     * @param string $uri
1402
     * @return string sparql query string
1403
     */
1404
    private function generateSubPropertyOfQuery($uri) {
1405
        $fcl = $this->generateFromClause();
1406
        $query = <<<EOQ
1407
SELECT ?superProperty $fcl
1408
WHERE {
1409
  <$uri> rdfs:subPropertyOf ?superProperty
1410
}
1411
EOQ;
1412
        return $query;
1413
    }
1414
    
1415
    /**
1416
     * Query the super properties of a provided property URI.
1417
     * @param string $uri URI of a propertyes
1418
     * @return array array super properties, or null if none exist
1419
     */
1420 View Code Duplication
    public function querySuperProperties($uri) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1421
        $query = $this->generateSubPropertyOfQuery($uri);
1422
        $result = $this->query($query);
1423
        $ret = array();
1424
        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...
1425
            if (isset($row->superProperty)) {
1426
                $ret[] = $row->superProperty->getUri();
1427
            }
1428
            
1429
        }
1430
        
1431
        if (sizeof($ret) > 0) {
1432
            // return result
1433
            return $ret;
1434
        } else {
1435
            // no result, return null
1436
            return null;
1437
        }
1438
    }
1439
1440
1441
    /**
1442
     * Generates a sparql query for queryNotation.
1443
     * @param string $uri
1444
     * @return string sparql query
1445
     */
1446
    private function generateNotationQuery($uri) {
1447
        $fcl = $this->generateFromClause();
1448
1449
        $query = <<<EOQ
1450
SELECT * $fcl
1451
WHERE {
1452
  <$uri> skos:notation ?notation .
1453
}
1454
EOQ;
1455
        return $query;
1456
    }
1457
1458
    /**
1459
     * Query for the notation of the concept (skos:notation) of a resource.
1460
     * @param string $uri
1461
     * @return string notation or null if it doesn't exist
1462
     */
1463
    public function queryNotation($uri) {
1464
        $query = $this->generateNotationQuery($uri);
1465
        $result = $this->query($query);
1466
        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...
1467
            if (isset($row->notation)) {
1468
                return $row->notation->getValue();
1469
            }
1470
        }
1471
        return null;
1472
    }
1473
1474
    /**
1475
     * Generates a sparql query for queryProperty.
1476
     * @param string $uri
1477
     * @param string $prop the name of the property eg. 'skos:broader'.
1478
     * @param string $lang
1479
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1480
     * @return string sparql query
1481
     */
1482 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...
1483
        $fcl = $this->generateFromClause();
1484
        $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1485
1486
        $query = <<<EOQ
1487
SELECT * $fcl
1488
WHERE {
1489
  <$uri> a skos:Concept .
1490
  OPTIONAL {
1491
    <$uri> $prop ?object .
1492
    OPTIONAL {
1493
      ?object skos:prefLabel ?label .
1494
      FILTER (langMatches(lang(?label), "$lang"))
1495
    }
1496
    OPTIONAL {
1497
      ?object skos:prefLabel ?label .
1498
      FILTER (lang(?label) = "")
1499
    }
1500
    $anylang
1501
  }
1502
}
1503
EOQ;
1504
        return $query;
1505
    }
1506
1507
    /**
1508
     * Transforms the sparql query result into an array or null if the concept doesn't exist.
1509
     * @param EasyRdf\Sparql\Result $result
1510
     * @param string $lang
1511
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1512
     */
1513
    private function transformPropertyQueryResults($result, $lang) {
1514
        $ret = array();
1515
        foreach ($result as $row) {
1516
            if (!isset($row->object)) {
1517
                return array();
1518
            }
1519
            // existing concept but no properties
1520
            if (isset($row->label)) {
1521
                if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1522
                    $ret[$row->object->getUri()]['label'] = $row->label->getValue();
1523
                }
1524
1525
            } else {
1526
                $ret[$row->object->getUri()]['label'] = null;
1527
            }
1528
        }
1529
        if (sizeof($ret) > 0) {
1530
            return $ret;
1531
        }
1532
        // existing concept, with properties
1533
        else {
1534
            return null;
1535
        }
1536
        // nonexistent concept
1537
    }
1538
1539
    /**
1540
     * Query a single property of a concept.
1541
     * @param string $uri
1542
     * @param string $prop the name of the property eg. 'skos:broader'.
1543
     * @param string $lang
1544
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1545
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1546
     */
1547
    public function queryProperty($uri, $prop, $lang, $anylang = false) {
1548
        $uri = is_array($uri) ? $uri[0] : $uri;
1549
        $query = $this->generatePropertyQuery($uri, $prop, $lang, $anylang);
1550
        $result = $this->query($query);
1551
        return $this->transformPropertyQueryResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1550 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...
1552
    }
1553
1554
    /**
1555
     * Query a single transitive property of a concept.
1556
     * @param string $uri
1557
     * @param array $props the name of the property eg. 'skos:broader'.
1558
     * @param string $lang
1559
     * @param integer $limit
1560
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1561
     * @return string sparql query
1562
     */
1563
    private function generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang) {
1564
        $uri = is_array($uri) ? $uri[0] : $uri;
1565
        $fcl = $this->generateFromClause();
1566
        $propertyClause = implode('|', $props);
1567
        $otherlang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1568
        // need to do a SPARQL subquery because LIMIT needs to be applied /after/
1569
        // the direct relationships have been collapsed into one string
1570
        $query = <<<EOQ
1571
SELECT * $fcl
1572
WHERE {
1573
  SELECT ?object ?label (GROUP_CONCAT(STR(?dir);separator=' ') as ?direct)
1574
  WHERE {
1575
    <$uri> a skos:Concept .
1576
    OPTIONAL {
1577
      <$uri> $propertyClause* ?object .
1578
      OPTIONAL {
1579
        ?object $propertyClause ?dir .
1580
      }
1581
    }
1582
    OPTIONAL {
1583
      ?object skos:prefLabel ?label .
1584
      FILTER (langMatches(lang(?label), "$lang"))
1585
    }
1586
    $otherlang
1587
  }
1588
  GROUP BY ?object ?label
1589
}
1590
LIMIT $limit
1591
EOQ;
1592
        return $query;
1593
    }
1594
1595
    /**
1596
     * Transforms the sparql query result object into an array.
1597
     * @param EasyRdf\Sparql\Result $result
1598
     * @param string $lang
1599
     * @param string $fallbacklang language to use if label is not available in the preferred language
1600
     * @return array of property values (key: URI, val: label), or null if concept doesn't exist
1601
     */
1602
    private function transformTransitivePropertyResults($result, $lang, $fallbacklang) {
1603
        $ret = array();
1604
        foreach ($result as $row) {
1605
            if (!isset($row->object)) {
1606
                return array();
1607
            }
1608
            // existing concept but no properties
1609
            if (isset($row->label)) {
1610
                $val = array('label' => $row->label->getValue());
1611
            } else {
1612
                $val = array('label' => null);
1613
            }
1614 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...
1615
                $val['direct'] = explode(' ', $row->direct->getValue());
1616
            }
1617
            // Preventing labels in a non preferred language overriding the preferred language.
1618
            if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1619
                if (!isset($row->label) || $row->label->getLang() === $lang) {
1620
                    $ret[$row->object->getUri()] = $val;
1621
                } elseif ($row->label->getLang() === $fallbacklang) {
1622
                    $val['label'] .= ' (' . $row->label->getLang() . ')';
1623
                    $ret[$row->object->getUri()] = $val;
1624
                }
1625
            }
1626
        }
1627
1628
        // second iteration of results to find labels for the ones that didn't have one in the preferred languages
1629
        foreach ($result as $row) {
1630
            if (isset($row->object) && array_key_exists($row->object->getUri(), $ret) === false) {
1631
                $val = array('label' => $row->label->getValue());
1632 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...
1633
                    $val['direct'] = explode(' ', $row->direct->getValue());
1634
                }
1635
                $ret[$row->object->getUri()] = $val;
1636
            }
1637
        }
1638
1639
        if (sizeof($ret) > 0) {
1640
            return $ret;
1641
        }
1642
        // existing concept, with properties
1643
        else {
1644
            return null;
1645
        }
1646
        // nonexistent concept
1647
    }
1648
1649
    /**
1650
     * Query a single transitive property of a concept.
1651
     * @param string $uri
1652
     * @param array $props the property/properties.
1653
     * @param string $lang
1654
     * @param string $fallbacklang language to use if label is not available in the preferred language
1655
     * @param integer $limit
1656
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1657
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1658
     */
1659
    public function queryTransitiveProperty($uri, $props, $lang, $limit, $anylang = false, $fallbacklang = '') {
1660
        $query = $this->generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang);
1661
        $result = $this->query($query);
1662
        return $this->transformTransitivePropertyResults($result, $lang, $fallbacklang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1661 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...
1663
    }
1664
1665
    /**
1666
     * Generates the query for a concepts skos:narrowers.
1667
     * @param string $uri
1668
     * @param string $lang
1669
     * @param string $fallback
1670
     * @return string sparql query
1671
     */
1672
    private function generateChildQuery($uri, $lang, $fallback, $props) {
1673
        $uri = is_array($uri) ? $uri[0] : $uri;
1674
        $fcl = $this->generateFromClause();
1675
        $propertyClause = implode('|', $props);
1676
        $query = <<<EOQ
1677
SELECT ?child ?label ?child ?grandchildren ?notation $fcl WHERE {
1678
  <$uri> a skos:Concept .
1679
  OPTIONAL {
1680
    ?child $propertyClause <$uri> .
1681
    OPTIONAL {
1682
      ?child skos:prefLabel ?label .
1683
      FILTER (langMatches(lang(?label), "$lang"))
1684
    }
1685
    OPTIONAL {
1686
      ?child skos:prefLabel ?label .
1687
      FILTER (langMatches(lang(?label), "$fallback"))
1688
    }
1689
    OPTIONAL { # other language case
1690
      ?child skos:prefLabel ?label .
1691
    }
1692
    OPTIONAL {
1693
      ?child skos:notation ?notation .
1694
    }
1695
    BIND ( EXISTS { ?a $propertyClause ?child . } AS ?grandchildren )
1696
  }
1697
}
1698
EOQ;
1699
        return $query;
1700
    }
1701
1702
    /**
1703
     * Transforms the sparql result object into an array.
1704
     * @param EasyRdf\Sparql\Result $result
1705
     * @param string $lang
1706
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1707
     */
1708
    private function transformNarrowerResults($result, $lang) {
1709
        $ret = array();
1710
        foreach ($result as $row) {
1711
            if (!isset($row->child)) {
1712
                return array();
1713
            }
1714
            // existing concept but no children
1715
1716
            $label = null;
1717
            if (isset($row->label)) {
1718
                if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
1719
                    $label = $row->label->getValue();
1720
                } else {
1721
                    $label = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1722
                }
1723
1724
            }
1725
            $childArray = array(
1726
                'uri' => $row->child->getUri(),
1727
                'prefLabel' => $label,
1728
                'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1729
            );
1730
            if (isset($row->notation)) {
1731
                $childArray['notation'] = $row->notation->getValue();
1732
            }
1733
1734
            $ret[] = $childArray;
1735
        }
1736
        if (sizeof($ret) > 0) {
1737
            return $ret;
1738
        }
1739
        // existing concept, with children
1740
        else {
1741
            return null;
1742
        }
1743
        // nonexistent concept
1744
    }
1745
1746
    /**
1747
     * Query the narrower concepts of a concept.
1748
     * @param string $uri
1749
     * @param string $lang
1750
     * @param string $fallback
1751
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1752
     */
1753
    public function queryChildren($uri, $lang, $fallback, $props) {
1754
        $query = $this->generateChildQuery($uri, $lang, $fallback, $props);
1755
        $result = $this->query($query);
1756
        return $this->transformNarrowerResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1755 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...
1757
    }
1758
1759
    /**
1760
     * Query the top concepts of a vocabulary.
1761
     * @param string $conceptSchemes concept schemes whose top concepts to query for
1762
     * @param string $lang language of labels
1763
     * @param string $fallback language to use if label is not available in the preferred language
1764
     */
1765
    public function queryTopConcepts($conceptSchemes, $lang, $fallback) {
1766
        if (!is_array($conceptSchemes)) {
1767
            $conceptSchemes = array($conceptSchemes);
1768
        }
1769
1770
        $values = $this->formatValues('?topuri', $conceptSchemes, 'uri');
1771
1772
        $fcl = $this->generateFromClause();
1773
        $query = <<<EOQ
1774
SELECT DISTINCT ?top ?topuri ?label ?notation ?children $fcl WHERE {
1775
  ?top skos:topConceptOf ?topuri .
1776
  OPTIONAL {
1777
    ?top skos:prefLabel ?label .
1778
    FILTER (langMatches(lang(?label), "$lang"))
1779
  }
1780
  OPTIONAL {
1781
    ?top skos:prefLabel ?label .
1782
    FILTER (langMatches(lang(?label), "$fallback"))
1783
  }
1784
  OPTIONAL { # fallback - other language case
1785
    ?top skos:prefLabel ?label .
1786
  }
1787
  OPTIONAL { ?top skos:notation ?notation . }
1788
  BIND ( EXISTS { ?top skos:narrower ?a . } AS ?children )
1789
  $values
1790
}
1791
EOQ;
1792
        $result = $this->query($query);
1793
        $ret = array();
1794
        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...
1795
            if (isset($row->top) && isset($row->label)) {
1796
                $label = $row->label->getValue();
1797 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...
1798
                    $label .= ' (' . $row->label->getLang() . ')';
1799
                }
1800
                $top = array('uri' => $row->top->getUri(), 'topConceptOf' => $row->topuri->getUri(), 'label' => $label, 'hasChildren' => filter_var($row->children->getValue(), FILTER_VALIDATE_BOOLEAN));
1801
                if (isset($row->notation)) {
1802
                    $top['notation'] = $row->notation->getValue();
1803
                }
1804
1805
                $ret[] = $top;
1806
            }
1807
        }
1808
1809
        return $ret;
1810
    }
1811
1812
    /**
1813
     * Generates a sparql query for finding the hierarchy for a concept.
1814
	 * A concept may be a top concept in multiple schemes, returned as a single whitespace-separated literal.
1815
     * @param string $uri concept uri.
1816
     * @param string $lang
1817
     * @param string $fallback language to use if label is not available in the preferred language
1818
     * @return string sparql query
1819
     */
1820
    private function generateParentListQuery($uri, $lang, $fallback, $props) {
1821
        $fcl = $this->generateFromClause();
1822
        $propertyClause = implode('|', $props);
1823
        $query = <<<EOQ
1824
SELECT ?broad ?parent ?children ?grandchildren
1825
(SAMPLE(?lab) as ?label) (SAMPLE(?childlab) as ?childlabel) (GROUP_CONCAT(?topcs; separator=" ") as ?tops) 
1826
(SAMPLE(?nota) as ?notation) (SAMPLE(?childnota) as ?childnotation) $fcl
1827
WHERE {
1828
  <$uri> a skos:Concept .
1829
  OPTIONAL {
1830
    <$uri> $propertyClause* ?broad .
1831
    OPTIONAL {
1832
      ?broad skos:prefLabel ?lab .
1833
      FILTER (langMatches(lang(?lab), "$lang"))
1834
    }
1835
    OPTIONAL {
1836
      ?broad skos:prefLabel ?lab .
1837
      FILTER (langMatches(lang(?lab), "$fallback"))
1838
    }
1839
    OPTIONAL { # fallback - other language case
1840
      ?broad skos:prefLabel ?lab .
1841
    }
1842
    OPTIONAL { ?broad skos:notation ?nota . }
1843
    OPTIONAL { ?broad $propertyClause ?parent . }
1844
    OPTIONAL { ?broad skos:narrower ?children .
1845
      OPTIONAL {
1846
        ?children skos:prefLabel ?childlab .
1847
        FILTER (langMatches(lang(?childlab), "$lang"))
1848
      }
1849
      OPTIONAL {
1850
        ?children skos:prefLabel ?childlab .
1851
        FILTER (langMatches(lang(?childlab), "$fallback"))
1852
      }
1853
      OPTIONAL { # fallback - other language case
1854
        ?children skos:prefLabel ?childlab .
1855
      }
1856
      OPTIONAL {
1857
        ?children skos:notation ?childnota .
1858
      }
1859
    }
1860
    BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren )
1861
    OPTIONAL { ?broad skos:topConceptOf ?topcs . }
1862
  }
1863
}
1864
GROUP BY ?broad ?parent ?member ?children ?grandchildren
1865
EOQ;
1866
        return $query;
1867
    }
1868
1869
    /**
1870
     * Transforms the result into an array.
1871
     * @param EasyRdf\Sparql\Result
1872
     * @param string $lang
1873
     * @return an array for the REST controller to encode.
1874
     */
1875
    private function transformParentListResults($result, $lang)
1876
    {
1877
        $ret = array();
1878
        foreach ($result as $row) {
1879
            if (!isset($row->broad)) {
1880
                // existing concept but no broaders
1881
                return array();
1882
            }
1883
            $uri = $row->broad->getUri();
1884
            if (!isset($ret[$uri])) {
1885
                $ret[$uri] = array('uri' => $uri);
1886
            }
1887
            if (isset($row->exact)) {
1888
                $ret[$uri]['exact'] = $row->exact->getUri();
1889
            }
1890
            if (isset($row->tops)) {
1891
               $topConceptsList=explode(" ", $row->tops->getValue());
1892
               // sort to garantee an alphabetical ordering of the URI
1893
               sort($topConceptsList);
1894
               $ret[$uri]['tops'] = $topConceptsList;
1895
            }
1896
            if (isset($row->children)) {
1897
                if (!isset($ret[$uri]['narrower'])) {
1898
                    $ret[$uri]['narrower'] = array();
1899
                }
1900
1901
                $label = null;
1902
                if (isset($row->childlabel)) {
1903
                    $label = $row->childlabel->getValue();
1904 View Code Duplication
                    if ($row->childlabel->getLang() !== $lang && strpos($row->childlabel->getLang(), $lang . "-") !== 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1905
                        $label .= " (" . $row->childlabel->getLang() . ")";
1906
                    }
1907
1908
                }
1909
1910
                $childArr = array(
1911
                    'uri' => $row->children->getUri(),
1912
                    'label' => $label,
1913
                    'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1914
                );
1915
                if (isset($row->childnotation)) {
1916
                    $childArr['notation'] = $row->childnotation->getValue();
1917
                }
1918
1919
                if (!in_array($childArr, $ret[$uri]['narrower'])) {
1920
                    $ret[$uri]['narrower'][] = $childArr;
1921
                }
1922
1923
            }
1924
            if (isset($row->label)) {
1925
                $preflabel = $row->label->getValue();
1926 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...
1927
                    $preflabel .= ' (' . $row->label->getLang() . ')';
1928
                }
1929
1930
                $ret[$uri]['prefLabel'] = $preflabel;
1931
            }
1932
            if (isset($row->notation)) {
1933
                $ret[$uri]['notation'] = $row->notation->getValue();
1934
            }
1935
1936
            if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) {
1937
                $ret[$uri]['broader'][] = $row->parent->getUri();
1938
            } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) {
1939
                $ret[$uri]['broader'][] = $row->parent->getUri();
1940
            }
1941
        }
1942
        if (sizeof($ret) > 0) {
1943
            // existing concept, with children
1944
            return $ret;
1945
        }
1946
        else {
1947
            // nonexistent concept
1948
            return null;
1949
        }
1950
    }
1951
1952
    /**
1953
     * Query for finding the hierarchy for a concept.
1954
     * @param string $uri concept uri.
1955
     * @param string $lang
1956
     * @param string $fallback language to use if label is not available in the preferred language
1957
     * @param array $props the hierarchy property/properties to use
1958
     * @return an array for the REST controller to encode.
1959
     */
1960
    public function queryParentList($uri, $lang, $fallback, $props) {
1961
        $query = $this->generateParentListQuery($uri, $lang, $fallback, $props);
1962
        $result = $this->query($query);
1963
        return $this->transformParentListResults($result, $lang);
1964
    }
1965
1966
    /**
1967
     * return a list of concept group instances, sorted by label
1968
     * @param string $groupClass URI of concept group class
1969
     * @param string $lang language of labels to return
1970
     * @return string sparql query
1971
     */
1972
    private function generateConceptGroupsQuery($groupClass, $lang) {
1973
        $fcl = $this->generateFromClause();
1974
        $query = <<<EOQ
1975
SELECT ?group (GROUP_CONCAT(DISTINCT STR(?child);separator=' ') as ?children) ?label ?members ?notation $fcl
1976
WHERE {
1977
  ?group a <$groupClass> .
1978
  OPTIONAL { ?group skos:member|isothes:subGroup ?child .
1979
             ?child a <$groupClass> }
1980
  BIND(EXISTS{?group skos:member ?submembers} as ?members)
1981
  OPTIONAL { ?group skos:prefLabel ?label }
1982
  OPTIONAL { ?group rdfs:label ?label }
1983
  FILTER (langMatches(lang(?label), '$lang'))
1984
  OPTIONAL { ?group skos:notation ?notation }
1985
}
1986
GROUP BY ?group ?label ?members ?notation
1987
ORDER BY lcase(?label)
1988
EOQ;
1989
        return $query;
1990
    }
1991
1992
    /**
1993
     * Transforms the sparql query result into an array.
1994
     * @param EasyRdf\Sparql\Result $result
1995
     * @return array
1996
     */
1997
    private function transformConceptGroupsResults($result) {
1998
        $ret = array();
1999
        foreach ($result as $row) {
2000
            if (!isset($row->group)) {
2001
                # no groups found, see issue #357
2002
                continue;
2003
            }
2004
            $group = array('uri' => $row->group->getURI());
2005
            if (isset($row->label)) {
2006
                $group['prefLabel'] = $row->label->getValue();
2007
            }
2008
2009
            if (isset($row->children)) {
2010
                $group['childGroups'] = explode(' ', $row->children->getValue());
2011
            }
2012
2013
            if (isset($row->members)) {
2014
                $group['hasMembers'] = $row->members->getValue();
2015
            }
2016
2017
            if (isset($row->notation)) {
2018
                $group['notation'] = $row->notation->getValue();
2019
            }
2020
2021
            $ret[] = $group;
2022
        }
2023
        return $ret;
2024
    }
2025
2026
    /**
2027
     * return a list of concept group instances, sorted by label
2028
     * @param string $groupClass URI of concept group class
2029
     * @param string $lang language of labels to return
2030
     * @return array Result array with group URI as key and group label as value
2031
     */
2032
    public function listConceptGroups($groupClass, $lang) {
2033
        $query = $this->generateConceptGroupsQuery($groupClass, $lang);
2034
        $result = $this->query($query);
2035
        return $this->transformConceptGroupsResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2034 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...
2036
    }
2037
2038
    /**
2039
     * Generates the sparql query for listConceptGroupContents
2040
     * @param string $groupClass URI of concept group class
2041
     * @param string $group URI of the concept group instance
2042
     * @param string $lang language of labels to return
2043
     * @param boolean $showDeprecated whether to include deprecated in the result
2044
     * @return string sparql query
2045
     */
2046
    private function generateConceptGroupContentsQuery($groupClass, $group, $lang, $showDeprecated = false) {
2047
        $fcl = $this->generateFromClause();
2048
        $filterDeprecated="";
2049
        if(!$showDeprecated){
2050
            $filterDeprecated="  FILTER NOT EXISTS { ?conc owl:deprecated true }";
2051
        }
2052
        $query = <<<EOQ
2053
SELECT ?conc ?super ?label ?members ?type ?notation $fcl
2054
WHERE {
2055
 <$group> a <$groupClass> .
2056
 { <$group> skos:member ?conc . } UNION { ?conc isothes:superGroup <$group> }
2057
$filterDeprecated
2058
 ?conc a ?type .
2059
 OPTIONAL { ?conc skos:prefLabel ?label .
2060
  FILTER (langMatches(lang(?label), '$lang'))
2061
 }
2062
 OPTIONAL { ?conc skos:prefLabel ?label . }
2063
 OPTIONAL { ?conc skos:notation ?notation }
2064
 BIND(EXISTS{?submembers isothes:superGroup ?conc} as ?super)
2065
 BIND(EXISTS{?conc skos:member ?submembers} as ?members)
2066
} ORDER BY lcase(?label)
2067
EOQ;
2068
        return $query;
2069
    }
2070
2071
    /**
2072
     * Transforms the sparql query result into an array.
2073
     * @param EasyRdf\Sparql\Result $result
2074
     * @param string $lang language of labels to return
2075
     * @return array
2076
     */
2077
    private function transformConceptGroupContentsResults($result, $lang) {
2078
        $ret = array();
2079
        $values = array();
2080
        foreach ($result as $row) {
2081
            if (!array_key_exists($row->conc->getURI(), $values)) {
2082
                $values[$row->conc->getURI()] = array(
2083
                    'uri' => $row->conc->getURI(),
2084
                    'isSuper' => $row->super->getValue(),
2085
                    'hasMembers' => $row->members->getValue(),
2086
                    'type' => array($row->type->shorten()),
2087
                );
2088
                if (isset($row->label)) {
2089
                    if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
2090
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue();
2091
                    } else {
2092
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue() . " (" . $row->label->getLang() . ")";
2093
                    }
2094
2095
                }
2096
                if (isset($row->notation)) {
2097
                    $values[$row->conc->getURI()]['notation'] = $row->notation->getValue();
2098
                }
2099
2100
            } else {
2101
                $values[$row->conc->getURI()]['type'][] = $row->type->shorten();
2102
            }
2103
        }
2104
2105
        foreach ($values as $val) {
2106
            $ret[] = $val;
2107
        }
2108
2109
        return $ret;
2110
    }
2111
2112
    /**
2113
     * return a list of concepts in a concept group
2114
     * @param string $groupClass URI of concept group class
2115
     * @param string $group URI of the concept group instance
2116
     * @param string $lang language of labels to return
2117
     * @param boolean $showDeprecated whether to include deprecated concepts in search results
2118
     * @return array Result array with concept URI as key and concept label as value
2119
     */
2120
    public function listConceptGroupContents($groupClass, $group, $lang,$showDeprecated = false) {
2121
        $query = $this->generateConceptGroupContentsQuery($groupClass, $group, $lang,$showDeprecated);
2122
        $result = $this->query($query);
2123
        return $this->transformConceptGroupContentsResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2122 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...
2124
    }
2125
2126
    /**
2127
     * Generates the sparql query for queryChangeList.
2128
     * @param string $lang language of labels to return.
2129
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2130
     * @return string sparql query
2131
     */
2132 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...
2133
        $fcl = $this->generateFromClause();
2134
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
2135
2136
        $query = <<<EOQ
2137
SELECT DISTINCT ?concept ?date ?label $fcl
2138
WHERE {
2139
  ?concept a skos:Concept .
2140
  ?concept $prop ?date .
2141
  ?concept skos:prefLabel ?label .
2142
  FILTER (langMatches(lang(?label), '$lang'))
2143
}
2144
ORDER BY DESC(YEAR(?date)) DESC(MONTH(?date)) LCASE(?label)
2145
LIMIT 200 $offset
2146
EOQ;
2147
        return $query;
2148
    }
2149
2150
    /**
2151
     * Transforms the sparql query result into an array.
2152
     * @param EasyRdf\Sparql\Result $result
2153
     * @return array
2154
     */
2155 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...
2156
        $ret = array();
2157
        foreach ($result as $row) {
2158
            $concept = array('uri' => $row->concept->getURI());
2159
            if (isset($row->label)) {
2160
                $concept['prefLabel'] = $row->label->getValue();
2161
            }
2162
2163
            if (isset($row->date)) {
2164
                $concept['date'] = $row->date->getValue();
2165
            }
2166
2167
            $ret[] = $concept;
2168
        }
2169
        return $ret;
2170
    }
2171
2172
    /**
2173
     * return a list of recently changed or entirely new concepts
2174
     * @param string $lang language of labels to return
2175
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2176
     * @return array Result array
2177
     */
2178
    public function queryChangeList($lang, $offset, $prop) {
2179
        $query = $this->generateChangeListQuery($lang, $offset, $prop);
2180
        $result = $this->query($query);
2181
        return $this->transformChangeListResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2180 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...
2182
    }
2183
}
2184