[BUGFIX] Respect move-placeholders when moving records in workspace
[Packages/TYPO3.CMS.git] / typo3 / sysext / version / class.tx_version_tcemain_commandmap.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2010-2011 Oliver Hader <oliver@typo3.org>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * Handles the t3lib_TCEmain command map and is only used in combination with t3lib_TCEmain.
30 */
31 class tx_version_tcemain_CommandMap {
32 const SCOPE_WorkspacesSwap = 'SCOPE_WorkspacesSwap';
33 const SCOPE_WorkspacesSetStage = 'SCOPE_WorkspacesSetStage';
34 const SCOPE_WorkspacesClear = 'SCOPE_WorkspacesClear';
35
36 const KEY_ScopeErrorMessage = 'KEY_ScopeErrorMessage';
37 const KEY_ScopeErrorCode = 'KEY_ScopeErrorCode';
38 const KEY_GetElementPropertiesCallback = 'KEY_GetElementPropertiesCallback';
39 const KEY_GetCommonPropertiesCallback = 'KEY_GetCommonPropertiesCallback';
40 const KEY_ElementConstructCallback = 'KEY_EventConstructCallback';
41 const KEY_ElementCreateChildReferenceCallback = 'KEY_ElementCreateChildReferenceCallback';
42 const KEY_ElementCreateParentReferenceCallback = 'KEY_ElementCreateParentReferenceCallback';
43 const KEY_PurgeWithErrorMessageGetIdCallback = 'KEY_PurgeWithErrorMessageGetIdCallback';
44 const KEY_UpdateGetIdCallback = 'KEY_UpdateGetIdCallback';
45 const KEY_TransformDependentElementsToUseLiveId = 'KEY_TransformDependentElementsToUseLiveId';
46
47 /**
48 * @var tx_version_tcemain
49 */
50 protected $parent;
51
52 /**
53 * @var t3lib_TCEmain
54 */
55 protected $tceMain;
56
57 /**
58 * @var array
59 */
60 protected $commandMap = array();
61
62 /**
63 * @var string
64 */
65 protected $workspacesSwapMode;
66
67 /**
68 * @var string
69 */
70 protected $workspacesChangeStageMode;
71
72 /**
73 * @var boolean
74 */
75 protected $workspacesConsiderReferences;
76
77 /**
78 * @var array
79 */
80 protected $scopes;
81
82 /**
83 * Creates this object.
84 *
85 * @param t3lib_TCEmain $parent
86 * @param array $commandMap
87 */
88 public function __construct(tx_version_tcemain $parent, t3lib_TCEmain $tceMain, array $commandMap) {
89 $this->setParent($parent);
90 $this->setTceMain($tceMain);
91 $this->set($commandMap);
92
93 $this->setWorkspacesSwapMode($this->getTceMain()->BE_USER->getTSConfigVal('options.workspaces.swapMode'));
94 $this->setWorkspacesChangeStageMode($this->getTceMain()->BE_USER->getTSConfigVal('options.workspaces.changeStageMode'));
95 $this->setWorkspacesConsiderReferences($this->getTceMain()->BE_USER->getTSConfigVal('options.workspaces.considerReferences'));
96
97 $this->constructScopes();
98 }
99
100 /**
101 * Gets the command map.
102 *
103 * @return array
104 */
105 public function get() {
106 return $this->commandMap;
107 }
108
109 /**
110 * Sets the command map.
111 *
112 * @param array $commandMap
113 * @return tx_version_tcemain_CommandMap
114 */
115 public function set(array $commandMap) {
116 $this->commandMap = $commandMap;
117 return $this;
118 }
119
120 /**
121 * Gets the parent object.
122 *
123 * @return tx_version_tcemain
124 */
125 public function getParent() {
126 return $this->parent;
127 }
128
129 /**
130 * Sets the parent object.
131 *
132 * @param tx_version_tcemain $parent
133 * @return tx_version_tcemain_CommandMap
134 */
135 public function setParent(tx_version_tcemain $parent) {
136 $this->parent = $parent;
137 return $this;
138 }
139
140 /**
141 * Gets the parent object.
142 *
143 * @return t3lib_TCEmain
144 */
145 public function getTceMain() {
146 return $this->tceMain;
147 }
148
149 /**
150 * Sets the parent object.
151 *
152 * @param t3lib_TCEmain $tceMain
153 * @return tx_version_tcemain_CommandMap
154 */
155 public function setTceMain(t3lib_TCEmain $tceMain) {
156 $this->tceMain = $tceMain;
157 return $this;
158 }
159
160 /**
161 * Sets the workspaces swap mode
162 * (see options.workspaces.swapMode).
163 *
164 * @param string $workspacesSwapMode
165 * @return tx_version_tcemain_CommandMap
166 */
167 public function setWorkspacesSwapMode($workspacesSwapMode) {
168 $this->workspacesSwapMode = (string)$workspacesSwapMode;
169 return $this;
170 }
171
172 /**
173 * Sets the workspaces change stage mode
174 * see options.workspaces.changeStageMode)
175 *
176 * @param string $workspacesChangeStageMode
177 * @return tx_version_tcemain_CommandMap
178 */
179 public function setWorkspacesChangeStageMode($workspacesChangeStageMode) {
180 $this->workspacesChangeStageMode = (string)$workspacesChangeStageMode;
181 return $this;
182 }
183
184 /**
185 * Sets the workspace behaviour to automatically consider references
186 * (see options.workspaces.considerReferences)
187 *
188 * @param boolean $workspacesConsiderReferences
189 * @return tx_version_tcemain_CommandMap
190 */
191 public function setWorkspacesConsiderReferences($workspacesConsiderReferences) {
192 $this->workspacesConsiderReferences = (bool)$workspacesConsiderReferences;
193 return $this;
194 }
195
196 /**
197 * Processes the command map.
198 *
199 * @return tx_version_tcemain_CommandMap
200 */
201 public function process() {
202 $this->resolveWorkspacesSwapDependencies();
203 $this->resolveWorkspacesSetStageDependencies();
204 $this->resolveWorkspacesClearDependencies();
205 return $this;
206 }
207
208 /**
209 * Invokes all items for swapping/publishing with a callback method.
210 *
211 * @param string $callbackMethod
212 * @param array $arguments Optional leading arguments for the callback method
213 * @return void
214 */
215 protected function invokeWorkspacesSwapItems($callbackMethod, array $arguments = array()) {
216 // Traverses the cmd[] array and fetches the accordant actions:
217 foreach ($this->commandMap as $table => $liveIdCollection) {
218 foreach ($liveIdCollection as $liveId => $commandCollection) {
219 foreach ($commandCollection as $command => $properties) {
220 if ($command === 'version' && isset($properties['action']) && $properties['action'] === 'swap') {
221 if (isset($properties['swapWith']) && t3lib_utility_Math::canBeInterpretedAsInteger($properties['swapWith'])) {
222 call_user_func_array(
223 array($this, $callbackMethod),
224 array_merge($arguments, array($table, $liveId, $properties))
225 );
226 }
227 }
228 }
229 }
230 }
231 }
232
233 /**
234 * Resolves workspaces related dependencies for swapping/publishing of the command map.
235 * Workspaces records that have children or (relative) parents which are versionized
236 * but not published with this request, are removed from the command map. Otherwise
237 * this would produce hanging record sets and lost references.
238 *
239 * @return void
240 */
241 protected function resolveWorkspacesSwapDependencies() {
242 $scope = self::SCOPE_WorkspacesSwap;
243 $dependency = $this->getDependencyUtility($scope);
244
245 if (t3lib_div::inList('any,pages', $this->workspacesSwapMode)) {
246 $this->invokeWorkspacesSwapItems('applyWorkspacesSwapBehaviour');
247 }
248 $this->invokeWorkspacesSwapItems('addWorkspacesSwapElements', array($dependency));
249
250 $this->applyWorkspacesDependencies($dependency, $scope);
251 }
252
253 /**
254 * Applies workspaces behaviour for swapping/publishing and takes care of the swapMode.
255 *
256 * @param string $table
257 * @param integer $liveId
258 * @param array $properties
259 * @return void
260 */
261 protected function applyWorkspacesSwapBehaviour($table, $liveId, array $properties) {
262 $extendedCommandMap = array();
263 $elementList = array();
264
265 // Fetch accordant elements if the swapMode is 'any' or 'pages':
266 if ($this->workspacesSwapMode === 'any' || $this->workspacesSwapMode === 'pages' && $table === 'pages') {
267 $elementList = $this->getParent()->findPageElementsForVersionSwap($table, $liveId, $properties['swapWith']);
268 }
269
270 foreach ($elementList as $elementTable => $elementIdArray) {
271 foreach ($elementIdArray as $elementIds) {
272 $extendedCommandMap[$elementTable][$elementIds[0]]['version'] = array_merge($properties, array('swapWith' => $elementIds[1]));
273 }
274 }
275
276 if (count($elementList) > 0) {
277 $this->remove($table, $liveId, 'version');
278 $this->mergeToBottom($extendedCommandMap);
279 }
280 }
281
282 /**
283 * Adds workspaces elements for swapping/publishing.
284 *
285 * @param t3lib_utility_Dependency $dependency
286 * @param string $table
287 * @param integer $liveId
288 * @param array $properties
289 * @return void
290 */
291 protected function addWorkspacesSwapElements(t3lib_utility_Dependency $dependency, $table, $liveId, array $properties) {
292 $elementList = array();
293
294 // Fetch accordant elements if the swapMode is 'any' or 'pages':
295 if ($this->workspacesSwapMode === 'any' || $this->workspacesSwapMode === 'pages' && $table === 'pages') {
296 $elementList = $this->getParent()->findPageElementsForVersionSwap($table, $liveId, $properties['swapWith']);
297 }
298
299 foreach ($elementList as $elementTable => $elementIdArray) {
300 foreach ($elementIdArray as $elementIds) {
301 $dependency->addElement(
302 $elementTable, $elementIds[1],
303 array('liveId' => $elementIds[0], 'properties' => array_merge($properties, array('swapWith' => $elementIds[1])))
304 );
305 }
306 }
307
308 if (count($elementList) === 0) {
309 $dependency->addElement(
310 $table, $properties['swapWith'], array('liveId' => $liveId, 'properties' => $properties)
311 );
312 }
313 }
314
315 /**
316 * Invokes all items for staging with a callback method.
317 *
318 * @param string $callbackMethod
319 * @param array $arguments Optional leading arguments for the callback method
320 * @return void
321 */
322 protected function invokeWorkspacesSetStageItems($callbackMethod, array $arguments = array()) {
323 // Traverses the cmd[] array and fetches the accordant actions:
324 foreach ($this->commandMap as $table => $liveIdCollection) {
325 foreach ($liveIdCollection as $liveIdList => $commandCollection) {
326 foreach ($commandCollection as $command => $properties) {
327 if ($command === 'version' && isset($properties['action']) && $properties['action'] === 'setStage') {
328 if (isset($properties['stageId']) && t3lib_utility_Math::canBeInterpretedAsInteger($properties['stageId'])) {
329 call_user_func_array(
330 array($this, $callbackMethod),
331 array_merge($arguments, array($table, $liveIdList, $properties))
332 );
333 }
334 }
335 }
336 }
337 }
338 }
339
340 /**
341 * Resolves workspaces related dependencies for staging of the command map.
342 * Workspaces records that have children or (relative) parents which are versionized
343 * but not staged with this request, are removed from the command map.
344 *
345 * @return void
346 */
347 protected function resolveWorkspacesSetStageDependencies() {
348 $scope = self::SCOPE_WorkspacesSetStage;
349 $dependency = $this->getDependencyUtility($scope);
350
351 if (t3lib_div::inList('any,pages', $this->workspacesChangeStageMode)) {
352 $this->invokeWorkspacesSetStageItems('applyWorkspacesSetStageBehaviour');
353 }
354 $this->invokeWorkspacesSetStageItems('explodeSetStage');
355 $this->invokeWorkspacesSetStageItems('addWorkspacesSetStageElements', array($dependency));
356
357 $this->applyWorkspacesDependencies($dependency, $scope);
358 }
359
360 /**
361 * Applies workspaces behaviour for staging and takes care of the changeStageMode.
362 *
363 * @param string $table
364 * @param string $liveIdList
365 * @param array $properties
366 * @return void
367 */
368 protected function applyWorkspacesSetStageBehaviour($table, $liveIdList, array $properties) {
369 $extendedCommandMap = array();
370 $liveIds = t3lib_div::trimExplode(',', $liveIdList, TRUE);
371 $elementList = array($table => $liveIds);
372
373 if (t3lib_div::inList('any,pages', $this->workspacesChangeStageMode)) {
374 if (count($liveIds) === 1) {
375 $workspaceRecord = t3lib_BEfunc::getRecord($table, $liveIds[0], 't3ver_wsid');
376 $workspaceId = $workspaceRecord['t3ver_wsid'];
377 } else {
378 $workspaceId = $this->getTceMain()->BE_USER->workspace;
379 }
380
381 if ($table === 'pages') {
382 // Find all elements from the same ws to change stage
383 $this->getParent()->findRealPageIds($liveIds);
384 $this->getParent()->findPageElementsForVersionStageChange($liveIds, $workspaceId, $elementList);
385 } elseif ($this->workspacesChangeStageMode === 'any') {
386 // Find page to change stage:
387 $pageIdList = array();
388 $this->getParent()->findPageIdsForVersionStateChange($table, $liveIds, $workspaceId, $pageIdList, $elementList);
389 // Find other elements from the same ws to change stage:
390 $this->getParent()->findPageElementsForVersionStageChange($pageIdList, $workspaceId, $elementList);
391 }
392 }
393
394 foreach ($elementList as $elementTable => $elementIds) {
395 foreach($elementIds as $elementId) {
396 $extendedCommandMap[$elementTable][$elementId]['version'] = $properties;
397 }
398 }
399
400 $this->remove($table, $liveIdList, 'version');
401 $this->mergeToBottom($extendedCommandMap);
402 }
403
404 /**
405 * Adds workspaces elements for staging.
406 *
407 * @param t3lib_utility_Dependency $dependency
408 * @param string $table
409 * @param string $liveIdList
410 * @param array $properties
411 * @return void
412 */
413 protected function addWorkspacesSetStageElements(t3lib_utility_Dependency $dependency, $table, $liveIdList, array $properties) {
414 $dependency->addElement(
415 $table, $liveIdList,
416 array('properties' => $properties)
417 );
418 }
419
420 /**
421 * Resolves workspaces related dependencies for clearing/flushing of the command map.
422 * Workspaces records that have children or (relative) parents which are versionized
423 * but not cleared/flushed with this request, are removed from the command map.
424 *
425 * @return void
426 */
427 protected function resolveWorkspacesClearDependencies() {
428 $scope = self::SCOPE_WorkspacesClear;
429 $dependency = $this->getDependencyUtility($scope);
430
431 // Traverses the cmd[] array and fetches the accordant actions:
432 foreach ($this->commandMap as $table => $liveIdCollection) {
433 foreach ($liveIdCollection as $liveId => $commandCollection) {
434 foreach ($commandCollection as $command => $properties) {
435 if ($command === 'version' && isset($properties['action']) && ($properties['action'] === 'clearWSID' || $properties['action'] === 'flush')) {
436 $dependency->addElement($table, $liveId, array('properties' => $properties));
437 }
438 }
439 }
440 }
441
442 $this->applyWorkspacesDependencies($dependency, $scope);
443 }
444
445 /**
446 * Explodes id-lists in the command map for staging actions.
447 *
448 * @throws RuntimeException
449 * @param string $table
450 * @param string $liveIdList
451 * @param array $properties
452 * @return void
453 */
454 protected function explodeSetStage($table, $liveIdList, array $properties) {
455 $extractedCommandMap = array();
456 $liveIds = t3lib_div::trimExplode(',', $liveIdList, TRUE);
457
458 if (count($liveIds) > 1) {
459 foreach ($liveIds as $liveId) {
460 if (isset($this->commandMap[$table][$liveId]['version'])) {
461 throw new RuntimeException('Command map for [' . $table . '][' . $liveId . '][version] was already set.', 1289391048);
462 }
463
464 $extractedCommandMap[$table][$liveId]['version'] = $properties;
465 }
466
467 $this->remove($table, $liveIdList, 'version');
468 $this->mergeToBottom($extractedCommandMap);
469 }
470 }
471
472 /**
473 * Applies the workspaces dependencies and removes incomplete structures or automatically
474 * completes them, depending on the options.workspaces.considerReferences setting
475 *
476 * @param t3lib_utility_Dependency $dependency
477 * @param string $scope
478 * @return void
479 */
480 protected function applyWorkspacesDependencies(t3lib_utility_Dependency $dependency, $scope) {
481 $transformDependentElementsToUseLiveId = $this->getScopeData($scope, self::KEY_TransformDependentElementsToUseLiveId);
482
483 $elementsToBeVersionized = $dependency->getElements();
484
485 // Use the uid of the live record instead of the workspace record:
486 if ($transformDependentElementsToUseLiveId) {
487 $elementsToBeVersionized = $this->transformDependentElementsToUseLiveId($elementsToBeVersionized);
488 }
489
490 $outerMostParents = $dependency->getOuterMostParents();
491 /** @var $outerMostParent t3lib_utility_Dependency_Element */
492 foreach ($outerMostParents as $outerMostParent) {
493 $dependentElements = $dependency->getNestedElements($outerMostParent);
494 if ($transformDependentElementsToUseLiveId) {
495 $dependentElements = $this->transformDependentElementsToUseLiveId($dependentElements);
496 }
497
498 // Gets the difference (intersection) between elements that were submitted by the user
499 // and the evaluation of all dependent records that should be used for this action instead:
500 $intersectingElements = array_intersect_key($dependentElements, $elementsToBeVersionized);
501
502 if (count($intersectingElements) > 0) {
503 // If at least one element intersects but not all, throw away all elements of the depdendent structure:
504 if (count($intersectingElements) !== count($dependentElements) && $this->workspacesConsiderReferences === FALSE) {
505 $this->purgeWithErrorMessage($intersectingElements, $scope);
506 // If everything is fine or references shall be considered automatically:
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 t3lib_utility_Dependency_Element */
523 foreach ($elements as $element) {
524 $table = $element->getTable();
525 $id = $this->processCallback(
526 $this->getScopeData($scope, self::KEY_PurgeWithErrorMessageGetIdCallback),
527 array($element)
528 );
529
530 $this->remove($table, $id, 'version');
531 $this->getTceMain()->log(
532 $table, $id,
533 5, 0, 1,
534 $this->getScopeData($scope, self::KEY_ScopeErrorMessage),
535 $this->getScopeData($scope, self::KEY_ScopeErrorCode),
536 array(
537 t3lib_BEfunc::getRecordTitle($table, t3lib_BEfunc::getRecord($table, $id)),
538 $table, $id
539 )
540 );
541 }
542 }
543
544 /**
545 * Updates the command map accordant to valid structures and takes care of the correct order.
546 *
547 * @param t3lib_utility_Dependency_Element $intersectingElement
548 * @param array $elements
549 * @param string $scope
550 * @return void
551 */
552 protected function update(t3lib_utility_Dependency_Element $intersectingElement, array $elements, $scope) {
553 $orderedCommandMap = array();
554
555 $commonProperties = array();
556
557 if ($this->getScopeData($scope, self::KEY_GetCommonPropertiesCallback)) {
558 $commonProperties = $this->processCallback(
559 $this->getScopeData($scope, self::KEY_GetCommonPropertiesCallback),
560 array($intersectingElement)
561 );
562 }
563
564 /** @var $element t3lib_utility_Dependency_Element */
565 foreach ($elements as $element) {
566 $table = $element->getTable();
567 $id = $this->processCallback(
568 $this->getScopeData($scope, self::KEY_UpdateGetIdCallback),
569 array($element)
570 );
571
572 $this->remove($table, $id, 'version');
573 $orderedCommandMap[$table][$id]['version'] = $commonProperties;
574
575 if ($this->getScopeData($scope, self::KEY_GetElementPropertiesCallback)) {
576 $orderedCommandMap[$table][$id]['version'] = array_merge(
577 $commonProperties,
578 $this->processCallback(
579 $this->getScopeData($scope, self::KEY_GetElementPropertiesCallback),
580 array($element)
581 )
582 );
583 }
584 }
585
586 // Ensure that ordered command map is on top of the command map:
587 $this->mergeToTop($orderedCommandMap);
588 }
589
590 /**
591 * Merges command map elements to the top of the current command map..
592 *
593 * @param array $commandMap
594 * @return void
595 */
596 protected function mergeToTop(array $commandMap) {
597 $this->commandMap = t3lib_div::array_merge_recursive_overrule($commandMap, $this->commandMap);
598 }
599
600 /**
601 * Merges command map elements to the bottom of the current command map.
602 *
603 * @param array $commandMap
604 * @return void
605 */
606 protected function mergeToBottom(array $commandMap) {
607 $this->commandMap = t3lib_div::array_merge_recursive_overrule($this->commandMap, $commandMap);
608 }
609
610 /**
611 * Removes an element from the command map.
612 *
613 * @param string $table
614 * @param string $id
615 * @param string $command (optional)
616 * @return void
617 */
618 protected function remove($table, $id, $command = NULL) {
619 if (is_string($command)) {
620 unset($this->commandMap[$table][$id][$command]);
621 } else {
622 unset($this->commandMap[$table][$id]);
623 }
624 }
625
626 /**
627 * Callback to get the liveId of an dependent element.
628 *
629 * @param t3lib_utility_Dependency_Element $element
630 * @return integer
631 */
632 protected function getElementLiveIdCallback(t3lib_utility_Dependency_Element $element) {
633 return $element->getDataValue('liveId');
634 }
635
636 /**
637 * Callback to get the real id of an dependent element.
638 *
639 * @param t3lib_utility_Dependency_Element $element
640 * @return integer
641 */
642 protected function getElementIdCallback(t3lib_utility_Dependency_Element $element) {
643 return $element->getId();
644 }
645
646 /**
647 * Callback to get the specific properties of a dependent element for swapping/publishing.
648 *
649 * @param t3lib_utility_Dependency_Element $element
650 * @return array
651 */
652 protected function getElementSwapPropertiesCallback(t3lib_utility_Dependency_Element $element) {
653 return array(
654 'swapWith' => $element->getId(),
655 );
656 }
657
658 /**
659 * Callback to get common properties of dependent elements for clearing.
660 *
661 * @param t3lib_utility_Dependency_Element $element
662 * @return array
663 */
664 protected function getCommonClearPropertiesCallback(t3lib_utility_Dependency_Element $element) {
665 $commonSwapProperties = array();
666
667 $elementProperties = $element->getDataValue('properties');
668 if (isset($elementProperties['action'])) {
669 $commonSwapProperties['action'] = $elementProperties['action'];
670 }
671
672 return $commonSwapProperties;
673 }
674
675 /**
676 * Callback to get common properties of dependent elements for swapping/publishing.
677 *
678 * @param t3lib_utility_Dependency_Element $element
679 * @return array
680 */
681 protected function getCommonSwapPropertiesCallback(t3lib_utility_Dependency_Element $element) {
682 $commonSwapProperties = array();
683
684 $elementProperties = $element->getDataValue('properties');
685 if (isset($elementProperties['action'])) {
686 $commonSwapProperties['action'] = $elementProperties['action'];
687 }
688 if (isset($elementProperties['swapIntoWS'])) {
689 $commonSwapProperties['swapIntoWS'] = $elementProperties['swapIntoWS'];
690 }
691
692 return $commonSwapProperties;
693 }
694
695 /**
696 * Callback to get the specific properties of a dependent element for staging.
697 *
698 * @param t3lib_utility_Dependency_Element $element
699 * @return array
700 */
701 protected function getElementSetStagePropertiesCallback(t3lib_utility_Dependency_Element $element) {
702 return $this->getCommonSetStagePropertiesCallback($element);
703 }
704
705 /**
706 * Callback to get common properties of dependent elements for staging.
707 *
708 * @param t3lib_utility_Dependency_Element $element
709 * @return array
710 */
711 protected function getCommonSetStagePropertiesCallback(t3lib_utility_Dependency_Element $element) {
712 $commonSetStageProperties = array();
713
714 $elementProperties = $element->getDataValue('properties');
715 if (isset($elementProperties['stageId'])) {
716 $commonSetStageProperties['stageId'] = $elementProperties['stageId'];
717 }
718 if (isset($elementProperties['comment'])) {
719 $commonSetStageProperties['comment'] = $elementProperties['comment'];
720 }
721 if (isset($elementProperties['action'])) {
722 $commonSetStageProperties['action'] = $elementProperties['action'];
723 }
724 if (isset($elementProperties['notificationAlternativeRecipients'])) {
725 $commonSetStageProperties['notificationAlternativeRecipients'] = $elementProperties['notificationAlternativeRecipients'];
726 }
727
728 return $commonSetStageProperties;
729 }
730
731 /**
732 * Gets an instance of the depency resolver utility.
733 *
734 * @param string $scope Scope identifier
735 * @return t3lib_utility_Dependency
736 */
737 protected function getDependencyUtility($scope) {
738
739 /** @var $dependency t3lib_utility_Dependency */
740 $dependency = t3lib_div::makeInstance('t3lib_utility_Dependency');
741 $dependency->setOuterMostParentsRequireReferences(TRUE);
742
743 if ($this->getScopeData($scope, self::KEY_ElementConstructCallback)) {
744 $dependency->setEventCallback(
745 t3lib_utility_Dependency_Element::EVENT_Construct,
746 $this->getDependencyCallback($this->getScopeData($scope, self::KEY_ElementConstructCallback))
747 );
748 }
749 if ($this->getScopeData($scope, self::KEY_ElementCreateChildReferenceCallback)) {
750 $dependency->setEventCallback(
751 t3lib_utility_Dependency_Element::EVENT_CreateChildReference,
752 $this->getDependencyCallback($this->getScopeData($scope, self::KEY_ElementCreateChildReferenceCallback))
753 );
754 }
755 if ($this->getScopeData($scope, self::KEY_ElementCreateParentReferenceCallback)) {
756 $dependency->setEventCallback(
757 t3lib_utility_Dependency_Element::EVENT_CreateParentReference,
758 $this->getDependencyCallback($this->getScopeData($scope, self::KEY_ElementCreateParentReferenceCallback))
759 );
760 }
761
762 return $dependency;
763 }
764
765 /**
766 * Callback to determine whether a new child reference shall be considered in the dependency resolver utility.
767 *
768 * @param array $callerArguments
769 * @param array $targetArgument
770 * @param t3lib_utility_Dependency_Element $caller
771 * @param string $eventName
772 * @return string Skip response (if required)
773 */
774 public function createNewDependentElementChildReferenceCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller, $eventName) {
775 /** @var $reference t3lib_utility_Dependency_Reference */
776 $reference = $callerArguments['reference'];
777
778 $fieldCOnfiguration = t3lib_BEfunc::getTcaFieldConfiguration($caller->getTable(), $reference->getField());
779
780 if (!$fieldCOnfiguration || !t3lib_div::inList('field,list', $this->getTceMain()->getInlineFieldType($fieldCOnfiguration))) {
781 return t3lib_utility_Dependency_Element::RESPONSE_Skip;
782 }
783 }
784
785 /**
786 * Callback to determine whether a new parent reference shall be considered in the dependency resolver utility.
787 *
788 * @param array $callerArguments
789 * @param array $targetArgument
790 * @param t3lib_utility_Dependency_Element $caller
791 * @param string $eventName
792 * @return string Skip response (if required)
793 */
794 public function createNewDependentElementParentReferenceCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller, $eventName) {
795 /** @var $reference t3lib_utility_Dependency_Reference */
796 $reference = $callerArguments['reference'];
797
798 $fieldCOnfiguration = t3lib_BEfunc::getTcaFieldConfiguration($reference->getElement()->getTable(), $reference->getField());
799
800 if (!$fieldCOnfiguration || !t3lib_div::inList('field,list', $this->getTceMain()->getInlineFieldType($fieldCOnfiguration))) {
801 return t3lib_utility_Dependency_Element::RESPONSE_Skip;
802 }
803 }
804
805 /**
806 * Callback to determine whether a new child reference shall be considered in the dependency resolver utility.
807 * Only elements that are a delete placeholder are considered.
808 *
809 * @param array $callerArguments
810 * @param array $targetArgument
811 * @param t3lib_utility_Dependency_Element $caller
812 * @param string $eventName
813 * @return string Skip response (if required)
814 */
815 public function createClearDependentElementChildReferenceCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller, $eventName) {
816 $response = $this->createNewDependentElementChildReferenceCallback($callerArguments, $targetArgument, $caller, $eventName);
817
818 if (empty($response)) {
819 /** @var $reference t3lib_utility_Dependency_Reference */
820 $reference = $callerArguments['reference'];
821
822 $record = $reference->getElement()->getRecord();
823 if ($record['t3ver_state'] != 2) {
824 $response = t3lib_utility_Dependency_Element::RESPONSE_Skip;
825 }
826 }
827
828 return $response;
829 }
830
831 /**
832 * Callback to determine whether a new parent reference shall be considered in the dependency resolver utility.
833 * Only elements that are a delete placeholder are considered.
834 *
835 * @param array $callerArguments
836 * @param array $targetArgument
837 * @param t3lib_utility_Dependency_Element $caller
838 * @param string $eventName
839 * @return string Skip response (if required)
840 */
841 public function createClearDependentElementParentReferenceCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller, $eventName) {
842 $response = $this->createNewDependentElementParentReferenceCallback($callerArguments, $targetArgument, $caller, $eventName);
843
844 if (empty($response)) {
845 /** @var $reference t3lib_utility_Dependency_Reference */
846 $reference = $callerArguments['reference'];
847
848 $record = $reference->getElement()->getRecord();
849 if ($record['t3ver_state'] != 2) {
850 $response = t3lib_utility_Dependency_Element::RESPONSE_Skip;
851 }
852 }
853
854 return $response;
855 }
856
857 /**
858 * Callback to add additional data to new elements created in the dependency resolver utility.
859 *
860 * @param t3lib_utility_Dependency_Element $caller
861 * @param array $callerArguments
862 * @param array $targetArgument
863 * @return void
864 */
865 public function createNewDependentElementCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller) {
866 if ($caller->hasDataValue('liveId') === FALSE) {
867 $liveId = t3lib_BEfunc::getLiveVersionIdOfRecord($caller->getTable(), $caller->getId());
868 if (is_null($liveId) === FALSE) {
869 $caller->setDataValue('liveId', $liveId);
870 }
871 }
872 }
873
874 /**
875 * Transforms dependent elements to use the liveId as array key.
876 *
877 * @param array $elements Depedent elements, each of type t3lib_utility_Dependency_Element
878 * @return array
879 */
880 protected function transformDependentElementsToUseLiveId(array $elements) {
881 $transformedElements = array();
882
883 /** @var $element t3lib_utility_Dependency_Element */
884 foreach ($elements as $element) {
885 $elementName = t3lib_utility_Dependency_Element::getIdentifier(
886 $element->getTable(), $element->getDataValue('liveId')
887 );
888 $transformedElements[$elementName] = $element;
889 }
890
891 return $transformedElements;
892 }
893
894 /**
895 * Constructs the scope settings.
896 * Currently the scopes for swapping/publishing and staging are available.
897 *
898 * @return void
899 */
900 protected function constructScopes() {
901 $this->scopes = array(
902 // settings for publishing and swapping:
903 self::SCOPE_WorkspacesSwap => array(
904 // error message and error code
905 self::KEY_ScopeErrorMessage => 'Record "%s" (%s:%s) cannot be swapped or published independently, because it is related to other new or modified records.',
906 self::KEY_ScopeErrorCode => 1288283630,
907 // callback functons used to modify the commandMap
908 // + element properties are specific for each element
909 // + common properties are the same for all elements
910 self::KEY_GetElementPropertiesCallback => 'getElementSwapPropertiesCallback',
911 self::KEY_GetCommonPropertiesCallback => 'getCommonSwapPropertiesCallback',
912 // callback function used, when a new element to be checked is added
913 self::KEY_ElementConstructCallback => 'createNewDependentElementCallback',
914 // callback function used to determine whether an element is a valid child or parent reference (e.g. IRRE)
915 self::KEY_ElementCreateChildReferenceCallback => 'createNewDependentElementChildReferenceCallback',
916 self::KEY_ElementCreateParentReferenceCallback => 'createNewDependentElementParentReferenceCallback',
917 // callback function used to get the correct record uid to be used in the error message
918 self::KEY_PurgeWithErrorMessageGetIdCallback => 'getElementLiveIdCallback',
919 // callback function used to fetch the correct record uid on modifying the commandMap
920 self::KEY_UpdateGetIdCallback => 'getElementLiveIdCallback',
921 // setting whether to use the uid of the live record instead of the workspace record
922 self::KEY_TransformDependentElementsToUseLiveId => TRUE,
923 ),
924 // settings for modifying the stage:
925 self::SCOPE_WorkspacesSetStage => array(
926 // error message and error code
927 self::KEY_ScopeErrorMessage => 'Record "%s" (%s:%s) cannot be sent to another stage independently, because it is related to other new or modified records.',
928 self::KEY_ScopeErrorCode => 1289342524,
929 // callback functons used to modify the commandMap
930 // + element properties are specific for each element
931 // + common properties are the same for all elements
932 self::KEY_GetElementPropertiesCallback => 'getElementSetStagePropertiesCallback',
933 self::KEY_GetCommonPropertiesCallback => 'getCommonSetStagePropertiesCallback',
934 // callback function used, when a new element to be checked is added
935 self::KEY_ElementConstructCallback => NULL,
936 // callback function used to determine whether an element is a valid child or parent reference (e.g. IRRE)
937 self::KEY_ElementCreateChildReferenceCallback => 'createNewDependentElementChildReferenceCallback',
938 self::KEY_ElementCreateParentReferenceCallback => 'createNewDependentElementParentReferenceCallback',
939 // callback function used to get the correct record uid to be used in the error message
940 self::KEY_PurgeWithErrorMessageGetIdCallback => 'getElementIdCallback',
941 // callback function used to fetch the correct record uid on modifying the commandMap
942 self::KEY_UpdateGetIdCallback => 'getElementIdCallback',
943 // setting whether to use the uid of the live record instead of the workspace record
944 self::KEY_TransformDependentElementsToUseLiveId => FALSE,
945 ),
946 // settings for clearing and flushing:
947 self::SCOPE_WorkspacesClear => array(
948 // error message and error code
949 self::KEY_ScopeErrorMessage => 'Record "%s" (%s:%s) cannot be flushed independently, because it is related to other new or modified records.',
950 self::KEY_ScopeErrorCode => 1300467990,
951 // callback functons used to modify the commandMap
952 // + element properties are specific for each element
953 // + common properties are the same for all elements
954 self::KEY_GetElementPropertiesCallback => NULL,
955 self::KEY_GetCommonPropertiesCallback => 'getCommonClearPropertiesCallback',
956 // callback function used, when a new element to be checked is added
957 self::KEY_ElementConstructCallback => NULL,
958 // callback function used to determine whether an element is a valid child or parent reference (e.g. IRRE)
959 self::KEY_ElementCreateChildReferenceCallback => 'createClearDependentElementChildReferenceCallback',
960 self::KEY_ElementCreateParentReferenceCallback => 'createClearDependentElementParentReferenceCallback',
961 // callback function used to get the correct record uid to be used in the error message
962 self::KEY_PurgeWithErrorMessageGetIdCallback => 'getElementIdCallback',
963 // callback function used to fetch the correct record uid on modifying the commandMap
964 self::KEY_UpdateGetIdCallback => 'getElementIdCallback',
965 // setting whether to use the uid of the live record instead of the workspace record
966 self::KEY_TransformDependentElementsToUseLiveId => FALSE,
967 ),
968 );
969 }
970
971 /**
972 * Gets data for a particular scope.
973 *
974 * @throws RuntimeException
975 * @param string $scope Scope identifier
976 * @param string $key
977 * @return string
978 */
979 protected function getScopeData($scope, $key) {
980 if (!isset($this->scopes[$scope])) {
981 throw new RuntimeException('Scope "' . $scope . '" is not defined.', 1289342187);
982 }
983
984 return $this->scopes[$scope][$key];
985 }
986
987 /**
988 * Gets a new callback to be used in the dependency resolver utility.
989 *
990 * @param string $method
991 * @param array $targetArguments
992 * @return t3lib_utility_Dependency_Callback
993 */
994 protected function getDependencyCallback($method, array $targetArguments = array()) {
995 return t3lib_div::makeInstance('t3lib_utility_Dependency_Callback', $this, $method, $targetArguments);
996 }
997
998 /**
999 * Processes a local callback inside this object.
1000 *
1001 * @param string $method
1002 * @param array $callbackArguments
1003 * @return mixed
1004 */
1005 protected function processCallback($method, array $callbackArguments) {
1006 return call_user_func_array(array($this, $method), $callbackArguments);
1007 }
1008 }
1009
1010 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/version/class.tx_version_tcemain_commandmap.php'])) {
1011 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/version/class.tx_version_tcemain_commandmap.php']);
1012 }
1013 ?>