Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 817 |
|
0.00% |
0 / 56 |
CRAP | |
0.00% |
0 / 1 |
tao_actions_RdfController | |
0.00% |
0 / 817 |
|
0.00% |
0 / 56 |
39006 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getClassService | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
validateInstanceRoot | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
30 | |||
validateInstancesRoot | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
isLocked | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
getCurrentClass | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
42 | |||
getCurrentInstance | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
validateUri | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
getRootClass | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
editClassProperties | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
editClass | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getClassForm | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
index | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getOntologyData | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getTreeOptionsFromRequest | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
110 | |||
addPermissions | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
computePermissions | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
56 | |||
editClassLabel | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
56 | |||
addInstance | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
20 | |||
addSubClass | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
20 | |||
addInstanceForm | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
20 | |||
createInstance | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
editInstance | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
12 | |||
cloneInstance | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
20 | |||
copyInstance | |
0.00% |
0 / 68 |
|
0.00% |
0 / 1 |
56 | |||
copyClass | |
0.00% |
0 / 54 |
|
0.00% |
0 / 1 |
30 | |||
moveInstance | |
0.00% |
0 / 62 |
|
0.00% |
0 / 1 |
56 | |||
moveResource | |
0.00% |
0 / 53 |
|
0.00% |
0 / 1 |
20 | |||
moveAll | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
30 | |||
translateInstance | |
0.00% |
0 / 41 |
|
0.00% |
0 / 1 |
240 | |||
getTranslatedData | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
delete | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
20 | |||
deleteResource | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
30 | |||
deleteClass | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
30 | |||
deleteAll | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
90 | |||
addClassProperty | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
removeClassProperty | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
hasWriteAccess | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
validateMoveRequest | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
moveAllInstances | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
20 | |||
getInstancesList | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
20 | |||
getInstancesToMove | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
72 | |||
move | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 | |||
returnJsonError | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
getExtraValidationRules | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getExcludedProperties | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getActionService | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getResourceService | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSignatureGenerator | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
createFormSignature | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
validateDestinationClass | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
20 | |||
getEventManager | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getClassDeleter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getResourceDeleter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getQueueDispatcher | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getResourceTransfer | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getResourceParentClass | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
30 |
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) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg |
19 | * (under the project TAO & TAO2); |
20 | * 2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung |
21 | * (under the project TAO-TRANSFER); |
22 | * 2009-2012 (update and modification) Public Research Centre Henri Tudor |
23 | * (under the project TAO-SUSTAIN & TAO-DEV); |
24 | * 2013-2022 (update and modification) Open Assessment Technologies SA. |
25 | */ |
26 | |
27 | use oat\oatbox\user\User; |
28 | use oat\oatbox\event\EventManager; |
29 | use oat\generis\model\OntologyRdfs; |
30 | use oat\tao\model\lock\LockManager; |
31 | use oat\tao\model\menu\MenuService; |
32 | use oat\tao\model\menu\ActionService; |
33 | use oat\tao\model\resources\Command\ResourceTransferCommand; |
34 | use oat\tao\model\resources\Contract\ResourceTransferInterface; |
35 | use oat\tao\model\resources\Service\ResourceTransferProxy; |
36 | use oat\tao\model\task\CopyClassTask; |
37 | use oat\generis\model\OntologyAwareTrait; |
38 | use oat\tao\model\search\tasks\IndexTrait; |
39 | use oat\tao\model\event\ResourceMovedEvent; |
40 | use oat\tao\model\resources\ResourceService; |
41 | use oat\tao\model\security\SecurityException; |
42 | use oat\tao\model\security\SignatureGenerator; |
43 | use oat\tao\model\security\SignatureValidator; |
44 | use oat\tao\model\taskQueue\TaskLogActionTrait; |
45 | use oat\tao\model\controller\SignedFormInstance; |
46 | use oat\tao\model\resources\Service\ClassDeleter; |
47 | use oat\tao\model\accessControl\PermissionChecker; |
48 | use tao_helpers_form_FormContainer as FormContainer; |
49 | use oat\tao\model\taskQueue\QueueDispatcherInterface; |
50 | use oat\generis\model\resource\Service\ResourceDeleter; |
51 | use oat\tao\model\ClassProperty\RemoveClassPropertyService; |
52 | use oat\tao\model\resources\Contract\ClassDeleterInterface; |
53 | use oat\tao\model\ClassProperty\AddClassPropertyFormFactory; |
54 | use oat\tao\model\resources\Exception\ClassDeletionException; |
55 | use oat\generis\model\resource\Contract\ResourceDeleterInterface; |
56 | use oat\tao\model\metadata\exception\InconsistencyConfigException; |
57 | use oat\tao\model\resources\Exception\PartialClassDeletionException; |
58 | |
59 | /** |
60 | * The TaoModule is an abstract controller, |
61 | * the tao children extensions Modules should extends the TaoModule to beneficiate the shared methods. |
62 | * It regroups the methods that can be applied on any extension (the rdf:Class managment for example) |
63 | * |
64 | * @author CRP Henri Tudor - TAO Team - {@link http://www.tao.lu} |
65 | * @license GPLv2 http://www.opensource.org/licenses/gpl-2.0.php |
66 | * |
67 | * phpcs:disable Squiz.Classes.ValidClassName |
68 | */ |
69 | abstract class tao_actions_RdfController extends tao_actions_CommonModule |
70 | { |
71 | use OntologyAwareTrait; |
72 | use TaskLogActionTrait; |
73 | use IndexTrait; |
74 | |
75 | /** @var SignatureValidator */ |
76 | protected $signatureValidator; |
77 | |
78 | public function __construct() |
79 | { |
80 | // maybe some day we can inject it |
81 | $this->signatureValidator = new SignatureValidator(); |
82 | |
83 | parent::__construct(); |
84 | } |
85 | |
86 | /** |
87 | * The Modules access the models throught the service instance |
88 | * |
89 | * @var tao_models_classes_Service |
90 | */ |
91 | protected $service = null; |
92 | |
93 | /** |
94 | * @return tao_models_classes_ClassService |
95 | */ |
96 | protected function getClassService() |
97 | { |
98 | if (is_null($this->service)) { |
99 | throw new common_exception_Error('No service defined for ' . get_called_class()); |
100 | } |
101 | return $this->service; |
102 | } |
103 | |
104 | /** |
105 | * @param string $uri |
106 | * |
107 | * @throws common_exception_Error |
108 | * @throws SecurityException |
109 | */ |
110 | protected function validateInstanceRoot($uri) |
111 | { |
112 | $instance = $this->getResource($uri); |
113 | |
114 | $root = $this->getRootClass(); |
115 | |
116 | if ($instance->isClass()) { |
117 | $class = new core_kernel_classes_Class($instance->getUri()); |
118 | |
119 | if (!($class->isSubClassOf($root) || $class->equals($root))) { |
120 | throw new SecurityException( |
121 | sprintf( |
122 | 'Security issue: class %s is not a subclass of %s', |
123 | $instance->getLabel(), |
124 | $root->getLabel() |
125 | ) |
126 | ); |
127 | } |
128 | |
129 | return; |
130 | } |
131 | |
132 | if (!$instance->isInstanceOf($root)) { |
133 | throw new SecurityException(sprintf( |
134 | 'Security issue: instance %s is not a child of %s', |
135 | $instance->getLabel(), |
136 | $root->getLabel() |
137 | )); |
138 | } |
139 | } |
140 | |
141 | protected function validateInstancesRoot($uris) |
142 | { |
143 | foreach ($uris as $uri) { |
144 | $this->validateInstanceRoot($uri); |
145 | } |
146 | } |
147 | |
148 | /** |
149 | * If you want strictly to check if the resource is locked, |
150 | * you should use LockManager::getImplementation()->isLocked($resource) |
151 | * Controller level convenience method to check if @resource is being locked, prepare data ans sets view, |
152 | * |
153 | * @param core_kernel_classes_Resource $resource |
154 | * @param $view |
155 | * |
156 | * @return boolean |
157 | */ |
158 | protected function isLocked($resource, $view = null) |
159 | { |
160 | |
161 | $lock = LockManager::getImplementation()->getLockData($resource); |
162 | if (!is_null($lock) && $lock->getOwnerId() != $this->getSession()->getUser()->getIdentifier()) { |
163 | //if (LockManager::getImplementation()->isLocked($resource)) { |
164 | $params = [ |
165 | 'id' => $resource->getUri(), |
166 | 'topclass-label' => $this->getRootClass()->getLabel() |
167 | ]; |
168 | if (!is_null($view)) { |
169 | $params['view'] = $view; |
170 | $params['ext'] = Context::getInstance()->getExtensionName(); |
171 | } |
172 | $this->forward('locked', 'Lock', 'tao', $params); |
173 | } |
174 | return false; |
175 | } |
176 | |
177 | /** |
178 | * get the current item class regarding the classUri' request parameter |
179 | * @return core_kernel_classes_Class the item class |
180 | * @throws Exception |
181 | */ |
182 | protected function getCurrentClass() |
183 | { |
184 | $classUri = tao_helpers_Uri::decode($this->getRequestParameter('classUri')); |
185 | if (is_null($classUri) || empty($classUri)) { |
186 | $class = null; |
187 | $resource = $this->getCurrentInstance(); |
188 | foreach ($resource->getTypes() as $type) { |
189 | $class = $type; |
190 | break; |
191 | } |
192 | if (is_null($class)) { |
193 | throw new Exception("No valid class uri found"); |
194 | } |
195 | $returnValue = $class; |
196 | } else { |
197 | if (!common_Utils::isUri($classUri)) { |
198 | throw new tao_models_classes_MissingRequestParameterException('classUri - expected to be valid URI'); |
199 | } |
200 | $returnValue = $this->getClass($classUri); |
201 | } |
202 | |
203 | return $returnValue; |
204 | } |
205 | |
206 | /** |
207 | * ! Please override me ! |
208 | * get the current instance regarding the uri and classUri in parameter |
209 | * @param string $parameterName |
210 | * |
211 | * @return core_kernel_classes_Resource |
212 | * @throws tao_models_classes_MissingRequestParameterException |
213 | */ |
214 | protected function getCurrentInstance($parameterName = 'uri') |
215 | { |
216 | $uri = tao_helpers_Uri::decode($this->getRequestParameter($parameterName)); |
217 | |
218 | $this->validateUri($uri); |
219 | |
220 | return $this->getResource($uri); |
221 | } |
222 | |
223 | protected function validateUri($uri) |
224 | { |
225 | if (!common_Utils::isUri($uri)) { |
226 | throw new tao_models_classes_MissingRequestParameterException('uri'); |
227 | } |
228 | } |
229 | |
230 | /** |
231 | * get the main class |
232 | * @return core_kernel_classes_Class |
233 | */ |
234 | abstract protected function getRootClass(); |
235 | |
236 | public function editClassProperties() |
237 | { |
238 | return $this->forward('index', 'PropertiesAuthoring', 'tao'); |
239 | } |
240 | |
241 | /** |
242 | * Deprecated alias for getClassForm |
243 | * |
244 | * @deprecated |
245 | */ |
246 | protected function editClass( |
247 | core_kernel_classes_Class $class, |
248 | core_kernel_classes_Resource $resource, |
249 | core_kernel_classes_Class $topclass = null |
250 | ) { |
251 | return $this->getClassForm($class, $resource, $topclass); |
252 | } |
253 | |
254 | protected function getClassForm($class, $resource, $topclass = null) |
255 | { |
256 | $controller = new tao_actions_PropertiesAuthoring(); |
257 | $controller->setServiceLocator($this->getServiceLocator()); |
258 | return $controller->getClassForm($class); |
259 | } |
260 | |
261 | /* |
262 | * Actions |
263 | */ |
264 | |
265 | /** |
266 | * Main action |
267 | * @return void |
268 | */ |
269 | public function index() |
270 | { |
271 | $this->setView('index.tpl'); |
272 | } |
273 | |
274 | /** |
275 | * Renders json data from the current ontology root class. |
276 | * |
277 | * The possible request parameters are the following: |
278 | * |
279 | * * uniqueNode: A URI indicating the returned hierarchy will be a single class, with a single children |
280 | * corresponding to the URI. |
281 | * * browse: |
282 | * * hideInstances: |
283 | * * chunk: |
284 | * * offset: |
285 | * * limit: |
286 | * * subclasses: |
287 | * * classUri: |
288 | * |
289 | * @return void |
290 | * @requiresRight classUri READ |
291 | */ |
292 | public function getOntologyData() |
293 | { |
294 | if (!$this->isXmlHttpRequest()) { |
295 | throw new common_exception_IsAjaxAction(__FUNCTION__); |
296 | } |
297 | $options = $this->getTreeOptionsFromRequest([]); |
298 | |
299 | //generate the tree from the given parameters |
300 | $tree = $this->getClassService()->toTree($options['class'], $options); |
301 | |
302 | //retrieve resources permissions |
303 | $user = \common_Session_SessionManager::getSession()->getUser(); |
304 | $permissions = $this->getResourceService()->getResourcesPermissions($user, $tree); |
305 | |
306 | //expose the tree |
307 | $this->returnJson([ |
308 | 'tree' => $tree, |
309 | 'permissions' => $permissions |
310 | ]); |
311 | } |
312 | |
313 | /** |
314 | * Get options to generate tree |
315 | * @return array |
316 | * @throws Exception |
317 | */ |
318 | protected function getTreeOptionsFromRequest($options = []) |
319 | { |
320 | $options = array_merge([ |
321 | 'subclasses' => true, |
322 | 'instances' => true, |
323 | 'highlightUri' => '', |
324 | 'chunk' => false, |
325 | 'offset' => 0, |
326 | 'limit' => 0 |
327 | ], $options); |
328 | |
329 | if ($this->hasRequestParameter('loadNode')) { |
330 | $options['uniqueNode'] = $this->getRequestParameter('loadNode'); |
331 | } |
332 | |
333 | if ($this->hasRequestParameter("selected")) { |
334 | $options['browse'] = [$this->getRequestParameter("selected")]; |
335 | } |
336 | |
337 | if ($this->hasRequestParameter('hideInstances')) { |
338 | if ((bool) $this->getRequestParameter('hideInstances')) { |
339 | $options['instances'] = false; |
340 | } |
341 | } |
342 | if ($this->hasRequestParameter('classUri')) { |
343 | $options['class'] = $this->getCurrentClass(); |
344 | $options['chunk'] = !$options['class']->equals($this->getRootClass()); |
345 | } else { |
346 | $options['class'] = $this->getRootClass(); |
347 | } |
348 | |
349 | if ($this->hasRequestParameter('offset')) { |
350 | $options['offset'] = $this->getRequestParameter('offset'); |
351 | } |
352 | |
353 | if ($this->hasRequestParameter('limit')) { |
354 | $options['limit'] = $this->getRequestParameter('limit'); |
355 | } |
356 | |
357 | if ($this->hasRequestParameter('order')) { |
358 | $options['order'] = tao_helpers_Uri::decode($this->getRequestParameter('order')); |
359 | } |
360 | |
361 | if ($this->hasRequestParameter('orderdir')) { |
362 | $options['orderdir'] = $this->getRequestParameter('orderdir'); |
363 | } |
364 | return $options; |
365 | } |
366 | |
367 | /** |
368 | * Add permission information to the tree structure |
369 | * |
370 | * @deprecated |
371 | * |
372 | * @param array $tree |
373 | * @return array |
374 | */ |
375 | protected function addPermissions($tree) |
376 | { |
377 | $user = $this->getSession()->getUser(); |
378 | |
379 | $section = MenuService::getSection( |
380 | $this->getRequestParameter('extension'), |
381 | $this->getRequestParameter('perspective'), |
382 | $this->getRequestParameter('section') |
383 | ); |
384 | |
385 | $actions = $section->getActions(); |
386 | |
387 | //then compute ACL for each node of the tree |
388 | $treeKeys = array_keys($tree); |
389 | if (isset($treeKeys[0]) && is_int($treeKeys[0])) { |
390 | foreach ($tree as $index => $treeNode) { |
391 | $tree[$index] = $this->computePermissions($actions, $user, $treeNode); |
392 | } |
393 | } else { |
394 | $tree = $this->computePermissions($actions, $user, $tree); |
395 | } |
396 | |
397 | return $tree; |
398 | } |
399 | |
400 | /** |
401 | * compulte permissions for a node against actions |
402 | * |
403 | * @deprecated |
404 | * |
405 | * @param array[] $actions the actions data with context, name and the resolver |
406 | * @param User $user the user |
407 | * @param array $node a tree node |
408 | * @return array the node augmented with permissions |
409 | */ |
410 | private function computePermissions($actions, $user, $node) |
411 | { |
412 | if (isset($node['attributes']['data-uri'])) { |
413 | if ($node['type'] == 'class') { |
414 | $params = ['classUri' => $node['attributes']['data-uri']]; |
415 | } else { |
416 | $params = []; |
417 | foreach ($node['attributes'] as $key => $value) { |
418 | if (substr($key, 0, strlen('data-')) == 'data-') { |
419 | $params[substr($key, strlen('data-'))] = $value; |
420 | } |
421 | } |
422 | } |
423 | $params['id'] = $node['attributes']['data-uri']; |
424 | |
425 | $node['permissions'] = $this->getActionService()->computePermissions($actions, $user, $params); |
426 | } |
427 | if (isset($node['children'])) { |
428 | foreach ($node['children'] as $index => $child) { |
429 | $node['children'][$index] = $this->computePermissions($actions, $user, $child); |
430 | } |
431 | } |
432 | return $node; |
433 | } |
434 | |
435 | /** |
436 | * Common action to view and change the label of a class |
437 | */ |
438 | public function editClassLabel() |
439 | { |
440 | $class = $this->getCurrentClass(); |
441 | $signature = $this->createFormSignature(); |
442 | |
443 | $classUri = $class->getUri(); |
444 | $hasWriteAccess = $this->hasWriteAccess($classUri) && $this->hasWriteAccessToAction(__FUNCTION__); |
445 | |
446 | $editClassLabelForm = new tao_actions_form_EditClassLabel( |
447 | $class, |
448 | $this->getRequestParameters(), |
449 | $signature, |
450 | [FormContainer::CSRF_PROTECTION_OPTION => true, FormContainer::IS_DISABLED => !$hasWriteAccess] |
451 | ); |
452 | |
453 | $myForm = $editClassLabelForm->getForm(); |
454 | |
455 | if ($myForm->isSubmited() && $myForm->isValid()) { |
456 | if ($hasWriteAccess) { |
457 | $labelFormElement = $myForm->getElement(tao_helpers_Uri::encode(OntologyRdfs::RDFS_LABEL)); |
458 | $labelFormElement && !$labelFormElement->isDisabled() && $class->setLabel( |
459 | $myForm->getValue(tao_helpers_Uri::encode(OntologyRdfs::RDFS_LABEL)) |
460 | ); |
461 | $this->setData('message', __('%s Class saved', $class->getLabel())); |
462 | } else { |
463 | $this->setData('errorMessage', __('You do not have the required rights to edit this resource.')); |
464 | } |
465 | |
466 | $this->setData('selectNode', tao_helpers_Uri::encode($classUri)); |
467 | $this->setData('reload', true); |
468 | } |
469 | |
470 | $this->setData('formTitle', __('Edit class %s', \tao_helpers_Display::htmlize($class->getLabel()))); |
471 | $this->setData('myForm', $myForm->render()); |
472 | $this->setView('form.tpl', 'tao'); |
473 | } |
474 | |
475 | /** |
476 | * Add an instance of the selected class |
477 | * |
478 | * @requiresRight id WRITE |
479 | * @requiresRight classUri WRITE |
480 | * |
481 | * @throws SecurityException |
482 | * @throws InconsistencyConfigException |
483 | * @throws common_exception_BadRequest |
484 | * @throws common_exception_Error |
485 | */ |
486 | public function addInstance() |
487 | { |
488 | if (!$this->isXmlHttpRequest()) { |
489 | throw new common_exception_BadRequest('wrong request mode'); |
490 | } |
491 | |
492 | $id = $this->getRequestParameter('id'); |
493 | |
494 | $this->validateInstanceRoot($id); |
495 | |
496 | try { |
497 | $this->validateCsrf(); |
498 | } catch (common_exception_Unauthorized $e) { |
499 | $this->response = $this->getPsrResponse()->withStatus(403, __('Unable to process your request')); |
500 | return; |
501 | } |
502 | |
503 | $this->signatureValidator->checkSignature( |
504 | $this->getRequestParameter('signature'), |
505 | $id |
506 | ); |
507 | |
508 | $response = []; |
509 | |
510 | $class = $this->getClass($id); |
511 | $label = $this->getClassService()->createUniqueLabel($class); |
512 | |
513 | $instance = $this->getClassService()->createInstance($class, $label); |
514 | |
515 | if ($instance instanceof core_kernel_classes_Resource) { |
516 | $response = [ |
517 | 'success' => true, |
518 | 'label' => $instance->getLabel(), |
519 | 'uri' => $instance->getUri() |
520 | ]; |
521 | } |
522 | |
523 | $this->returnJson($response); |
524 | } |
525 | |
526 | /** |
527 | * Add a subclass to the currently selected class |
528 | * @requiresRight id WRITE |
529 | * @requiresRight classUri WRITE |
530 | * |
531 | * @throws Exception |
532 | * @throws common_exception_BadRequest |
533 | */ |
534 | public function addSubClass() |
535 | { |
536 | if (!$this->isXmlHttpRequest()) { |
537 | throw new common_exception_BadRequest('wrong request mode'); |
538 | } |
539 | |
540 | $classId = $this->getRequestParameter('id'); |
541 | |
542 | $this->signatureValidator->checkSignature( |
543 | $this->getRequestParameter('signature'), |
544 | $classId |
545 | ); |
546 | |
547 | try { |
548 | $this->validateCsrf(); |
549 | } catch (common_exception_Unauthorized $e) { |
550 | $this->response = $this->getPsrResponse()->withStatus(403, __('Unable to process your request')); |
551 | return; |
552 | } |
553 | |
554 | $this->validateInstanceRoot($classId); |
555 | |
556 | $parent = $this->getClass($classId); |
557 | $class = $this->getClassService()->createSubClass($parent); |
558 | if ($class instanceof core_kernel_classes_Class) { |
559 | $this->returnJson([ |
560 | 'success' => true, |
561 | 'label' => $class->getLabel(), |
562 | 'uri' => tao_helpers_Uri::encode($class->getUri()) |
563 | ]); |
564 | } |
565 | } |
566 | |
567 | /** |
568 | * Add an instance of the selected class |
569 | * @throws common_exception_BadRequest |
570 | * @return void |
571 | */ |
572 | public function addInstanceForm() |
573 | { |
574 | if (!$this->isXmlHttpRequest()) { |
575 | throw new common_exception_BadRequest('wrong request mode'); |
576 | } |
577 | |
578 | $class = $this->getCurrentClass(); |
579 | $formContainer = new tao_actions_form_CreateInstance( |
580 | [$class], |
581 | [ |
582 | FormContainer::CSRF_PROTECTION_OPTION => true, |
583 | FormContainer::ADDITIONAL_VALIDATORS => $this->getExtraValidationRules(), |
584 | tao_actions_form_CreateInstance::EXCLUDED_PROPERTIES => $this->getExcludedProperties(), |
585 | ] |
586 | ); |
587 | |
588 | $addInstanceForm = $formContainer->getForm(); |
589 | |
590 | if ($addInstanceForm->isSubmited() && $addInstanceForm->isValid()) { |
591 | $properties = $addInstanceForm->getValues(); |
592 | $instance = $this->createInstance([$class], $properties); |
593 | |
594 | $this->setData('message', __('%s created', $instance->getLabel())); |
595 | $this->setData('reload', true); |
596 | } |
597 | |
598 | $this->setData('formTitle', __('Create instance of ') . $class->getLabel()); |
599 | $this->setData('myForm', $addInstanceForm->render()); |
600 | |
601 | $this->setView('form.tpl', 'tao'); |
602 | } |
603 | |
604 | /** |
605 | * creates the instance |
606 | * |
607 | * @param array $classes |
608 | * @param array $properties |
609 | * @return core_kernel_classes_Resource |
610 | */ |
611 | protected function createInstance($classes, $properties) |
612 | { |
613 | $first = array_shift($classes); |
614 | $instance = $first->createInstanceWithProperties($properties); |
615 | foreach ($classes as $class) { |
616 | $instance = $this->getResource(''); |
617 | $instance->setType($class); |
618 | } |
619 | return $instance; |
620 | } |
621 | |
622 | public function editInstance() |
623 | { |
624 | $class = $this->getCurrentClass(); |
625 | $instance = $this->getCurrentInstance(); |
626 | $myFormContainer = new SignedFormInstance( |
627 | $class, |
628 | $instance, |
629 | [ |
630 | FormContainer::CSRF_PROTECTION_OPTION => true, |
631 | FormContainer::ADDITIONAL_VALIDATORS => $this->getExtraValidationRules(), |
632 | tao_actions_form_Instance::EXCLUDED_PROPERTIES => $this->getExcludedProperties() |
633 | ] |
634 | ); |
635 | |
636 | $myForm = $myFormContainer->getForm(); |
637 | if ($myForm->isSubmited() && $myForm->isValid()) { |
638 | $values = $myForm->getValues(); |
639 | // save properties |
640 | $binder = new tao_models_classes_dataBinding_GenerisFormDataBinder($instance); |
641 | $binder->bind($values); |
642 | $message = __('Instance saved'); |
643 | |
644 | $this->setData('message', $message); |
645 | $this->setData('reload', true); |
646 | } |
647 | |
648 | $this->setData('formTitle', __('Edit Instance')); |
649 | $this->setData('myForm', $myForm->render()); |
650 | $this->setView('form.tpl', 'tao'); |
651 | } |
652 | |
653 | /** |
654 | * Duplicate the current instance |
655 | * render a JSON response |
656 | * |
657 | * @throws common_Exception |
658 | * @throws common_exception_BadRequest |
659 | * @throws common_exception_Error |
660 | * @throws tao_models_classes_MissingRequestParameterException |
661 | * |
662 | * @return void |
663 | * @requiresRight uri READ |
664 | * @requiresRight classUri WRITE |
665 | */ |
666 | public function cloneInstance() |
667 | { |
668 | if (!$this->isXmlHttpRequest()) { |
669 | throw new common_exception_BadRequest('wrong request mode'); |
670 | } |
671 | |
672 | $uri = $this->getRequestParameter('uri'); |
673 | |
674 | $this->signatureValidator->checkSignature( |
675 | $this->getRequestParameter('signature'), |
676 | $uri |
677 | ); |
678 | |
679 | try { |
680 | $this->validateCsrf(); |
681 | } catch (common_exception_Unauthorized $e) { |
682 | $this->response = $this->getPsrResponse()->withStatus(403, __('Unable to process your request')); |
683 | return; |
684 | } |
685 | $this->validateInstanceRoot($uri); |
686 | |
687 | $clone = $this->getClassService()->cloneInstance($this->getCurrentInstance(), $this->getCurrentClass()); |
688 | if ($clone !== null) { |
689 | $this->returnJson([ |
690 | 'success' => true, |
691 | 'message' => __('Successfully cloned instance as %s', $clone->getLabel()), |
692 | 'label' => $clone->getLabel(), |
693 | 'uri' => tao_helpers_Uri::encode($clone->getUri()) |
694 | ]); |
695 | } |
696 | } |
697 | |
698 | /** |
699 | * Copy a resource to a destination |
700 | * |
701 | * @requiresRight uri READ |
702 | */ |
703 | public function copyInstance() |
704 | { |
705 | if ( |
706 | $this->hasRequestParameter('destinationClassUri') |
707 | && $this->hasRequestParameter('uri') |
708 | && common_Utils::isUri($this->getRequestParameter('destinationClassUri')) |
709 | ) { |
710 | try { |
711 | $this->validateCsrf(); |
712 | } catch (common_exception_Unauthorized $e) { |
713 | $this->response = $this->getPsrResponse()->withStatus(403, __('Unable to process your request')); |
714 | return; |
715 | } |
716 | $this->validateInstanceRoot( |
717 | $this->getRequestParameter('uri') |
718 | ); |
719 | |
720 | $this->signatureValidator->checkSignature( |
721 | $this->getRequestParameter('signature'), |
722 | $this->getRequestParameter('uri') |
723 | ); |
724 | |
725 | $instance = $this->getCurrentInstance(); |
726 | $destinationClass = $this->getClass($this->getRequestParameter('destinationClassUri')); |
727 | |
728 | if ($this->hasWriteAccess($destinationClass->getUri())) { |
729 | try { |
730 | $result = $this->getResourceTransfer()->transfer( |
731 | new ResourceTransferCommand( |
732 | $instance->getUri(), |
733 | $destinationClass->getUri(), |
734 | $this->getRequestParameter('aclMode'), |
735 | ResourceTransferCommand::TRANSFER_MODE_COPY |
736 | ) |
737 | ); |
738 | |
739 | $copy = $this->getResource($result->getDestination()); |
740 | |
741 | return $this->returnJson( |
742 | [ |
743 | 'success' => true, |
744 | 'data' => [ |
745 | 'label' => $copy->getLabel(), |
746 | 'uri' => $copy->getUri(), |
747 | ] |
748 | ] |
749 | ); |
750 | } catch (Throwable $exception) { |
751 | $this->logError( |
752 | sprintf( |
753 | 'Error copying instance %s to %s: %s', |
754 | $instance->getUri(), |
755 | $destinationClass->getUri(), |
756 | $exception->getMessage() . ' - ' . $exception->getTraceAsString() |
757 | ) |
758 | ); |
759 | |
760 | return $this->returnJson( |
761 | [ |
762 | 'success' => false, |
763 | 'errorCode' => 204, |
764 | 'errorMessage' => __("Unable to copy the resource") |
765 | ], |
766 | 204 |
767 | ); |
768 | } |
769 | } |
770 | |
771 | return $this->returnJson( |
772 | [ |
773 | 'success' => false, |
774 | 'errorCode' => 401, |
775 | 'errorMessage' => __("Permission denied to write in the selected class") |
776 | ], |
777 | 401 |
778 | ); |
779 | } |
780 | |
781 | return $this->returnJson( |
782 | [ |
783 | 'success' => false, |
784 | 'errorCode' => 412, |
785 | 'errorMessage' => __('Missing Parameters') |
786 | ], |
787 | 412 |
788 | ); |
789 | } |
790 | |
791 | /** |
792 | * @requiresRight classUri READ |
793 | */ |
794 | public function copyClass(): void |
795 | { |
796 | $parsedBody = $this->getPsrRequest()->getParsedBody(); |
797 | |
798 | if (empty($parsedBody['classUri']) || empty($parsedBody['uri'])) { |
799 | $this->returnJson( |
800 | [ |
801 | 'success' => false, |
802 | 'errorCode' => 412, |
803 | 'errorMessage' => __('Missing Parameters'), |
804 | ], |
805 | 412 |
806 | ); |
807 | |
808 | return; |
809 | } |
810 | |
811 | $this->validateInstanceRoot($parsedBody['uri']); |
812 | $this->signatureValidator->checkSignature($parsedBody['signature'] ?? null, $parsedBody['uri']); |
813 | |
814 | $currentClass = $this->getClass($parsedBody['uri']); |
815 | $destinationClass = $this->getClass($parsedBody['classUri']); |
816 | |
817 | if (!$this->hasWriteAccess($destinationClass->getUri())) { |
818 | $this->returnJson( |
819 | [ |
820 | 'success' => false, |
821 | 'errorCode' => 401, |
822 | 'errorMessage' => __('Permission denied to write in the selected class'), |
823 | ], |
824 | 401 |
825 | ); |
826 | |
827 | return; |
828 | } |
829 | |
830 | try { |
831 | $task = $this->getQueueDispatcher()->createTask( |
832 | new CopyClassTask(), |
833 | [ |
834 | CopyClassTask::PARAM_CLASS_URI => $currentClass->getUri(), |
835 | CopyClassTask::PARAM_DESTINATION_CLASS_URI => $destinationClass->getUri(), |
836 | CopyClassTask::PARAM_ACL_MODE => $parsedBody['aclMode'] ?? null, |
837 | ], |
838 | __( |
839 | 'Copying class "%s" to "%s"', |
840 | $currentClass->getLabel(), |
841 | $destinationClass->getLabel() |
842 | ) |
843 | ); |
844 | |
845 | $this->returnTaskJson($task); |
846 | } catch (Throwable $exception) { |
847 | $this->logError( |
848 | sprintf( |
849 | 'Error copying class %s to %s: %s', |
850 | $currentClass->getUri(), |
851 | $destinationClass->getUri(), |
852 | $exception->getMessage() . ' - ' . $exception->getTraceAsString() |
853 | ) |
854 | ); |
855 | $this->returnJson( |
856 | [ |
857 | 'success' => false, |
858 | 'errorMessage' => __('Failed to copy class.'), |
859 | ] |
860 | ); |
861 | } |
862 | } |
863 | |
864 | /** |
865 | * Move an instance from a class to another |
866 | * @return void |
867 | * @requiresRight uri WRITE |
868 | * @requiresRight destinationClassUri WRITE |
869 | */ |
870 | public function moveInstance() |
871 | { |
872 | if (!$this->hasRequestParameter('destinationClassUri') || !$this->hasRequestParameter('uri')) { |
873 | return $this->returnJson([]); |
874 | } |
875 | |
876 | try { |
877 | $this->validateCsrf(); |
878 | } catch (common_exception_Unauthorized $e) { |
879 | $this->response = $this->getPsrResponse()->withStatus(403, __('Unable to process your request')); |
880 | |
881 | return; |
882 | } |
883 | |
884 | $id = $this->getRequestParameter('uri'); |
885 | $this->validateInstanceRoot($id); |
886 | |
887 | $this->signatureValidator->checkSignature($this->getRequestParameter('signature'), $id); |
888 | |
889 | $instance = $this->getResource($id); |
890 | $types = $instance->getTypes(); |
891 | $class = reset($types); |
892 | $destinationUri = tao_helpers_Uri::decode($this->getRequestParameter('destinationClassUri')); |
893 | $this->validateDestinationClass($destinationUri, $class->getUri()); |
894 | $destinationClass = $this->getClass($destinationUri); |
895 | $confirmed = $this->getRequestParameter('confirmed'); |
896 | |
897 | try { |
898 | if (!filter_var($confirmed, FILTER_VALIDATE_BOOLEAN)) { |
899 | $diff = $this->getClassService()->getPropertyDiff($class, $destinationClass); |
900 | |
901 | if (!empty($diff)) { |
902 | return $this->returnJson( |
903 | [ |
904 | 'status' => 'diff', |
905 | 'data' => $diff, |
906 | ] |
907 | ); |
908 | } |
909 | } |
910 | |
911 | $result = $this->getResourceTransfer()->transfer( |
912 | new ResourceTransferCommand( |
913 | $instance->getUri(), |
914 | $destinationUri, |
915 | $this->getRequestParameter('aclMode'), |
916 | ResourceTransferCommand::TRANSFER_MODE_MOVE |
917 | ) |
918 | ); |
919 | |
920 | return $this->returnJson( |
921 | [ |
922 | 'status' => true, |
923 | 'data' => [ |
924 | $result->getDestination() => [ |
925 | 'success' => true, |
926 | 'message' => sprintf( |
927 | 'Instance "%s" has been successfully moved to "%s"', |
928 | $result->getDestination(), |
929 | $destinationUri |
930 | ), |
931 | ], |
932 | ], |
933 | ] |
934 | ); |
935 | } catch (Throwable $exception) { |
936 | $this->logError( |
937 | sprintf( |
938 | 'Error moving instance %s to %s: %s', |
939 | $instance->getUri(), |
940 | $destinationClass->getUri(), |
941 | $exception->getMessage() . ' - ' . $exception->getTraceAsString() |
942 | ) |
943 | ); |
944 | |
945 | return $this->returnJson( |
946 | [ |
947 | 'status' => false, |
948 | ] |
949 | ); |
950 | } |
951 | } |
952 | |
953 | /** |
954 | * Move a single resource to another class |
955 | * |
956 | * @requiresRight uri WRITE |
957 | * |
958 | * @throws common_exception_Error |
959 | * @throws common_exception_MethodNotAllowed |
960 | */ |
961 | public function moveResource() |
962 | { |
963 | try { |
964 | if (!$this->hasRequestParameter('uri')) { |
965 | throw new InvalidArgumentException('Resource uri must be specified.'); |
966 | } |
967 | |
968 | $data = $this->getRequestParameter('uri'); |
969 | $id = $data['id']; |
970 | $destinationClassUri = $this->getRequestParameter('destinationClassUri'); |
971 | |
972 | try { |
973 | $this->validateCsrf(); |
974 | } catch (common_exception_Unauthorized $e) { |
975 | $this->response = $this->getPsrResponse()->withStatus( |
976 | 403, |
977 | __('Unable to process your request') |
978 | ); |
979 | |
980 | return; |
981 | } |
982 | |
983 | $this->validateUri($id); |
984 | $this->validateInstanceRoot($id); |
985 | $this->signatureValidator->checkSignature($data['signature'], $id); |
986 | $this->validateMoveRequest(); |
987 | $this->validateDestinationClass($destinationClassUri, $this->getResourceParentClass($id)); |
988 | |
989 | $result = $this->getResourceTransfer()->transfer( |
990 | new ResourceTransferCommand( |
991 | $id, |
992 | $destinationClassUri, |
993 | $this->getRequestParameter('aclMode'), |
994 | ResourceTransferCommand::TRANSFER_MODE_MOVE |
995 | ) |
996 | ); |
997 | |
998 | return $this->returnJson( |
999 | [ |
1000 | 'success' => true, |
1001 | 'data' => [ |
1002 | $result->getDestination() => [ |
1003 | 'success' => true, |
1004 | 'message' => sprintf( |
1005 | 'Resource "%s" has been successfully moved to "%s"', |
1006 | $id, |
1007 | $destinationClassUri |
1008 | ) |
1009 | ], |
1010 | ], |
1011 | ] |
1012 | ); |
1013 | } catch (InvalidArgumentException $e) { |
1014 | $this->returnJson( |
1015 | [ |
1016 | 'success' => true, |
1017 | 'data' => [ |
1018 | 'failed' => [ |
1019 | 'success' => false, |
1020 | 'message' => $e->getMessage(), |
1021 | ], |
1022 | ], |
1023 | ], |
1024 | 204 |
1025 | ); |
1026 | } |
1027 | } |
1028 | |
1029 | /** |
1030 | * Move all specififed resources to the given destination root class |
1031 | * |
1032 | * @throws common_exception_Error |
1033 | * @throws common_exception_MethodNotAllowed |
1034 | * @requiresRight id WRITE |
1035 | */ |
1036 | public function moveAll() |
1037 | { |
1038 | try { |
1039 | if (!$this->hasRequestParameter('ids')) { |
1040 | throw new InvalidArgumentException('Resource ids must be specified.'); |
1041 | } |
1042 | $ids = []; |
1043 | |
1044 | foreach ($this->getRequestParameter('ids') as $id) { |
1045 | $ids[] = $id['id']; |
1046 | } |
1047 | |
1048 | $this->validateInstancesRoot($ids); |
1049 | |
1050 | if (empty($ids)) { |
1051 | throw new InvalidArgumentException('No instances specified.'); |
1052 | } |
1053 | |
1054 | $this->signatureValidator->checkSignatures( |
1055 | $this->getRequestParameter('ids') |
1056 | ); |
1057 | |
1058 | $this->validateMoveRequest(); |
1059 | |
1060 | $response = $this->moveAllInstances($ids); |
1061 | $this->returnJson($response); |
1062 | } catch (InvalidArgumentException $e) { |
1063 | $this->returnJsonError($e->getMessage()); |
1064 | } |
1065 | } |
1066 | |
1067 | /** |
1068 | * Render the form to translate a Resource instance |
1069 | * @return void |
1070 | * @throws common_exception_Error |
1071 | * @throws tao_models_classes_MissingRequestParameterException |
1072 | * @requiresRight id WRITE |
1073 | */ |
1074 | public function translateInstance() |
1075 | { |
1076 | $instance = $this->getCurrentInstance(); |
1077 | |
1078 | $formContainer = new tao_actions_form_Translate( |
1079 | $this->getCurrentClass(), |
1080 | $instance, |
1081 | [FormContainer::CSRF_PROTECTION_OPTION => true] |
1082 | ); |
1083 | $myForm = $formContainer->getForm(); |
1084 | |
1085 | if ($this->hasRequestParameter('target_lang')) { |
1086 | $targetLang = $this->getRequestParameter('target_lang'); |
1087 | $availableLanguages = tao_helpers_I18n::getAvailableLangsByUsage( |
1088 | $this->getResource(tao_models_classes_LanguageService::INSTANCE_LANGUAGE_USAGE_DATA) |
1089 | ); |
1090 | |
1091 | if (in_array($targetLang, $availableLanguages)) { |
1092 | $langElt = $myForm->getElement('translate_lang'); |
1093 | $langElt->setValue($targetLang); |
1094 | $langElt->setAttribute('readonly', 'true'); |
1095 | |
1096 | $trData = $this->getClassService()->getTranslatedProperties($instance, $targetLang); |
1097 | foreach ($trData as $key => $value) { |
1098 | $element = $myForm->getElement(tao_helpers_Uri::encode($key)); |
1099 | if ($element !== null) { |
1100 | $element->setValue($value); |
1101 | } |
1102 | } |
1103 | } |
1104 | } |
1105 | |
1106 | if ($myForm->isSubmited() && $myForm->isValid()) { |
1107 | $values = $myForm->getValues(); |
1108 | if (isset($values['translate_lang'])) { |
1109 | $datalang = $this->getSession()->getDataLanguage(); |
1110 | $lang = $values['translate_lang']; |
1111 | |
1112 | $translated = 0; |
1113 | foreach ($values as $key => $value) { |
1114 | if (0 === strpos($key, 'http')) { |
1115 | $value = trim($value); |
1116 | $property = $this->getProperty($key); |
1117 | if (empty($value)) { |
1118 | if ($datalang !== $lang && $lang !== '') { |
1119 | $instance->removePropertyValueByLg($property, $lang); |
1120 | } |
1121 | } elseif ($instance->editPropertyValueByLg($property, $value, $lang)) { |
1122 | $translated++; |
1123 | } |
1124 | } |
1125 | } |
1126 | if ($translated > 0) { |
1127 | $this->setData('message', __('Translation saved')); |
1128 | } |
1129 | } |
1130 | } |
1131 | |
1132 | $this->setData('myForm', $myForm->render()); |
1133 | $this->setData('formTitle', __('Translate')); |
1134 | $this->setView('form.tpl', 'tao'); |
1135 | } |
1136 | |
1137 | /** |
1138 | * load the translated data of an instance regarding the given lang |
1139 | * |
1140 | * @throws common_exception_BadRequest |
1141 | * @throws common_exception_Error |
1142 | * @throws tao_models_classes_MissingRequestParameterException |
1143 | * |
1144 | * @return void |
1145 | */ |
1146 | public function getTranslatedData() |
1147 | { |
1148 | if (!$this->isXmlHttpRequest()) { |
1149 | throw new common_exception_BadRequest('wrong request mode'); |
1150 | } |
1151 | $data = []; |
1152 | if ($this->hasRequestParameter('lang')) { |
1153 | $data = tao_helpers_Uri::encodeArray( |
1154 | $this->getClassService()->getTranslatedProperties( |
1155 | $this->getCurrentInstance(), |
1156 | $this->getRequestParameter('lang') |
1157 | ), |
1158 | tao_helpers_Uri::ENCODE_ARRAY_KEYS |
1159 | ); |
1160 | } |
1161 | $this->returnJson($data); |
1162 | } |
1163 | |
1164 | /** |
1165 | * delete an instance or a class |
1166 | * called via ajax |
1167 | * |
1168 | * @throws common_exception_BadRequest |
1169 | * @throws common_exception_MissingParameter |
1170 | * |
1171 | * @requiresRight id WRITE |
1172 | */ |
1173 | public function delete() |
1174 | { |
1175 | if (!$this->isXmlHttpRequest()) { |
1176 | throw new common_exception_BadRequest('wrong request mode'); |
1177 | } |
1178 | |
1179 | if ($this->hasRequestParameter('uri')) { |
1180 | $this->forward( |
1181 | 'deleteResource', |
1182 | null, |
1183 | null, |
1184 | ( |
1185 | [ |
1186 | 'id' => tao_helpers_Uri::decode($this->getRequestParameter('uri')) |
1187 | ] |
1188 | ) |
1189 | ); |
1190 | } elseif ($this->hasRequestParameter('classUri')) { |
1191 | $this->forward( |
1192 | 'deleteClass', |
1193 | null, |
1194 | null, |
1195 | ( |
1196 | [ |
1197 | 'id' => tao_helpers_Uri::decode($this->getRequestParameter('classUri')) |
1198 | ] |
1199 | ) |
1200 | ); |
1201 | } else { |
1202 | throw new common_exception_MissingParameter(); |
1203 | } |
1204 | } |
1205 | |
1206 | /** |
1207 | * Generic resource deletion action |
1208 | * |
1209 | * @throws Exception |
1210 | * @throws common_exception_BadRequest |
1211 | * @requiresRight id WRITE |
1212 | */ |
1213 | public function deleteResource() |
1214 | { |
1215 | if (!$this->isXmlHttpRequest() || !$this->hasRequestParameter('id')) { |
1216 | throw new common_exception_BadRequest('wrong request mode'); |
1217 | } |
1218 | |
1219 | $id = $this->getRequestParameter('id'); |
1220 | |
1221 | // Csrf token validation |
1222 | try { |
1223 | $this->validateCsrf(); |
1224 | } catch (common_exception_Unauthorized $e) { |
1225 | $this->response = $this->getPsrResponse()->withStatus(403, __('Unable to process your request')); |
1226 | return; |
1227 | } |
1228 | |
1229 | $this->validateInstanceRoot($id); |
1230 | |
1231 | $this->signatureValidator->checkSignature( |
1232 | $this->getRequestParameter('signature'), |
1233 | $id |
1234 | ); |
1235 | |
1236 | $resource = $this->getResource($this->getRequestParameter('id')); |
1237 | |
1238 | try { |
1239 | $this->getResourceDeleter()->delete($resource); |
1240 | $deleted = true; |
1241 | $message = __('Successfully deleted %s', $resource->getLabel()); |
1242 | } catch (ResourceDeletionException $exception) { |
1243 | $deleted = false; |
1244 | $message = $exception->getUserMessage(); |
1245 | } |
1246 | |
1247 | return $this->returnJson([ |
1248 | 'success' => $deleted, |
1249 | 'message' => $message, |
1250 | 'deleted' => $deleted, |
1251 | ]); |
1252 | } |
1253 | |
1254 | /** |
1255 | * Generic class deletion action |
1256 | * |
1257 | * @throws Exception |
1258 | * @throws common_exception_BadRequest |
1259 | * @requiresRight id WRITE |
1260 | */ |
1261 | public function deleteClass() |
1262 | { |
1263 | if (!$this->isXmlHttpRequest() || !$this->hasRequestParameter('id')) { |
1264 | throw new common_exception_BadRequest('wrong request mode'); |
1265 | } |
1266 | |
1267 | $id = $this->getRequestParameter('id'); |
1268 | |
1269 | // Csrf token validation |
1270 | try { |
1271 | $this->validateCsrf(); |
1272 | } catch (common_exception_Unauthorized $e) { |
1273 | $this->response = $this->getPsrResponse()->withStatus(403, __('Unable to process your request')); |
1274 | return; |
1275 | } |
1276 | |
1277 | $this->validateInstanceRoot($id); |
1278 | |
1279 | $this->signatureValidator->checkSignature( |
1280 | $this->getRequestParameter('signature'), |
1281 | $id |
1282 | ); |
1283 | |
1284 | $class = $this->getClass($id); |
1285 | $label = $class->getLabel(); |
1286 | $classDeleter = $this->getClassDeleter(); |
1287 | |
1288 | try { |
1289 | $classDeleter->delete($class); |
1290 | $success = true; |
1291 | $deleted = true; |
1292 | $message = __('%s has been deleted', $label); |
1293 | } catch (PartialClassDeletionException | ClassDeletionException $exception) { |
1294 | $success = $exception instanceof PartialClassDeletionException; |
1295 | $deleted = false; |
1296 | |
1297 | $message = $exception->getUserMessage(); |
1298 | } |
1299 | |
1300 | $this->returnJson([ |
1301 | 'success' => $success, |
1302 | 'message' => $message, |
1303 | 'deleted' => $deleted, |
1304 | ]); |
1305 | } |
1306 | |
1307 | /** |
1308 | * Delete all given resources |
1309 | * |
1310 | * @requiresRight ids WRITE |
1311 | * |
1312 | * @throws Exception |
1313 | * @throws common_exception_BadRequest |
1314 | */ |
1315 | public function deleteAll() |
1316 | { |
1317 | $response = [ |
1318 | 'success' => true, |
1319 | 'deleted' => [] |
1320 | ]; |
1321 | if (!$this->isXmlHttpRequest()) { |
1322 | throw new common_exception_BadRequest('wrong request mode'); |
1323 | } |
1324 | |
1325 | // Csrf token validation |
1326 | try { |
1327 | $this->validateCsrf(); |
1328 | } catch (common_exception_Unauthorized $e) { |
1329 | $this->response = $this->getPsrResponse()->withStatus(403, __('Unable to process your request')); |
1330 | return; |
1331 | } |
1332 | |
1333 | $ids = []; |
1334 | |
1335 | foreach ($this->getRequestParameter('ids') as $id) { |
1336 | $ids[] = $id['id']; |
1337 | } |
1338 | |
1339 | $this->signatureValidator->checkSignatures($this->getRequestParameter('ids')); |
1340 | |
1341 | $this->validateInstancesRoot($ids); |
1342 | |
1343 | foreach ($ids as $id) { |
1344 | $deleted = false; |
1345 | $deletedResourceLabel = ''; |
1346 | try { |
1347 | if ($this->hasWriteAccess($id)) { |
1348 | $resource = new \core_kernel_classes_Resource($id); |
1349 | if ($resource->isClass()) { |
1350 | $class = new core_kernel_classes_Class($id); |
1351 | $deletedResourceLabel = $class->getLabel(); |
1352 | $deleted = $this->getClassService()->deleteClass($class); |
1353 | } else { |
1354 | $deletedResourceLabel = $resource->getLabel(); |
1355 | $deleted = $this->getClassService()->deleteResource($resource); |
1356 | } |
1357 | } |
1358 | } catch (\common_Exception $ce) { |
1359 | \common_Logger::w('Unable to remove resource ' . $id . ' : ' . $ce->getMessage()); |
1360 | } |
1361 | if ($deleted) { |
1362 | $response['deleted'][] = $id; |
1363 | } |
1364 | $response['message'] = __('Successfully deleted %s', $deletedResourceLabel); |
1365 | } |
1366 | |
1367 | return $this->returnJson($response); |
1368 | } |
1369 | |
1370 | /** |
1371 | * Copy of \tao_actions_PropertiesAuthoring::addClassProperty to split access between extensions |
1372 | * |
1373 | * @requiresRight id WRITE |
1374 | */ |
1375 | public function addClassProperty(AddClassPropertyFormFactory $addClassPropertyFormFactory): void |
1376 | { |
1377 | if (!$this->isXmlHttpRequest()) { |
1378 | throw new common_exception_BadRequest('wrong request mode'); |
1379 | } |
1380 | |
1381 | $myForm = $addClassPropertyFormFactory->add( |
1382 | $this->getPsrRequest(), |
1383 | $this->hasWriteAccessToAction(__FUNCTION__) |
1384 | ); |
1385 | |
1386 | $this->setData('data', $myForm->renderElements()); |
1387 | $this->setView('blank.tpl', 'tao'); |
1388 | } |
1389 | |
1390 | /** |
1391 | * Copy of \tao_actions_PropertiesAuthoring::removeClassProperty to split access between extensions |
1392 | * |
1393 | * @requiresRight classUri WRITE |
1394 | * @throws common_Exception |
1395 | */ |
1396 | public function removeClassProperty(RemoveClassPropertyService $removeClassPropertyService): void |
1397 | { |
1398 | if (!$this->isXmlHttpRequest()) { |
1399 | throw new common_exception_BadRequest('wrong request mode'); |
1400 | } |
1401 | |
1402 | $success = $removeClassPropertyService->remove($this->getPsrRequest()); |
1403 | |
1404 | if ($success) { |
1405 | $this->returnJson(['success' => true]); |
1406 | } else { |
1407 | $this->returnError(__('Unable to remove the property.')); |
1408 | } |
1409 | } |
1410 | |
1411 | /** |
1412 | * Test whenever the current user has "WRITE" access to the specified id |
1413 | * |
1414 | * @param string $resourceId |
1415 | * @return boolean |
1416 | */ |
1417 | protected function hasWriteAccess($resourceId) |
1418 | { |
1419 | /** @var PermissionChecker $permissionChecker */ |
1420 | $permissionChecker = $this->getServiceLocator()->get(PermissionChecker::class); |
1421 | |
1422 | return $permissionChecker->hasWriteAccess($resourceId); |
1423 | } |
1424 | |
1425 | /** |
1426 | * Validate request with all required parameters |
1427 | * |
1428 | * @throws common_exception_Error |
1429 | * @throws common_exception_MethodNotAllowed If it is not POST method |
1430 | * @throws InvalidArgumentException If parameters are not correct |
1431 | */ |
1432 | protected function validateMoveRequest() |
1433 | { |
1434 | if (!$this->isRequestPost()) { |
1435 | throw new common_exception_MethodNotAllowed('Only POST method is allowed to move instances.'); |
1436 | } |
1437 | |
1438 | if (!$this->hasRequestParameter('destinationClassUri')) { |
1439 | throw new InvalidArgumentException('Destination class must be specified'); |
1440 | } |
1441 | |
1442 | $destinationClass = new core_kernel_classes_Class($this->getRequestParameter('destinationClassUri')); |
1443 | if (!$destinationClass->isClass()) { |
1444 | throw new InvalidArgumentException('Destination class must be a valid class'); |
1445 | } |
1446 | } |
1447 | |
1448 | /** |
1449 | * Move instances to another class |
1450 | * |
1451 | * { |
1452 | * "destinationClassUri": "http://test.it", |
1453 | * "ids": [ |
1454 | * "http://resource1", |
1455 | * "http://resource2", |
1456 | * "http://class1", |
1457 | * "http://class2" |
1458 | * ] |
1459 | * } |
1460 | * @requiresRight destinationClassUri WRITE |
1461 | * @params array $ids The list of instance uris to move |
1462 | * |
1463 | * @throws common_exception_Error |
1464 | */ |
1465 | protected function moveAllInstances(array $ids) |
1466 | { |
1467 | $rootClass = $this->getClassService()->getRootClass(); |
1468 | |
1469 | if (in_array($rootClass->getUri(), $ids)) { |
1470 | throw new InvalidArgumentException(sprintf('Root class "%s" cannot be moved', $rootClass->getUri())); |
1471 | } |
1472 | |
1473 | $destinationClass = new core_kernel_classes_Class($this->getRequestParameter('destinationClassUri')); |
1474 | |
1475 | if (!$destinationClass->isSubClassOf($rootClass) && $destinationClass->getUri() != $rootClass->getUri()) { |
1476 | throw new InvalidArgumentException( |
1477 | sprintf( |
1478 | 'Instance "%s" cannot be moved to another root class', |
1479 | $destinationClass->getUri() |
1480 | ) |
1481 | ); |
1482 | } |
1483 | |
1484 | [$statuses, $instances, $classes] = $this->getInstancesList($ids); |
1485 | $movableInstances = $this->getInstancesToMove($classes, $instances, $statuses); |
1486 | |
1487 | $statuses = $this->move($destinationClass, $movableInstances, $statuses); |
1488 | |
1489 | return [ |
1490 | 'success' => true, |
1491 | 'data' => $statuses |
1492 | ]; |
1493 | } |
1494 | |
1495 | /** |
1496 | * Gets list of existing instances/classes |
1497 | * |
1498 | * @param array $ids list of ids asked to be moved |
1499 | * @return array |
1500 | */ |
1501 | private function getInstancesList(array $ids) |
1502 | { |
1503 | $statuses = $instances = $classes = []; |
1504 | |
1505 | foreach ($ids as $key => $instance) { |
1506 | $instance = $this->getResource($instance); |
1507 | if ($instance->isClass()) { |
1508 | $instance = $this->getClass($instance); |
1509 | $classes[] = $instance; |
1510 | } elseif (!$instance->exists()) { |
1511 | $statuses[$instance->getUri()] = [ |
1512 | 'success' => false, |
1513 | 'message' => sprintf('Instance "%s" does not exist', $instance->getUri()), |
1514 | ]; |
1515 | break; |
1516 | } |
1517 | $instances[$key] = $instance; |
1518 | } |
1519 | |
1520 | return [ |
1521 | $statuses, |
1522 | $instances, |
1523 | $classes |
1524 | ]; |
1525 | } |
1526 | |
1527 | /** |
1528 | * Get movable instances from the list of instances |
1529 | * |
1530 | * @param array $classes |
1531 | * @param array $instances |
1532 | * |
1533 | * @return array |
1534 | */ |
1535 | private function getInstancesToMove(array $classes = [], array $instances = [], array &$statuses = []) |
1536 | { |
1537 | $movableInstances = []; |
1538 | |
1539 | // Check if a class belong to class to move |
1540 | /** @var core_kernel_classes_Resource|core_kernel_classes_Class $instance */ |
1541 | foreach ($instances as $instance) { |
1542 | $isValid = true; |
1543 | foreach ($classes as $class) { |
1544 | if ($instance instanceof core_kernel_classes_Class) { |
1545 | //Disallow moving a class to $class. True only for classes which are already subclasses of $class |
1546 | if ($class->getUri() != $instance->getUri() && $instance->isSubClassOf($class)) { |
1547 | $statuses[$instance->getUri()] = [ |
1548 | 'success' => false, |
1549 | 'message' => sprintf( |
1550 | 'Instance "%s" cannot be moved to class to move "%s"', |
1551 | $instance->getUri(), |
1552 | $class->getUri() |
1553 | ), |
1554 | ]; |
1555 | $isValid = false; |
1556 | break; |
1557 | } |
1558 | } else { |
1559 | //Disallow moving instances to $class. True only for instances which already belongs to $class |
1560 | if ($instance->isInstanceOf($class)) { |
1561 | $statuses[$instance->getUri()] = [ |
1562 | 'success' => false, |
1563 | 'message' => sprintf( |
1564 | 'Instance "%s" cannot be moved to class to move "%s"', |
1565 | $instance->getUri(), |
1566 | $class->getUri() |
1567 | ), |
1568 | ]; |
1569 | $isValid = false; |
1570 | break; |
1571 | } |
1572 | } |
1573 | } |
1574 | if ($isValid) { |
1575 | $movableInstances[$instance->getUri()] = $instance; |
1576 | } |
1577 | } |
1578 | |
1579 | return $movableInstances; |
1580 | } |
1581 | |
1582 | /** |
1583 | * Move movableInstances to the destinationClass |
1584 | * |
1585 | * @param core_kernel_classes_Class $destinationClass class to move to |
1586 | * @param array $movableInstances list of instances available to move |
1587 | * @param array $statuses list of statuses for instances asked to be moved |
1588 | * |
1589 | * @return array $statuses updated list of statuses |
1590 | */ |
1591 | private function move( |
1592 | core_kernel_classes_Class $destinationClass, |
1593 | array $movableInstances = [], |
1594 | array $statuses = [] |
1595 | ) { |
1596 | /** @var core_kernel_classes_Resource $movableInstance */ |
1597 | foreach ($movableInstances as $movableInstance) { |
1598 | $statuses[$movableInstance->getUri()] = [ |
1599 | 'success' => $success = $this->getClassService()->changeClass($movableInstance, $destinationClass), |
1600 | ]; |
1601 | if ($success === true) { |
1602 | $statuses[$movableInstance->getUri()]['message'] = sprintf( |
1603 | 'Instance "%s" has been successfully moved to "%s"', |
1604 | $movableInstance->getUri(), |
1605 | $destinationClass->getUri() |
1606 | ); |
1607 | |
1608 | $this->getEventManager()->trigger(new ResourceMovedEvent($movableInstance, $destinationClass)); |
1609 | } else { |
1610 | $statuses[$movableInstance->getUri()]['message'] = sprintf( |
1611 | 'An error has occurred while persisting instance "%s"', |
1612 | $movableInstance->getUri() |
1613 | ); |
1614 | } |
1615 | } |
1616 | |
1617 | return $statuses; |
1618 | } |
1619 | |
1620 | /** |
1621 | * Return a formatted error message with code 406 |
1622 | * |
1623 | * @param $message |
1624 | * @param int $httpStatusCode |
1625 | */ |
1626 | protected function returnJsonError($message, $httpStatusCode = 406) |
1627 | { |
1628 | $response = [ |
1629 | 'success' => false, |
1630 | 'errorCode' => $httpStatusCode, |
1631 | 'errorMessage' => $message |
1632 | ]; |
1633 | $this->returnJson($response, 406); |
1634 | } |
1635 | |
1636 | protected function getExtraValidationRules(): array |
1637 | { |
1638 | return []; |
1639 | } |
1640 | |
1641 | protected function getExcludedProperties(): array |
1642 | { |
1643 | return []; |
1644 | } |
1645 | |
1646 | /** |
1647 | * @return ActionService |
1648 | */ |
1649 | private function getActionService() |
1650 | { |
1651 | return $this->getServiceLocator()->get(ActionService::SERVICE_ID); |
1652 | } |
1653 | |
1654 | /** |
1655 | * Get the resource service |
1656 | * @return ResourceService |
1657 | */ |
1658 | protected function getResourceService() |
1659 | { |
1660 | return $this->getServiceLocator()->get(ResourceService::SERVICE_ID); |
1661 | } |
1662 | |
1663 | /** |
1664 | * @return SignatureGenerator |
1665 | */ |
1666 | private function getSignatureGenerator() |
1667 | { |
1668 | return $this->getServiceLocator()->get(SignatureGenerator::SERVICE_ID); |
1669 | } |
1670 | |
1671 | /** |
1672 | * @return string |
1673 | * |
1674 | * @throws InconsistencyConfigException |
1675 | */ |
1676 | private function createFormSignature() |
1677 | { |
1678 | return $this->getSignatureGenerator()->generate( |
1679 | tao_helpers_Uri::encode($this->getRequestParameter('classUri')) |
1680 | ); |
1681 | } |
1682 | |
1683 | /** |
1684 | * @param $destinationUri |
1685 | * @param $currentClassUri |
1686 | */ |
1687 | private function validateDestinationClass($destinationUri, $currentClassUri) |
1688 | { |
1689 | $destinationClass = $this->getClass($destinationUri); |
1690 | if (empty($destinationUri) || $destinationUri === $currentClassUri || !$destinationClass->exists()) { |
1691 | throw new InvalidArgumentException('The selected class already contains the resource'); |
1692 | } |
1693 | } |
1694 | |
1695 | private function getEventManager(): EventManager |
1696 | { |
1697 | return $this->getServiceLocator()->get(EventManager::SERVICE_ID); |
1698 | } |
1699 | |
1700 | private function getClassDeleter(): ClassDeleterInterface |
1701 | { |
1702 | return $this->getPsrContainer()->get(ClassDeleter::class); |
1703 | } |
1704 | |
1705 | private function getResourceDeleter(): ResourceDeleterInterface |
1706 | { |
1707 | return $this->getPsrContainer()->get(ResourceDeleter::class); |
1708 | } |
1709 | |
1710 | private function getQueueDispatcher(): QueueDispatcherInterface |
1711 | { |
1712 | return $this->getPsrContainer()->get(QueueDispatcherInterface::SERVICE_ID); |
1713 | } |
1714 | |
1715 | private function getResourceTransfer(): ResourceTransferInterface |
1716 | { |
1717 | return $this->getPsrContainer()->get(ResourceTransferProxy::class); |
1718 | } |
1719 | |
1720 | private function getResourceParentClass(string $id): string |
1721 | { |
1722 | if ($this->getResource($id)->isClass()) { |
1723 | $directParent = $this->getResource($id)->getParentClassesIds(); |
1724 | return reset($directParent); |
1725 | } |
1726 | |
1727 | $types = $this->getResource($id)->getTypes(); |
1728 | |
1729 | if (count($types) !== 1) { |
1730 | throw new InvalidArgumentException('Resource has no class or resource types are ambiguous'); |
1731 | } |
1732 | |
1733 | /** @var core_kernel_classes_Class $type */ |
1734 | $type = reset($types); |
1735 | |
1736 | if (!($type instanceof core_kernel_classes_Class) && !$type->isClass()) { |
1737 | throw new InvalidArgumentException('Type is not a class'); |
1738 | } |
1739 | |
1740 | return $type->getUri(); |
1741 | } |
1742 | } |