[BUGFIX] Fix message "Translate to"
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / DataHandling / DataHandler.php
index 69e7e39..f1db6f3 100644 (file)
@@ -929,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;
                 }
@@ -1918,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);
@@ -2091,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) {
@@ -2304,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);
                                             }
@@ -2811,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;
@@ -2948,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 */
@@ -3010,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);
                     }
                 }
             }
@@ -3039,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;
@@ -3071,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-') {
@@ -3088,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])) {
@@ -3098,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
@@ -3397,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;
         }
@@ -3405,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;
         }
@@ -3413,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;
         }
@@ -3422,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;
         }
@@ -3433,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;
         }
@@ -3631,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());
                     }
                 }
             }
@@ -3809,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;
             }
@@ -3968,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;
@@ -3981,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
@@ -4606,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] !== '') {
@@ -5885,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;
                         }
                     }