Completed
Push — master ( 2d5e4d...c85d15 )
by
unknown
02:02 queued 11s
created

GenericSparql::transformAlphabeticalListResults()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 36

Duplication

Lines 4
Ratio 11.11 %

Importance

Changes 0
Metric Value
cc 6
nc 8
nop 1
dl 4
loc 36
rs 8.7217
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
     * @param \EasyRdf\Resource|null $qualifier alphabetical list qualifier resource or null (default: null)
1200
     * @return string sparql query
1201
     */
1202
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated = false, $qualifier = null) {
1203
        $fcl = $this->generateFromClause();
1204
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1205
        $values = $this->formatValues('?type', $classes, 'uri');
1206
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
1207
        $conditions = $this->formatFilterConditions($letter, $lang);
1208
        $filtercondLabel = $conditions['filterpref'];
1209
        $filtercondALabel = $conditions['filteralt'];
1210
        $qualifierClause = $qualifier ? "OPTIONAL { ?s <" . $qualifier->getURI() . "> ?qualifier }" : "";
1211
        $filterDeprecated="";
1212
        if(!$showDeprecated){
1213
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
1214
        }
1215
        $query = <<<EOQ
1216
SELECT DISTINCT ?s ?label ?alabel ?qualifier $fcl
1217
WHERE {
1218
  {
1219
    ?s skos:prefLabel ?label .
1220
    FILTER (
1221
      $filtercondLabel
1222
    )
1223
  }
1224
  UNION
1225
  {
1226
    {
1227
      ?s skos:altLabel ?alabel .
1228
      FILTER (
1229
        $filtercondALabel
1230
      )
1231
    }
1232
    {
1233
      ?s skos:prefLabel ?label .
1234
      FILTER (langMatches(lang(?label), '$lang'))
1235
    }
1236
  }
1237
  ?s a ?type .
1238
  $qualifierClause
1239
  $filterDeprecated
1240
  $values
1241
}
1242
ORDER BY LCASE(STR(COALESCE(?alabel, ?label))) STR(?s) LCASE(STR(?qualifier)) $limitandoffset
1243
EOQ;
1244
        return $query;
1245
    }
1246
1247
    /**
1248
     * Transforms the alphabetical list query results into an array format.
1249
     * @param EasyRdf\Sparql\Result $results
1250
     * @return array
1251
     */
1252
    private function transformAlphabeticalListResults($results) {
1253
        $ret = array();
1254
1255
        foreach ($results as $row) {
1256
            if (!isset($row->s)) {
1257
                continue;
1258
            }
1259
            // don't break if query returns a single dummy result
1260
1261
            $hit = array();
1262
            $hit['uri'] = $row->s->getUri();
1263
1264
            $hit['localname'] = $row->s->localName();
1265
1266
            $hit['prefLabel'] = $row->label->getValue();
1267
            $hit['lang'] = $row->label->getLang();
1268
1269 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...
1270
                $hit['altLabel'] = $row->alabel->getValue();
1271
                $hit['lang'] = $row->alabel->getLang();
1272
            }
1273
1274
            if (isset($row->qualifier)) {
1275
                if ($row->qualifier instanceof EasyRdf\Literal) {
1276
                    $hit['qualifier'] = $row->qualifier->getValue();
1277
                }
1278
                else {
1279
                    $hit['qualifier'] = $row->qualifier->localName();
1280
                }
1281
            }
1282
1283
            $ret[] = $hit;
1284
        }
1285
1286
        return $ret;
1287
    }
1288
1289
    /**
1290
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1291
     * '*!' (special characters) and '*' (everything) are accepted.
1292
     * @param string $letter the letter (or special class) to search for
1293
     * @param string $lang language of labels
1294
     * @param integer $limit limits the amount of results
1295
     * @param integer $offset offsets the result set
1296
     * @param array $classes
1297
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1298
     * @param \EasyRdf\Resource|null $qualifier alphabetical list qualifier resource or null (default: null)
1299
     */
1300
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null, $showDeprecated = false, $qualifier = null) {
1301
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated, $qualifier);
1302
        $results = $this->query($query);
1303
        return $this->transformAlphabeticalListResults($results);
0 ignored issues
show
Bug introduced by
It seems like $results defined by $this->query($query) on line 1302 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...
1304
    }
1305
1306
    /**
1307
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1308
     * Note that we force the datatype of the result variable otherwise Virtuoso does not properly interpret the DISTINCT and we have duplicated results
1309
     * @param string $lang language
1310
     * @return string sparql query
1311
     */
