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