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