1312
    private function generateFirstCharactersQuery($lang, $classes) {
1313
        $fcl = $this->generateFromClause();
1314
        $classes = (isset($classes) && sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1315
        $values = $this->formatValues('?type', $classes, 'uri');
1316
        $query = <<<EOQ
1317
SELECT DISTINCT (ucase(str(substr(?label, 1, 1))) as ?l) $fcl WHERE {
1318
  ?c skos:prefLabel ?label .
1319
  ?c a ?type
1320
  FILTER(langMatches(lang(?label), '$lang'))
1321
  $values
1322
}
1323
EOQ;
1324
        return $query;
1325
    }
1326
1327
    /**
1328
     * Transforms the first characters query results into an array format.
1329
     * @param EasyRdf\Sparql\Result $result
1330
     * @return array
1331
     */
1332
    private function transformFirstCharactersResults($result) {
1333
        $ret = array();
1334
        foreach ($result as $row) {
1335
            $ret[] = $row->l->getValue();
1336
        }
1337
        return $ret;
1338
    }
1339
1340
    /**
1341
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1342
     * @param string $lang language
1343
     * @return array array of characters
1344
     */
1345
    public function queryFirstCharacters($lang, $classes = null) {
1346
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1347
        $result = $this->query($query);
1348
        return $this->transformFirstCharactersResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1347 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...
1349
    }
1350
1351
    /**
1352
     * @param string $uri
1353
     * @param string $lang
1354
     * @return string sparql query string
1355
     */
1356
    private function generateLabelQuery($uri, $lang) {
1357
        $fcl = $this->generateFromClause();
1358
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1359
        $query = <<<EOQ
1360
SELECT ?label $fcl
1361
WHERE {
1362
  <$uri> a ?type .
1363
  OPTIONAL {
1364
    <$uri> skos:prefLabel ?label .
1365
    $labelcondLabel
1366
  }
1367
  OPTIONAL {
1368
    <$uri> rdfs:label ?label .
1369
    $labelcondLabel
1370
  }
1371
  OPTIONAL {
1372
    <$uri> dc:title ?label .
1373
    $labelcondLabel
1374
  }
1375
  OPTIONAL {
1376
    <$uri> dc11:title ?label .
1377
    $labelcondLabel
1378
  }
1379
}
1380
EOQ;
1381
        return $query;
1382
    }
1383
1384
    /**
1385
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1386
     * @param string $uri
1387
     * @param string $lang
1388
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1389
     */
1390 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...
1391
        $query = $this->generateLabelQuery($uri, $lang);
1392
        $result = $this->query($query);
1393
        $ret = array();
1394
        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...
1395
            if (!isset($row->label)) {
1396
                // existing concept but no labels
1397
                return array();
1398
            }
1399
            $ret[$row->label->getLang()] = $row->label;
1400
        }
1401
1402
        if (sizeof($ret) > 0) {
1403
            // existing concept, with label(s)
1404
            return $ret;
1405
        } else {
1406
            // nonexistent concept
1407
            return null;
1408
        }
1409
    }
1410
    
1411
    /**
1412
     * Generates a SPARQL query to retrieve the super properties of a given property URI.
1413
     * Note this must be executed in the graph where this information is available.
1414
     * @param string $uri
1415
     * @return string sparql query string
1416
     */
1417
    private function generateSubPropertyOfQuery($uri) {
1418
        $fcl = $this->generateFromClause();
1419
        $query = <<<EOQ
1420
SELECT ?superProperty $fcl
1421
WHERE {
1422
  <$uri> rdfs:subPropertyOf ?superProperty
1423
}
1424
EOQ;
1425
        return $query;
1426
    }
1427
    
1428
    /**
1429
     * Query the super properties of a provided property URI.
1430
     * @param string $uri URI of a propertyes
1431
     * @return array array super properties, or null if none exist
1432
     */
1433 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...
1434
        $query = $this->generateSubPropertyOfQuery($uri);
1435
        $result = $this->query($query);
1436
        $ret = array();
1437
        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...
1438
            if (isset($row->superProperty)) {
1439
                $ret[] = $row->superProperty->getUri();
1440
            }
1441
            
1442
        }
1443
        
1444
        if (sizeof($ret) > 0) {
1445
            // return result
1446
            return $ret;
1447
        } else {
1448
            // no result, return null
1449
            return null;
1450
        }
1451
    }
1452
1453
1454
    /**
1455
     * Generates a sparql query for queryNotation.
1456
     * @param string $uri
1457
     * @return string sparql query
1458
     */
