[BUGFIX] Fix message "Translate to"
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / DataHandling / DataHandler.php
index 06e1e2b..f1db6f3 100644 (file)
@@ -670,6 +670,14 @@ class DataHandler
     protected static $recordsToClearCacheFor = array();
 
     /**
+     * Internal cache for pids of records which were deleted. It's not possible
+     * to retrieve the parent folder/page at a later stage
+     *
+     * @var array
+     */
+    protected static $recordPidsForDeletedRecords = array();
+
+    /**
      * Database layer. Identical to $GLOBALS['TYPO3_DB']
      *
      * @var DatabaseConnection
@@ -921,7 +929,7 @@ class DataHandler
                 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['checkModifyAccessList'] as $classData) {
                     $hookObject = GeneralUtility::getUserObj($classData);
                     if (!$hookObject instanceof DataHandlerCheckModifyAccessListHookInterface) {
-                        throw new \UnexpectedValueException('$hookObject must implement interface \\TYPO3\\CMS\\Core\\DataHandling\\DataHandlerCheckModifyAccessListHookInterface', 1251892472);
+                        throw new \UnexpectedValueException($classData . ' must implement interface ' . DataHandlerCheckModifyAccessListHookInterface::class, 1251892472);
                     }
                     $this->checkModifyAccessListHookObjects[] = $hookObject;
                 }
@@ -1167,6 +1175,7 @@ class DataHandler
                                 /** @var $tce DataHandler */
                                 $tce = GeneralUtility::makeInstance(__CLASS__);
                                 $tce->stripslashes_values = false;
+                                $tce->enableLogging = $this->enableLogging;
                                 // Setting up command for creating a new version of the record:
                                 $cmd = array();
                                 $cmd[$table][$id]['version'] = array(
@@ -1547,7 +1556,7 @@ class DataHandler
             $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']] = serialize($originalLanguage_diffStorage);
         }
         // Checking for RTE-transformations of fields:
