Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 256 |
|
0.00% |
0 / 21 |
CRAP | |
0.00% |
0 / 1 |
core_kernel_persistence_starsql_Class | |
0.00% |
0 / 256 |
|
0.00% |
0 / 21 |
4032 | |
0.00% |
0 / 1 |
getSubClasses | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
20 | |||
isSubClassOf | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
getParentClasses | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 | |||
getProperties | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
42 | |||
getInstances | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
setInstance | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setSubClassOf | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
setProperty | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
createInstance | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
30 | |||
createSubClass | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
30 | |||
createProperty | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
20 | |||
searchInstances | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
countInstances | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getInstancesPropertyValues | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
unsetProperty | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
createInstanceWithProperties | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
deleteInstances | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
getFilterQuery | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
6 | |||
addFilters | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
90 | |||
getClassFilter | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
updateUri | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU General Public License |
6 | * as published by the Free Software Foundation; under version 2 |
7 | * of the License (non-upgradable). |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * |
18 | * Copyright (c) 2023 (original work) Open Assessment Technologies SA ; |
19 | */ |
20 | |
21 | declare(strict_types=1); |
22 | |
23 | use oat\generis\model\data\event\ClassPropertyCreatedEvent; |
24 | use oat\generis\model\GenerisRdf; |
25 | use oat\generis\model\kernel\persistence\Filter; |
26 | use oat\generis\model\kernel\uri\UriProvider; |
27 | use oat\generis\model\OntologyRdf; |
28 | use oat\generis\model\OntologyRdfs; |
29 | use oat\oatbox\event\EventManagerAwareTrait; |
30 | use oat\search\helper\SupportedOperatorHelper; |
31 | use oat\search\QueryBuilder; |
32 | use WikibaseSolutions\CypherDSL\Query; |
33 | |
34 | use function WikibaseSolutions\CypherDSL\node; |
35 | use function WikibaseSolutions\CypherDSL\parameter; |
36 | use function WikibaseSolutions\CypherDSL\procedure; |
37 | use function WikibaseSolutions\CypherDSL\query; |
38 | use function WikibaseSolutions\CypherDSL\variable; |
39 | |
40 | class core_kernel_persistence_starsql_Class extends core_kernel_persistence_starsql_Resource implements |
41 | core_kernel_persistence_ClassInterface |
42 | { |
43 | use EventManagerAwareTrait; |
44 | |
45 | public function getSubClasses(core_kernel_classes_Class $resource, $recursive = false) |
46 | { |
47 | $uri = $resource->getUri(); |
48 | $relationship = OntologyRdfs::RDFS_SUBCLASSOF; |
49 | if (!empty($recursive)) { |
50 | $query = <<<CYPHER |
51 | MATCH (startNode:Resource {uri: \$uri}) |
52 | MATCH (descendantNode)-[:`{$relationship}`*]->(startNode) |
53 | RETURN descendantNode.uri |
54 | CYPHER; |
55 | } else { |
56 | $query = <<<CYPHER |
57 | MATCH (startNode:Resource {uri: \$uri}) |
58 | MATCH (descendantNode)-[:`{$relationship}`]->(startNode) |
59 | RETURN descendantNode.uri |
60 | CYPHER; |
61 | } |
62 | |
63 | // \common_Logger::i('getSubClasses(): ' . var_export($query, true)); |
64 | $results = $this->getPersistence()->run($query, ['uri' => $uri]); |
65 | $returnValue = []; |
66 | foreach ($results as $result) { |
67 | $uri = $result->current(); |
68 | if (!$uri) { |
69 | continue; |
70 | } |
71 | $subClass = $this->getModel()->getClass($uri); |
72 | $returnValue[$subClass->getUri()] = $subClass ; |
73 | } |
74 | |
75 | return $returnValue; |
76 | } |
77 | |
78 | public function isSubClassOf(core_kernel_classes_Class $resource, core_kernel_classes_Class $parentClass) |
79 | { |
80 | // @TODO would it be worth it to check direct relationship of node:IS_SUBCLASS_OF? |
81 | $parentSubClasses = $parentClass->getSubClasses(true); |
82 | foreach ($parentSubClasses as $subClass) { |
83 | if ($subClass->getUri() === $resource->getUri()) { |
84 | return true; |
85 | } |
86 | } |
87 | |
88 | return false; |
89 | } |
90 | |
91 | public function getParentClasses(core_kernel_classes_Class $resource, $recursive = false) |
92 | { |
93 | $uri = $resource->getUri(); |
94 | $relationship = OntologyRdfs::RDFS_SUBCLASSOF; |
95 | if (!empty($recursive)) { |
96 | $query = <<<CYPHER |
97 | MATCH (startNode:Resource {uri: \$uri}) |
98 | MATCH (startNode)-[:`{$relationship}`*]->(ancestorNode) |
99 | RETURN ancestorNode.uri |
100 | CYPHER; |
101 | } else { |
102 | $query = <<<CYPHER |
103 | MATCH (startNode:Resource {uri: \$uri}) |
104 | MATCH (startNode)-[:`{$relationship}`]->(ancestorNode) |
105 | RETURN ancestorNode.uri |
106 | CYPHER; |
107 | } |
108 | |
109 | $results = $this->getPersistence()->run($query, ['uri' => $uri]); |
110 | $returnValue = []; |
111 | foreach ($results as $result) { |
112 | $uri = $result->current(); |
113 | $parentClass = $this->getModel()->getClass($uri); |
114 | $returnValue[$parentClass->getUri()] = $parentClass ; |
115 | } |
116 | |
117 | return $returnValue; |
118 | } |
119 | |
120 | public function getProperties(core_kernel_classes_Class $resource, $recursive = false) |
121 | { |
122 | $uri = $resource->getUri(); |
123 | $relationship = OntologyRdfs::RDFS_DOMAIN; |
124 | $query = <<<CYPHER |
125 | MATCH (startNode:Resource {uri: \$uri}) |
126 | MATCH (descendantNode)-[:`{$relationship}`]->(startNode) |
127 | RETURN descendantNode.uri |
128 | CYPHER; |
129 | $results = $this->getPersistence()->run($query, ['uri' => $uri]); |
130 | $returnValue = []; |
131 | foreach ($results as $result) { |
132 | $uri = $result->current(); |
133 | if (!$uri) { |
134 | continue; |
135 | } |
136 | $property = $this->getModel()->getProperty($uri); |
137 | $returnValue[$property->getUri()] = $property; |
138 | } |
139 | |
140 | if ($recursive == true) { |
141 | $parentClasses = $this->getParentClasses($resource, true); |
142 | foreach ($parentClasses as $parent) { |
143 | if ($parent->getUri() != OntologyRdfs::RDFS_CLASS) { |
144 | $returnValue = array_merge($returnValue, $parent->getProperties(false)); |
145 | } |
146 | } |
147 | } |
148 | |
149 | return $returnValue; |
150 | } |
151 | |
152 | public function getInstances(core_kernel_classes_Class $resource, $recursive = false, $params = []) |
153 | { |
154 | $returnValue = []; |
155 | |
156 | $params = array_merge($params, ['like' => false, 'recursive' => $recursive]); |
157 | |
158 | $search = $this->getModel()->getSearchInterface(); |
159 | $query = $this->getFilterQuery($search->query(), $resource, [], $params); |
160 | |
161 | $resultList = $search->getGateway()->search($query); |
162 | foreach ($resultList as $resource) { |
163 | $returnValue[$resource->getUri()] = $resource; |
164 | } |
165 | |
166 | return $returnValue; |
167 | } |
168 | |
169 | /** |
170 | * @deprecated |
171 | */ |
172 | public function setInstance(core_kernel_classes_Class $resource, core_kernel_classes_Resource $instance) |
173 | { |
174 | throw new common_exception_DeprecatedApiMethod(__METHOD__ . ' is deprecated. '); |
175 | } |
176 | |
177 | public function setSubClassOf(core_kernel_classes_Class $resource, core_kernel_classes_Class $iClass): bool |
178 | { |
179 | $subClassOf = $this->getModel()->getProperty(OntologyRdfs::RDFS_SUBCLASSOF); |
180 | $returnValue = $this->setPropertyValue($resource, $subClassOf, $iClass->getUri()); |
181 | |
182 | return (bool) $returnValue; |
183 | } |
184 | |
185 | /** |
186 | * @deprecated |
187 | */ |
188 | public function setProperty(core_kernel_classes_Class $resource, core_kernel_classes_Property $property) |
189 | { |
190 | throw new common_exception_DeprecatedApiMethod(__METHOD__ . ' is deprecated. '); |
191 | } |
192 | |
193 | public function createInstance(core_kernel_classes_Class $resource, $label = '', $comment = '', $uri = '') |
194 | { |
195 | if ($uri == '') { |
196 | $subject = $this->getServiceLocator()->get(UriProvider::SERVICE_ID)->provide(); |
197 | } elseif ($uri[0] == '#') { //$uri should start with # and be well formed |
198 | $modelUri = common_ext_NamespaceManager::singleton()->getLocalNamespace()->getUri(); |
199 | $subject = rtrim($modelUri, '#') . $uri; |
200 | } else { |
201 | $subject = $uri; |
202 | } |
203 | |
204 | $session = $this->getServiceLocator()->get(\oat\oatbox\session\SessionService::SERVICE_ID)->getCurrentSession(); |
205 | $sessionLanguage = $this->getDataLanguage(); |
206 | $node = node()->addProperty('uri', $uriParameter = parameter()) |
207 | ->addLabel('Resource'); |
208 | if (!empty($label)) { |
209 | $node->addProperty(OntologyRdfs::RDFS_LABEL, [$label . '@' . $sessionLanguage]); |
210 | } |
211 | if (!empty($comment)) { |
212 | $node->addProperty(OntologyRdfs::RDFS_COMMENT, [$comment . '@' . $sessionLanguage]); |
213 | } |
214 | |
215 | $node->addProperty( |
216 | 'http://www.tao.lu/Ontologies/TAO.rdf#UpdatedBy', |
217 | (string)$session->getUser()->getIdentifier() |
218 | ); |
219 | $node->addProperty( |
220 | 'http://www.tao.lu/Ontologies/TAO.rdf#UpdatedAt', |
221 | procedure()::raw('timestamp') |
222 | ); |
223 | |
224 | $nodeForRelationship = node()->withVariable($variableForRelatedResource = variable()); |
225 | $relatedResource = node('Resource') |
226 | ->withProperties(['uri' => $relatedUri = parameter()]) |
227 | ->withVariable($variableForRelatedResource); |
228 | $node = $node->relationshipTo($nodeForRelationship, OntologyRdf::RDF_TYPE); |
229 | |
230 | $query = query() |
231 | ->match($relatedResource) |
232 | ->create($node); |
233 | $this->getPersistence()->run( |
234 | $query->build(), |
235 | [$uriParameter->getParameter() => $subject, $relatedUri->getParameter() => $resource->getUri()] |
236 | ); |
237 | |
238 | return $this->getModel()->getResource($subject); |
239 | } |
240 | |
241 | /** |
242 | * (non-PHPdoc) |
243 | * @see core_kernel_persistence_ClassInterface::createSubClass() |
244 | */ |
245 | public function createSubClass(core_kernel_classes_Class $resource, $label = '', $comment = '', $uri = '') |
246 | { |
247 | if (!empty($uri)) { |
248 | common_Logger::w('Use of parameter uri in ' . __METHOD__ . ' is deprecated'); |
249 | } |
250 | $uri = empty($uri) ? $this->getServiceLocator()->get(UriProvider::SERVICE_ID)->provide() : $uri; |
251 | $returnValue = $this->getModel()->getClass($uri); |
252 | $properties = [ |
253 | OntologyRdfs::RDFS_SUBCLASSOF => $resource, |
254 | ]; |
255 | if (!empty($label)) { |
256 | $properties[OntologyRdfs::RDFS_LABEL] = $label; |
257 | } |
258 | if (!empty($comment)) { |
259 | $properties[OntologyRdfs::RDFS_COMMENT] = $comment; |
260 | } |
261 | |
262 | $returnValue->setPropertiesValues($properties); |
263 | return $returnValue; |
264 | } |
265 | |
266 | public function createProperty( |
267 | core_kernel_classes_Class $resource, |
268 | $label = '', |
269 | $comment = '', |
270 | $isLgDependent = false |
271 | ) { |
272 | $returnValue = null; |
273 | |
274 | $propertyClass = $this->getModel()->getClass(OntologyRdf::RDF_PROPERTY); |
275 | $properties = [ |
276 | OntologyRdfs::RDFS_DOMAIN => $resource->getUri(), |
277 | GenerisRdf::PROPERTY_IS_LG_DEPENDENT => ((bool)$isLgDependent) |
278 | ? GenerisRdf::GENERIS_TRUE |
279 | : GenerisRdf::GENERIS_FALSE, |
280 | ]; |
281 | if (!empty($label)) { |
282 | $properties[OntologyRdfs::RDFS_LABEL] = $label; |
283 | } |
284 | if (!empty($comment)) { |
285 | $properties[OntologyRdfs::RDFS_COMMENT] = $comment; |
286 | } |
287 | $propertyInstance = $propertyClass->createInstanceWithProperties($properties); |
288 | |
289 | $returnValue = $this->getModel()->getProperty($propertyInstance->getUri()); |
290 | |
291 | $this->getEventManager()->trigger( |
292 | new ClassPropertyCreatedEvent( |
293 | $resource, |
294 | [ |
295 | 'propertyUri' => $propertyInstance->getUri(), |
296 | 'propertyLabel' => $propertyInstance->getLabel(), |
297 | ] |
298 | ) |
299 | ); |
300 | |
301 | return $returnValue; |
302 | } |
303 | |
304 | /** |
305 | * @deprecated |
306 | */ |
307 | public function searchInstances(core_kernel_classes_Class $resource, $propertyFilters = [], $options = []) |
308 | { |
309 | $returnValue = []; |
310 | |
311 | $search = $this->getModel()->getSearchInterface(); |
312 | $query = $this->getFilterQuery($search->query(), $resource, $propertyFilters, $options); |
313 | $resultList = $search->getGateway()->search($query); |
314 | |
315 | foreach ($resultList as $resource) { |
316 | $returnValue[$resource->getUri()] = $resource; |
317 | } |
318 | |
319 | return $returnValue; |
320 | } |
321 | |
322 | public function countInstances( |
323 | core_kernel_classes_Class $resource, |
324 | $propertyFilters = [], |
325 | $options = [] |
326 | ) { |
327 | $search = $this->getModel()->getSearchInterface(); |
328 | $query = $this->getFilterQuery($search->query(), $resource, $propertyFilters, $options); |
329 | |
330 | return $search->getGateway()->count($query); |
331 | } |
332 | |
333 | public function getInstancesPropertyValues( |
334 | core_kernel_classes_Class $resource, |
335 | core_kernel_classes_Property $property, |
336 | $propertyFilters = [], |
337 | $options = [] |
338 | ) { |
339 | $search = $this->getModel()->getSearchInterface(); |
340 | $query = $this->getFilterQuery($search->query(), $resource, $propertyFilters, $options); |
341 | |
342 | $resultSet = $search->getGateway()->searchTriples($query, $property->getUri(), $options['distinct'] ?? false); |
343 | |
344 | $valueList = []; |
345 | /** @var core_kernel_classes_Triple $triple */ |
346 | foreach ($resultSet as $triple) { |
347 | $valueList[] = common_Utils::toResource($triple->object); |
348 | } |
349 | |
350 | return $valueList; |
351 | } |
352 | |
353 | /** |
354 | * @deprecated |
355 | */ |
356 | public function unsetProperty(core_kernel_classes_Class $resource, core_kernel_classes_Property $property) |
357 | { |
358 | throw new common_exception_DeprecatedApiMethod(__METHOD__ . ' is deprecated. '); |
359 | } |
360 | |
361 | public function createInstanceWithProperties(core_kernel_classes_Class $type, $properties) |
362 | { |
363 | if (isset($properties[OntologyRdf::RDF_TYPE])) { |
364 | throw new core_kernel_persistence_Exception( |
365 | 'Additional types in createInstanceWithProperties not permitted' |
366 | ); |
367 | } |
368 | |
369 | $properties[OntologyRdf::RDF_TYPE] = $type; |
370 | $returnValue = $this->getModel()->getResource( |
371 | $this->getServiceLocator()->get(UriProvider::SERVICE_ID)->provide() |
372 | ); |
373 | $returnValue->setPropertiesValues($properties); |
374 | |
375 | return $returnValue; |
376 | } |
377 | |
378 | public function deleteInstances(core_kernel_classes_Class $resource, $resources, $deleteReference = false) |
379 | { |
380 | //TODO: We need to figure out if commented checks below is still correct. |
381 | // $class = $this->getModel()->getClass($resource->getUri()); |
382 | // if (!$class->exists() || empty($resources)) { |
383 | if (empty($resources)) { |
384 | return false; |
385 | } |
386 | |
387 | $uris = []; |
388 | foreach ($resources as $r) { |
389 | $uri = (($r instanceof core_kernel_classes_Resource) ? $r->getUri() : $r); |
390 | $uris[] = $uri; |
391 | } |
392 | |
393 | $node = Query::node('Resource'); |
394 | $query = Query::new() |
395 | ->match($node) |
396 | ->where($node->property('uri')->in($uris)) |
397 | ->delete($node, $deleteReference); |
398 | |
399 | $this->getPersistence()->run($query->build()); |
400 | |
401 | return true; |
402 | } |
403 | |
404 | private function getFilterQuery( |
405 | QueryBuilder $query, |
406 | core_kernel_classes_Class $resource, |
407 | array $propertyFilters = [], |
408 | array $options = [] |
409 | ): QueryBuilder { |
410 | $queryOptions = $query->getOptions(); |
411 | |
412 | $queryOptions = array_merge( |
413 | $queryOptions, |
414 | $this->getClassFilter($options, $resource, $queryOptions), |
415 | [ |
416 | 'language' => $options['lang'] ?? '', |
417 | ] |
418 | ); |
419 | |
420 | $query->setOptions($queryOptions); |
421 | |
422 | $order = $options['order'] ?? ''; |
423 | if (!empty($order)) { |
424 | $orderDir = $options['orderdir'] ?? 'ASC'; |
425 | $query->sort([$order => strtolower($orderDir)]); |
426 | } |
427 | $query |
428 | ->setLimit($options['limit'] ?? 0) |
429 | ->setOffset($options['offset'] ?? 0); |
430 | |
431 | |
432 | $this->addFilters($query, $propertyFilters, $options); |
433 | |
434 | return $query; |
435 | } |
436 | |
437 | /** |
438 | * @param QueryBuilder $query |
439 | * @param array $propertyFilters |
440 | * @param array $options |
441 | */ |
442 | private function addFilters(QueryBuilder $query, array $propertyFilters, array $options): void |
443 | { |
444 | $isLikeOperator = $options['like'] ?? true; |
445 | $and = (!isset($options['chaining']) || (strtolower($options['chaining']) === 'and')); |
446 | |
447 | $criteria = $query->newQuery(); |
448 | foreach ($propertyFilters as $filterProperty => $filterValue) { |
449 | if ($filterValue instanceof Filter) { |
450 | $propertyUri = $filterValue->getKey(); |
451 | $operator = $filterValue->getOperator(); |
452 | $mainValue = $filterValue->getValue(); |
453 | $extraValues = $filterValue->getOrConditionValues(); |
454 | } else { |
455 | $propertyUri = $filterProperty; |
456 | $operator = $isLikeOperator ? SupportedOperatorHelper::CONTAIN : SupportedOperatorHelper::EQUAL; |
457 | |
458 | if (is_array($filterValue) && !empty($filterValue)) { |
459 | $mainValue = array_shift($filterValue); |
460 | $extraValues = $filterValue; |
461 | } else { |
462 | $mainValue = $filterValue; |
463 | $extraValues = []; |
464 | } |
465 | } |
466 | |
467 | $criteria->addCriterion( |
468 | $propertyUri, |
469 | $operator, |
470 | $mainValue |
471 | ); |
472 | |
473 | foreach ($extraValues as $value) { |
474 | $criteria->addOr($value); |
475 | } |
476 | |
477 | if (!$and) { |
478 | $query->setOr($criteria); |
479 | $criteria = $query->newQuery(); |
480 | } else { |
481 | $query->setCriteria($criteria); |
482 | } |
483 | } |
484 | } |
485 | |
486 | /** |
487 | * @param array $options |
488 | * @param core_kernel_classes_Class $resource |
489 | * @param array $queryOptions |
490 | * |
491 | * @return array |
492 | */ |
493 | private function getClassFilter(array $options, core_kernel_classes_Class $resource, array $queryOptions): array |
494 | { |
495 | $rdftypes = []; |
496 | |
497 | if (isset($options['additionalClasses'])) { |
498 | foreach ($options['additionalClasses'] as $aC) { |
499 | $rdftypes[] = ($aC instanceof core_kernel_classes_Resource) ? $aC->getUri() : $aC; |
500 | } |
501 | } |
502 | |
503 | $rdftypes = array_unique($rdftypes); |
504 | |
505 | $queryOptions['type'] = [ |
506 | 'resource' => $resource, |
507 | 'recursive' => $options['recursive'] ?? false, |
508 | 'extraClassUriList' => $rdftypes, |
509 | ]; |
510 | |
511 | return $queryOptions; |
512 | } |
513 | |
514 | public function updateUri(core_kernel_classes_Class $resource, string $newUri): void |
515 | { |
516 | $query = <<<CYPHER |
517 | MATCH (n:Resource {uri: \$original_uri}) |
518 | SET n.uri = \$uri |
519 | CYPHER; |
520 | |
521 | $this->getPersistence()->run($query, ['original_uri' => $resource->getUri(), 'uri' => $newUri]); |
522 | } |
523 | } |