1459
    private function generateNotationQuery($uri) {
1460
        $fcl = $this->generateFromClause();
1461
1462
        $query = <<<EOQ
1463
SELECT * $fcl
1464
WHERE {
1465
  <$uri> skos:notation ?notation .
1466
}
1467
EOQ;
1468
        return $query;
1469
    }
1470
1471
    /**
1472
     * Query for the notation of the concept (skos:notation) of a resource.
1473
     * @param string $uri
1474
     * @return string notation or null if it doesn't exist
1475
     */
1476
    public function queryNotation($uri) {
1477
        $query = $this->generateNotationQuery($uri);
1478
        $result = $this->query($query);
1479
        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...
1480
            if (isset($row->notation)) {
1481
                return $row->notation->getValue();
1482
            }
1483
        }
1484
        return null;
1485
    }
1486
1487
    /**
1488
     * Generates a sparql query for queryProperty.
1489
     * @param string $uri
1490
     * @param string $prop the name of the property eg. 'skos:broader'.
1491
     * @param string $lang
1492
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1493
     * @return string sparql query
1494
     */
1495 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...
1496
        $fcl = $this->generateFromClause();
1497
        $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1498
1499
        $query = <<<EOQ
1500
SELECT * $fcl
1501
WHERE {
1502
  <$uri> a skos:Concept .
1503
  OPTIONAL {
1504
    <$uri> $prop ?object .
1505
    OPTIONAL {
1506
      ?object skos:prefLabel ?label .
1507
      FILTER (langMatches(lang(?label), "$lang"))
1508
    }
1509
    OPTIONAL {
1510
      ?object skos:prefLabel ?label .
1511
      FILTER (lang(?label) = "")
1512
    }
1513
    $anylang
1514
  }
1515
}
1516
EOQ;
1517
        return $query;
1518
    }
1519
1520
    /**
1521
     * Transforms the sparql query result into an array or null if the concept doesn't exist.
1522
     * @param EasyRdf\Sparql\Result $result
1523
     * @param string $lang
1524
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1525
     */
1526
    private function transformPropertyQueryResults($result, $lang) {
1527
        $ret = array();
1528
        foreach ($result as $row) {
1529
            if (!isset($row->object)) {
1530
                return array();
1531
            }
1532
            // existing concept but no properties
1533
            if (isset($row->label)) {
1534
                if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1535
                    $ret[$row->object->getUri()]['label'] = $row->label->getValue();
1536
                }
1537
1538
            } else {
1539
                $ret[$row->object->getUri()]['label'] = null;
1540
            }
1541
        }
1542
        if (sizeof($ret) > 0) {
1543
            return $ret;
1544
        }
1545
        // existing concept, with properties
1546
        else {
1547
            return null;
1548
        }
1549
        // nonexistent concept
1550
    }
1551
1552
    /**
1553
     * Query a single property of a concept.
1554
     * @param string $uri
1555
     * @param string $prop the name of the property eg. 'skos:broader'.
1556
     * @param string $lang
1557
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1558
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1559
     */
1560
    public function queryProperty($uri, $prop, $lang, $anylang = false) {
1561
        $uri = is_array($uri) ? $uri[0] : $uri;
1562
        $query = $this->generatePropertyQuery($uri, $prop, $lang, $anylang);
1563
        $result = $this->query($query);
1564
        return $this->transformPropertyQueryResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1563 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...
1565
    }
1566
1567
    /**
1568
     * Query a single transitive property of a concept.
1569
     * @param string $uri
1570
     * @param array $props the name of the property eg. 'skos:broader'.
1571
     * @param string $lang
1572
     * @param integer $limit
1573
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1574
     * @return string sparql query
1575
     */
1576
    private function generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang) {
1577
        $uri = is_array($uri) ? $uri[0] : $uri;
1578
        $fcl = $this->generateFromClause();
1579
        $propertyClause = implode('|', $props);
1580
        $otherlang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1581
        // need to do a SPARQL subquery because LIMIT needs to be applied /after/
1582
        // the direct relationships have been collapsed into one string
1583
        $query = <<<EOQ
1584
SELECT * $fcl
1585
WHERE {
1586
  SELECT ?object ?label (GROUP_CONCAT(STR(?dir);separator=' ') as ?direct)
1587
  WHERE {
1588
    <$uri> a skos:Concept .
1589
    OPTIONAL {
1590
      <$uri> $propertyClause* ?object .
1591
      OPTIONAL {
1592
        ?object $propertyClause ?dir .
1593
      }
1594
    }
1595
    OPTIONAL {
1596
      ?object skos:prefLabel ?label .
1597
      FILTER (langMatches(lang(?label), "$lang"))
1598
    }
1599
    $otherlang
1600
  }
1601
  GROUP BY ?object ?label
1602
}
1603
LIMIT $limit
1604
EOQ;
1605
        return $query;
1606
    }
