[!!!][TASK] Workspaces: Always consider references
[Packages/TYPO3.CMS.git] / typo3 / sysext / version / Classes / DataHandler / CommandMap.php
1 <?php
2 namespace TYPO3\CMS\Version\DataHandler;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Version\Dependency\ElementEntity;
20
21 /**
22 * Handles the \TYPO3\CMS\Core\DataHandling\DataHandler command map and is
23 * only used in combination with \TYPO3\CMS\Core\DataHandling\DataHandler
24 */
25 class CommandMap
26 {
27 const SCOPE_WorkspacesSwap = 'SCOPE_WorkspacesSwap';
28 const SCOPE_WorkspacesSetStage = 'SCOPE_WorkspacesSetStage';
29 const SCOPE_WorkspacesClear = 'SCOPE_WorkspacesClear';
30 const KEY_GetElementPropertiesCallback = 'KEY_GetElementPropertiesCallback';
31 const KEY_GetCommonPropertiesCallback = 'KEY_GetCommonPropertiesCallback';
32 const KEY_ElementConstructCallback = 'KEY_EventConstructCallback';
33 const KEY_ElementCreateChildReferenceCallback = 'KEY_ElementCreateChildReferenceCallback';
34 const KEY_ElementCreateParentReferenceCallback = 'KEY_ElementCreateParentReferenceCallback';
35 const KEY_UpdateGetIdCallback = 'KEY_UpdateGetIdCallback';
36 const KEY_TransformDependentElementsToUseLiveId = 'KEY_TransformDependentElementsToUseLiveId';
37
38 /**
39 * @var \TYPO3\CMS\Version\Hook\DataHandlerHook
40 */
41 protected $parent;
42
43 /**
44 * @var \TYPO3\CMS\Core\DataHandling\DataHandler
45 */
46 protected $tceMain;
47
48 /**
49 * @var array
50 */
51 protected $commandMap = [];
52
53 /**
54 * @var int
55 */
56 protected $workspace;
57
58 /**
59 * @var string
60 */
61 protected $workspacesSwapMode;
62
63 /**
64 * @var string
65 */
66 protected $workspacesChangeStageMode;
67
68 /**
69 * @var array
70 */
71 protected $scopes;
72
73 /**
74 * @var \TYPO3\CMS\Version\Dependency\ElementEntityProcessor
75 */
76 protected $elementEntityProcessor;
77
78 /**
79 * Creates this object.
80 *
81 * @param \TYPO3\CMS\Version\Hook\DataHandlerHook $parent
82 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain
83 * @param array $commandMap
84 * @param int $workspace
85 */
86 public function __construct(\TYPO3\CMS\Version\Hook\DataHandlerHook $parent, \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain, array $commandMap, $workspace)
87 {
88 $this->setParent($parent);
89 $this->setTceMain($tceMain);
90 $this->set($commandMap);
91 $this->setWorkspace($workspace);
92 $this->setWorkspacesSwapMode($this->getTceMain()->BE_USER->getTSConfigVal('options.workspaces.swapMode'));
93 $this->setWorkspacesChangeStageMode($this->getTceMain()->BE_USER->getTSConfigVal('options.workspaces.changeStageMode'));
94 $this->constructScopes();
95 }
96
97 /**
98 * Gets the command map.
99 *
100 * @return array
101 */
102 public function get()
103 {
104 return $this->commandMap;
105 }
106
107 /**
108 * Sets the command map.
109 *
110 * @param array $commandMap
111 * @return \TYPO3\CMS\Version\DataHandler\CommandMap
112 */
113 public function set(array $commandMap)
114 {
115 $this->commandMap = $commandMap;
116 return $this;
117 }
118
119 /**
120 * Gets the parent object.
121 *
122 * @return \TYPO3\CMS\Version\Hook\DataHandlerHook
123 */
124 public function getParent()
125 {
126 return $this->parent;
127 }
128
129 /**
130 * Sets the parent object.
131 *
132 * @param \TYPO3\CMS\Version\Hook\DataHandlerHook $parent
133 * @return \TYPO3\CMS\Version\DataHandler\CommandMap
134 */
135 public function setParent(\TYPO3\CMS\Version\Hook\DataHandlerHook $parent)
136 {
137 $this->parent = $parent;
138 return $this;
139 }
140
141 /**
142 * Gets the parent object.
143 *
144 * @return \TYPO3\CMS\Core\DataHandling\DataHandler
145 */
146 public function getTceMain()
147 {
148 return $this->tceMain;
149 }
150
151 /**
152 * Sets the parent object.
153 *
154 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain
155 * @return \TYPO3\CMS\Version\DataHandler\CommandMap
156 */
157 public function setTceMain(\TYPO3\CMS\Core\DataHandling\DataHandler $tceMain)
158 {
159 $this->tceMain = $tceMain;
160 return $this;
161 }
162
163 /**
164 * Sets the current workspace.
165 *
166 * @param int $workspace
167 */
168 public function setWorkspace($workspace)
169 {
170 $this->workspace = (int)$workspace;
171 }
172
173 /**
174 * Gets the current workspace.
175 *
176 * @return int
177 */
178 public function getWorkspace()
179 {
180 return $this->workspace;
181 }
182
183 /**
184 * Sets the workspaces swap mode
185 * (see options.workspaces.swapMode).
186 *
187 * @param string $workspacesSwapMode
188 * @return \TYPO3\CMS\Version\DataHandler\CommandMap
189 */
190 public function setWorkspacesSwapMode($workspacesSwapMode)
191 {
192 $this->workspacesSwapMode = (string)$workspacesSwapMode;
193 return $this;
194 }
195
196 /**
197 * Sets the workspaces change stage mode
198 * see options.workspaces.changeStageMode)
199 *
200 * @param string $workspacesChangeStageMode
201 * @return \TYPO3\CMS\Version\DataHandler\CommandMap
202 */
203 public function setWorkspacesChangeStageMode($workspacesChangeStageMode)
204 {
205 $this->workspacesChangeStageMode = (string)$workspacesChangeStageMode;
206 return $this;
207 }
208
209 /**
210 * Gets the element entity processor.
211 *
212 * @return \TYPO3\CMS\Version\Dependency\ElementEntityProcessor
213 */
214 protected function getElementEntityProcessor()
215 {
216 if (!isset($this->elementEntityProcessor)) {
217 $this->elementEntityProcessor = GeneralUtility::makeInstance(
218 \TYPO3\CMS\Version\Dependency\ElementEntityProcessor::class
219 );
220 $this->elementEntityProcessor->setWorkspace($this->getWorkspace());
221 }
222 return $this->elementEntityProcessor;
223 }
224
225 /**
226 * Processes the command map.
227 *
228 * @return \TYPO3\CMS\Version\DataHandler\CommandMap
229 */
230 public function process()
231 {
232 $this->resolveWorkspacesSwapDependencies();
233 $this->resolveWorkspacesSetStageDependencies();
234 $this->resolveWorkspacesClearDependencies();
235 return $this;
236 }
237
238 /**
239 * Invokes all items for swapping/publishing with a callback method.
240 *
241 * @param string $callbackMethod
242 * @param array $arguments Optional leading arguments for the callback method
243 */
244 protected function invokeWorkspacesSwapItems($callbackMethod, array $arguments = [])
245 {
246 // Traverses the cmd[] array and fetches the accordant actions:
247 foreach ($this->commandMap as $table => $liveIdCollection) {
248 foreach ($liveIdCollection as $liveId => $commandCollection) {
249 foreach ($commandCollection as $command => $properties) {
250 if ($command === 'version' && isset($properties['action']) && $properties['action'] === 'swap') {
251 if (isset($properties['swapWith']) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($properties['swapWith'])) {
252 call_user_func_array([$this, $callbackMethod], array_merge($arguments, [$table, $liveId, $properties]));
253 }
254 }
255 }
256 }
257 }
258 }
259
260 /**
261 * Resolves workspaces related dependencies for swapping/publishing of the command map.
262 * Workspaces records that have children or (relative) parents which are versionized
263 * but not published with this request, are removed from the command map. Otherwise
264 * this would produce hanging record sets and lost references.
265 */
266 protected function resolveWorkspacesSwapDependencies()
267 {
268 $scope = self::SCOPE_WorkspacesSwap;
269 $dependency = $this->getDependencyUtility($scope);
270 if ($this->workspacesSwapMode === 'any' || $this->workspacesSwapMode === 'pages') {
271 $this->invokeWorkspacesSwapItems('applyWorkspacesSwapBehaviour');
272 }
273 $this->invokeWorkspacesSwapItems('addWorkspacesSwapElements', [$dependency]);
274 $this->applyWorkspacesDependencies($dependency, $scope);
275 }
276
277 /**
278 * Applies workspaces behaviour for swapping/publishing and takes care of the swapMode.
279 *
280 * @param string $table
281 * @param int $liveId
282 * @param array $properties
283 */
284 protected function applyWorkspacesSwapBehaviour($table, $liveId, array $properties)
285 {
286 $extendedCommandMap = [];
287 $elementList = [];
288 // Fetch accordant elements if the swapMode is 'any' or 'pages':
289 if ($this->workspacesSwapMode === 'any' || $this->workspacesSwapMode === 'pages' && $table === 'pages') {
290 $elementList = $this->getParent()->findPageElementsForVersionSwap($table, $liveId, $properties['swapWith']);
291 }
292 foreach ($elementList as $elementTable => $elementIdArray) {
293 foreach ($elementIdArray as $elementIds) {
294 $extendedCommandMap[$elementTable][$elementIds[0]]['version'] = array_merge($properties, ['swapWith' => $elementIds[1]]);
295 }
296 }
297 if (!empty($elementList)) {
298 $this->remove($table, $liveId, 'version');
299 $this->mergeToBottom($extendedCommandMap);
300 }
301 }
302
303 /**
304 * Adds workspaces elements for swapping/publishing.
305 *
306 * @param \TYPO3\CMS\Version\Dependency\DependencyResolver $dependency
307 * @param string $table
308 * @param int $liveId
309 * @param array $properties
310 */
311 protected function addWorkspacesSwapElements(\TYPO3\CMS\Version\Dependency\DependencyResolver $dependency, $table, $liveId, array $properties)
312 {
313 $elementList = [];
314 // Fetch accordant elements if the swapMode is 'any' or 'pages':
315 if ($this->workspacesSwapMode === 'any' || $this->workspacesSwapMode === 'pages' && $table === 'pages') {
316 $elementList = $this->getParent()->findPageElementsForVersionSwap($table, $liveId, $properties['swapWith']);
317 }
318 foreach ($elementList as $elementTable => $elementIdArray) {
319 foreach ($elementIdArray as $elementIds) {
320 $dependency->addElement($elementTable, $elementIds[1], ['liveId' => $elementIds[0], 'properties' => array_merge($properties, ['swapWith' => $elementIds[1]])]);
321 }
322 }
323 if (empty($elementList)) {
324 $dependency->addElement($table, $properties['swapWith'], ['liveId' => $liveId, 'properties' => $properties]);
325 }
326 }
327
328 /**
329 * Invokes all items for staging with a callback method.
330 *
331 * @param string $callbackMethod
332 * @param array $arguments Optional leading arguments for the callback method
333 */
334 protected function invokeWorkspacesSetStageItems($callbackMethod, array $arguments = [])
335 {
336 // Traverses the cmd[] array and fetches the accordant actions:
337 foreach ($this->commandMap as $table => $versionIdCollection) {
338 foreach ($versionIdCollection as $versionIdList => $commandCollection) {
339 foreach ($commandCollection as $command => $properties) {
340 if ($command === 'version' && isset($properties['action']) && $properties['action'] === 'setStage') {
341 if (isset($properties['stageId']) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($properties['stageId'])) {
342 call_user_func_array([$this, $callbackMethod], array_merge($arguments, [$table, $versionIdList, $properties]));
343 }
344 }
345 }
346 }
347 }
348 }
349
350 /**
351 * Resolves workspaces related dependencies for staging of the command map.
352 * Workspaces records that have children or (relative) parents which are versionized
353 * but not staged with this request, are removed from the command map.
354 */
355 protected function resolveWorkspacesSetStageDependencies()
356 {
357 $scope = self::SCOPE_WorkspacesSetStage;
358 $dependency = $this->getDependencyUtility($scope);
359 if ($this->workspacesChangeStageMode === 'any' || $this->workspacesChangeStageMode === 'pages') {
360 $this->invokeWorkspacesSetStageItems('applyWorkspacesSetStageBehaviour');
361 }
362 $this->invokeWorkspacesSetStageItems('explodeSetStage');
363 $this->invokeWorkspacesSetStageItems('addWorkspacesSetStageElements', [$dependency]);
364 $this->applyWorkspacesDependencies($dependency, $scope);
365 }
366
367 /**
368 * Applies workspaces behaviour for staging and takes care of the changeStageMode.
369 *
370 * @param string $table
371 * @param string $versionIdList
372 * @param array $properties
373 */
374 protected function applyWorkspacesSetStageBehaviour($table, $versionIdList, array $properties)
375 {
376 $extendedCommandMap = [];
377 $versionIds = GeneralUtility::trimExplode(',', $versionIdList, true);
378 $elementList = [$table => $versionIds];
379 if ($this->workspacesChangeStageMode === 'any' || $this->workspacesChangeStageMode === 'pages') {
380 if (count($versionIds) === 1) {
381 $workspaceRecord = BackendUtility::getRecord($table, $versionIds[0], 't3ver_wsid');
382 $workspaceId = $workspaceRecord['t3ver_wsid'];
383 } else {
384 $workspaceId = $this->getWorkspace();
385 }
386 if ($table === 'pages') {
387 // Find all elements from the same ws to change stage
388 $livePageIds = $versionIds;
389 $this->getParent()->findRealPageIds($livePageIds);
390 $this->getParent()->findPageElementsForVersionStageChange($livePageIds, $workspaceId, $elementList);
391 } elseif ($this->workspacesChangeStageMode === 'any') {
392 // Find page to change stage:
393 $pageIdList = [];
394 $this->getParent()->findPageIdsForVersionStateChange($table, $versionIds, $workspaceId, $pageIdList, $elementList);
395 // Find other elements from the same ws to change stage:
396 $this->getParent()->findPageElementsForVersionStageChange($pageIdList, $workspaceId, $elementList);
397 }
398 }
399 foreach ($elementList as $elementTable => $elementIds) {
400 foreach ($elementIds as $elementId) {
401 $extendedCommandMap[$elementTable][$elementId]['version'] = $properties;
402 }
403 }
404 $this->remove($table, $versionIds, 'version');
405 $this->mergeToBottom($extendedCommandMap);
406 }
407
408 /**
409 * Adds workspaces elements for staging.
410 *
411 * @param \TYPO3\CMS\Version\Dependency\DependencyResolver $dependency
412 * @param string $table
413 * @param string $versionId
414 * @param array $properties
415 */
416 protected function addWorkspacesSetStageElements(\TYPO3\CMS\Version\Dependency\DependencyResolver $dependency, $table, $versionId, array $properties)
417 {
418 $dependency->addElement($table, $versionId, ['versionId' => $versionId, 'properties' => $properties]);
419 }
420
421 /**
422 * Resolves workspaces related dependencies for clearing/flushing of the command map.
423 * Workspaces records that have children or (relative) parents which are versionized
424 * but not cleared/flushed with this request, are removed from the command map.
425 */
426 protected function resolveWorkspacesClearDependencies()
427 {
428 $scope = self::SCOPE_WorkspacesClear;
429 $dependency = $this->getDependencyUtility($scope);
430 // Traverses the cmd[] array and fetches the accordant actions:
431 foreach ($this->commandMap as $table => $versionIdCollection) {
432 foreach ($versionIdCollection as $versionId => $commandCollection) {
433 foreach ($commandCollection as $command => $properties) {
434 if ($command === 'version' && isset($properties['action']) && ($properties['action'] === 'clearWSID' || $properties['action'] === 'flush')) {
435 $dependency->addElement($table, $versionId, ['versionId' => $versionId, 'properties' => $properties]);
436 }
437 }
438 }
439 }
440 $this->applyWorkspacesDependencies($dependency, $scope);
441 }
442
443 /**
444 * Explodes id-lists in the command map for staging actions.
445 *
446 * @throws \RuntimeException
447 * @param string $table
448 * @param string $versionIdList
449 * @param array $properties
450 */
451 protected function explodeSetStage($table, $versionIdList, array $properties)
452 {
453 $extractedCommandMap = [];
454 $versionIds = GeneralUtility::trimExplode(',', $versionIdList, true);
455 if (count($versionIds) > 1) {
456 foreach ($versionIds as $versionId) {
457 if (isset($this->commandMap[$table][$versionId]['version'])) {
458 throw new \RuntimeException('Command map for [' . $table . '][' . $versionId . '][version] was already set.', 1289391048);
459 }
460 $extractedCommandMap[$table][$versionId]['version'] = $properties;
461 }
462 $this->remove($table, $versionIdList, 'version');
463 $this->mergeToBottom($extractedCommandMap);
464 }
465 }
466
467 /**
468 * Applies the workspaces dependencies and removes incomplete structures or automatically
469 * completes them
470 *
471 * @param \TYPO3\CMS\Version\Dependency\DependencyResolver $dependency
472 * @param string $scope
473 */
474 protected function applyWorkspacesDependencies(\TYPO3\CMS\Version\Dependency\DependencyResolver $dependency, $scope)
475 {
476 $transformDependentElementsToUseLiveId = $this->getScopeData($scope, self::KEY_TransformDependentElementsToUseLiveId);
477 $elementsToBeVersioned = $dependency->getElements();
478 // Use the uid of the live record instead of the workspace record:
479 if ($transformDependentElementsToUseLiveId) {
480 $elementsToBeVersioned = $this->getElementEntityProcessor()->transformDependentElementsToUseLiveId($elementsToBeVersioned);
481 }
482 $outerMostParents = $dependency->getOuterMostParents();
483 /** @var $outerMostParent ElementEntity */
484 foreach ($outerMostParents as $outerMostParent) {
485 $dependentElements = $dependency->getNestedElements($outerMostParent);
486 if ($transformDependentElementsToUseLiveId) {
487 $dependentElements = $this->getElementEntityProcessor()->transformDependentElementsToUseLiveId($dependentElements);
488 }
489 // Gets the difference (intersection) between elements that were submitted by the user
490 // and the evaluation of all dependent records that should be used for this action instead:
491 $intersectingElements = array_intersect_key($dependentElements, $elementsToBeVersioned);
492 if (!empty($intersectingElements)) {
493 $this->update(current($intersectingElements), $dependentElements, $scope);
494 }
495 }
496 }
497
498 /**
499 * Updates the command map accordant to valid structures and takes care of the correct order.
500 *
501 * @param ElementEntity $intersectingElement
502 * @param array $elements
503 * @param string $scope
504 */
505 protected function update(ElementEntity $intersectingElement, array $elements, $scope)
506 {
507 $orderedCommandMap = [];
508 $commonProperties = [];
509 if ($this->getScopeData($scope, self::KEY_GetCommonPropertiesCallback)) {
510 $commonProperties = $this->processCallback($this->getScopeData($scope, self::KEY_GetCommonPropertiesCallback), [$intersectingElement]);
511 }
512 /** @var $element ElementEntity */
513 foreach ($elements as $element) {
514 $table = $element->getTable();
515 $id = $this->processCallback($this->getScopeData($scope, self::KEY_UpdateGetIdCallback), [$element]);
516 $this->remove($table, $id, 'version');
517 if ($element->isInvalid()) {
518 continue;
519 }
520 $orderedCommandMap[$table][$id]['version'] = $commonProperties;
521 if ($this->getScopeData($scope, self::KEY_GetElementPropertiesCallback)) {
522 $orderedCommandMap[$table][$id]['version'] = array_merge($commonProperties, $this->processCallback($this->getScopeData($scope, self::KEY_GetElementPropertiesCallback), [$element]));
523 }
524 }
525 // Ensure that ordered command map is on top of the command map:
526 $this->mergeToTop($orderedCommandMap);
527 }
528
529 /**
530 * Merges command map elements to the top of the current command map..
531 *
532 * @param array $commandMap
533 */
534 protected function mergeToTop(array $commandMap)
535 {
536 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($commandMap, $this->commandMap);
537 $this->commandMap = $commandMap;
538 }
539
540 /**
541 * Merges command map elements to the bottom of the current command map.
542 *
543 * @param array $commandMap
544 */
545 protected function mergeToBottom(array $commandMap)
546 {
547 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($this->commandMap, $commandMap);
548 }
549
550 /**
551 * Removes an element from the command map.
552 *
553 * @param string $table
554 * @param string $id
555 * @param string $command (optional)
556 */
557 protected function remove($table, $id, $command = null)
558 {
559 if (is_string($command)) {
560 unset($this->commandMap[$table][$id][$command]);
561 } else {
562 unset($this->commandMap[$table][$id]);
563 }
564 }
565
566 /**
567 * Callback to get the liveId of an dependent element.
568 *
569 * @param ElementEntity $element
570 * @return int
571 */
572 protected function getElementLiveIdCallback(ElementEntity $element)
573 {
574 return $element->getDataValue('liveId');
575 }
576
577 /**
578 * Callback to get the real id of an dependent element.
579 *
580 * @param ElementEntity $element
581 * @return int
582 */
583 protected function getElementIdCallback(ElementEntity $element)
584 {
585 return $element->getId();
586 }
587
588 /**
589 * Callback to get the specific properties of a dependent element for swapping/publishing.
590 *
591 * @param ElementEntity $element
592 * @return array
593 */
594 protected function getElementSwapPropertiesCallback(ElementEntity $element)
595 {
596 return [
597 'swapWith' => $element->getId()
598 ];
599 }
600
601 /**
602 * Callback to get common properties of dependent elements for clearing.
603 *
604 * @param ElementEntity $element
605 * @return array
606 */
607 protected function getCommonClearPropertiesCallback(ElementEntity $element)
608 {
609 $commonSwapProperties = [];
610 $elementProperties = $element->getDataValue('properties');
611 if (isset($elementProperties['action'])) {
612 $commonSwapProperties['action'] = $elementProperties['action'];
613 }
614 return $commonSwapProperties;
615 }
616
617 /**
618 * Callback to get common properties of dependent elements for swapping/publishing.
619 *
620 * @param ElementEntity $element
621 * @return array
622 */
623 protected function getCommonSwapPropertiesCallback(ElementEntity $element)
624 {
625 $commonSwapProperties = [];
626 $elementProperties = $element->getDataValue('properties');
627 if (isset($elementProperties['action'])) {
628 $commonSwapProperties['action'] = $elementProperties['action'];
629 }
630 if (isset($elementProperties['swapIntoWS'])) {
631 $commonSwapProperties['swapIntoWS'] = $elementProperties['swapIntoWS'];
632 }
633 if (isset($elementProperties['comment'])) {
634 $commonSwapProperties['comment'] = $elementProperties['comment'];
635 }
636 if (isset($elementProperties['notificationAlternativeRecipients'])) {
637 $commonSwapProperties['notificationAlternativeRecipients'] = $elementProperties['notificationAlternativeRecipients'];
638 }
639
640 return $commonSwapProperties;
641 }
642
643 /**
644 * Callback to get the specific properties of a dependent element for staging.
645 *
646 * @param ElementEntity $element
647 * @return array
648 */
649 protected function getElementSetStagePropertiesCallback(ElementEntity $element)
650 {
651 return $this->getCommonSetStagePropertiesCallback($element);
652 }
653
654 /**
655 * Callback to get common properties of dependent elements for staging.
656 *
657 * @param ElementEntity $element
658 * @return array
659 */
660 protected function getCommonSetStagePropertiesCallback(ElementEntity $element)
661 {
662 $commonSetStageProperties = [];
663 $elementProperties = $element->getDataValue('properties');
664 if (isset($elementProperties['stageId'])) {
665 $commonSetStageProperties['stageId'] = $elementProperties['stageId'];
666 }
667 if (isset($elementProperties['comment'])) {
668 $commonSetStageProperties['comment'] = $elementProperties['comment'];
669 }
670 if (isset($elementProperties['action'])) {
671 $commonSetStageProperties['action'] = $elementProperties['action'];
672 }
673 if (isset($elementProperties['notificationAlternativeRecipients'])) {
674 $commonSetStageProperties['notificationAlternativeRecipients'] = $elementProperties['notificationAlternativeRecipients'];
675 }
676 return $commonSetStageProperties;
677 }
678
679 /**
680 * Gets an instance of the depency resolver utility.
681 *
682 * @param string $scope Scope identifier
683 * @return \TYPO3\CMS\Version\Dependency\DependencyResolver
684 */
685 protected function getDependencyUtility($scope)
686 {
687 /** @var $dependency \TYPO3\CMS\Version\Dependency\DependencyResolver */
688 $dependency = GeneralUtility::makeInstance(\TYPO3\CMS\Version\Dependency\DependencyResolver::class);
689 $dependency->setWorkspace($this->getWorkspace());
690 $dependency->setOuterMostParentsRequireReferences(true);
691 if ($this->getScopeData($scope, self::KEY_ElementConstructCallback)) {
692 $dependency->setEventCallback(ElementEntity::EVENT_Construct, $this->getDependencyCallback($this->getScopeData($scope, self::KEY_ElementConstructCallback)));
693 }
694 if ($this->getScopeData($scope, self::KEY_ElementCreateChildReferenceCallback)) {
695 $dependency->setEventCallback(ElementEntity::EVENT_CreateChildReference, $this->getDependencyCallback($this->getScopeData($scope, self::KEY_ElementCreateChildReferenceCallback)));
696 }
697 if ($this->getScopeData($scope, self::KEY_ElementCreateParentReferenceCallback)) {
698 $dependency->setEventCallback(ElementEntity::EVENT_CreateParentReference, $this->getDependencyCallback($this->getScopeData($scope, self::KEY_ElementCreateParentReferenceCallback)));
699 }
700 return $dependency;
701 }
702
703 /**
704 * Constructs the scope settings.
705 * Currently the scopes for swapping/publishing and staging are available.
706 */
707 protected function constructScopes()
708 {
709 $this->scopes = [
710 // settings for publishing and swapping:
711 self::SCOPE_WorkspacesSwap => [
712 // callback functons used to modify the commandMap
713 // + element properties are specific for each element
714 // + common properties are the same for all elements
715 self::KEY_GetElementPropertiesCallback => 'getElementSwapPropertiesCallback',
716 self::KEY_GetCommonPropertiesCallback => 'getCommonSwapPropertiesCallback',
717 // callback function used, when a new element to be checked is added
718 self::KEY_ElementConstructCallback => 'createNewDependentElementCallback',
719 // callback function used to determine whether an element is a valid child or parent reference (e.g. IRRE)
720 self::KEY_ElementCreateChildReferenceCallback => 'createNewDependentElementChildReferenceCallback',
721 self::KEY_ElementCreateParentReferenceCallback => 'createNewDependentElementParentReferenceCallback',
722 // callback function used to fetch the correct record uid on modifying the commandMap
723 self::KEY_UpdateGetIdCallback => 'getElementLiveIdCallback',
724 // setting whether to use the uid of the live record instead of the workspace record
725 self::KEY_TransformDependentElementsToUseLiveId => true
726 ],
727 // settings for modifying the stage:
728 self::SCOPE_WorkspacesSetStage => [
729 // callback functons used to modify the commandMap
730 // + element properties are specific for each element
731 // + common properties are the same for all elements
732 self::KEY_GetElementPropertiesCallback => 'getElementSetStagePropertiesCallback',
733 self::KEY_GetCommonPropertiesCallback => 'getCommonSetStagePropertiesCallback',
734 // callback function used, when a new element to be checked is added
735 self::KEY_ElementConstructCallback => null,
736 // callback function used to determine whether an element is a valid child or parent reference (e.g. IRRE)
737 self::KEY_ElementCreateChildReferenceCallback => 'createNewDependentElementChildReferenceCallback',
738 self::KEY_ElementCreateParentReferenceCallback => 'createNewDependentElementParentReferenceCallback',
739 // callback function used to fetch the correct record uid on modifying the commandMap
740 self::KEY_UpdateGetIdCallback => 'getElementIdCallback',
741 // setting whether to use the uid of the live record instead of the workspace record
742 self::KEY_TransformDependentElementsToUseLiveId => false
743 ],
744 // settings for clearing and flushing:
745 self::SCOPE_WorkspacesClear => [
746 // callback functons used to modify the commandMap
747 // + element properties are specific for each element
748 // + common properties are the same for all elements
749 self::KEY_GetElementPropertiesCallback => null,
750 self::KEY_GetCommonPropertiesCallback => 'getCommonClearPropertiesCallback',
751 // callback function used, when a new element to be checked is added
752 self::KEY_ElementConstructCallback => null,
753 // callback function used to determine whether an element is a valid child or parent reference (e.g. IRRE)
754 self::KEY_ElementCreateChildReferenceCallback => 'createClearDependentElementChildReferenceCallback',
755 self::KEY_ElementCreateParentReferenceCallback => 'createClearDependentElementParentReferenceCallback',
756 // callback function used to fetch the correct record uid on modifying the commandMap
757 self::KEY_UpdateGetIdCallback => 'getElementIdCallback',
758 // setting whether to use the uid of the live record instead of the workspace record
759 self::KEY_TransformDependentElementsToUseLiveId => false
760 ]
761 ];
762 }
763
764 /**
765 * Gets data for a particular scope.
766 *
767 * @throws \RuntimeException
768 * @param string $scope Scope identifier
769 * @param string $key
770 * @return string
771 */
772 protected function getScopeData($scope, $key)
773 {
774 if (!isset($this->scopes[$scope])) {
775 throw new \RuntimeException('Scope "' . $scope . '" is not defined.', 1289342187);
776 }
777 return $this->scopes[$scope][$key];
778 }
779
780 /**
781 * Gets a new callback to be used in the dependency resolver utility.
782 *
783 * @param string $method
784 * @param array $targetArguments
785 * @return \TYPO3\CMS\Version\Dependency\EventCallback
786 */
787 protected function getDependencyCallback($method, array $targetArguments = [])
788 {
789 return GeneralUtility::makeInstance(
790 \TYPO3\CMS\Version\Dependency\EventCallback::class,
791 $this->getElementEntityProcessor(),
792 $method,
793 $targetArguments
794 );
795 }
796
797 /**
798 * Processes a local callback inside this object.
799 *
800 * @param string $method
801 * @param array $callbackArguments
802 * @return mixed
803 */
804 protected function processCallback($method, array $callbackArguments)
805 {
806 return call_user_func_array([$this, $method], $callbackArguments);
807 }
808 }