Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
86.14% |
87 / 101 |
|
72.73% |
8 / 11 |
CRAP | |
0.00% |
0 / 1 |
ClassDeleter | |
86.14% |
87 / 101 |
|
72.73% |
8 / 11 |
37.08 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
delete | |
54.55% |
12 / 22 |
|
0.00% |
0 / 1 |
5.50 | |||
deleteClassRecursively | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
deleteClass | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
deleteClassContent | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
deleteInstances | |
88.00% |
22 / 25 |
|
0.00% |
0 / 1 |
7.08 | |||
deleteProperties | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
deleteProperty | |
92.31% |
12 / 13 |
|
0.00% |
0 / 1 |
3.00 | |||
defineResourceType | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
createQuery | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
filterInstances | |
100.00% |
3 / 3 |
|
100.00% |
1 / 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) 2021 (original work) Open Assessment Technologies SA; |
19 | */ |
20 | |
21 | declare(strict_types=1); |
22 | |
23 | namespace oat\tao\model\resources\Service; |
24 | |
25 | use oat\tao\model\resources\relation\FindAllQuery; |
26 | use oat\tao\model\resources\relation\ResourceRelationCollection; |
27 | use oat\tao\model\resources\relation\service\ResourceRelationServiceProxy; |
28 | use oat\tao\model\TaoOntology; |
29 | use Throwable; |
30 | use core_kernel_classes_Class; |
31 | use core_kernel_classes_Property; |
32 | use oat\generis\model\data\Ontology; |
33 | use oat\tao\model\search\index\OntologyIndex; |
34 | use oat\tao\model\accessControl\PermissionCheckerInterface; |
35 | use oat\tao\model\resources\Contract\ClassDeleterInterface; |
36 | use oat\tao\model\Specification\ClassSpecificationInterface; |
37 | use oat\tao\model\resources\Exception\ClassDeletionException; |
38 | use oat\generis\model\resource\Context\ResourceRepositoryContext; |
39 | use oat\generis\model\resource\Contract\ResourceRepositoryInterface; |
40 | use oat\tao\model\resources\Exception\PartialClassDeletionException; |
41 | |
42 | class ClassDeleter implements ClassDeleterInterface |
43 | { |
44 | private const RELATION_RESOURCE_MAP = [ |
45 | TaoOntology::CLASS_URI_ITEM => 'itemClass' |
46 | ]; |
47 | private const PROPERTY_INDEX = OntologyIndex::PROPERTY_INDEX; |
48 | |
49 | /** @var ClassSpecificationInterface */ |
50 | private $rootClassSpecification; |
51 | |
52 | /** @var PermissionCheckerInterface */ |
53 | private $permissionChecker; |
54 | |
55 | /** @var Ontology */ |
56 | private $ontology; |
57 | |
58 | /** @var ResourceRepositoryInterface */ |
59 | private $resourceRepository; |
60 | |
61 | /** @var ResourceRepositoryInterface */ |
62 | private $classRepository; |
63 | |
64 | /** @var core_kernel_classes_Property */ |
65 | private $propertyIndex; |
66 | |
67 | /** @var core_kernel_classes_Class|null */ |
68 | private $selectedClass; |
69 | private ResourceRelationServiceProxy $resourceRelationServiceProxy; |
70 | |
71 | public function __construct( |
72 | ClassSpecificationInterface $rootClassSpecification, |
73 | PermissionCheckerInterface $permissionChecker, |
74 | Ontology $ontology, |
75 | ResourceRepositoryInterface $resourceRepository, |
76 | ResourceRepositoryInterface $classRepository, |
77 | ResourceRelationServiceProxy $resourceRelationServiceProxy |
78 | ) { |
79 | $this->rootClassSpecification = $rootClassSpecification; |
80 | $this->permissionChecker = $permissionChecker; |
81 | $this->ontology = $ontology; |
82 | $this->resourceRepository = $resourceRepository; |
83 | $this->classRepository = $classRepository; |
84 | $this->resourceRelationServiceProxy = $resourceRelationServiceProxy; |
85 | |
86 | $this->propertyIndex = $ontology->getProperty(self::PROPERTY_INDEX); |
87 | } |
88 | |
89 | public function delete(core_kernel_classes_Class $class): void |
90 | { |
91 | if ($this->rootClassSpecification->isSatisfiedBy($class)) { |
92 | throw new ClassDeletionException( |
93 | 'The class provided for deletion cannot be the root class.', |
94 | __('You cannot delete the root node') |
95 | ); |
96 | } |
97 | |
98 | try { |
99 | $this->selectedClass = $class; |
100 | $this->deleteClassRecursively($class); |
101 | } catch (Throwable $exception) { |
102 | throw new PartialClassDeletionException( |
103 | sprintf( |
104 | 'Unable to delete class "%s::%s" (%s).', |
105 | $class->getLabel(), |
106 | $class->getUri(), |
107 | $exception->getMessage() |
108 | ), |
109 | __('Unable to delete the selected resource') |
110 | ); |
111 | } |
112 | |
113 | if ($class->exists()) { |
114 | throw new PartialClassDeletionException( |
115 | 'Some of resources has not be deleted', |
116 | __('Some of resources has not be deleted') |
117 | ); |
118 | } |
119 | } |
120 | |
121 | private function deleteClassRecursively(core_kernel_classes_Class $class): bool |
122 | { |
123 | if (!$this->permissionChecker->hasReadAccess($class->getUri())) { |
124 | return false; |
125 | } |
126 | |
127 | $isClassDeletable = true; |
128 | |
129 | foreach ($class->getSubClasses() as $subClass) { |
130 | $isClassDeletable = $this->deleteClassRecursively($subClass) && $isClassDeletable; |
131 | } |
132 | |
133 | return $this->deleteClass($class, $isClassDeletable); |
134 | } |
135 | |
136 | /** |
137 | * @param bool $isClassDeletable Class is not deletable if it contains at least one protected subclass, |
138 | * instance or property |
139 | */ |
140 | private function deleteClass(core_kernel_classes_Class $class, bool $isClassDeletable): bool |
141 | { |
142 | if ($this->deleteClassContent($class, $isClassDeletable)) { |
143 | $this->classRepository->delete( |
144 | new ResourceRepositoryContext( |
145 | [ |
146 | ResourceRepositoryContext::PARAM_CLASS => $class, |
147 | ResourceRepositoryContext::PARAM_SELECTED_CLASS => $this->selectedClass, |
148 | ] |
149 | ) |
150 | ); |
151 | |
152 | return true; |
153 | } |
154 | |
155 | return false; |
156 | } |
157 | |
158 | private function deleteClassContent(core_kernel_classes_Class $class, bool $isClassDeletable): bool |
159 | { |
160 | return $this->deleteInstances($class) |
161 | && $isClassDeletable |
162 | && $this->permissionChecker->hasWriteAccess($class->getUri()) |
163 | && $this->deleteProperties($class->getProperties()); |
164 | } |
165 | |
166 | private function deleteInstances(core_kernel_classes_Class $class): bool |
167 | { |
168 | $status = true; |
169 | $resources = $class->getInstances(); |
170 | if ($query = $this->createQuery($class)) { |
171 | $itemsInUse = $this->resourceRelationServiceProxy->findRelations($query); |
172 | if ($itemsInUse->jsonSerialize()) { |
173 | $resources = $this->filterInstances($resources, $itemsInUse); |
174 | $status = false; |
175 | } |
176 | } |
177 | |
178 | foreach ($resources as $instance) { |
179 | if (!$instance->exists()) { |
180 | continue; |
181 | } |
182 | |
183 | if (!$this->permissionChecker->hasWriteAccess($instance->getUri())) { |
184 | $status = false; |
185 | |
186 | continue; |
187 | } |
188 | |
189 | try { |
190 | $this->resourceRepository->delete( |
191 | new ResourceRepositoryContext( |
192 | [ |
193 | ResourceRepositoryContext::PARAM_RESOURCE => $instance, |
194 | ResourceRepositoryContext::PARAM_SELECTED_CLASS => $this->selectedClass, |
195 | ResourceRepositoryContext::PARAM_PARENT_CLASS => $class, |
196 | ] |
197 | ) |
198 | ); |
199 | } catch (Throwable $exception) { |
200 | $status = false; |
201 | } |
202 | } |
203 | |
204 | return $status; |
205 | } |
206 | |
207 | /** |
208 | * @param core_kernel_classes_Property[] $properties |
209 | */ |
210 | private function deleteProperties(array $properties): bool |
211 | { |
212 | $status = true; |
213 | |
214 | foreach ($properties as $property) { |
215 | $status = $this->deleteProperty($property) && $status; |
216 | } |
217 | |
218 | return $status; |
219 | } |
220 | |
221 | private function deleteProperty(core_kernel_classes_Property $property): bool |
222 | { |
223 | $indexes = $property->getPropertyValues($this->propertyIndex); |
224 | |
225 | if (!$property->delete(true)) { |
226 | return false; |
227 | } |
228 | |
229 | foreach ($indexes as $indexUri) { |
230 | $this->resourceRepository->delete( |
231 | new ResourceRepositoryContext( |
232 | [ |
233 | ResourceRepositoryContext::PARAM_RESOURCE => $this->ontology->getResource($indexUri), |
234 | ResourceRepositoryContext::PARAM_DELETE_REFERENCE => true, |
235 | ] |
236 | ) |
237 | ); |
238 | } |
239 | |
240 | return true; |
241 | } |
242 | |
243 | private function defineResourceType(core_kernel_classes_Class $class): ?string |
244 | { |
245 | if (isset(self::RELATION_RESOURCE_MAP[$class->getRootId()])) { |
246 | return self::RELATION_RESOURCE_MAP[$class->getRootId()]; |
247 | } |
248 | |
249 | return null; |
250 | } |
251 | |
252 | private function createQuery($class): ?FindAllQuery |
253 | { |
254 | if ($this->defineResourceType($class)) { |
255 | return new FindAllQuery(null, $class->getUri(), $this->defineResourceType($class)); |
256 | } |
257 | |
258 | return null; |
259 | } |
260 | |
261 | private function filterInstances(array $resourceCollection, ResourceRelationCollection $itemsInUse): iterable |
262 | { |
263 | foreach ($itemsInUse->getIterator() as $item) { |
264 | unset($resourceCollection[$item->getId()]); |
265 | } |
266 | |
267 | return $resourceCollection; |
268 | } |
269 | } |