1607
1608
    /**
1609
     * Transforms the sparql query result object into an array.
1610
     * @param EasyRdf\Sparql\Result $result
1611
     * @param string $lang
1612
     * @param string $fallbacklang language to use if label is not available in the preferred language
1613
     * @return array of property values (key: URI, val: label), or null if concept doesn't exist
1614
     */
1615
    private function transformTransitivePropertyResults($result, $lang, $fallbacklang) {
1616
        $ret = array();
1617
        foreach ($result as $row) {
1618
            if (!isset($row->object)) {
1619
                return array();
1620
            }
1621
            // existing concept but no properties
1622
            if (isset($row->label)) {
1623
                $val = array('label' => $row->label->getValue());
1624
            } else {
1625
                $val = array('label' => null);
1626
            }
1627 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...
1628
                $val['direct'] = explode(' ', $row->direct->getValue());
1629
            }
1630
            // Preventing labels in a non preferred language overriding the preferred language.
1631
            if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1632
                if (!isset($row->label) || $row->label->getLang() === $lang) {
1633
                    $ret[$row->object->getUri()] = $val;
1634
                } elseif ($row->label->getLang() === $fallbacklang) {
1635
                    $val['label'] .= ' (' . $row->label->getLang() . ')';
1636
                    $ret[$row->object->getUri()] = $val;
1637
                }
1638
            }
1639
        }
1640
1641
        // second iteration of results to find labels for the ones that didn't have one in the preferred languages
1642
        foreach ($result as $row) {
1643
            if (isset($row->object) && array_key_exists($row->object->getUri(), $ret) === false) {
1644
                $val = array('label' => $row->label->getValue());
1645 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...
1646
                    $val['direct'] = explode(' ', $row->direct->getValue());
1647
                }
1648
                $ret[$row->object->getUri()] = $val;
1649
            }
1650
        }
1651
1652
        if (sizeof($ret) > 0) {
1653
            return $ret;
1654
        }
1655
        // existing concept, with properties
1656
        else {
1657
            return null;
1658
        }
1659
        // nonexistent concept
1660
    }
1661
1662
    /**
1663
     * Query a single transitive property of a concept.
1664
     * @param string $uri
1665
     * @param array $props the property/properties.
1666
     * @param string $lang
1667
     * @param string $fallbacklang language to use if label is not available in the preferred language
1668
     * @param integer $limit
1669
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1670
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1671
     */
1672
    public function queryTransitiveProperty($uri, $props, $lang, $limit, $anylang = false, $fallbacklang = '') {
1673
        $query = $this->generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang);
1674
        $result = $this->query($query);
1675
        return $this->transformTransitivePropertyResults($result, $lang, $fallbacklang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1674 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...
1676
    }
1677
1678
    /**
1679
     * Generates the query for a concepts skos:narrowers.
1680
     * @param string $uri
1681
     * @param string $lang
1682
     * @param string $fallback
1683
     * @return string sparql query
1684
     */
1685
    private function generateChildQuery($uri, $lang, $fallback, $props) {
1686
        $uri = is_array($uri) ? $uri[0] : $uri;
1687
        $fcl = $this->generateFromClause();
1688
        $propertyClause = implode('|', $props);
1689
        $query = <<<EOQ
1690
SELECT ?child ?label ?child ?grandchildren ?notation $fcl WHERE {
1691
  <$uri> a skos:Concept .
1692
  OPTIONAL {
1693
    ?child $propertyClause <$uri> .
1694
    OPTIONAL {
1695
      ?child skos:prefLabel ?label .
1696
      FILTER (langMatches(lang(?label), "$lang"))
1697
    }
1698
    OPTIONAL {
1699
      ?child skos:prefLabel ?label .
1700
      FILTER (langMatches(lang(?label), "$fallback"))
1701
    }
1702
    OPTIONAL { # other language case
1703
      ?child skos:prefLabel ?label .
1704
    }
1705
    OPTIONAL {
1706
      ?child skos:notation ?notation .
1707
    }
1708
    BIND ( EXISTS { ?a $propertyClause ?child . } AS ?grandchildren )
1709
  }
1710
}
1711
EOQ;
1712
        return $query;
1713
    }
1714
1715
    /**
1716
     * Transforms the sparql result object into an array.
1717
     * @param EasyRdf\Sparql\Result $result
1718
     * @param string $lang
1719
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1720
     */