-        $types_fieldConfig = BackendUtility::getTCAtypes($table, $currentRecord);
+        $types_fieldConfig = BackendUtility::getTCAtypes($table, $this->checkValue_currentRecord);
         $theTypeString = null;
         if (is_array($types_fieldConfig)) {
             foreach ($types_fieldConfig as $vconf) {
@@ -1559,12 +1568,12 @@ class DataHandler
                 // Look for transformation flag:
                 if ((string)$incomingFieldArray['_TRANSFORM_' . $vconf['field']] === 'RTE') {
                     if ($theTypeString === null) {
-                        $theTypeString = BackendUtility::getTCAtypeValue($table, $currentRecord);
+                        $theTypeString = BackendUtility::getTCAtypeValue($table, $this->checkValue_currentRecord);
                     }
                     $RTEsetup = $this->BE_USER->getTSConfig('RTE', BackendUtility::getPagesTSconfig($tscPID));
                     $thisConfig = BackendUtility::RTEsetup($RTEsetup['properties'], $table, $vconf['field'], $theTypeString);
                     $fieldArray[$vconf['field']] = $this->transformRichtextContentToDatabase(
-                        $fieldArray[$vconf['field']], $table, $vconf['field'], $vconf['spec'], $thisConfig, $currentRecord['pid']
+                        $fieldArray[$vconf['field']], $table, $vconf['field'], $vconf['spec'], $thisConfig, $this->checkValue_currentRecord['pid']
                     );
                 }
             }
@@ -1909,16 +1918,31 @@ class DataHandler
      */
     protected function checkValueForCheck($res, $value, $tcaFieldConf, $table, $id, $realPid, $field)
     {
-        $itemC = count($tcaFieldConf['items']);
+        $items = $tcaFieldConf['items'];
+        if ($tcaFieldConf['itemsProcFunc']) {
+            /** @var ItemProcessingService $processingService */
+            $processingService = GeneralUtility::makeInstance(ItemProcessingService::class);
+            $items = $processingService->getProcessingItems($table, $realPid, $field,
+                $this->checkValue_currentRecord,
+                $tcaFieldConf, $tcaFieldConf['items']);
+        }
+
+        $itemC = count($items);
         if (!$itemC) {
             $itemC = 1;
         }
         $maxV = pow(2, $itemC) - 1;
         if ($value < 0) {
+            // @todo: throw LogicException here? Negative values for checkbox items do not make sense and indicate a coding error.
             $value = 0;
         }
         if ($value > $maxV) {
-            $value = $maxV;
+            // @todo: This case is pretty ugly: If there is an itemsProcFunc registered, and if it returns a dynamic,
+            // @todo: changing list of items, then it may happen that a value is transformed and vanished checkboxes
+            // @todo: are permanently removed from the value.
+            // @todo: Suggestion: Throw an exception instead? Maybe a specific, catchable exception that generates a
+            // @todo: error message to the user - dynamic item sets via itemProcFunc on check would be a bad idea anyway.
+            $value = $value & $maxV;
         }
         if ($field && $realPid >= 0 && $value > 0 && !empty($tcaFieldConf['eval'])) {
             $evalCodesArray = GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
@@ -2082,31 +2106,30 @@ class DataHandler
         if ($tcaFieldConf['type'] == 'group') {
             switch ($tcaFieldConf['internal_type']) {
                 case 'file_reference':
-
                 case 'file':
                     $valueArray = $this->checkValue_group_select_file($valueArray, $tcaFieldConf, $curValue, $uploadedFiles, $status, $table, $id, $recFID);
                     break;
-                case 'db':
-                    $valueArray = $this->checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, 'group', $table, $field);
-                    break;
             }
         }
         // For select types which has a foreign table attached:
         $unsetResult = false;
-        if ($tcaFieldConf['type'] == 'select' && $tcaFieldConf['foreign_table']) {
+        if (
+            $tcaFieldConf['type'] === 'group' && $tcaFieldConf['internal_type'] === 'db'
+            || $tcaFieldConf['type'] === 'select' && ($tcaFieldConf['foreign_table'] || isset($tcaFieldConf['special']) && $tcaFieldConf['special'] === 'languages')
+        ) {
             // check, if there is a NEW... id in the value, that should be substituted later
             if (strpos($value, 'NEW') !== false) {
                 $this->remapStackRecords[$table][$id] = array('remapStackIndex' => count($this->remapStack));
                 $this->addNewValuesToRemapStackChildIds($valueArray);
                 $this->remapStack[] = array(
                     'func' => 'checkValue_group_select_processDBdata',
-                    'args' => array($valueArray, $tcaFieldConf, $id, $status, 'select', $table, $field),
+                    'args' => array($valueArray, $tcaFieldConf, $id, $status, $tcaFieldConf['type'], $table, $field),
                     'pos' => array('valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 5),
                     'field' => $field
                 );
                 $unsetResult = true;
             } else {
-                $valueArray = $this->checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, 'select', $table, $field);
+                $valueArray = $this->checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, $tcaFieldConf['type'], $table, $field);
             }
         }
         if (!$unsetResult) {
@@ -2295,7 +2318,7 @@ class DataHandler
                                             foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processUpload'] as $classRef) {
                                                 $hookObject = GeneralUtility::getUserObj($classRef);
                                                 if (!$hookObject instanceof DataHandlerProcessUploadHookInterface) {
-                                                    throw new \UnexpectedValueException('$hookObject must implement interface TYPO3\\CMS\\Core\\DataHandling\\DataHandlerProcessUploadHookInterface', 1279962349);
+                                                    throw new \UnexpectedValueException($classRef . ' must implement interface ' . DataHandlerProcessUploadHookInterface::class, 1279962349);
                                                 }
                                                 $hookObject->processUpload_postProcessAction($theDestFile, $this);
                                             }
@@ -2802,7 +2825,7 @@ class DataHandler
                 case 'date':
                 case 'datetime':
                     $value = (int)$value;
-                    if ($value > 0 && !$this->dontProcessTransformations) {
+                    if ($value !== 0 && !$this->dontProcessTransformations) {
                         $value -= date('Z', $value);
                     }
                     break;
@@ -2874,7 +2897,9 @@ class DataHandler
                     }
                     break;
                 case 'email':
-                    $this->checkValue_input_ValidateEmail($value, $set);
+                    if ((string)$value !== '') {
+                        $this->checkValue_input_ValidateEmail($value, $set);
+                    }
                     break;
                 default:
                     if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
@@ -2937,7 +2962,13 @@ class DataHandler
      */
     public function checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, $type, $currentTable, $currentField)
     {
-        $tables = $type == 'group' ? $tcaFieldConf['allowed'] : $tcaFieldConf['foreign_table'];
+        if ($type === 'group') {
+            $tables = $tcaFieldConf['allowed'];
+        } elseif (!empty($tcaFieldConf['special']) && $tcaFieldConf['special'] === 'languages') {
+            $tables = 'sys_language';
+        } else {
+            $tables = $tcaFieldConf['foreign_table'];
+        }
         $prep = $type == 'group' ? $tcaFieldConf['prepend_tname'] : '';
         $newRelations = implode(',', $valueArray);
         /** @var $dbAnalysis RelationHandler */
@@ -2999,17 +3030,18 @@ class DataHandler
      * @param array $dataStructArray Data structure for the form (might be sheets or not). Only values in the data array which has a configuration in the data structure will be processed.
      * @param array $pParams A set of parameters to pass through for the calling of the evaluation functions
      * @param string $callBackFunc Optional call back function, see checkValue_flex_procInData_travDS()  DEPRECATED, use \TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools instead for traversal!
+     * @param array $workspaceOptions
      * @return array The modified 'data' part.
      * @see checkValue_flex_procInData_travDS()
      */
-    public function checkValue_flex_procInData($dataPart, $dataPart_current, $uploadedFiles, $dataStructArray, $pParams, $callBackFunc = '')
+    public function checkValue_flex_procInData($dataPart, $dataPart_current, $uploadedFiles, $dataStructArray, $pParams, $callBackFunc = '', array $workspaceOptions = array())
     {
         if (is_array($dataPart)) {
             foreach ($dataPart as $sKey => $sheetDef) {
                 list($dataStruct, $actualSheet) = GeneralUtility::resolveSheetDefInDS($dataStructArray, $sKey);
                 if (is_array($dataStruct) && $actualSheet == $sKey && is_array($sheetDef)) {
                     foreach ($sheetDef as $lKey => $lData) {
-                        $this->checkValue_flex_procInData_travDS($dataPart[$sKey][$lKey], $dataPart_current[$sKey][$lKey], $uploadedFiles[$sKey][$lKey], $dataStruct['ROOT']['el'], $pParams, $callBackFunc, $sKey . '/' . $lKey . '/');
+                        $this->checkValue_flex_procInData_travDS($dataPart[$sKey][$lKey], $dataPart_current[$sKey][$lKey], $uploadedFiles[$sKey][$lKey], $dataStruct['ROOT']['el'], $pParams, $callBackFunc, $sKey . '/' . $lKey . '/', $workspaceOptions);
                     }
                 }
             }
@@ -3028,10 +3060,11 @@ class DataHandler
      * @param array $pParams A set of parameters to pass through for the calling of the evaluation functions / call back function
      * @param string $callBackFunc Call back function, default is checkValue_SW(). If $this->callBackObj is set to an object, the callback function in that object is called instead.
      * @param string $structurePath
+     * @param array $workspaceOptions
      * @return void
      * @see checkValue_flex_procInData()
      */
-    public function checkValue_flex_procInData_travDS(&$dataValues, $dataValues_current, $uploadedFiles, $DSelements, $pParams, $callBackFunc, $structurePath)
+    public function checkValue_flex_procInData_travDS(&$dataValues, $dataValues_current, $uploadedFiles, $DSelements, $pParams, $callBackFunc, $structurePath, array $workspaceOptions = array())
     {
         if (!is_array($DSelements)) {
             return;
@@ -3060,7 +3093,7 @@ class DataHandler
                             continue;
                         }
 
-                        $this->checkValue_flex_procInData_travDS($dataValues[$key]['el'][$ik][$theKey]['el'], is_array($dataValues_current[$key]['el'][$ik]) ? $dataValues_current[$key]['el'][$ik][$theKey]['el'] : array(), $uploadedFiles[$key]['el'][$ik][$theKey]['el'], $DSelements[$key]['el'][$theKey]['el'], $pParams, $callBackFunc, $structurePath . $key . '/el/' . $ik . '/' . $theKey . '/el/');
+                        $this->checkValue_flex_procInData_travDS($dataValues[$key]['el'][$ik][$theKey]['el'], is_array($dataValues_current[$key]['el'][$ik]) ? $dataValues_current[$key]['el'][$ik][$theKey]['el'] : array(), $uploadedFiles[$key]['el'][$ik][$theKey]['el'], $DSelements[$key]['el'][$theKey]['el'], $pParams, $callBackFunc, $structurePath . $key . '/el/' . $ik . '/' . $theKey . '/el/', $workspaceOptions);
                         // If element is added dynamically in the flexform of TCEforms, we map the ID-string to the next numerical index we can have in that particular section of elements:
                         // The fact that the order changes is not important since order is controlled by a separately submitted index.
                         if (substr($ik, 0, 3) == 'ID-') {
@@ -3077,7 +3110,7 @@ class DataHandler
                     if (!isset($dataValues[$key]['el'])) {
                         $dataValues[$key]['el'] = array();
                     }
-                    $this->checkValue_flex_procInData_travDS($dataValues[$key]['el'], $dataValues_current[$key]['el'], $uploadedFiles[$key]['el'], $DSelements[$key]['el'], $pParams, $callBackFunc, $structurePath . $key . '/el/');
+                    $this->checkValue_flex_procInData_travDS($dataValues[$key]['el'], $dataValues_current[$key]['el'], $uploadedFiles[$key]['el'], $DSelements[$key]['el'], $pParams, $callBackFunc, $structurePath . $key . '/el/', $workspaceOptions);
                 }
             } else {
                 if (!is_array($dsConf['TCEforms']['config']) || !is_array($dataValues[$key])) {
@@ -3087,9 +3120,9 @@ class DataHandler
                 foreach ($dataValues[$key] as $vKey => $data) {
                     if ($callBackFunc) {
                         if (is_object($this->callBackObj)) {
-                            $res = $this->callBackObj->{$callBackFunc}($pParams, $dsConf['TCEforms']['config'], $dataValues[$key][$vKey], $dataValues_current[$key][$vKey], $uploadedFiles[$key][$vKey], $structurePath . $key . '/' . $vKey . '/');
+                            $res = $this->callBackObj->{$callBackFunc}($pParams, $dsConf['TCEforms']['config'], $dataValues[$key][$vKey], $dataValues_current[$key][$vKey], $uploadedFiles[$key][$vKey], $structurePath . $key . '/' . $vKey . '/', $workspaceOptions);
                         } else {
-                            $res = $this->{$callBackFunc}($pParams, $dsConf['TCEforms']['config'], $dataValues[$key][$vKey], $dataValues_current[$key][$vKey], $uploadedFiles[$key][$vKey], $structurePath . $key . '/' . $vKey . '/');
+                            $res = $this->{$callBackFunc}($pParams, $dsConf['TCEforms']['config'], $dataValues[$key][$vKey], $dataValues_current[$key][$vKey], $uploadedFiles[$key][$vKey], $structurePath . $key . '/' . $vKey . '/', $workspaceOptions);
                         }
                     } else {
                         // Default
@@ -3386,7 +3419,7 @@ class DataHandler
         }
         if ($this->isRecordCopied($table, $uid)) {
             if (!empty($overrideValues)) {
-                $this->log($table, $uid, 5, 0, 1, 'Repeated attempt to copy record "' . $table . ':' . $uid . '" with override values');
+                $this->log($table, $uid, 1, 0, 1, 'Repeated attempt to copy record "%s:%s" with override values', -1, array($table, $uid));
             }
             return null;
         }
@@ -3394,7 +3427,7 @@ class DataHandler
         // This checks if the record can be selected which is all that a copy action requires.
         if (!$this->doesRecordExist($table, $uid, 'show')) {
             if ($this->enableLogging) {
-                $this->log($table, $uid, 3, 0, 1, 'Attempt to copy record without permission');
+                $this->log($table, $uid, 1, 0, 1, 'Attempt to copy record "%s:%s" without permission', -1, array($table, $uid));
             }
             return null;
         }
@@ -3402,7 +3435,7 @@ class DataHandler
         // Check if table is allowed on destination page
         if ($destPid >= 0 && !$this->isTableAllowedForThisPage($destPid, $table)) {
             if ($this->enableLogging) {
-                $this->log($table, $uid, 3, 0, 1, 'Attempt to insert record on a page that can\'t store record type.');
+                $this->log($table, $uid, 1, 0, 1, 'Attempt to insert record "%s:%s" on a page (%s) that can\'t store record type.', -1, array($table, $uid, $destPid));
             }
             return null;
         }
@@ -3411,7 +3444,7 @@ class DataHandler
         //Used to check language and general editing rights
         if (!$ignoreLocalization && ($language <= 0 || !$this->BE_USER->checkLanguageAccess($language)) && !$this->BE_USER->recordEditAccessInternals($table, $uid, false, false, $fullLanguageCheckNeeded)) {
             if ($this->enableLogging) {
-                $this->log($table, $uid, 3, 0, 1, 'Attempt to copy record without having permissions to do so. [' . $this->BE_USER->errorMsg . '].');
+                $this->log($table, $uid, 1, 0, 1, 'Attempt to copy record "%s:%s" without having permissions to do so. [' . $this->BE_USER->errorMsg . '].', -1, array($table, $uid));
             }
             return null;
         }
@@ -3422,7 +3455,7 @@ class DataHandler
         $row = BackendUtility::getRecordWSOL($table, $uid);
         if (!is_array($row)) {
             if ($this->enableLogging) {
-                $this->log($table, $uid, 3, 0, 1, 'Attempt to copy record that did not exist!');
+                $this->log($table, $uid, 1, 0, 1, 'Attempt to copy record that did not exist!');
             }
             return null;
         }
@@ -3620,16 +3653,19 @@ class DataHandler
                             true
                         );
                     }
-
-                    foreach ($rows as $row) {
-                        // Skip localized records that will be processed in
-                        // copyL10nOverlayRecords() on copying the default language record
-                        $transOrigPointer = $row[$transOrigPointerField];
-                        if ($row[$languageField] > 0 && $transOrigPointer > 0 && isset($rows[$transOrigPointer])) {
-                            continue;
+                    if (is_array($rows)) {
+                        foreach ($rows as $row) {
+                            // Skip localized records that will be processed in
+                            // copyL10nOverlayRecords() on copying the default language record
+                            $transOrigPointer = $row[$transOrigPointerField];
+                            if ($row[$languageField] > 0 && $transOrigPointer > 0 && isset($rows[$transOrigPointer])) {
+                                continue;
+                            }
+                            // Copying each of the underlying records...
+                            $this->copyRecord($table, $row['uid'], $theNewRootID);
                         }
-                        // Copying each of the underlying records...
-                        $this->copyRecord($table, $row['uid'], $theNewRootID);
+                    } elseif ($this->enableLogging) {
+                        $this->log($table, $uid, 5, 0, 1, 'An SQL error occurred: ' . $this->databaseConnection->sql_error());
                     }
                 }
             }
@@ -3798,7 +3834,7 @@ class DataHandler
             $currentValueArray = GeneralUtility::xml2array($value);
             // Traversing the XML structure, processing files:
             if (is_array($currentValueArray)) {
-                $currentValueArray['data'] = $this->checkValue_flex_procInData($currentValueArray['data'], array(), array(), $dataStructArray, array($table, $uid, $field, $realDestPid), 'copyRecord_flexFormCallBack');
+                $currentValueArray['data'] = $this->checkValue_flex_procInData($currentValueArray['data'], array(), array(), $dataStructArray, array($table, $uid, $field, $realDestPid), 'copyRecord_flexFormCallBack', $workspaceOptions);
                 // Setting value as an array! -> which means the input will be processed according to the 'flex' type when the new copy is created.
                 $value = $currentValueArray;
             }
@@ -3957,12 +3993,14 @@ class DataHandler
      * @param array $pParams Array of parameters in num-indexes: table, uid, field
      * @param array $dsConf TCA field configuration (from Data Structure XML)
      * @param string $dataValue The value of the flexForm field
-     * @param string $dataValue_ext1 Not used.
-     * @param string $dataValue_ext2 Not used.
+     * @param string $_1 Not used.
+     * @param string $_2 Not used.
+     * @param string $_3 Not used.
+     * @param array $workspaceOptions
      * @return array Result array with key "value" containing the value of the processing.
      * @see copyRecord(), checkValue_flex_procInData_travDS()
      */
-    public function copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
+    public function copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $_1, $_2, $_3, $workspaceOptions)
     {
         // Extract parameters:
         list($table, $uid, $field, $realDestPid) = $pParams;
@@ -3970,7 +4008,7 @@ class DataHandler
         $dataValue = $this->copyRecord_procFilesRefs($dsConf, $uid, $dataValue);
         // If references are set for this field, set flag so they can be corrected later (in ->remapListedDBRecords())
         if (($this->isReferenceField($dsConf) || $this->getInlineFieldType($dsConf) !== false) && (string)$dataValue !== '') {
-            $dataValue = $this->copyRecord_procBasedOnFieldType($table, $uid, $field, $dataValue, array(), $dsConf, $realDestPid);
+            $dataValue = $this->copyRecord_procBasedOnFieldType($table, $uid, $field, $dataValue, array(), $dsConf, $realDestPid, 0, $workspaceOptions);
             $this->registerDBList[$table][$uid][$field] = 'FlexForm_reference';
         }
         // Return
@@ -4291,7 +4329,8 @@ class DataHandler
         if ($destPid >= 0) {
             if ($table != 'pages' || $this->destNotInsideSelf($destPid, $uid)) {
                 // Clear cache before moving
-                $this->registerRecordIdForPageCacheClearing($table, $uid);
+                list($parentUid) = BackendUtility::getTSCpid($table, $uid, '');
+                $this->registerRecordIdForPageCacheClearing($table, $uid, $parentUid);
                 // Setting PID
                 $updateFields['pid'] = $destPid;
                 // Table is sorted by 'sortby'
@@ -4594,6 +4633,7 @@ class DataHandler
         }
         // Set exclude Fields:
         foreach ($GLOBALS['TCA'][$Ttable]['columns'] as $fN => $fCfg) {
+            $translateToMsg = '';
             // Check if we are just prefixing:
             if ($fCfg['l10n_mode'] == 'prefixLangTitle') {
                 if (($fCfg['config']['type'] == 'text' || $fCfg['config']['type'] == 'input') && (string)$row[$fN] !== '') {
@@ -4703,7 +4743,7 @@ class DataHandler
         $action = $command['action'];
         $ids = $command['ids'];
 
-        if (!$field || !GeneralUtility::inList('localize,synchronize', $action) && empty($ids) || !isset($GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
+        if (!$field || !($action === 'localize' || $action === 'synchronize') && empty($ids) || !isset($GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
             return;
         }
 
@@ -4784,6 +4824,7 @@ class DataHandler
             /** @var DataHandler $tce */
             $tce = GeneralUtility::makeInstance(__CLASS__);
             $tce->stripslashes_values = false;
+            $tce->enableLogging = $this->enableLogging;
             $tce->start(array(), $removeArray);
             $tce->process_cmdmap();
             unset($tce);
@@ -4881,7 +4922,9 @@ class DataHandler
                     $versionState = VersionState::cast($verRec['t3ver_state']);
                     if ($versionState->equals(VersionState::MOVE_POINTER)) {
                         $versionMovePlaceholder = BackendUtility::getMovePlaceholder($table, $uid, 'uid', $verRec['t3ver_wsid']);
-                        $this->deleteEl($table, $versionMovePlaceholder['uid'], true, $forceHardDelete);
+                        if (!empty($versionMovePlaceholder)) {
+                            $this->deleteEl($table, $versionMovePlaceholder['uid'], true, $forceHardDelete);
+                        }
                     }
                 }
             }
@@ -4939,7 +4982,8 @@ class DataHandler
         }
 
         // Clear cache before deleting the record, else the correct page cannot be identified by clear_cache
-        $this->registerRecordIdForPageCacheClearing($table, $uid);
+        list($parentUid) = BackendUtility::getTSCpid($table, $uid, '');
+        $this->registerRecordIdForPageCacheClearing($table, $uid, $parentUid);
         $deleteField = $GLOBALS['TCA'][$table]['ctrl']['delete'];
         if ($deleteField && !$forceHardDelete) {
             $updateFields = array(
@@ -5063,6 +5107,11 @@ class DataHandler
      */
     public function deletePages($uid, $force = false, $forceHardDelete = false)
     {
+        $uid = (int)$uid;
+        if ($uid === 0) {
+            $this->newlog2('Deleting all pages starting from the root-page is disabled.', 'pages', 0, 0, 2);
+            return;
+        }
         // Getting list of pages to delete:
         if ($force) {
             // Returns the branch WITHOUT permission checks (0 secures that)
@@ -5078,7 +5127,7 @@ class DataHandler
             }
         } else {
             /** @var FlashMessage $flashMessage */
-            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, htmlspecialchars($res), '', FlashMessage::ERROR, true);
+            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $res, '', FlashMessage::ERROR, true);
             /** @var $flashMessageService FlashMessageService */
             $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
             /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
@@ -5136,9 +5185,11 @@ class DataHandler
     protected function copyMovedRecordToNewLocation($table, $uid)
     {
         if ($this->BE_USER->workspace > 0) {
-            // Check move placeholder at workspace
+            $originalRecord = BackendUtility::getRecord($table, $uid);
             $movePlaceholder = BackendUtility::getMovePlaceholder($table, $uid);
-            if ($movePlaceholder !== false) {
+            // Check whether target page to copied to is different to current page
+            // Cloning on the same page is superfluous and does not help at all
+            if (!empty($originalRecord) && !empty($movePlaceholder) && (int)$originalRecord['pid'] !== (int)$movePlaceholder['pid']) {
                 // If move placeholder exists, copy to new location
                 // This will create a New placeholder on the new location
                 // and a version for this new placeholder
@@ -5152,6 +5203,7 @@ class DataHandler
                 /** @var DataHandler $dataHandler */
                 $dataHandler = GeneralUtility::makeInstance(__CLASS__);
                 $dataHandler->stripslashes_values = false;
+                $dataHandler->enableLogging = $this->enableLogging;
                 $dataHandler->neverHideAtCopy = true;
                 $dataHandler->start(array(), $command);
                 $dataHandler->process_cmdmap();
@@ -5272,6 +5324,9 @@ class DataHandler
     {
         $conf = $GLOBALS['TCA'][$table]['columns'];
         $row = BackendUtility::getRecord($table, $uid, '*', '', false);
+        if (empty($row)) {
+            return;
+        }
         foreach ($row as $field => $value) {
             $this->deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf[$field]['config'], $undeleteRecord);
         }
@@ -5607,6 +5662,7 @@ class DataHandler
         $copyTCE = GeneralUtility::makeInstance(__CLASS__);
         $copyTCE->stripslashes_values = $stripslashesValues;
         $copyTCE->copyTree = $this->copyTree;
+        $copyTCE->enableLogging = $this->enableLogging;
         // Copy forth the cached TSconfig
         $copyTCE->cachedTSconfig = $this->cachedTSconfig;
         // Transformations should NOT be carried out during copy
@@ -5857,16 +5913,27 @@ class DataHandler
                 }
                 // Replace relations to NEW...-IDs in field value (uids of child records):
                 if (is_array($valueArray)) {
-                    $foreign_table = $tcaFieldConf['foreign_table'];
                     foreach ($valueArray as $key => $value) {
                         if (strpos($value, 'NEW') !== false) {
+                            if (strpos($value, '_') === false) {
+                                $affectedTable = $tcaFieldConf['foreign_table'];
+                                $prependTable = false;
+                            } else {
+                                $parts = explode('_', $value);
+                                $value = array_pop($parts);
+                                $affectedTable = implode('_', $parts);
+                                $prependTable = true;
+                            }
                             $value = $this->substNEWwithIDs[$value];
                             // The record is new, but was also auto-versionized and has another new id:
-                            if (isset($this->autoVersionIdMap[$foreign_table][$value])) {
-                                $value = $this->autoVersionIdMap[$foreign_table][$value];
+                            if (isset($this->autoVersionIdMap[$affectedTable][$value])) {
+                                $value = $this->autoVersionIdMap[$affectedTable][$value];
+                            }
+                            if ($prependTable) {
+                                $value = $affectedTable . '_' . $value;
                             }
                             // Set a hint that this was a new child record:
-                            $this->newRelatedIDs[$foreign_table][] = $value;
+                            $this->newRelatedIDs[$affectedTable][] = $value;
                             $valueArray[$key] = $value;
                         }
                     }
@@ -7246,6 +7313,23 @@ class DataHandler
     }
 
     /**
+     * Gets UID of parent record. If record is deleted it will be looked up in
+     * an array built before the record was deleted
+     *
+     * @param string $table Table where record lives/lived
+     * @param int $uid Record UID
+     * @return int[] Parent UIDs
+     */
+    protected function getOriginalParentOfRecord($table, $uid)
+    {
+        if (isset(self::$recordPidsForDeletedRecords[$table][$uid])) {
+            return self::$recordPidsForDeletedRecords[$table][$uid];
+        }
+        list($parentUid) = BackendUtility::getTSCpid($table, $uid, '');
+        return array($parentUid);
+    }
+
+    /**
      * Return TSconfig for a page id
      *
      * @param int $tscPID Page id (PID) from which to get configuration.
@@ -7702,15 +7786,22 @@ class DataHandler
      *
      * @param string $table Table name of record that was just updated.
      * @param int $uid UID of updated / inserted record
+     * @param int $pid REAL PID of page of a deleted/moved record to get TSconfig in ClearCache.
      * @return void
      * @internal This method is not meant to be called directly but only from the core itself or from hooks
      */
-    public function registerRecordIdForPageCacheClearing($table, $uid)
+    public function registerRecordIdForPageCacheClearing($table, $uid, $pid = null)
     {
         if (!is_array(static::$recordsToClearCacheFor[$table])) {
             static::$recordsToClearCacheFor[$table] = array();
         }
         static::$recordsToClearCacheFor[$table][] = (int)$uid;
+        if ($pid !== null) {
+            if (!is_array(static::$recordPidsForDeletedRecords[$table])) {
+                static::$recordPidsForDeletedRecords[$table] = array();
+            }
+            static::$recordPidsForDeletedRecords[$table][$uid][] = (int)$pid;
+        }
     }
 
     /**
@@ -7721,87 +7812,19 @@ class DataHandler
     {
         $tagsToClear = array();
         $clearCacheCommands = array();
+
         foreach (static::$recordsToClearCacheFor as $table => $uids) {
             foreach (array_unique($uids) as $uid) {
-                $pageUid = 0;
                 if (!isset($GLOBALS['TCA'][$table]) || $uid <= 0) {
                     return;
                 }
-                // Get Page TSconfig relevant:
-                list($tscPID) = BackendUtility::getTSCpid($table, $uid, '');
-                $TSConfig = $this->getTCEMAIN_TSconfig($tscPID);
-                if (empty($TSConfig['clearCache_disable'])) {
-                    // If table is "pages":
-                    $pageIdsThatNeedCacheFlush = array();
-                    if ($table === 'pages' || $table === 'pages_language_overlay') {
-                        if ($table === 'pages_language_overlay') {
-                            $pageUid = $this->getPID($table, $uid);
-                        } else {
-                            $pageUid = $uid;
-                        }
-                        // Builds list of pages on the SAME level as this page (siblings)
-                        $res_tmp = $this->databaseConnection->exec_SELECTquery('A.pid AS pid, B.uid AS uid', 'pages A, pages B', 'A.uid=' . (int)$pageUid . ' AND B.pid=A.pid AND B.deleted=0');
-                        $pid_tmp = 0;
-                        while ($row_tmp = $this->databaseConnection->sql_fetch_assoc($res_tmp)) {
-                            $pageIdsThatNeedCacheFlush[] = (int)$row_tmp['uid'];
-                            $pid_tmp = $row_tmp['pid'];
-                            // Add children as well:
-                            if ($TSConfig['clearCache_pageSiblingChildren']) {
-                                $res_tmp2 = $this->databaseConnection->exec_SELECTquery('uid', 'pages', 'pid=' . (int)$row_tmp['uid'] . ' AND deleted=0');
-                                while ($row_tmp2 = $this->databaseConnection->sql_fetch_assoc($res_tmp2)) {
-                                    $pageIdsThatNeedCacheFlush[] = (int)$row_tmp2['uid'];
-                                }
-                                $this->databaseConnection->sql_free_result($res_tmp2);
-                            }
-                        }
-                        $this->databaseConnection->sql_free_result($res_tmp);
-                        // Finally, add the parent page as well:
-                        $pageIdsThatNeedCacheFlush[] = (int)$pid_tmp;
-                        // Add grand-parent as well:
-                        if ($TSConfig['clearCache_pageGrandParent']) {
-                            $res_tmp = $this->databaseConnection->exec_SELECTquery('pid', 'pages', 'uid=' . (int)$pid_tmp);
-                            if ($row_tmp = $this->databaseConnection->sql_fetch_assoc($res_tmp)) {
-                                $pageIdsThatNeedCacheFlush[] = (int)$row_tmp['pid'];
-                            }
-                            $this->databaseConnection->sql_free_result($res_tmp);
-                        }
-                    } else {
-                        // For other tables than "pages", delete cache for the records "parent page".
-                        $pageIdsThatNeedCacheFlush[] = $pageUid = (int)$this->getPID($table, $uid);
-                    }
-                    // Call pre-processing function for clearing of cache for page ids:
-                    if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval'])) {
-                        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval'] as $funcName) {
-                            $_params = array('pageIdArray' => &$pageIdsThatNeedCacheFlush, 'table' => $table, 'uid' => $uid, 'functionID' => 'clear_cache()');
-                            // Returns the array of ids to clear, FALSE if nothing should be cleared! Never an empty array!
-                            GeneralUtility::callUserFunction($funcName, $_params, $this);
-                        }
-                    }
-                    // Delete cache for selected pages:
-                    foreach ($pageIdsThatNeedCacheFlush as $pageId) {
-                        // Workspaces always use "-1" as the page id which do not
-                        // point to real pages and caches at all. Flushing caches for
-                        // those records does not make sense and decreases performance
-                        if ($pageId >= 0) {
-                            $tagsToClear['pageId_' . $pageId] = true;
-                        }
-                    }
-                    // Queue delete cache for current table and record
-                    $tagsToClear[$table] = true;
-                    $tagsToClear[$table . '_' . $uid] = true;
-                }
-                // Clear cache for pages entered in TSconfig:
-                if (!empty($TSConfig['clearCacheCmd'])) {
-                    $commands = GeneralUtility::trimExplode(',', $TSConfig['clearCacheCmd'], true);
-                    $clearCacheCommands = array_unique($commands);
-                    unset($commands);
-                }
-                // Call post processing function for clear-cache:
-                if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'])) {
-                    $_params = array('table' => $table, 'uid' => $uid, 'uid_page' => $pageUid, 'TSConfig' => $TSConfig);
-                    foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'] as $_funcRef) {
-                        GeneralUtility::callUserFunction($_funcRef, $_params, $this);
-                    }
+                // For move commands we may get more then 1 parent.
+                $pageUids = $this->getOriginalParentOfRecord($table, $uid);
+                foreach ($pageUids as $originalParent) {
+                    list($tagsToClearFromPrepare, $clearCacheCommandsFromPrepare)
+                        = $this->prepareCacheFlush($table, $uid, $originalParent);
+                    $tagsToClear = array_merge($tagsToClear, $tagsToClearFromPrepare);
+                    $clearCacheCommands = array_merge($clearCacheCommands, $clearCacheCommandsFromPrepare);
                 }
             }
         }
@@ -7819,6 +7842,103 @@ class DataHandler
 
         // Reset the cache clearing array
         static::$recordsToClearCacheFor = array();
+
+        // Reset the original pid array
+        static::$recordPidsForDeletedRecords = array();
+    }
+
+    /**
+     * Prepare the cache clearing
+     *
+     * @param string $table Table name of record that needs to be cleared
+     * @param int $uid UID of record for which the cache needs to be cleared
+     * @param int $pid Original pid of the page of the record which the cache needs to be cleared
+     * @return array Array with tagsToClear and clearCacheCommands
+     * @internal This function is internal only it may be changed/removed also in minor version numbers.
+     */
+    protected function prepareCacheFlush($table, $uid, $pid)
+    {
+        $tagsToClear = array();
+        $clearCacheCommands = array();
+        $pageUid = 0;
+        // Get Page TSconfig relevant:
+        $TSConfig = $this->getTCEMAIN_TSconfig($pid);
+        if (empty($TSConfig['clearCache_disable'])) {
+            // If table is "pages":
+            $pageIdsThatNeedCacheFlush = array();
+            if ($table === 'pages' || $table === 'pages_language_overlay') {
+                if ($table === 'pages_language_overlay') {
+                    $pageUid = $this->getPID($table, $uid);
+                } else {
+                    $pageUid = $uid;
+                }
+                // Builds list of pages on the SAME level as this page (siblings)
+                $res_tmp = $this->databaseConnection->exec_SELECTquery('A.pid AS pid, B.uid AS uid', 'pages A, pages B', 'A.uid=' . (int)$pageUid . ' AND B.pid=A.pid AND B.deleted=0');
+                $pid_tmp = 0;
+                while ($row_tmp = $this->databaseConnection->sql_fetch_assoc($res_tmp)) {
+                    $pageIdsThatNeedCacheFlush[] = (int)$row_tmp['uid'];
+                    $pid_tmp = $row_tmp['pid'];
+                    // Add children as well:
+                    if ($TSConfig['clearCache_pageSiblingChildren']) {
+                        $res_tmp2 = $this->databaseConnection->exec_SELECTquery('uid', 'pages', 'pid=' . (int)$row_tmp['uid'] . ' AND deleted=0');
+                        while ($row_tmp2 = $this->databaseConnection->sql_fetch_assoc($res_tmp2)) {
+                            $pageIdsThatNeedCacheFlush[] = (int)$row_tmp2['uid'];
+                        }
+                        $this->databaseConnection->sql_free_result($res_tmp2);
+                    }
+                }
+                $this->databaseConnection->sql_free_result($res_tmp);
+                // Finally, add the parent page as well:
+                $pageIdsThatNeedCacheFlush[] = (int)$pid_tmp;
+                // Add grand-parent as well:
+                if ($TSConfig['clearCache_pageGrandParent']) {
+                    $res_tmp = $this->databaseConnection->exec_SELECTquery('pid', 'pages', 'uid=' . (int)$pid_tmp);
+                    if ($row_tmp = $this->databaseConnection->sql_fetch_assoc($res_tmp)) {
+                        $pageIdsThatNeedCacheFlush[] = (int)$row_tmp['pid'];
+                    }
+                    $this->databaseConnection->sql_free_result($res_tmp);
+                }
+            } else {
+                // For other tables than "pages", delete cache for the records "parent page".
+                $pageIdsThatNeedCacheFlush[] = $pageUid = (int)$this->getPID($table, $uid);
+            }
+            // Call pre-processing function for clearing of cache for page ids:
+            if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval'])) {
+                foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval'] as $funcName) {
+                    $_params = array('pageIdArray' => &$pageIdsThatNeedCacheFlush, 'table' => $table, 'uid' => $uid, 'functionID' => 'clear_cache()');
+                    // Returns the array of ids to clear, FALSE if nothing should be cleared! Never an empty array!
+                    GeneralUtility::callUserFunction($funcName, $_params, $this);
+                }
+            }
+            // Delete cache for selected pages:
+            foreach ($pageIdsThatNeedCacheFlush as $pageId) {
+                // Workspaces always use "-1" as the page id which do not
+                // point to real pages and caches at all. Flushing caches for
+                // those records does not make sense and decreases performance
+                if ($pageId >= 0) {
+                    $tagsToClear['pageId_' . $pageId] = true;
+                }
+            }
+            // Queue delete cache for current table and record
+            $tagsToClear[$table] = true;
+            $tagsToClear[$table . '_' . $uid] = true;
+        }
+        // Clear cache for pages entered in TSconfig:
+        if (!empty($TSConfig['clearCacheCmd'])) {
+            $commands = GeneralUtility::trimExplode(',', $TSConfig['clearCacheCmd'], true);
+            $clearCacheCommands = array_unique($commands);
+        }
+        // Call post processing function for clear-cache:
+        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'])) {
+            $_params = array('table' => $table, 'uid' => $uid, 'uid_page' => $pageUid, 'TSConfig' => $TSConfig);
+            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'] as $_funcRef) {
+                GeneralUtility::callUserFunction($_funcRef, $_params, $this);
+            }
+        }
+        return array(
+            $tagsToClear,
+            $clearCacheCommands
+        );
     }
 
     /**
@@ -8019,7 +8139,7 @@ class DataHandler
             $log_data = unserialize($row['log_data']);
             $msg = $row['error'] . ': ' . sprintf($row['details'], $log_data[0], $log_data[1], $log_data[2], $log_data[3], $log_data[4]);
             /** @var FlashMessage $flashMessage */
-            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, htmlspecialchars($msg), '', FlashMessage::ERROR, true);
+            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, '', FlashMessage::ERROR, true);
             /** @var $flashMessageService FlashMessageService */
             $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
             $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();