[BUGFIX] Removing child records in a workspace does not work
authorOliver Hader <oliver@typo3.org>
Thu, 17 Mar 2011 17:58:19 +0000 (18:58 +0100)
committerOliver Hader <oliver@typo3.org>
Tue, 10 May 2011 12:42:26 +0000 (14:42 +0200)
These parts are not working with IRRE in workspaces:
Removing children in TCEforms
Removing children in the list view
Reverting changes in the workspace module

Change-Id: I91313235e57c193e7103d065c02d2005ef74ab86
Resolves: #25352
Releases: 4.6, 4.5
Reviewed-on: http://review.typo3.org/1195
Reviewed-by: Oliver Hader
Tested-by: Oliver Hader
t3lib/class.t3lib_tcemain.php
t3lib/utility/dependency/class.t3lib_utility_dependency_element.php

index 18cf067..f8eba5f 100644 (file)
@@ -342,6 +342,12 @@ class t3lib_TCEmain {
        protected $checkModifyAccessListHookObjects;
        protected $version_remapMMForVersionSwap_reg;
 
+       /**
+        * The outer most instance of t3lib_TCEmain
+        * (t3lib_TCEmain instantiates itself on versioning and localization)
+        * @var t3lib_TCEmain
+        */
+       protected $outerMostInstance = NULL;
 
        /**
         * Initializing.
@@ -578,6 +584,9 @@ class t3lib_TCEmain {
                        // Keep versionized(!) relations here locally:
                $registerDBList = array();
 
+               $this->registerElementsToBeDeleted();
+               $this->datamap = $this->unsetElementsToBeDeleted($this->datamap);
+
                        // Editing frozen:
                if ($this->BE_USER->workspace !== 0 && $this->BE_USER->workspaceRec['freeze']) {
                        $this->newlog('All editing in this workspace has been frozen!', 1);
@@ -944,6 +953,10 @@ class t3lib_TCEmain {
                                $hookObj->processDatamap_afterAllOperations($this);
                        }
                }
+
+               if ($this->isOuterMostInstance()) {
+                       $this->resetElementsToBeDeleted();
+               }
        }
 
        /**
@@ -2941,14 +2954,22 @@ class t3lib_TCEmain {
         * This function is used to create new versions of a record.
         * NOTICE: DOES NOT CHECK PERMISSIONS to create! And since page permissions are just passed through and not changed to the user who executes the copy we cannot enforce permissions without getting an incomplete copy - unless we change permissions of course.
         *
-        * @param       string          Element table
-        * @param       integer         Element UID
-        * @param       integer         Element PID (real PID, not checked)
-        * @param       array           Override array - must NOT contain any fields not in the table!
+        * @param       string          $table Element table
+        * @param       integer         $uid Element UID
+        * @param       integer         $pid Element PID (real PID, not checked)
+        * @param       array           $overrideArray Override array - must NOT contain any fields not in the table!
+        * @return      array           $workspaceOptions Options to be forwarded if actions happen on a workspace currently
         * @return      integer         Returns the new ID of the record (if applicable)
         */
-       function copyRecord_raw($table, $uid, $pid, $overrideArray = array()) {
+       function copyRecord_raw($table, $uid, $pid, $overrideArray = array(), array $workspaceOptions = array()) {
                $uid = intval($uid);
+
+               // Stop any actions if the record is marked to be deleted:
+               // (this can occur if IRRE elements are versionized and child elements are removed)
+               if ($this->isElementToBeDeleted($table, $uid)) {
+                       return NULL;
+               }
+
                        // Only copy if the table is defined in TCA, a uid is given and the record wasn't copied before:
                if ($GLOBALS['TCA'][$table] && $uid && !$this->isRecordCopied($table, $uid)) {
                        t3lib_div::loadTCA($table);
@@ -2972,7 +2993,7 @@ class t3lib_TCEmain {
                                                        $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
                                                        if (is_array($conf)) {
                                                                        // Processing based on the TCA config field type (files, references, flexforms...)
-                                                               $value = $this->copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $pid);
+                                                               $value = $this->copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $pid, 0, $workspaceOptions);
                                                        }
 
                                                                // Add value to array.
@@ -3062,19 +3083,19 @@ class t3lib_TCEmain {
        /**
         * Processing/Preparing content for copyRecord() function
         *
-        * @param       string          Table name
-        * @param       integer         Record uid
-        * @param       string          Field name being processed
-        * @param       string          Input value to be processed.
-        * @param       array           Record array
-        * @param       array           TCA field configuration
-        * @param       integer         Real page id (pid) the record is copied to
-        * @param       integer         Language ID (from sys_language table) used in the duplicated record
-        * @return      mixed           Processed value. Normally a string/integer, but can be an array for flexforms!
+        * @param       string          $table Table name
+        * @param       integer         $uid Record uid
+        * @param       string          $field Field name being processed
+        * @param       string          $value Input value to be processed.
+        * @param       array           $row Record array
+        * @param       array           $conf TCA field configuration
+        * @param       integer         $realDestPid Real page id (pid) the record is copied to
+        * @param       integer         $language Language ID (from sys_language table) used in the duplicated record
+        * @return      array           $workspaceOptions Options to be forwarded if actions happen on a workspace currently
         * @access private
         * @see copyRecord()
         */
-       function copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $realDestPid, $language = 0) {
+       function copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $realDestPid, $language = 0, array $workspaceOptions = array()) {
 
                        // Process references and files, currently that means only the files, prepending absolute paths (so the TCEmain engine will detect the file as new and one that should be made into a copy)
                $value = $this->copyRecord_procFilesRefs($conf, $uid, $value);
@@ -3142,13 +3163,17 @@ class t3lib_TCEmain {
                                                        );
                                                                // If workspace version does not exist, create a new one:
                                                        if ($workspaceVersion === FALSE) {
-                                                               $newId = $this->versionizeRecord($v['table'], $v['id'], 'Auto-created for WS #' . $this->BE_USER->workspace);
+                                                               $newId = $this->versionizeRecord(
+                                                                       $v['table'], $v['id'],
+                                                                       (isset($workspaceOptions['label']) ? $workspaceOptions['label'] : 'Auto-created for WS #' . $this->BE_USER->workspace),
+                                                                       (isset($workspaceOptions['delete']) ? $workspaceOptions['delete'] : FALSE)
+                                                               );
                                                                // If workspace version already exists, use it:
                                                        } else {
                                                                $newId = $workspaceVersion['uid'];
                                                        }
                                                } else {
-                                                       $newId = $this->copyRecord_raw($v['table'], $v['id'], $realDestPid);
+                                                       $newId = $this->copyRecord_raw($v['table'], $v['id'], $realDestPid, array(), $workspaceOptions);
                                                }
                                        }
 
@@ -4466,6 +4491,12 @@ class t3lib_TCEmain {
        function versionizeRecord($table, $id, $label, $delete = FALSE, $versionizeTree = -1) {
                $id = intval($id);
 
+               // Stop any actions if the record is marked to be deleted:
+               // (this can occur if IRRE elements are versionized and child elements are removed)
+               if ($this->isElementToBeDeleted($table, $id)) {
+                       return NULL;
+               }
+
                if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $id > 0) {
                        if ($this->doesRecordExist($table, $id, 'show')) {
                                if ($this->BE_USER->workspaceVersioningTypeAccess($versionizeTree)) {
@@ -4520,7 +4551,16 @@ class t3lib_TCEmain {
                                                                        if ($workspaceCheck) {
 
                                                                                        // Create raw-copy and return result:
-                                                                               return $this->copyRecord_raw($table, $id, -1, $overrideArray);
+                                                                                       // The information of the label to be used for the workspace record
+                                                                                       // as well as the information whether the record shall be removed
+                                                                                       // must be forwarded (creating remove placeholders on a workspace are
+                                                                                       // done by copying the record and override several fields).
+                                                                               $workspaceOptions = array();
+                                                                               if ($delete) {
+                                                                                       $workspaceOptions['delete'] = $delete;
+                                                                                       $workspaceOptions['label'] = $label;
+                                                                               }
+                                                                               return $this->copyRecord_raw($table, $id, -1, $overrideArray, $workspaceOptions);
                                                                        } else {
                                                                                $this->newlog('Record "' . $table . ':' . $id . '" you wanted to versionize was already a version in the workspace (wsid=' . $this->BE_USER->workspace . ')!', 1);
                                                                        }
@@ -7057,6 +7097,140 @@ class t3lib_TCEmain {
                        }
                }
        }
+
+       /**
+        * Gets the outer most instance of t3lib_TCEmain.
+        * Since t3lib_TCEmain can create nested objects of itself,
+        * this method helps to determine the first (= outer most) one.
+        *
+        * @return t3lib_TCEmain
+        */
+       protected function getOuterMostInstance() {
+               if (!isset($this->outerMostInstance)) {
+                       $stack = array_reverse(debug_backtrace(TRUE));
+
+                       foreach ($stack as $stackItem) {
+                               if (isset($stackItem['object']) && $stackItem['object'] instanceof t3lib_TCEmain) {
+                                       $this->outerMostInstance = $stackItem['object'];
+                                       break;
+                               }
+                       }
+               }
+
+               return $this->outerMostInstance;
+       }
+
+       /**
+        * Determines whether the this object is the outer most instance of t3lib_TCEmain.
+        * Since t3lib_TCEmain can create nested objects of itself,
+        * this method helps to determine the first (= outer most) one.
+        *
+        * @return boolean
+        */
+       public function isOuterMostInstance() {
+               return ($this->getOuterMostInstance() === $this);
+       }
+
+       /**
+        * Gets an instance of the memory cache.
+        *
+        * @return t3lib_cache_frontend_VariableFrontend
+        */
+       protected function getMemoryCache() {
+               t3lib_cache::initializeCachingFramework();
+               $cacheIdentifier = 't3lib_TCEmain';
+
+               if ($GLOBALS['typo3CacheManager']->hasCache($cacheIdentifier) === FALSE) {
+                       $GLOBALS['typo3CacheFactory']->create(
+                               $cacheIdentifier,
+                               't3lib_cache_frontend_VariableFrontend',
+                               't3lib_cache_backend_TransientMemoryBackend'
+                       );
+               }
+
+               return $GLOBALS['typo3CacheManager']->getCache($cacheIdentifier);
+       }
+
+       /**
+        * Determines whether an element was registered to be deleted in the registry.
+        *
+        * @param string $table Name of the table
+        * @param integer $id Uid of the record
+        * @return boolean
+        * @see registerElementsToBeDeleted
+        * @see resetElementsToBeDeleted
+        * @see copyRecord_raw
+        * @see versionizeRecord
+        */
+       protected function isElementToBeDeleted($table, $id) {
+               $elementsToBeDeleted = (array) $this->getMemoryCache()->get('elementsToBeDeleted');
+               return (isset($elementsToBeDeleted[$table][$id]));
+       }
+
+       /**
+        * Registers elements to be deleted in the registry.
+        *
+        * @return void
+        * @see process_datamap
+        */
+       protected function registerElementsToBeDeleted() {
+               $elementsToBeDeleted = (array) $this->getMemoryCache()->get('elementsToBeDeleted');
+               $this->getMemoryCache()->set(
+                       'elementsToBeDeleted',
+                       array_merge($elementsToBeDeleted, $this->getCommandMapElements('delete'))
+               );
+       }
+
+       /**
+        * Resets the elements to be deleted in the registry.
+        *
+        * @return void
+        * @see process_datamap
+        */
+       protected function resetElementsToBeDeleted() {
+               $this->getMemoryCache()->remove('elementsToBeDeleted');
+       }
+
+       /**
+        * Unsets elements (e.g. of the data map) that shall be deleted.
+        * This avoids to modify records that will be deleted later on.
+        *
+        * @param array $elements Elements to be modified
+        * @return array
+        */
+       protected function unsetElementsToBeDeleted(array $elements) {
+               $elements = t3lib_div::arrayDiffAssocRecursive($elements, $this->getCommandMapElements('delete'));
+
+               foreach ($elements as $key => $value) {
+                       if (empty($value)) {
+                               unset($elements[$key]);
+                       }
+               }
+
+               return $elements;
+       }
+
+       /**
+        * Gets elements of the command map that match a particular command.
+        *
+        * @param string $needle The command to be matched
+        * @return array
+        */
+       protected function getCommandMapElements($needle) {
+               $elements = array();
+
+               foreach ($this->cmdmap as $tableName => $idArray) {
+                       foreach ($idArray as $id => $commandArray) {
+                               foreach ($commandArray as $command => $value) {
+                                       if ($value && $command == $needle) {
+                                               $elements[$tableName][$id] = TRUE;
+                                       }
+                               }
+                       }
+               }
+
+               return $elements;
+       }
 }
 
 
index 4fe4cff..1ac9d7a 100644 (file)
@@ -52,6 +52,11 @@ class t3lib_utility_Dependency_Element {
        protected $data;
 
        /**
+        * @var array
+        */
+       protected $record;
+
+       /**
         * @var t3lib_utility_Dependency
         */
        protected $dependency;
@@ -313,5 +318,25 @@ class t3lib_utility_Dependency_Element {
        public static function getIdentifier($table, $id) {
                return $table . ':' . $id;
        }
+
+       /**
+        * Gets the database record of this element.
+        *
+        * @return array
+        */
+       public function getRecord() {
+               if (!isset($this->record)) {
+                       $this->record = array();
+                       $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                               '*',
+                               $this->getTable(),
+                               'uid=' . $this->getId()
+                       );
+                       if (is_array($rows)) {
+                               $this->record = $rows[0];
+                       }
+               }
+               return $this->record;
+       }
 }
 ?>
\ No newline at end of file