1721
    private function transformNarrowerResults($result, $lang) {
1722
        $ret = array();
1723
        foreach ($result as $row) {
1724
            if (!isset($row->child)) {
1725
                return array();
1726
            }
1727
            // existing concept but no children
1728
1729
            $label = null;
1730
            if (isset($row->label)) {
1731
                if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
1732
                    $label = $row->label->getValue();
1733
                } else {
1734
                    $label = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1735
                }
1736
1737
            }
1738
            $childArray = array(
1739
                'uri' => $row->child->getUri(),
1740
                'prefLabel' => $label,
1741
                'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1742
            );
1743
            if (isset($row->notation)) {
1744
                $childArray['notation'] = $row->notation->getValue();
1745
            }
1746
1747
            $ret[] = $childArray;
1748
        }
1749
        if (sizeof($ret) > 0) {
1750
            return $ret;
1751
        }
1752
        // existing concept, with children
1753
        else {
1754
            return null;
1755
        }
1756
        // nonexistent concept
1757
    }
1758
1759
    /**
1760
     * Query the narrower concepts of a concept.
1761
     * @param string $uri
1762
     * @param string $lang
1763
     * @param string $fallback
1764
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1765
     */
1766
    public function queryChildren($uri, $lang, $fallback, $props) {
1767
        $query = $this->generateChildQuery($uri, $lang, $fallback, $props);
1768
        $result = $this->query($query);
1769
        return $this->transformNarrowerResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1768 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...
1770
    }
1771
1772
    /**
1773
     * Query the top concepts of a vocabulary.
1774
     * @param string $conceptSchemes concept schemes whose top concepts to query for
1775
     * @param string $lang language of labels
1776
     * @param string $fallback language to use if label is not available in the preferred language
1777
     */
1778
    public function queryTopConcepts($conceptSchemes, $lang, $fallback) {
1779
        if (!is_array($conceptSchemes)) {
1780
            $conceptSchemes = array($conceptSchemes);
1781
        }
1782
1783
        $values = $this->formatValues('?topuri', $conceptSchemes, 'uri');
1784
1785
        $fcl = $this->generateFromClause();
1786
        $query = <<<EOQ
1787
SELECT DISTINCT ?top ?topuri ?label ?notation ?children $fcl WHERE {
1788
  ?top skos:topConceptOf ?topuri .
1789
  OPTIONAL {
1790
    ?top skos:prefLabel ?label .
1791
    FILTER (langMatches(lang(?label), "$lang"))
1792
  }
1793
  OPTIONAL {
1794
    ?top skos:prefLabel ?label .
1795
    FILTER (langMatches(lang(?label), "$fallback"))
1796
  }
1797
  OPTIONAL { # fallback - other language case
1798
    ?top skos:prefLabel ?label .
1799
  }
1800
  OPTIONAL { ?top skos:notation ?notation . }
1801
  BIND ( EXISTS { ?top skos:narrower ?a . } AS ?children )
1802
  $values
1803
}
1804
EOQ;
1805
        $result = $this->query($query);
1806
        $ret = array();
1807
        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...
1808
            if (isset($row->top) && isset($row->label)) {
1809
                $label = $row->label->getValue();
1810 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...
1811
                    $label .= ' (' . $row->label->getLang() . ')';
1812
                }
1813
                $top = array('uri' => $row->top->getUri(), 'topConceptOf' => $row->topuri->getUri(), 'label' => $label, 'hasChildren' => filter_var($row->children->getValue(), FILTER_VALIDATE_BOOLEAN));
1814
                if (isset($row->notation)) {
1815
                    $top['notation'] = $row->notation->getValue();
1816
                }
1817
1818
                $ret[] = $top;
1819
            }
1820
        }
1821
1822
        return $ret;
1823
    }
1824
1825
    /**
1826
     * Generates a sparql query for finding the hierarchy for a concept.
1827
	 * A concept may be a top concept in multiple schemes, returned as a single whitespace-separated literal.
1828
     * @param string $uri concept uri.
1829
     * @param string $lang
1830
     * @param string $fallback language to use if label is not available in the preferred language
1831
     * @return string sparql query
1832
     */
1833
    private function generateParentListQuery($uri, $lang, $fallback, $props) {
1834
        $fcl = $this->generateFromClause();
1835
        $propertyClause = implode('|', $props);
1836
        $query = <<<EOQ
1837
SELECT ?broad ?parent ?children ?grandchildren
1838
(SAMPLE(?lab) as ?label) (SAMPLE(?childlab) as ?childlabel) (GROUP_CONCAT(?topcs; separator=" ") as ?tops) 
1839
(SAMPLE(?nota) as ?notation) (SAMPLE(?childnota) as ?childnotation) $fcl
1840
WHERE {
1841
  <$uri> a skos:Concept .
1842
  OPTIONAL {
1843
    <$uri> $propertyClause* ?broad .
1844
    OPTIONAL {
1845
      ?broad skos:prefLabel ?lab .
1846
      FILTER (langMatches(lang(?lab), "$lang"))
1847
    }
1848
    OPTIONAL {
1849
      ?broad skos:prefLabel ?lab .
1850
      FILTER (langMatches(lang(?lab), "$fallback"))
1851
    }
1852
    OPTIONAL { # fallback - other language case
1853
      ?broad skos:prefLabel ?lab .
1854
    }
1855
    OPTIONAL { ?broad skos:notation ?nota . }
1856
    OPTIONAL { ?broad $propertyClause ?parent . }
1857
    OPTIONAL { ?broad skos:narrower ?children .
1858
      OPTIONAL {
1859
        ?children skos:prefLabel ?childlab .
1860
        FILTER (langMatches(lang(?childlab), "$lang"))
1861
      }
1862
      OPTIONAL {
1863
        ?children skos:prefLabel ?childlab .
1864
        FILTER (langMatches(lang(?childlab), "$fallback"))
1865
      }
1866
      OPTIONAL { # fallback - other language case
1867
        ?children skos:prefLabel ?childlab .
1868
      }
1869
      OPTIONAL {
1870
        ?children skos:notation ?childnota .
1871
      }
1872
    }
1873
    BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren )
1874
    OPTIONAL { ?broad skos:topConceptOf ?topcs . }
1875
  }
1876
}
1877
GROUP BY ?broad ?parent ?member ?children ?grandchildren
1878
EOQ;
1879
        return $query;
1880
    }
1881
1882
    /**
1883
     * Transforms the result into an array.
1884
     * @param EasyRdf\Sparql\Result
1885
     * @param string $lang
1886
     * @return an array for the REST controller to encode.
1887
     */
1888
    private function transformParentListResults($result, $lang)
1889
    {
1890
        $ret = array();
1891
        foreach ($result as $row) {
1892
            if (!isset($row->broad)) {
1893
                // existing concept but no broaders
1894
                return array();
1895
            }
1896
            $uri = $row->broad->getUri();
1897
            if (!isset($ret[$uri])) {
1898
                $ret[$uri] = array('uri' => $uri);
1899
            }
1900
            if (isset($row->exact)) {
1901
                $ret[$uri]['exact'] = $row->exact->getUri();
1902
            }
1903
            if (isset($row->tops)) {
1904
               $topConceptsList=explode(" ", $row->tops->getValue());
1905
               // sort to garantee an alphabetical ordering of the URI
1906
               sort($topConceptsList);
1907
               $ret[$uri]['tops'] = $topConceptsList;
1908
            }
1909
            if (isset($row->children)) {
1910
                if (!isset($ret[$uri]['narrower'])) {
1911
                    $ret[$uri]['narrower'] = array();
1912
                }
1913
1914
                $label = null;
1915
                if (isset($row->childlabel)) {
1916
                    $label = $row->childlabel->getValue();
1917 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...
1918
                        $label .= " (" . $row->childlabel->getLang() . ")";
1919
                    }
1920
1921
                }
1922
1923
                $childArr = array(
1924
                    'uri' => $row->children->getUri(),
1925
                    'label' => $label,
1926
                    'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1927
                );
1928
                if (isset($row->childnotation)) {
1929
                    $childArr['notation'] = $row->childnotation->getValue();
1930
                }
1931
1932
                if (!in_array($childArr, $ret[$uri]['narrower'])) {
1933
                    $ret[$uri]['narrower'][] = $childArr;
1934
                }
1935
1936
            }
1937
            if (isset($row->label)) {
1938
                $preflabel = $row->label->getValue();
1939 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...
1940
                    $preflabel .= ' (' . $row->label->getLang() . ')';
1941
                }
1942
1943
                $ret[$uri]['prefLabel'] = $preflabel;
1944
            }
1945
            if (isset($row->notation)) {
1946
                $ret[$uri]['notation'] = $row->notation->getValue();
1947
            }
1948
1949
            if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) {
1950
                $ret[$uri]['broader'][] = $row->parent->getUri();
1951
            } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) {
1952
                $ret[$uri]['broader'][] = $row->parent->getUri();
1953
            }
1954
        }
1955
        if (sizeof($ret) > 0) {
1956
            // existing concept, with children
1957
            return $ret;
1958
        }
1959
        else {
1960
            // nonexistent concept
1961
            return null;
1962
        }
1963
    }
1964
1965
    /**
1966
     * Query for finding the hierarchy for a concept.
1967
     * @param string $uri concept uri.
1968
     * @param string $lang
1969
     * @param string $fallback language to use if label is not available in the preferred language
1970
     * @param array $props the hierarchy property/properties to use
1971
     * @return an array for the REST controller to encode.
1972
     */
1973
    public function queryParentList($uri, $lang, $fallback, $props) {
1974
        $query = $this->generateParentListQuery($uri, $lang, $fallback, $props);
1975
        $result = $this->query($query);
1976
        return $this->transformParentListResults($result, $lang);
1977
    }
1978
1979
    /**
1980
     * return a list of concept group instances, sorted by label
1981
     * @param string $groupClass URI of concept group class
1982
     * @param string $lang language of labels to return
1983
     * @return string sparql query
1984
     */
1985
    private function generateConceptGroupsQuery($groupClass, $lang) {
1986
        $fcl = $this->generateFromClause();
1987
        $query = <<<EOQ
1988
SELECT ?group (GROUP_CONCAT(DISTINCT STR(?child);separator=' ') as ?children) ?label ?members ?notation $fcl
1989
WHERE {
1990
  ?group a <$groupClass> .
1991
  OPTIONAL { ?group skos:member|isothes:subGroup ?child .
1992
             ?child a <$groupClass> }
1993
  BIND(EXISTS{?group skos:member ?submembers} as ?members)
1994
  OPTIONAL { ?group skos:prefLabel ?label }
1995
  OPTIONAL { ?group rdfs:label ?label }
1996
  FILTER (langMatches(lang(?label), '$lang'))
1997
  OPTIONAL { ?group skos:notation ?notation }
1998
}
1999
GROUP BY ?group ?label ?members ?notation
2000
ORDER BY lcase(?label)
2001
EOQ;
2002
        return $query;
2003
    }
2004
2005
    /**
2006
     * Transforms the sparql query result into an array.
2007
     * @param EasyRdf\Sparql\Result $result
2008
     * @return array
2009
     */
2010
    private function transformConceptGroupsResults($result) {
2011
        $ret = array();
2012
        foreach ($result as $row) {
2013
            if (!isset($row->group)) {
2014
                # no groups found, see issue #357
2015
                continue;
2016
            }
2017
            $group = array('uri' => $row->group->getURI());
2018
            if (isset($row->label)) {
2019
                $group['prefLabel'] = $row->label->getValue();
2020
            }
2021
2022
            if (isset($row->children)) {
2023
                $group['childGroups'] = explode(' ', $row->children->getValue());
2024
            }
2025
2026
            if (isset($row->members)) {
2027
                $group['hasMembers'] = $row->members->getValue();
2028
            }
2029
2030
            if (isset($row->notation)) {
2031
                $group['notation'] = $row->notation->getValue();
2032
            }
2033
2034
            $ret[] = $group;
2035
        }
2036
        return $ret;
2037
    }
2038
2039
    /**
2040
     * return a list of concept group instances, sorted by label
2041
     * @param string $groupClass URI of concept group class
2042
     * @param string $lang language of labels to return
2043
     * @return array Result array with group URI as key and group label as value
2044
     */
2045
    public function listConceptGroups($groupClass, $lang) {
2046
        $query = $this->generateConceptGroupsQuery($groupClass, $lang);
2047
        $result = $this->query($query);
2048
        return $this->transformConceptGroupsResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2047 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...
2049
    }
2050
2051
    /**
2052
     * Generates the sparql query for listConceptGroupContents
2053
     * @param string $groupClass URI of concept group class
2054
     * @param string $group URI of the concept group instance
2055
     * @param string $lang language of labels to return
2056
     * @param boolean $showDeprecated whether to include deprecated in the result
2057
     * @return string sparql query
2058
     */
2059
    private function generateConceptGroupContentsQuery($groupClass, $group, $lang, $showDeprecated = false) {
2060
        $fcl = $this->generateFromClause();
2061
        $filterDeprecated="";
2062
        if(!$showDeprecated){
2063
            $filterDeprecated="  FILTER NOT EXISTS { ?conc owl:deprecated true }";
2064
        }
2065
        $query = <<<EOQ
2066
SELECT ?conc ?super ?label ?members ?type ?notation $fcl
2067
WHERE {
2068
 <$group> a <$groupClass> .
2069
 { <$group> skos:member ?conc . } UNION { ?conc isothes:superGroup <$group> }
2070
$filterDeprecated
2071
 ?conc a ?type .
2072
 OPTIONAL { ?conc skos:prefLabel ?label .
2073
  FILTER (langMatches(lang(?label), '$lang'))
2074
 }
2075
 OPTIONAL { ?conc skos:prefLabel ?label . }
2076
 OPTIONAL { ?conc skos:notation ?notation }
2077
 BIND(EXISTS{?submembers isothes:superGroup ?conc} as ?super)
2078
 BIND(EXISTS{?conc skos:member ?submembers} as ?members)
2079
} ORDER BY lcase(?label)
2080
EOQ;
2081
        return $query;
2082
    }
2083
2084
    /**
2085
     * Transforms the sparql query result into an array.
2086
     * @param EasyRdf\Sparql\Result $result
2087
     * @param string $lang language of labels to return
2088
     * @return array
2089
     */
2090
    private function transformConceptGroupContentsResults($result, $lang) {
2091
        $ret = array();
2092
        $values = array();
2093
        foreach ($result as $row) {
2094
            if (!array_key_exists($row->conc->getURI(), $values)) {
2095
                $values[$row->conc->getURI()] = array(
2096
                    'uri' => $row->conc->getURI(),
2097
                    'isSuper' => $row->super->getValue(),
2098
                    'hasMembers' => $row->members->getValue(),
2099
                    'type' => array($row->type->shorten()),
2100
                );
2101
                if (isset($row->label)) {
2102
                    if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
2103
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue();
2104
                    } else {
2105
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue() . " (" . $row->label->getLang() . ")";
2106
                    }
2107
2108
                }
2109
                if (isset($row->notation)) {
2110
                    $values[$row->conc->getURI()]['notation'] = $row->notation->getValue();
2111
                }
2112
2113
            } else {
2114
                $values[$row->conc->getURI()]['type'][] = $row->type->shorten();
2115
            }
2116
        }
2117
2118
        foreach ($values as $val) {
2119
            $ret[] = $val;
2120
        }
2121
2122
        return $ret;
2123
    }
2124
2125
    /**
2126
     * return a list of concepts in a concept group
2127
     * @param string $groupClass URI of concept group class
2128
     * @param string $group URI of the concept group instance
2129
     * @param string $lang language of labels to return
2130
     * @param boolean $showDeprecated whether to include deprecated concepts in search results
2131
     * @return array Result array with concept URI as key and concept label as value
2132
     */
2133
    public function listConceptGroupContents($groupClass, $group, $lang,$showDeprecated = false) {
2134
        $query = $this->generateConceptGroupContentsQuery($groupClass, $group, $lang,$showDeprecated);
2135
        $result = $this->query($query);
2136
        return $this->transformConceptGroupContentsResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2135 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...
2137
    }
2138
2139
    /**
2140
     * Generates the sparql query for queryChangeList.
2141
     * @param string $lang language of labels to return.
2142
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2143
     * @return string sparql query
2144
     */
2145 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...
2146
        $fcl = $this->generateFromClause();
2147
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
2148
2149
        $query = <<<EOQ
2150
SELECT DISTINCT ?concept ?date ?label $fcl
2151
WHERE {
2152
  ?concept a skos:Concept .
2153
  ?concept $prop ?date .
2154
  ?concept skos:prefLabel ?label .
2155
  FILTER (langMatches(lang(?label), '$lang'))
2156
}
2157
ORDER BY DESC(YEAR(?date)) DESC(MONTH(?date)) LCASE(?label)
2158
LIMIT 200 $offset
2159
EOQ;
2160
        return $query;
2161
    }
2162
2163
    /**
2164
     * Transforms the sparql query result into an array.
2165
     * @param EasyRdf\Sparql\Result $result
2166
     * @return array
2167
     */
2168 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...
2169
        $ret = array();
2170
        foreach ($result as $row) {
2171
            $concept = array('uri' => $row->concept->getURI());
2172
            if (isset($row->label)) {
2173
                $concept['prefLabel'] = $row->label->getValue();
2174
            }
2175
2176
            if (isset($row->date)) {
2177
                $concept['date'] = $row->date->getValue();
2178
            }
2179
2180
            $ret[] = $concept;
2181
        }
2182
        return $ret;
2183
    }
2184
2185
    /**
2186
     * return a list of recently changed or entirely new concepts
2187
     * @param string $lang language of labels to return
2188
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2189
     * @return array Result array
2190
     */
2191
    public function queryChangeList($lang, $offset, $prop) {
2192
        $query = $this->generateChangeListQuery($lang, $offset, $prop);
2193
        $result = $this->query($query);
2194
        return $this->transformChangeListResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2193 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...
2195
    }
2196
}
2197