‪TYPO3CMS  9.5
DataHandler.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
17 use Doctrine\DBAL\DBALException;
18 use Doctrine\DBAL\Driver\Statement;
19 use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
20 use Doctrine\DBAL\Platforms\SqlitePlatform;
21 use Doctrine\DBAL\Platforms\SQLServerPlatform;
22 use Doctrine\DBAL\Types\IntegerType;
23 use Psr\Log\LoggerAwareInterface;
24 use Psr\Log\LoggerAwareTrait;
66 
80 class ‪DataHandler implements LoggerAwareInterface
81 {
82  use LoggerAwareTrait;
84 
88  protected ‪$deprecatedPublicProperties = [
89  'updateModeL10NdiffData' => 'Using updateModeL10NdiffData is deprecated and will not be possible anymore in TYPO3 v10.0.',
90  'updateModeL10NdiffDataClear' => 'Using updateModeL10NdiffDataClear is deprecated and will not be possible anymore in TYPO3 v10.0.',
91  ];
92 
93  // *********************
94  // Public variables you can configure before using the class:
95  // *********************
102  public ‪$storeLogMessages = true;
103 
109  public ‪$enableLogging = true;
110 
117  public ‪$reverseOrder = false;
118 
125  public ‪$checkSimilar = true;
126 
133  public ‪$checkStoredRecords = true;
134 
141 
148  public ‪$deleteTree = false;
149 
155  public ‪$neverHideAtCopy = false;
156 
162  public ‪$isImporting = false;
163 
169  public ‪$dontProcessTransformations = false;
170 
178  protected ‪$useTransOrigPointerField = true;
179 
187  protected ‪$updateModeL10NdiffData = true;
188 
196 
203  public ‪$bypassWorkspaceRestrictions = false;
204 
211  public ‪$bypassFileHandling = false;
212 
219  public ‪$bypassAccessCheckForRecords = false;
220 
227  public ‪$copyWhichTables = '*';
228 
236  public ‪$copyTree = 0;
237 
246  public ‪$defaultValues = [];
247 
255  public ‪$overrideValues = [];
256 
264  public ‪$alternativeFileName = [];
265 
271  public ‪$alternativeFilePath = [];
272 
280  public ‪$data_disableFields = [];
281 
291  public ‪$suggestedInsertUids = [];
292 
299  public ‪$callBackObj;
300 
301  // *********************
302  // Internal variables (mapping arrays) which can be used (read-only) from outside
303  // *********************
309  public ‪$autoVersionIdMap = [];
310 
316  public ‪$substNEWwithIDs = [];
317 
323  public ‪$substNEWwithIDs_table = [];
324 
330  public ‪$newRelatedIDs = [];
331 
337  public ‪$copyMappingArray_merged = [];
338 
344  protected ‪$deletedRecords = [];
345 
351  public ‪$copiedFileMap = [];
352 
358  public ‪$RTEmagic_copyIndex = [];
359 
365  public ‪$errorLog = [];
366 
372  public ‪$pagetreeRefreshFieldsFromPages = ['pid', 'sorting', 'deleted', 'hidden', 'title', 'doktype', 'is_siteroot', 'fe_group', 'nav_hide', 'nav_title', 'module', 'starttime', 'endtime', 'content_from_pid', 'extendToSubpages'];
373 
379  public ‪$pagetreeNeedsRefresh = false;
380 
381  // *********************
382  // Internal Variables, do not touch.
383  // *********************
384 
385  // Variables set in init() function:
386 
392  public ‪$BE_USER;
393 
399  public ‪$userid;
400 
406  public ‪$username;
407 
413  public ‪$admin;
414 
420  public ‪$defaultPermissions = [
421  'user' => 'show,edit,delete,new,editcontent',
422  'group' => 'show,edit,new,editcontent',
423  'everybody' => ''
424  ];
425 
431  protected ‪$excludedTablesAndFields = [];
432 
439  protected ‪$control = [];
440 
446  public ‪$datamap = [];
447 
453  public ‪$cmdmap = [];
454 
460  protected ‪$mmHistoryRecords = [];
461 
467  protected ‪$historyRecords = [];
468 
469  // Internal static:
475  public ‪$pMap = [
476  'show' => 1,
477  // 1st bit
478  'edit' => 2,
479  // 2nd bit
480  'delete' => 4,
481  // 3rd bit
482  'new' => 8,
483  // 4th bit
484  'editcontent' => 16
485  ];
486 
494  public ‪$sortIntervals = 256;
495 
496  // Internal caching arrays
502  protected ‪$recUpdateAccessCache = [];
503 
509  protected ‪$recInsertAccessCache = [];
510 
516  protected ‪$isRecordInWebMount_Cache = [];
517 
523  protected ‪$isInWebMount_Cache = [];
524 
530  protected ‪$pageCache = [];
531 
532  // Other arrays:
538  public ‪$dbAnalysisStore = [];
539 
545  public ‪$removeFilesStore = [];
546 
552  public ‪$uploadedFileArray = [];
553 
559  public ‪$registerDBList = [];
560 
566  public ‪$registerDBPids = [];
567 
578  public ‪$copyMappingArray = [];
579 
585  public ‪$remapStack = [];
586 
593  public ‪$remapStackRecords = [];
594 
600  protected ‪$remapStackChildIds = [];
601 
607  protected ‪$remapStackActions = [];
608 
614  protected ‪$remapStackRefIndex = [];
615 
622 
629  public ‪$callFromImpExp = false;
630 
631  // Various
638  public ‪$fileFunc;
639 
645  public ‪$checkValue_currentRecord = [];
646 
652  public ‪$autoVersioningUpdate = false;
653 
659  protected ‪$disableDeleteClause = false;
660 
665 
670 
677  protected ‪$outerMostInstance;
678 
684  protected static ‪$recordsToClearCacheFor = [];
685 
692  protected static ‪$recordPidsForDeletedRecords = [];
693 
699  protected ‪$runtimeCache;
700 
706  protected ‪$cachePrefixNestedElementCalls = 'core-datahandler-nestedElementCalls-';
707 
711  public function ‪__construct()
712  {
713  $this->checkStoredRecords = (bool)‪$GLOBALS['TYPO3_CONF_VARS']['BE']['checkStoredRecords'];
714  $this->checkStoredRecords_loose = (bool)‪$GLOBALS['TYPO3_CONF_VARS']['BE']['checkStoredRecordsLoose'];
715  $this->runtimeCache = $this->‪getRuntimeCache();
716  }
717 
721  public function ‪setControl(array ‪$control)
722  {
723  $this->control = ‪$control;
724  }
725 
735  public function ‪start($data, $cmd, $altUserObject = null)
736  {
737  // Initializing BE_USER
738  $this->BE_USER = is_object($altUserObject) ? $altUserObject : ‪$GLOBALS['BE_USER'];
739  $this->userid = $this->BE_USER->user['uid'] ?? 0;
740  $this->username = $this->BE_USER->user['username'] ?? '';
741  $this->admin = $this->BE_USER->user['admin'] ?? false;
742  if ($this->BE_USER->uc['recursiveDelete'] ?? false) {
743  $this->deleteTree = 1;
744  }
745 
746  // Get default values from user TSConfig
747  $tcaDefaultOverride = $this->BE_USER->getTSConfig()['TCAdefaults.'] ?? null;
748  if (is_array($tcaDefaultOverride)) {
749  $this->‪setDefaultsFromUserTS($tcaDefaultOverride);
750  }
751 
752  // Initializing default permissions for pages
753  ‪$defaultPermissions = ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPermissions'];
754  if (isset(‪$defaultPermissions['user'])) {
755  $this->defaultPermissions['user'] = ‪$defaultPermissions['user'];
756  }
757  if (isset(‪$defaultPermissions['group'])) {
758  $this->defaultPermissions['group'] = ‪$defaultPermissions['group'];
759  }
760  if (isset(‪$defaultPermissions['everybody'])) {
761  $this->defaultPermissions['everybody'] = ‪$defaultPermissions['everybody'];
762  }
763  // generates the excludelist, based on TCA/exclude-flag and non_exclude_fields for the user:
764  if (!$this->admin) {
765  $this->excludedTablesAndFields = array_flip($this->‪getExcludeListArray());
766  }
767  // Setting the data and cmd arrays
768  if (is_array($data)) {
769  reset($data);
770  $this->datamap = $data;
771  }
772  if (is_array($cmd)) {
773  reset($cmd);
774  $this->cmdmap = $cmd;
775  }
776  }
777 
784  public function ‪setMirror($mirror)
785  {
786  if (!is_array($mirror)) {
787  return;
788  }
789 
790  foreach ($mirror as $table => $uid_array) {
791  if (!isset($this->datamap[$table])) {
792  continue;
793  }
794 
795  foreach ($uid_array as $id => $uidList) {
796  if (!isset($this->datamap[$table][$id])) {
797  continue;
798  }
799 
800  $theIdsInArray = GeneralUtility::trimExplode(',', $uidList, true);
801  foreach ($theIdsInArray as $copyToUid) {
802  $this->datamap[$table][$copyToUid] = $this->datamap[$table][$id];
803  }
804  }
805  }
806  }
807 
813  public function ‪setDefaultsFromUserTS($userTS)
814  {
815  if (!is_array($userTS)) {
816  return;
817  }
818 
819  foreach ($userTS as $k => $v) {
820  $k = mb_substr($k, 0, -1);
821  if (!$k || !is_array($v) || !isset(‪$GLOBALS['TCA'][$k])) {
822  continue;
823  }
824 
825  if (is_array($this->defaultValues[$k])) {
826  $this->defaultValues[$k] = array_merge($this->defaultValues[$k], $v);
827  } else {
828  $this->defaultValues[$k] = $v;
829  }
830  }
831  }
832 
845  protected function ‪applyDefaultsForFieldArray(string $table, ?array $tcaDefaults, array $prepopulatedFieldArray): array
846  {
847  // Re-apply $this->defaultValues settings
848  $this->‪setDefaultsFromUserTS($tcaDefaults);
849  $cleanFieldArray = $this->‪newFieldArray($table);
850  if (isset($prepopulatedFieldArray['pid'])) {
851  $cleanFieldArray['pid'] = $prepopulatedFieldArray['pid'];
852  }
853  $sortColumn = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? null;
854  if ($sortColumn !== null && isset($prepopulatedFieldArray[$sortColumn])) {
855  $cleanFieldArray[$sortColumn] = $prepopulatedFieldArray[$sortColumn];
856  }
857  return $cleanFieldArray;
858  }
859 
866  public function ‪process_uploads($postFiles)
867  {
868  if (!is_array($postFiles)) {
869  return;
870  }
871 
872  // Editing frozen:
873  if ($this->BE_USER->workspace !== 0 && $this->BE_USER->workspaceRec['freeze']) {
874  $this->‪newlog('All editing in this workspace has been frozen!', 1);
875  return;
876  }
877  $subA = reset($postFiles);
878  if (is_array($subA)) {
879  if (is_array($subA['name']) && is_array($subA['type']) && is_array($subA['tmp_name']) && is_array($subA['size'])) {
880  // Initialize the uploadedFilesArray:
881  $this->uploadedFileArray = [];
882  // For each entry:
883  foreach ($subA as $key => $values) {
884  $this->‪process_uploads_traverseArray($this->uploadedFileArray, $values, $key);
885  }
886  } else {
887  $this->uploadedFileArray = $subA;
888  }
889  }
890  }
891 
901  public function ‪process_uploads_traverseArray(&$outputArr, $inputArr, $keyToSet)
902  {
903  if (is_array($inputArr)) {
904  foreach ($inputArr as $key => $value) {
905  $this->‪process_uploads_traverseArray($outputArr[$key], $inputArr[$key], $keyToSet);
906  }
907  } else {
908  $outputArr[$keyToSet] = $inputArr;
909  }
910  }
911 
912  /*********************************************
913  *
914  * HOOKS
915  *
916  *********************************************/
930  public function ‪hook_processDatamap_afterDatabaseOperations(&$hookObjectsArr, &$status, &$table, &$id, &$fieldArray)
931  {
932  // Process hook directly:
933  if (!isset($this->remapStackRecords[$table][$id])) {
934  foreach ($hookObjectsArr as $hookObj) {
935  if (method_exists($hookObj, 'processDatamap_afterDatabaseOperations')) {
936  $hookObj->processDatamap_afterDatabaseOperations($status, $table, $id, $fieldArray, $this);
937  }
938  }
939  } else {
940  $this->remapStackRecords[$table][$id]['processDatamap_afterDatabaseOperations'] = [
941  'status' => $status,
942  'fieldArray' => $fieldArray,
943  'hookObjectsArr' => $hookObjectsArr
944  ];
945  }
946  }
947 
955  protected function ‪getCheckModifyAccessListHookObjects()
956  {
957  if (!isset($this->checkModifyAccessListHookObjects)) {
958  $this->checkModifyAccessListHookObjects = [];
959  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['checkModifyAccessList'] ?? [] as $className) {
960  $hookObject = GeneralUtility::makeInstance($className);
961  if (!$hookObject instanceof DataHandlerCheckModifyAccessListHookInterface) {
962  throw new \UnexpectedValueException($className . ' must implement interface ' . DataHandlerCheckModifyAccessListHookInterface::class, 1251892472);
963  }
964  $this->checkModifyAccessListHookObjects[] = $hookObject;
965  }
966  }
968  }
969 
970  /*********************************************
971  *
972  * PROCESSING DATA
973  *
974  *********************************************/
981  public function ‪process_datamap()
982  {
983  $this->‪controlActiveElements();
984 
985  // Keep versionized(!) relations here locally:
986  $registerDBList = [];
988  $this->datamap = $this->‪unsetElementsToBeDeleted($this->datamap);
989  // Editing frozen:
990  if ($this->BE_USER->workspace !== 0 && $this->BE_USER->workspaceRec['freeze']) {
991  $this->‪newlog('All editing in this workspace has been frozen!', 1);
992  return false;
993  }
994  // First prepare user defined objects (if any) for hooks which extend this function:
995  $hookObjectsArr = [];
996  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'] ?? [] as $className) {
997  $hookObject = GeneralUtility::makeInstance($className);
998  if (method_exists($hookObject, 'processDatamap_beforeStart')) {
999  $hookObject->processDatamap_beforeStart($this);
1000  }
1001  $hookObjectsArr[] = $hookObject;
1002  }
1003  // Pre-process data-map and synchronize localization states
1004  $this->datamap = GeneralUtility::makeInstance(SlugEnricher::class)->enrichDataMap($this->datamap);
1005  $this->datamap = ‪DataMapProcessor::instance($this->datamap, $this->BE_USER)->‪process();
1006  // Organize tables so that the pages-table is always processed first. This is required if you want to make sure that content pointing to a new page will be created.
1007  $orderOfTables = [];
1008  // Set pages first.
1009  if (isset($this->datamap['pages'])) {
1010  $orderOfTables[] = 'pages';
1011  }
1012  $orderOfTables = array_unique(array_merge($orderOfTables, array_keys($this->datamap)));
1013  // Process the tables...
1014  foreach ($orderOfTables as $table) {
1015  // Check if
1016  // - table is set in $GLOBALS['TCA'],
1017  // - table is NOT readOnly
1018  // - the table is set with content in the data-array (if not, there's nothing to process...)
1019  // - permissions for tableaccess OK
1020  $modifyAccessList = $this->‪checkModifyAccessList($table);
1021  if (!$modifyAccessList) {
1022  $this->‪log($table, 0, 2, 0, 1, 'Attempt to modify table \'%s\' without permission', 1, [$table]);
1023  }
1024  if (!isset(‪$GLOBALS['TCA'][$table]) || $this->‪tableReadOnly($table) || !is_array($this->datamap[$table]) || !$modifyAccessList) {
1025  continue;
1026  }
1027 
1028  if ($this->reverseOrder) {
1029  $this->datamap[$table] = array_reverse($this->datamap[$table], 1);
1030  }
1031  // For each record from the table, do:
1032  // $id is the record uid, may be a string if new records...
1033  // $incomingFieldArray is the array of fields
1034  foreach ($this->datamap[$table] as $id => $incomingFieldArray) {
1035  if (!is_array($incomingFieldArray)) {
1036  continue;
1037  }
1038  $theRealPid = null;
1039 
1040  // Hook: processDatamap_preProcessFieldArray
1041  foreach ($hookObjectsArr as $hookObj) {
1042  if (method_exists($hookObj, 'processDatamap_preProcessFieldArray')) {
1043  $hookObj->processDatamap_preProcessFieldArray($incomingFieldArray, $table, $id, $this);
1044  }
1045  }
1046  // ******************************
1047  // Checking access to the record
1048  // ******************************
1049  $createNewVersion = false;
1050  $recordAccess = false;
1051  $old_pid_value = '';
1052  $this->autoVersioningUpdate = false;
1053  // Is it a new record? (Then Id is a string)
1055  // Get a fieldArray with tca default values
1056  $fieldArray = $this->‪newFieldArray($table);
1057  // A pid must be set for new records.
1058  if (isset($incomingFieldArray['pid'])) {
1059  $pid_value = $incomingFieldArray['pid'];
1060  // Checking and finding numerical pid, it may be a string-reference to another value
1061  $canProceed = true;
1062  // If a NEW... id
1063  if (strpos($pid_value, 'NEW') !== false) {
1064  if ($pid_value[0] === '-') {
1065  $negFlag = -1;
1066  $pid_value = substr($pid_value, 1);
1067  } else {
1068  $negFlag = 1;
1069  }
1070  // Trying to find the correct numerical value as it should be mapped by earlier processing of another new record.
1071  if (isset($this->substNEWwithIDs[$pid_value])) {
1072  if ($negFlag === 1) {
1073  $old_pid_value = $this->substNEWwithIDs[$pid_value];
1074  }
1075  $pid_value = (int)($negFlag * $this->substNEWwithIDs[$pid_value]);
1076  } else {
1077  $canProceed = false;
1078  }
1079  }
1080  $pid_value = (int)$pid_value;
1081  if ($canProceed) {
1082  $fieldArray = $this->‪resolveSortingAndPidForNewRecord($table, $pid_value, $fieldArray);
1083  }
1084  }
1085  $theRealPid = $fieldArray['pid'];
1086  // Now, check if we may insert records on this pid.
1087  if ($theRealPid >= 0) {
1088  // Checks if records can be inserted on this $pid.
1089  // If this is a page translation, the check needs to be done for the l10n_parent record
1090  if ($table === 'pages' && $incomingFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0 && $incomingFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] > 0) {
1091  $recordAccess = $this->‪checkRecordInsertAccess($table, $incomingFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']]);
1092  } else {
1093  $recordAccess = $this->‪checkRecordInsertAccess($table, $theRealPid);
1094  }
1095  if ($recordAccess) {
1096  $this->‪addDefaultPermittedLanguageIfNotSet($table, $incomingFieldArray);
1097  $recordAccess = $this->BE_USER->recordEditAccessInternals($table, $incomingFieldArray, true);
1098  if (!$recordAccess) {
1099  $this->‪newlog('recordEditAccessInternals() check failed. [' . $this->BE_USER->errorMsg . ']', 1);
1100  } elseif (!$this->bypassWorkspaceRestrictions) {
1101  // Workspace related processing:
1102  // If LIVE records cannot be created in the current PID due to workspace restrictions, prepare creation of placeholder-record
1103  if ($res = $this->BE_USER->workspaceAllowLiveRecordsInPID($theRealPid, $table)) {
1104  if ($res < 0) {
1105  $recordAccess = false;
1106  $this->‪newlog('Stage for versioning root point and users access level did not allow for editing', 1);
1107  }
1108  } else {
1109  // So, if no live records were allowed, we have to create a new version of this record:
1110  if (‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1111  $createNewVersion = true;
1112  } else {
1113  $recordAccess = false;
1114  $this->‪newlog('Record could not be created in this workspace in this branch', 1);
1115  }
1116  }
1117  }
1118  }
1119  } else {
1120  $this->logger->debug('Internal ERROR: pid should not be less than zero!');
1121  }
1122  // Yes new record, change $record_status to 'insert'
1123  $status = 'new';
1124  } else {
1125  // Nope... $id is a number
1126  $fieldArray = [];
1127  $recordAccess = $this->‪checkRecordUpdateAccess($table, $id, $incomingFieldArray, $hookObjectsArr);
1128  if (!$recordAccess) {
1129  if ($this->enableLogging) {
1130  $propArr = $this->‪getRecordProperties($table, $id);
1131  $this->‪log($table, $id, 2, 0, 1, 'Attempt to modify record \'%s\' (%s) without permission. Or non-existing page.', 2, [$propArr['header'], $table . ':' . $id], $propArr['event_pid']);
1132  }
1133  continue;
1134  }
1135  // Next check of the record permissions (internals)
1136  $recordAccess = $this->BE_USER->recordEditAccessInternals($table, $id);
1137  if (!$recordAccess) {
1138  $this->‪newlog('recordEditAccessInternals() check failed. [' . $this->BE_USER->errorMsg . ']', 1);
1139  } else {
1140  // Here we fetch the PID of the record that we point to...
1141  $tempdata = $this->‪recordInfo($table, $id, 'pid' . (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) ? ',t3ver_wsid,t3ver_stage' : ''));
1142  $theRealPid = $tempdata['pid'] ?? null;
1143  // Use the new id of the versionized record we're trying to write to:
1144  // (This record is a child record of a parent and has already been versionized.)
1145  if (!empty($this->autoVersionIdMap[$table][$id])) {
1146  // For the reason that creating a new version of this record, automatically
1147  // created related child records (e.g. "IRRE"), update the accordant field:
1148  $this->‪getVersionizedIncomingFieldArray($table, $id, $incomingFieldArray, ‪$registerDBList);
1149  // Use the new id of the copied/versionized record:
1150  $id = $this->autoVersionIdMap[$table][$id];
1151  $recordAccess = true;
1152  $this->autoVersioningUpdate = true;
1153  } elseif (!$this->bypassWorkspaceRestrictions && ($errorCode = $this->BE_USER->workspaceCannotEditRecord($table, $tempdata))) {
1154  $recordAccess = false;
1155  // Versioning is required and it must be offline version!
1156  // Check if there already is a workspace version
1157  $WSversion = ‪BackendUtility::getWorkspaceVersionOfRecord($this->BE_USER->workspace, $table, $id, 'uid,t3ver_oid');
1158  if ($WSversion) {
1159  $id = $WSversion['uid'];
1160  $recordAccess = true;
1161  } elseif ($this->BE_USER->workspaceAllowAutoCreation($table, $id, $theRealPid)) {
1162  // new version of a record created in a workspace - so always refresh pagetree to indicate there is a change in the workspace
1163  $this->pagetreeNeedsRefresh = true;
1164 
1166  $tce = GeneralUtility::makeInstance(__CLASS__);
1167  $tce->enableLogging = ‪$this->enableLogging;
1168  // Setting up command for creating a new version of the record:
1169  $cmd = [];
1170  $cmd[$table][$id]['version'] = [
1171  'action' => 'new',
1172  // Default is to create a version of the individual records... element versioning that is.
1173  'label' => 'Auto-created for WS #' . $this->BE_USER->workspace
1174  ];
1175  $tce->start([], $cmd, $this->BE_USER);
1176  $tce->process_cmdmap();
1177  $this->errorLog = array_merge($this->errorLog, $tce->errorLog);
1178  // If copying was successful, share the new uids (also of related children):
1179  if (!empty($tce->copyMappingArray[$table][$id])) {
1180  foreach ($tce->copyMappingArray as $origTable => $origIdArray) {
1181  foreach ($origIdArray as $origId => $newId) {
1182  $this->uploadedFileArray[$origTable][$newId] = $this->uploadedFileArray[$origTable][$origId];
1183  $this->autoVersionIdMap[$origTable][$origId] = $newId;
1184  }
1185  }
1186  ‪ArrayUtility::mergeRecursiveWithOverrule($this->RTEmagic_copyIndex, $tce->RTEmagic_copyIndex);
1187  // See where RTEmagic_copyIndex is used inside fillInFieldArray() for more information...
1188  // Update registerDBList, that holds the copied relations to child records:
1189  ‪$registerDBList = array_merge(‪$registerDBList, $tce->registerDBList);
1190  // For the reason that creating a new version of this record, automatically
1191  // created related child records (e.g. "IRRE"), update the accordant field:
1192  $this->‪getVersionizedIncomingFieldArray($table, $id, $incomingFieldArray, ‪$registerDBList);
1193  // Use the new id of the copied/versionized record:
1194  $id = $this->autoVersionIdMap[$table][$id];
1195  $recordAccess = true;
1196  $this->autoVersioningUpdate = true;
1197  } else {
1198  $this->‪newlog('Could not be edited in offline workspace in the branch where found (failure state: \'' . $errorCode . '\'). Auto-creation of version failed!', 1);
1199  }
1200  } else {
1201  $this->newlog('Could not be edited in offline workspace in the branch where found (failure state: \'' . $errorCode . '\'). Auto-creation of version not allowed in workspace!', 1);
1202  }
1203  }
1204  }
1205  // The default is 'update'
1206  $status = 'update';
1207  }
1208  // If access was granted above, proceed to create or update record:
1209  if (!$recordAccess) {
1210  continue;
1211  }
1212 
1213  // Here the "pid" is set IF NOT the old pid was a string pointing to a place in the subst-id array.
1214  list($tscPID) = BackendUtility::getTSCpid($table, $id, $old_pid_value ? $old_pid_value : $fieldArray['pid']);
1215  if ($status === 'new') {
1216  // Apply TCAdefaults from pageTS
1217  $TSConfig = BackendUtility::getPagesTSconfig($tscPID);
1218  $fieldArray = $this->applyDefaultsForFieldArray($table, $TSConfig['TCAdefaults.'] ?? null, $fieldArray);
1219  $TSConfig = $TSConfig['TCEMAIN.'] ?? [];
1220  if ($table === 'pages' && isset($TSConfig['permissions.']) && is_array($TSConfig['permissions.'])) {
1221  $fieldArray = $this->setTSconfigPermissions($fieldArray, $TSConfig['permissions.']);
1222  }
1223  }
1224  // Processing of all fields in incomingFieldArray and setting them in $fieldArray
1225  $fieldArray = $this->fillInFieldArray($table, $id, $fieldArray, $incomingFieldArray, $theRealPid, $status, $tscPID);
1226  $newVersion_placeholderFieldArray = [];
1227  if ($createNewVersion) {
1228  // create a placeholder array with already processed field content
1229  $newVersion_placeholderFieldArray = $fieldArray;
1230  }
1231  // NOTICE! All manipulation beyond this point bypasses both "excludeFields" AND possible "MM" relations / file uploads to field!
1232  // Forcing some values unto field array:
1233  // NOTICE: This overriding is potentially dangerous; permissions per field is not checked!!!
1234  $fieldArray = $this->overrideFieldArray($table, $fieldArray);
1235  if ($createNewVersion) {
1236  $newVersion_placeholderFieldArray = $this->overrideFieldArray($table, $newVersion_placeholderFieldArray);
1237  }
1238  // Setting system fields
1239  if ($status === 'new') {
1240  if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1241  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
1242  if ($createNewVersion) {
1243  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
1244  }
1245  }
1246  if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
1247  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $this->userid;
1248  if ($createNewVersion) {
1249  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $this->userid;
1250  }
1251  }
1252  } elseif ($this->checkSimilar) {
1253  // Removing fields which are equal to the current value:
1254  $fieldArray = $this->compareFieldArrayWithCurrentAndUnset($table, $id, $fieldArray);
1255  }
1256  if ($GLOBALS['TCA'][$table]['ctrl']['tstamp'] && !empty($fieldArray)) {
1257  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
1258  if ($createNewVersion) {
1259  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
1260  }
1261  }
1262  // Set stage to "Editing" to make sure we restart the workflow
1263  if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1264  $fieldArray['t3ver_stage'] = 0;
1265  }
1266  // Hook: processDatamap_postProcessFieldArray
1267  foreach ($hookObjectsArr as $hookObj) {
1268  if (method_exists($hookObj, 'processDatamap_postProcessFieldArray')) {
1269  $hookObj->processDatamap_postProcessFieldArray($status, $table, $id, $fieldArray, $this);
1270  }
1271  }
1272  // Performing insert/update. If fieldArray has been unset by some userfunction (see hook above), don't do anything
1273  // Kasper: Unsetting the fieldArray is dangerous; MM relations might be saved already and files could have been uploaded that are now "lost"
1274  if (is_array($fieldArray)) {
1275  if ($status === 'new') {
1276  if ($table === 'pages') {
1277  // for new pages always a refresh is needed
1278  $this->pagetreeNeedsRefresh = true;
1279  }
1280 
1281  // This creates a new version of the record with online placeholder and offline version
1282  if ($createNewVersion) {
1283  // new record created in a workspace - so always refresh pagetree to indicate there is a change in the workspace
1284  $this->pagetreeNeedsRefresh = true;
1285 
1286  $newVersion_placeholderFieldArray['t3ver_label'] = 'INITIAL PLACEHOLDER';
1287  // Setting placeholder state value for temporary record
1288  $newVersion_placeholderFieldArray['t3ver_state'] = (string)new ‪VersionState(‪VersionState::NEW_PLACEHOLDER);
1289  // Setting workspace - only so display of place holders can filter out those from other workspaces.
1290  $newVersion_placeholderFieldArray['t3ver_wsid'] = $this->BE_USER->workspace;
1291  $newVersion_placeholderFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['label']] = $this->‪getPlaceholderTitleForTableLabel($table);
1292  // Saving placeholder as 'original'
1293  $this->‪insertDB($table, $id, $newVersion_placeholderFieldArray, false, (int)($incomingFieldArray['uid'] ?? 0));
1294  // For the actual new offline version, set versioning values to point to placeholder:
1295  $fieldArray['pid'] = -1;
1296  $fieldArray['t3ver_oid'] = $this->substNEWwithIDs[$id];
1297  $fieldArray['t3ver_id'] = 1;
1298  // Setting placeholder state value for version (so it can know it is currently a new version...)
1299  $fieldArray['t3ver_state'] = (string)new ‪VersionState(‪VersionState::NEW_PLACEHOLDER_VERSION);
1300  $fieldArray['t3ver_label'] = 'First draft version';
1301  $fieldArray['t3ver_wsid'] = $this->BE_USER->workspace;
1302  // When inserted, $this->substNEWwithIDs[$id] will be changed to the uid of THIS version and so the interface will pick it up just nice!
1303  $phShadowId = $this->‪insertDB($table, $id, $fieldArray, true, 0, true);
1304  if ($phShadowId) {
1305  // Processes fields of the placeholder record:
1306  $this->‪triggerRemapAction($table, $id, [$this, 'placeholderShadowing'], [$table, $phShadowId]);
1307  // Hold auto-versionized ids of placeholders:
1308  $this->autoVersionIdMap[$table][$this->substNEWwithIDs[$id]] = $phShadowId;
1309  }
1310  } else {
1311  $this->‪insertDB($table, $id, $fieldArray, false, (int)($incomingFieldArray['uid'] ?? 0));
1312  }
1313  } else {
1314  if ($table === 'pages') {
1315  // only a certain number of fields needs to be checked for updates
1316  // if $this->checkSimilar is TRUE, fields with unchanged values are already removed here
1317  $fieldsToCheck = array_intersect($this->pagetreeRefreshFieldsFromPages, array_keys($fieldArray));
1318  if (!empty($fieldsToCheck)) {
1319  $this->pagetreeNeedsRefresh = true;
1320  }
1321  }
1322  $this->‪updateDB($table, $id, $fieldArray);
1323  $this->‪placeholderShadowing($table, $id);
1324  }
1325  }
1326  // Hook: processDatamap_afterDatabaseOperations
1327  // Note: When using the hook after INSERT operations, you will only get the temporary NEW... id passed to your hook as $id,
1328  // but you can easily translate it to the real uid of the inserted record using the $this->substNEWwithIDs array.
1329  $this->‪hook_processDatamap_afterDatabaseOperations($hookObjectsArr, $status, $table, $id, $fieldArray);
1330  }
1331  }
1332  // Process the stack of relations to remap/correct
1333  $this->‪processRemapStack();
1334  $this->‪dbAnalysisStoreExec();
1335  $this->‪removeRegisteredFiles();
1336  // Hook: processDatamap_afterAllOperations
1337  // Note: When this hook gets called, all operations on the submitted data have been finished.
1338  foreach ($hookObjectsArr as $hookObj) {
1339  if (method_exists($hookObj, 'processDatamap_afterAllOperations')) {
1340  $hookObj->processDatamap_afterAllOperations($this);
1341  }
1342  }
1343  if ($this->‪isOuterMostInstance()) {
1344  $this->‪processClearCacheQueue();
1345  $this->‪resetElementsToBeDeleted();
1346  }
1347  }
1348 
1355  protected function ‪normalizeTimeFormat(string $table, string $value, string $dbType): string
1356  {
1357  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
1358  $platform = $connection->getDatabasePlatform();
1359  if ($platform instanceof SQLServerPlatform) {
1360  $defaultLength = ‪QueryHelper::getDateTimeFormats()[$dbType]['empty'];
1361  $value = substr(
1362  $value,
1363  0,
1364  strlen($defaultLength)
1365  );
1366  }
1367  return $value;
1368  }
1369 
1381  protected function ‪resolveSortingAndPidForNewRecord(string $table, int $pid, array $fieldArray): array
1382  {
1383  $sortColumn = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
1384  // Points to a page on which to insert the element, possibly in the top of the page
1385  if ($pid >= 0) {
1386  // Ensure that the "pid" is not a translated page ID, but the default page ID
1387  $pid = $this->‪getDefaultLanguagePageId($pid);
1388  // The numerical pid is inserted in the data array
1389  $fieldArray['pid'] = $pid;
1390  // If this table is sorted we better find the top sorting number
1391  if ($sortColumn) {
1392  $fieldArray[$sortColumn] = $this->‪getSortNumber($table, 0, $pid);
1393  }
1394  } elseif ($sortColumn) {
1395  // Points to another record before itself
1396  // If this table is sorted we better find the top sorting number
1397  // Because $pid is < 0, getSortNumber() returns an array
1398  $sortingInfo = $this->‪getSortNumber($table, 0, $pid);
1399  $fieldArray['pid'] = $sortingInfo['pid'];
1400  $fieldArray[$sortColumn] = $sortingInfo['sortNumber'];
1401  } else {
1402  // Here we fetch the PID of the record that we point to
1403  $record = $this->‪recordInfo($table, abs($pid), 'pid');
1404  // Ensure that the "pid" is not a translated page ID, but the default page ID
1405  $fieldArray['pid'] = $this->‪getDefaultLanguagePageId($record['pid']);
1406  }
1407  return $fieldArray;
1408  }
1409 
1416  public function ‪placeholderShadowing($table, $id)
1417  {
1418  $liveRecord = ‪BackendUtility::getLiveVersionOfRecord($table, $id, '*');
1419  if (empty($liveRecord)) {
1420  return;
1421  }
1423  $liveState = ‪VersionState::cast($liveRecord['t3ver_state']);
1424  $versionRecord = ‪BackendUtility::getRecord($table, $id);
1425  $versionState = ‪VersionState::cast($versionRecord['t3ver_state']);
1426 
1427  if (!$liveState->indicatesPlaceholder() && !$versionState->indicatesPlaceholder()) {
1428  return;
1429  }
1430  $factory = GeneralUtility::makeInstance(
1431  PlaceholderShadowColumnsResolver::class,
1432  $table,
1433  ‪$GLOBALS['TCA'][$table] ?? []
1434  );
1435 
1436  if ($versionState->equals(‪VersionState::MOVE_POINTER)) {
1437  $placeholderRecord = ‪BackendUtility::getMovePlaceholder($table, $liveRecord['uid'], '*', $versionRecord['t3ver_wsid']);
1438  $shadowColumns = $factory->forMovePlaceholder();
1439  } elseif ($liveState->indicatesPlaceholder()) {
1440  $placeholderRecord = $liveRecord;
1441  $shadowColumns = $factory->forNewPlaceholder();
1442  } else {
1443  return;
1444  }
1445  if (empty($shadowColumns)) {
1446  return;
1447  }
1448 
1449  $placeholderValues = [];
1450  foreach ($shadowColumns as $fieldName) {
1451  if ((string)$versionRecord[$fieldName] !== (string)$placeholderRecord[$fieldName]) {
1452  $placeholderValues[$fieldName] = $versionRecord[$fieldName];
1453  }
1454  }
1455  if (empty($placeholderValues)) {
1456  return;
1457  }
1458 
1459  if ($this->enableLogging) {
1460  $this->‪log($table, $placeholderRecord['uid'], 0, 0, 0, 'Shadowing done on fields <i>' . implode(',', array_keys($placeholderRecord)) . '</i> in placeholder record ' . $table . ':' . $liveRecord['uid'] . ' (offline version UID=' . $id . ')', -1, [], $this->‪eventPid($table, $liveRecord['uid'], $liveRecord['pid']));
1461  }
1462  $this->‪updateDB($table, $placeholderRecord['uid'], $placeholderValues);
1463  }
1464 
1472  public function ‪getPlaceholderTitleForTableLabel($table, $placeholderContent = null)
1473  {
1474  if ($placeholderContent === null) {
1475  $placeholderContent = 'PLACEHOLDER';
1476  }
1477 
1478  $labelPlaceholder = '[' . $placeholderContent . ', WS#' . $this->BE_USER->workspace . ']';
1479  $labelField = ‪$GLOBALS['TCA'][$table]['ctrl']['label'];
1480  if (!isset(‪$GLOBALS['TCA'][$table]['columns'][$labelField]['config']['eval'])) {
1481  return $labelPlaceholder;
1482  }
1483  $evalCodesArray = GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][$table]['columns'][$labelField]['config']['eval'], true);
1484  $transformedLabel = $this->‪checkValue_input_Eval($labelPlaceholder, $evalCodesArray, '', $table);
1485  return $transformedLabel['value'] ?? $labelPlaceholder;
1486  }
1487 
1501  public function ‪fillInFieldArray($table, $id, $fieldArray, $incomingFieldArray, $realPid, $status, $tscPID)
1502  {
1503  // Initialize:
1504  $originalLanguageRecord = null;
1505  $originalLanguage_diffStorage = null;
1506  $diffStorageFlag = false;
1507  // Setting 'currentRecord' and 'checkValueRecord':
1508  if (strpos($id, 'NEW') !== false) {
1509  // Must have the 'current' array - not the values after processing below...
1510  $checkValueRecord = $fieldArray;
1511  // IF $incomingFieldArray is an array, overlay it.
1512  // The point is that when new records are created as copies with flex type fields there might be a field containing information about which DataStructure to use and without that information the flexforms cannot be correctly processed.... This should be OK since the $checkValueRecord is used by the flexform evaluation only anyways...
1513  if (is_array($incomingFieldArray) && is_array($checkValueRecord)) {
1514  ‪ArrayUtility::mergeRecursiveWithOverrule($checkValueRecord, $incomingFieldArray);
1515  }
1516  $currentRecord = $checkValueRecord;
1517  } else {
1518  // We must use the current values as basis for this!
1519  $currentRecord = ($checkValueRecord = $this->‪recordInfo($table, $id, '*'));
1520  // This is done to make the pid positive for offline versions; Necessary to have diff-view for page translations in workspaces.
1521  ‪BackendUtility::fixVersioningPid($table, $currentRecord);
1522  }
1523 
1524  // Get original language record if available:
1525  if (is_array($currentRecord)
1526  && ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']
1527  && ‪$GLOBALS['TCA'][$table]['ctrl']['languageField']
1528  && $currentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0
1529  && ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1530  && (int)$currentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] > 0
1531  ) {
1532  $originalLanguageRecord = $this->‪recordInfo($table, $currentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']], '*');
1533  ‪BackendUtility::workspaceOL($table, $originalLanguageRecord);
1534  $originalLanguage_diffStorage = unserialize(
1535  $currentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']],
1536  ['allowed_classes' => false]
1537  );
1538  }
1539 
1540  $this->checkValue_currentRecord = $checkValueRecord;
1541  // In the following all incoming value-fields are tested:
1542  // - Are the user allowed to change the field?
1543  // - Is the field uid/pid (which are already set)
1544  // - perms-fields for pages-table, then do special things...
1545  // - If the field is nothing of the above and the field is configured in TCA, the fieldvalues are evaluated by ->checkValue
1546  // If everything is OK, the field is entered into $fieldArray[]
1547  foreach ($incomingFieldArray as $field => $fieldValue) {
1548  if (isset($this->excludedTablesAndFields[$table . '-' . $field]) || $this->data_disableFields[$table][$id][$field]) {
1549  continue;
1550  }
1551 
1552  // The field must be editable.
1553  // Checking if a value for language can be changed:
1554  $languageDeny = ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] && (string)‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] === (string)$field && !$this->BE_USER->checkLanguageAccess($fieldValue);
1555  if ($languageDeny) {
1556  continue;
1557  }
1558 
1559  switch ($field) {
1560  case 'uid':
1561  case 'pid':
1562  // Nothing happens, already set
1563  break;
1564  case 'perms_userid':
1565  case 'perms_groupid':
1566  case 'perms_user':
1567  case 'perms_group':
1568  case 'perms_everybody':
1569  // Permissions can be edited by the owner or the administrator
1570  if ($table === 'pages' && ($this->admin || $status === 'new' || $this->‪pageInfo($id, 'perms_userid') == $this->userid)) {
1571  $value = (int)$fieldValue;
1572  switch ($field) {
1573  case 'perms_userid':
1574  case 'perms_groupid':
1575  $fieldArray[$field] = $value;
1576  break;
1577  default:
1578  if ($value >= 0 && $value < (2 ** 5)) {
1579  $fieldArray[$field] = $value;
1580  }
1581  }
1582  }
1583  break;
1584  case 't3ver_oid':
1585  case 't3ver_id':
1586  case 't3ver_wsid':
1587  case 't3ver_state':
1588  case 't3ver_count':
1589  case 't3ver_stage':
1590  case 't3ver_tstamp':
1591  // t3ver_label is not here because it CAN be edited as a regular field!
1592  break;
1593  case 'l10n_state':
1594  $fieldArray[$field] = $fieldValue;
1595  break;
1596  default:
1597  if (isset(‪$GLOBALS['TCA'][$table]['columns'][$field])) {
1598  // Evaluating the value
1599  $res = $this->‪checkValue($table, $field, $fieldValue, $id, $status, $realPid, $tscPID, $incomingFieldArray);
1600  if (array_key_exists('value', $res)) {
1601  $fieldArray[$field] = $res['value'];
1602  }
1603  // Add the value of the original record to the diff-storage content:
1604  if (‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']) {
1605  $originalLanguage_diffStorage[$field] = $this->updateModeL10NdiffDataClear ? '' : $originalLanguageRecord[$field];
1606  $diffStorageFlag = true;
1607  }
1608  // If autoversioning is happening we need to perform a nasty hack. The case is parallel to a similar hack inside checkValue_group_select_file().
1609  // When a copy or version is made of a record, a search is made for any RTEmagic* images in fields having the "images" soft reference parser applied.
1610  // That should be TRUE for RTE fields. If any are found they are duplicated to new names and the file reference in the bodytext is updated accordingly.
1611  // However, with auto-versioning the submitted content of the field will just overwrite the corrected values. This leaves a) lost RTEmagic files and b) creates a double reference to the old files.
1612  // The only solution I can come up with is detecting when auto versioning happens, then see if any RTEmagic images was copied and if so make a stupid string-replace of the content !
1613  if ($this->autoVersioningUpdate === true) {
1614  if (is_array($this->RTEmagic_copyIndex[$table][$id][$field])) {
1615  foreach ($this->RTEmagic_copyIndex[$table][$id][$field] as $oldRTEmagicName => $newRTEmagicName) {
1616  $fieldArray[$field] = str_replace(' src="' . $oldRTEmagicName . '"', ' src="' . $newRTEmagicName . '"', $fieldArray[$field]);
1617  }
1618  }
1619  }
1620  } elseif (‪$GLOBALS['TCA'][$table]['ctrl']['origUid'] === $field) {
1621  // Allow value for original UID to pass by...
1622  $fieldArray[$field] = $fieldValue;
1623  }
1624  }
1625  }
1626 
1627  // Dealing with a page translation, setting "sorting", "pid", "perms_*" to the same values as the original record
1628  if ($table === 'pages' && is_array($originalLanguageRecord)) {
1629  $fieldArray['sorting'] = $originalLanguageRecord['sorting'];
1630  $fieldArray['perms_userid'] = $originalLanguageRecord['perms_userid'];
1631  $fieldArray['perms_groupid'] = $originalLanguageRecord['perms_groupid'];
1632  $fieldArray['perms_user'] = $originalLanguageRecord['perms_user'];
1633  $fieldArray['perms_group'] = $originalLanguageRecord['perms_group'];
1634  $fieldArray['perms_everybody'] = $originalLanguageRecord['perms_everybody'];
1635  }
1636 
1637  // Add diff-storage information:
1638  if ($diffStorageFlag
1639  && !array_key_exists(‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'], $fieldArray)
1640  ) {
1641  // If the field is set it would probably be because of an undo-operation - in which case we should not update the field of course...
1642  $fieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']] = serialize($originalLanguage_diffStorage);
1643  }
1644  // Return fieldArray
1645  return $fieldArray;
1646  }
1647 
1648  /*********************************************
1649  *
1650  * Evaluation of input values
1651  *
1652  ********************************************/
1668  public function ‪checkValue($table, $field, $value, $id, $status, $realPid, $tscPID, $incomingFieldArray = [])
1669  {
1670  // Result array
1671  $res = [];
1672 
1673  // Processing special case of field pages.doktype
1674  if ($table === 'pages' && $field === 'doktype') {
1675  // If the user may not use this specific doktype, we issue a warning
1676  if (!($this->admin || GeneralUtility::inList($this->BE_USER->groupData['pagetypes_select'], $value))) {
1677  if ($this->enableLogging) {
1678  $propArr = $this->‪getRecordProperties($table, $id);
1679  $this->‪log($table, $id, 5, 0, 1, 'You cannot change the \'doktype\' of page \'%s\' to the desired value.', 1, [$propArr['header']], $propArr['event_pid']);
1680  }
1681  return $res;
1682  }
1683  if ($status === 'update') {
1684  // This checks 1) if we should check for disallowed tables and 2) if there are records from disallowed tables on the current page
1685  $onlyAllowedTables = ‪$GLOBALS['PAGES_TYPES'][$value]['onlyAllowedTables'] ?? ‪$GLOBALS['PAGES_TYPES']['default']['onlyAllowedTables'];
1686  if ($onlyAllowedTables) {
1687  // use the real page id (default language)
1688  $recordId = $this->‪getDefaultLanguagePageId($id);
1689  $theWrongTables = $this->‪doesPageHaveUnallowedTables($recordId, $value);
1690  if ($theWrongTables) {
1691  if ($this->enableLogging) {
1692  $propArr = $this->‪getRecordProperties($table, $id);
1693  $this->‪log($table, $id, 5, 0, 1, '\'doktype\' of page \'%s\' could not be changed because the page contains records from disallowed tables; %s', 2, [$propArr['header'], $theWrongTables], $propArr['event_pid']);
1694  }
1695  return $res;
1696  }
1697  }
1698  }
1699  }
1700 
1701  $curValue = null;
1702  if ((int)$id !== 0) {
1703  // Get current value:
1704  $curValueRec = $this->‪recordInfo($table, $id, $field);
1705  // isset() won't work here, since values can be NULL
1706  if ($curValueRec !== null && array_key_exists($field, $curValueRec)) {
1707  $curValue = $curValueRec[$field];
1708  }
1709  }
1710 
1711  if ($table === 'be_users'
1712  && ($field === 'admin' || $field === 'password')
1713  && $status === 'update'
1714  ) {
1715  // Do not allow a non system maintainer admin to change admin flag and password of system maintainers
1716  $systemMaintainers = array_map('intval', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? []);
1717  // False if current user is not in system maintainer list or if switch to user mode is active
1718  $isCurrentUserSystemMaintainer = $this->BE_USER->isSystemMaintainer();
1719  $isTargetUserInSystemMaintainerList = in_array((int)$id, $systemMaintainers, true);
1720  if ($field === 'admin') {
1721  $isFieldChanged = (int)$curValueRec[$field] !== (int)$value;
1722  } else {
1723  $isFieldChanged = $curValueRec[$field] !== $value;
1724  }
1725  if (!$isCurrentUserSystemMaintainer && $isTargetUserInSystemMaintainerList && $isFieldChanged) {
1726  $value = $curValueRec[$field];
1727  $message = GeneralUtility::makeInstance(
1728  FlashMessage::class,
1729  $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.adminCanNotChangeSystemMaintainer'),
1730  '',
1732  true
1733  );
1734  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1735  $flashMessageService->getMessageQueueByIdentifier()->enqueue($message);
1736  }
1737  }
1738 
1739  // Getting config for the field
1740  $tcaFieldConf = $this->‪resolveFieldConfigurationAndRespectColumnsOverrides($table, $field);
1741 
1742  // Create $recFID only for those types that need it
1743  if (
1744  $tcaFieldConf['type'] === 'flex'
1745  || $tcaFieldConf['type'] === 'group' && ($tcaFieldConf['internal_type'] === 'file' || $tcaFieldConf['internal_type'] === 'file_reference')
1746  ) {
1747  // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Deprecation logged by TcaMigration class. Remove type=group handling.
1748  $recFID = $table . ':' . $id . ':' . $field;
1749  } else {
1750  $recFID = null;
1751  }
1752 
1753  // Perform processing:
1754  $res = $this->‪checkValue_SW($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $field, $this->uploadedFileArray[$table][$id][$field], $tscPID, ['incomingFieldArray' => $incomingFieldArray]);
1755  return $res;
1756  }
1757 
1768  protected function ‪resolveFieldConfigurationAndRespectColumnsOverrides(string $table, string $field): array
1769  {
1770  $tcaFieldConf = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'];
1771  $recordType = ‪BackendUtility::getTCAtypeValue($table, $this->checkValue_currentRecord);
1772  $columnsOverridesConfigOfField = ‪$GLOBALS['TCA'][$table]['types'][$recordType]['columnsOverrides'][$field]['config'] ?? null;
1773  if ($columnsOverridesConfigOfField) {
1774  ‪ArrayUtility::mergeRecursiveWithOverrule($tcaFieldConf, $columnsOverridesConfigOfField);
1775  }
1776  return $tcaFieldConf;
1777  }
1778 
1798  public function ‪checkValue_SW($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $field, $uploadedFiles, $tscPID, array $additionalData = null)
1799  {
1800  // Convert to NULL value if defined in TCA
1801  if ($value === null && !empty($tcaFieldConf['eval']) && GeneralUtility::inList($tcaFieldConf['eval'], 'null')) {
1802  $res = ['value' => null];
1803  return $res;
1804  }
1805 
1806  switch ($tcaFieldConf['type']) {
1807  case 'text':
1808  $res = $this->‪checkValueForText($value, $tcaFieldConf, $table, $id, $realPid, $field);
1809  break;
1810  case 'passthrough':
1811  case 'imageManipulation':
1812  case 'user':
1813  $res['value'] = $value;
1814  break;
1815  case 'input':
1816  $res = $this->‪checkValueForInput($value, $tcaFieldConf, $table, $id, $realPid, $field);
1817  break;
1818  case 'slug':
1819  $res = $this->‪checkValueForSlug((string)$value, $tcaFieldConf, $table, $id, (int)$realPid, $field, $additionalData['incomingFieldArray'] ?? []);
1820  break;
1821  case 'check':
1822  $res = $this->‪checkValueForCheck($res, $value, $tcaFieldConf, $table, $id, $realPid, $field);
1823  break;
1824  case 'radio':
1825  $res = $this->‪checkValueForRadio($res, $value, $tcaFieldConf, $table, $id, $realPid, $field);
1826  break;
1827  case 'group':
1828  case 'select':
1829  $res = $this->‪checkValueForGroupSelect($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $recFID, $uploadedFiles, $field);
1830  break;
1831  case 'inline':
1832  $res = $this->‪checkValueForInline($res, $value, $tcaFieldConf, $table, $id, $status, $field, $additionalData);
1833  break;
1834  case 'flex':
1835  // FlexForms are only allowed for real fields.
1836  if ($field) {
1837  $res = $this->‪checkValueForFlex($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $tscPID, $uploadedFiles, $field);
1838  }
1839  break;
1840  default:
1841  // Do nothing
1842  }
1843  $res = $this->‪checkValueForInternalReferences($res, $value, $tcaFieldConf, $table, $id, $field);
1844  return $res;
1845  }
1846 
1865  protected function ‪checkValueForInternalReferences(array $res, $value, $tcaFieldConf, $table, $id, $field)
1866  {
1867  $relevantFieldNames = [
1868  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null,
1869  ‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'] ?? null,
1870  ];
1872  if (
1873  // in case field is empty
1874  empty($field)
1875  // in case the field is not relevant
1876  || !in_array($field, $relevantFieldNames)
1877  // in case the 'value' index has been unset already
1878  || !array_key_exists('value', $res)
1879  // in case it's not a NEW-identifier
1880  || strpos($value, 'NEW') === false
1881  ) {
1882  return $res;
1883  }
1884 
1885  $valueArray = [$value];
1886  $this->remapStackRecords[$table][$id] = ['remapStackIndex' => count($this->remapStack)];
1887  $this->‪addNewValuesToRemapStackChildIds($valueArray);
1888  $this->remapStack[] = [
1889  'args' => [$valueArray, $tcaFieldConf, $id, $table, $field],
1890  'pos' => ['valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 3],
1891  'field' => $field
1892  ];
1893  unset($res['value']);
1894 
1895  return $res;
1896  }
1897 
1909  protected function ‪checkValueForText($value, $tcaFieldConf, $table, $id, $realPid, $field)
1910  {
1911  if (isset($tcaFieldConf['eval']) && $tcaFieldConf['eval'] !== '') {
1912  $cacheId = $this->‪getFieldEvalCacheIdentifier($tcaFieldConf['eval']);
1913  $evalCodesArray = $this->runtimeCache->get($cacheId);
1914  if (!is_array($evalCodesArray)) {
1915  $evalCodesArray = GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
1916  $this->runtimeCache->set($cacheId, $evalCodesArray);
1917  }
1918  $valueArray = $this->‪checkValue_text_Eval($value, $evalCodesArray, $tcaFieldConf['is_in']);
1919  } else {
1920  $valueArray = ['value' => $value];
1921  }
1922 
1923  // Handle richtext transformations
1924  if ($this->dontProcessTransformations) {
1925  return $valueArray;
1926  }
1927  if (isset($tcaFieldConf['enableRichtext']) && (bool)$tcaFieldConf['enableRichtext'] === true) {
1928  $recordType = ‪BackendUtility::getTCAtypeValue($table, $this->checkValue_currentRecord);
1929  $richtextConfigurationProvider = GeneralUtility::makeInstance(Richtext::class);
1930  $richtextConfiguration = $richtextConfigurationProvider->getConfiguration($table, $field, $realPid, $recordType, $tcaFieldConf);
1931  $parseHTML = GeneralUtility::makeInstance(RteHtmlParser::class);
1932  $parseHTML->init($table . ':' . $field, $realPid);
1933  $valueArray['value'] = $parseHTML->RTE_transform($value, [], 'db', $richtextConfiguration);
1934  }
1935 
1936  return $valueArray;
1937  }
1938 
1950  protected function ‪checkValueForInput($value, $tcaFieldConf, $table, $id, $realPid, $field)
1951  {
1952  // Handle native date/time fields
1953  $isDateOrDateTimeField = false;
1954  $format = '';
1955  $emptyValue = '';
1956  $dateTimeTypes = ‪QueryHelper::getDateTimeTypes();
1957  // normal integer "date" fields (timestamps) are handled in checkValue_input_Eval
1958  if (isset($tcaFieldConf['dbType']) && in_array($tcaFieldConf['dbType'], $dateTimeTypes, true)) {
1959  if (empty($value)) {
1960  $value = null;
1961  } else {
1962  $isDateOrDateTimeField = true;
1963  $dateTimeFormats = ‪QueryHelper::getDateTimeFormats();
1964  $format = $dateTimeFormats[$tcaFieldConf['dbType']]['format'];
1965 
1966  // Convert the date/time into a timestamp for the sake of the checks
1967  $emptyValue = $dateTimeFormats[$tcaFieldConf['dbType']]['empty'];
1968  // We expect the ISO 8601 $value to contain a UTC timezone specifier.
1969  // We explicitly fallback to UTC if no timezone specifier is given (e.g. for copy operations).
1970  $dateTime = new \DateTime($value, new \DateTimeZone('UTC'));
1971  // The timestamp (UTC) returned by getTimestamp() will be converted to
1972  // a local time string by gmdate() later.
1973  $value = $value === $emptyValue ? null : $dateTime->getTimestamp();
1974  }
1975  }
1976  // Secures the string-length to be less than max.
1977  if (isset($tcaFieldConf['max']) && (int)$tcaFieldConf['max'] > 0) {
1978  $value = mb_substr((string)$value, 0, (int)$tcaFieldConf['max'], 'utf-8');
1979  }
1980 
1981  if (empty($tcaFieldConf['eval'])) {
1982  $res = ['value' => $value];
1983  } else {
1984  // Process evaluation settings:
1985  $cacheId = $this->‪getFieldEvalCacheIdentifier($tcaFieldConf['eval']);
1986  $evalCodesArray = $this->runtimeCache->get($cacheId);
1987  if (!is_array($evalCodesArray)) {
1988  $evalCodesArray = GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
1989  $this->runtimeCache->set($cacheId, $evalCodesArray);
1990  }
1991 
1992  $res = $this->‪checkValue_input_Eval($value, $evalCodesArray, $tcaFieldConf['is_in'] ?? '', $table);
1993  if (isset($tcaFieldConf['dbType']) && isset($res['value']) && !$res['value']) {
1994  // set the value to null if we have an empty value for a native field
1995  $res['value'] = null;
1996  }
1997 
1998  // Process UNIQUE settings:
1999  // Field is NOT set for flexForms - which also means that uniqueInPid and unique is NOT available for flexForm fields! Also getUnique should not be done for versioning and if PID is -1 ($realPid<0) then versioning is happening...
2000  if ($field && $realPid >= 0 && !empty($res['value'])) {
2001  if (in_array('uniqueInPid', $evalCodesArray, true)) {
2002  $res['value'] = $this->‪getUnique($table, $field, $res['value'], $id, $realPid);
2003  }
2004  if ($res['value'] && in_array('unique', $evalCodesArray, true)) {
2005  $res['value'] = $this->‪getUnique($table, $field, $res['value'], $id);
2006  }
2007  }
2008  }
2009 
2010  // Checking range of value:
2011  // @todo: The "checkbox" option was removed for type=input, this check could be probably relaxed?
2012  if (
2013  isset($tcaFieldConf['range']) && $tcaFieldConf['range']
2014  && (!isset($tcaFieldConf['checkbox']) || $res['value'] != $tcaFieldConf['checkbox'])
2015  && (!isset($tcaFieldConf['default']) || (int)$res['value'] !== (int)$tcaFieldConf['default'])
2016  ) {
2017  if (isset($tcaFieldConf['range']['upper']) && (int)$res['value'] > (int)$tcaFieldConf['range']['upper']) {
2018  $res['value'] = (int)$tcaFieldConf['range']['upper'];
2019  }
2020  if (isset($tcaFieldConf['range']['lower']) && (int)$res['value'] < (int)$tcaFieldConf['range']['lower']) {
2021  $res['value'] = (int)$tcaFieldConf['range']['lower'];
2022  }
2023  }
2024 
2025  // Handle native date/time fields
2026  if ($isDateOrDateTimeField) {
2027  // Convert the timestamp back to a date/time
2028  $res['value'] = $res['value'] ? gmdate($format, $res['value']) : $emptyValue;
2029  }
2030  return $res;
2031  }
2032 
2046  protected function ‪checkValueForSlug(string $value, array $tcaFieldConf, string $table, $id, int $realPid, string $field, array $incomingFieldArray = []): array
2047  {
2048  $workspaceId = $this->BE_USER->workspace;
2049  $helper = GeneralUtility::makeInstance(SlugHelper::class, $table, $field, $tcaFieldConf, $workspaceId);
2050  $fullRecord = array_replace_recursive($this->checkValue_currentRecord, $incomingFieldArray ?? []);
2051  // Generate a value if there is none, otherwise ensure that all characters are cleaned up
2052  if ($value === '') {
2053  $value = $helper->generate($fullRecord, $realPid);
2054  } else {
2055  $value = $helper->sanitize($value);
2056  }
2057 
2058  // In case a workspace is given, and the $realPid(!) still is negative
2059  // this is most probably triggered by versionizeRecord() and a raw record
2060  // copy - thus, uniqueness cannot be determined without having the
2061  // real information
2062  // @todo This is still not explicit, but probably should be
2063  if ($workspaceId > 0 && $realPid === -1
2065  ) {
2066  return ['value' => $value];
2067  }
2068 
2069  // Return directly in case no evaluations are defined
2070  if (empty($tcaFieldConf['eval'])) {
2071  return ['value' => $value];
2072  }
2073 
2074  $state = ‪RecordStateFactory::forName($table)
2075  ->fromArray($fullRecord, $realPid, $id);
2076  $evalCodesArray = GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
2077  if (in_array('unique', $evalCodesArray, true)) {
2078  $value = $helper->buildSlugForUniqueInTable($value, $state);
2079  }
2080  if (in_array('uniqueInSite', $evalCodesArray, true)) {
2081  $value = $helper->buildSlugForUniqueInSite($value, $state);
2082  }
2083  if (in_array('uniqueInPid', $evalCodesArray, true)) {
2084  $value = $helper->buildSlugForUniqueInPid($value, $state);
2085  }
2086 
2087  return ['value' => $value];
2088  }
2089 
2102  protected function ‪checkValueForCheck($res, $value, $tcaFieldConf, $table, $id, $realPid, $field)
2103  {
2104  $items = $tcaFieldConf['items'];
2105  if (!empty($tcaFieldConf['itemsProcFunc'])) {
2107  $processingService = GeneralUtility::makeInstance(ItemProcessingService::class);
2108  $items = $processingService->getProcessingItems(
2109  $table,
2110  $realPid,
2111  $field,
2112  $this->checkValue_currentRecord,
2113  $tcaFieldConf,
2114  $tcaFieldConf['items']
2115  );
2116  }
2117 
2118  $itemC = 0;
2119  if ($items !== null) {
2120  $itemC = count($items);
2121  }
2122  if (!$itemC) {
2123  $itemC = 1;
2124  }
2125  $maxV = (2 ** $itemC) - 1;
2126  if ($value < 0) {
2127  // @todo: throw LogicException here? Negative values for checkbox items do not make sense and indicate a coding error.
2128  $value = 0;
2129  }
2130  if ($value > $maxV) {
2131  // @todo: This case is pretty ugly: If there is an itemsProcFunc registered, and if it returns a dynamic,
2132  // @todo: changing list of items, then it may happen that a value is transformed and vanished checkboxes
2133  // @todo: are permanently removed from the value.
2134  // @todo: Suggestion: Throw an exception instead? Maybe a specific, catchable exception that generates a
2135  // @todo: error message to the user - dynamic item sets via itemProcFunc on check would be a bad idea anyway.
2136  $value = $value & $maxV;
2137  }
2138  if ($field && $realPid >= 0 && $value > 0 && !empty($tcaFieldConf['eval'])) {
2139  $evalCodesArray = GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
2140  $otherRecordsWithSameValue = [];
2141  $maxCheckedRecords = 0;
2142  if (in_array('maximumRecordsCheckedInPid', $evalCodesArray, true)) {
2143  $otherRecordsWithSameValue = $this->‪getRecordsWithSameValue($table, $id, $field, $value, $realPid);
2144  $maxCheckedRecords = (int)$tcaFieldConf['validation']['maximumRecordsCheckedInPid'];
2145  }
2146  if (in_array('maximumRecordsChecked', $evalCodesArray, true)) {
2147  $otherRecordsWithSameValue = $this->‪getRecordsWithSameValue($table, $id, $field, $value);
2148  $maxCheckedRecords = (int)$tcaFieldConf['validation']['maximumRecordsChecked'];
2149  }
2150 
2151  // there are more than enough records with value "1" in the DB
2152  // if so, set this value to "0" again
2153  if ($maxCheckedRecords && count($otherRecordsWithSameValue) >= $maxCheckedRecords) {
2154  $value = 0;
2155  $this->‪log($table, $id, 5, 0, 1, 'Could not activate checkbox for field "%s". A total of %s record(s) can have this checkbox activated. Uncheck other records first in order to activate the checkbox of this record.', -1, [$this->‪getLanguageService()->sL(‪BackendUtility::getItemLabel($table, $field)), $maxCheckedRecords]);
2156  }
2157  }
2158  $res['value'] = $value;
2159  return $res;
2160  }
2161 
2174  protected function ‪checkValueForRadio($res, $value, $tcaFieldConf, $table, $id, $pid, $field)
2175  {
2176  if (is_array($tcaFieldConf['items'])) {
2177  foreach ($tcaFieldConf['items'] as $set) {
2178  if ((string)$set[1] === (string)$value) {
2179  $res['value'] = $value;
2180  break;
2181  }
2182  }
2183  }
2184 
2185  // if no value was found and an itemsProcFunc is defined, check that for the value
2186  if ($tcaFieldConf['itemsProcFunc'] && empty($res['value'])) {
2187  $processingService = GeneralUtility::makeInstance(ItemProcessingService::class);
2188  $processedItems = $processingService->getProcessingItems(
2189  $table,
2190  $pid,
2191  $field,
2192  $this->checkValue_currentRecord,
2193  $tcaFieldConf,
2194  $tcaFieldConf['items']
2195  );
2196 
2197  foreach ($processedItems as $set) {
2198  if ((string)$set[1] === (string)$value) {
2199  $res['value'] = $value;
2200  break;
2201  }
2202  }
2203  }
2204 
2205  return $res;
2206  }
2207 
2223  protected function ‪checkValueForGroupSelect($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $recFID, $uploadedFiles, $field)
2224  {
2225  // Detecting if value sent is an array and if so, implode it around a comma:
2226  if (is_array($value)) {
2227  $value = implode(',', $value);
2228  }
2229  // This converts all occurrences of '&#123;' to the byte 123 in the string - this is needed in very rare cases where file names with special characters (e.g. ???, umlaut) gets sent to the server as HTML entities instead of bytes. The error is done only by MSIE, not Mozilla and Opera.
2230  // Anyway, this should NOT disturb anything else:
2231  $value = $this->‪convNumEntityToByteValue($value);
2232  // When values are sent as group or select they come as comma-separated values which are exploded by this function:
2233  $valueArray = $this->‪checkValue_group_select_explodeSelectGroupValue($value);
2234  // If multiple is not set, remove duplicates:
2235  if (!$tcaFieldConf['multiple']) {
2236  $valueArray = array_unique($valueArray);
2237  }
2238  // If an exclusive key is found, discard all others:
2239  if ($tcaFieldConf['type'] === 'select' && $tcaFieldConf['exclusiveKeys']) {
2240  $exclusiveKeys = GeneralUtility::trimExplode(',', $tcaFieldConf['exclusiveKeys']);
2241  foreach ($valueArray as $index => $key) {
2242  if (in_array($key, $exclusiveKeys, true)) {
2243  $valueArray = [$index => $key];
2244  break;
2245  }
2246  }
2247  }
2248  // This could be a good spot for parsing the array through a validation-function which checks if the values are correct (except that database references are not in their final form - but that is the point, isn't it?)
2249  // NOTE!!! Must check max-items of files before the later check because that check would just leave out file names if there are too many!!
2250  $valueArray = $this->‪applyFiltersToValues($tcaFieldConf, $valueArray);
2251  // Checking for select / authMode, removing elements from $valueArray if any of them is not allowed!
2252  if ($tcaFieldConf['type'] === 'select' && $tcaFieldConf['authMode']) {
2253  $preCount = count($valueArray);
2254  foreach ($valueArray as $index => $key) {
2255  if (!$this->BE_USER->checkAuthMode($table, $field, $key, $tcaFieldConf['authMode'])) {
2256  unset($valueArray[$index]);
2257  }
2258  }
2259  // During the check it turns out that the value / all values were removed - we respond by simply returning an empty array so nothing is written to DB for this field.
2260  if ($preCount && empty($valueArray)) {
2261  return [];
2262  }
2263  }
2264  // For group types:
2265  if ($tcaFieldConf['type'] === 'group'
2266  && in_array($tcaFieldConf['internal_type'], ['file', 'file_reference'], true)) {
2267  // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Deprecation logged by TcaMigration class.
2268  $valueArray = $this->‪checkValue_group_select_file($valueArray, $tcaFieldConf, $curValue, $uploadedFiles, $status, $table, $id, $recFID);
2269  }
2270  // For select types which has a foreign table attached:
2271  $unsetResult = false;
2272  if (
2273  $tcaFieldConf['type'] === 'group' && $tcaFieldConf['internal_type'] === 'db'
2274  || $tcaFieldConf['type'] === 'select' && ($tcaFieldConf['foreign_table'] || isset($tcaFieldConf['special']) && $tcaFieldConf['special'] === 'languages')
2275  ) {
2276  // check, if there is a NEW... id in the value, that should be substituted later
2277  if (strpos($value, 'NEW') !== false) {
2278  $this->remapStackRecords[$table][$id] = ['remapStackIndex' => count($this->remapStack)];
2279  $this->‪addNewValuesToRemapStackChildIds($valueArray);
2280  $this->remapStack[] = [
2281  'func' => 'checkValue_group_select_processDBdata',
2282  'args' => [$valueArray, $tcaFieldConf, $id, $status, $tcaFieldConf['type'], $table, $field],
2283  'pos' => ['valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 5],
2284  'field' => $field
2285  ];
2286  $unsetResult = true;
2287  } else {
2288  $valueArray = $this->‪checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, $tcaFieldConf['type'], $table, $field);
2289  }
2290  }
2291  if (!$unsetResult) {
2292  $newVal = $this->‪checkValue_checkMax($tcaFieldConf, $valueArray);
2293  $res['value'] = $this->‪castReferenceValue(implode(',', $newVal), $tcaFieldConf);
2294  } else {
2295  unset($res['value']);
2296  }
2297  return $res;
2298  }
2299 
2308  protected function ‪applyFiltersToValues(array $tcaFieldConfiguration, array $values)
2309  {
2310  if (empty($tcaFieldConfiguration['filter']) || !is_array($tcaFieldConfiguration['filter'])) {
2311  return $values;
2312  }
2313  foreach ($tcaFieldConfiguration['filter'] as $filter) {
2314  if (empty($filter['userFunc'])) {
2315  continue;
2316  }
2317  $parameters = $filter['parameters'] ?: [];
2318  $parameters['values'] = $values;
2319  $parameters['tcaFieldConfig'] = $tcaFieldConfiguration;
2320  $values = GeneralUtility::callUserFunction($filter['userFunc'], $parameters, $this);
2321  if (!is_array($values)) {
2322  throw new \RuntimeException('Failed calling filter userFunc.', 1336051942);
2323  }
2324  }
2325  return $values;
2326  }
2327 
2344  public function ‪checkValue_group_select_file($valueArray, $tcaFieldConf, $curValue, ‪$uploadedFileArray, $status, $table, $id, $recFID)
2345  {
2346  // If file handling should NOT be bypassed, do processing:
2347  if (!$this->bypassFileHandling) {
2348  // If any files are uploaded, add them to value array
2349  // Numeric index means that there are multiple files
2350  if (isset(‪$uploadedFileArray[0])) {
2351  $uploadedFiles = ‪$uploadedFileArray;
2352  } else {
2353  // There is only one file
2354  $uploadedFiles = [‪$uploadedFileArray];
2355  }
2356  foreach ($uploadedFiles as ‪$uploadedFileArray) {
2357  if (!empty(‪$uploadedFileArray['name']) && ‪$uploadedFileArray['tmp_name'] !== 'none') {
2358  $valueArray[] = ‪$uploadedFileArray['tmp_name'];
2359  $this->alternativeFileName[‪$uploadedFileArray['tmp_name']] = ‪$uploadedFileArray['name'];
2360  }
2361  }
2362  // Creating fileFunc object.
2363  if (!$this->fileFunc) {
2364  $this->fileFunc = GeneralUtility::makeInstance(BasicFileUtility::class);
2365  }
2366  // Setting permitted extensions.
2367  $this->fileFunc->setFileExtensionPermissions($tcaFieldConf['allowed'], $tcaFieldConf['disallowed'] ?: '*');
2368  }
2369  // If there is an upload folder defined:
2370  if ($tcaFieldConf['uploadfolder'] && $tcaFieldConf['internal_type'] === 'file') {
2371  $currentFilesForHistory = null;
2372  // If filehandling should NOT be bypassed, do processing:
2373  if (!$this->bypassFileHandling) {
2374  // For logging..
2375  $propArr = $this->‪getRecordProperties($table, $id);
2376  // Get destination path:
2377  $dest = ‪Environment::getPublicPath() . '/' . $tcaFieldConf['uploadfolder'];
2378  // If we are updating:
2379  if ($status === 'update') {
2380  // Traverse the input values and convert to absolute filenames in case the update happens to an autoVersionized record.
2381  // Background: This is a horrible workaround! The problem is that when a record is auto-versionized the files of the record get copied and therefore get new names which is overridden with the names from the original record in the incoming data meaning both lost files and double-references!
2382  // The only solution I could come up with (except removing support for managing files when autoversioning) was to convert all relative files to absolute names so they are copied again (and existing files deleted). This should keep references intact but means that some files are copied, then deleted after being copied _again_.
2383  // Actually, the same problem applies to database references in case auto-versioning would include sub-records since in such a case references are remapped - and they would be overridden due to the same principle then.
2384  // Illustration of the problem comes here:
2385  // We have a record 123 with a file logo.gif. We open and edit the files header in a workspace. So a new version is automatically made.
2386  // The versions uid is 456 and the file is copied to "logo_01.gif". But the form data that we sent was based on uid 123 and hence contains the filename "logo.gif" from the original.
2387  // The file management code below will do two things: First it will blindly accept "logo.gif" as a file attached to the record (thus creating a double reference) and secondly it will find that "logo_01.gif" was not in the incoming filelist and therefore should be deleted.
2388  // If we prefix the incoming file "logo.gif" with its absolute path it will be seen as a new file added. Thus it will be copied to "logo_02.gif". "logo_01.gif" will still be deleted but since the files are the same the difference is zero - only more processing and file copying for no reason. But it will work.
2389  if ($this->autoVersioningUpdate === true) {
2390  foreach ($valueArray as $key => $theFile) {
2391  // If it is an already attached file...
2392  if ($theFile === ‪PathUtility::basename($theFile)) {
2393  $valueArray[$key] = ‪Environment::getPublicPath() . '/' . $tcaFieldConf['uploadfolder'] . '/' . $theFile;
2394  }
2395  }
2396  }
2397  // Finding the CURRENT files listed, either from MM or from the current record.
2398  $theFileValues = [];
2399  // If MM relations for the files also!
2400  if ($tcaFieldConf['MM']) {
2401  $dbAnalysis = $this->‪createRelationHandlerInstance();
2403  $dbAnalysis->start('', 'files', $tcaFieldConf['MM'], $id);
2404  foreach ($dbAnalysis->itemArray as $item) {
2405  if ($item['id']) {
2406  $theFileValues[] = $item['id'];
2407  }
2408  }
2409  } else {
2410  $theFileValues = GeneralUtility::trimExplode(',', $curValue, true);
2411  }
2412  $currentFilesForHistory = implode(',', $theFileValues);
2413  // DELETE files: If existing files were found, traverse those and register files for deletion which has been removed:
2414  if (!empty($theFileValues)) {
2415  // Traverse the input values and for all input values which match an EXISTING value, remove the existing from $theFileValues array (this will result in an array of all the existing files which should be deleted!)
2416  foreach ($valueArray as $key => $theFile) {
2417  if ($theFile && strpos(GeneralUtility::fixWindowsFilePath($theFile), '/') === false) {
2418  $theFileValues = ‪ArrayUtility::removeArrayEntryByValue($theFileValues, $theFile);
2419  }
2420  }
2421  // This array contains the filenames in the uploadfolder that should be deleted:
2422  foreach ($theFileValues as $key => $theFile) {
2423  $theFile = trim($theFile);
2424  if (@is_file($dest . '/' . $theFile)) {
2425  $this->removeFilesStore[] = $dest . '/' . $theFile;
2426  } elseif ($theFile) {
2427  $this->‪log($table, $id, 5, 0, 1, 'Could not delete file \'%s\' (does not exist). (%s)', 10, [$dest . '/' . $theFile, $recFID], $propArr['event_pid']);
2428  }
2429  }
2430  }
2431  }
2432  // Traverse the submitted values:
2433  foreach ($valueArray as $key => $theFile) {
2434  // Init:
2435  $maxSize = (int)$tcaFieldConf['max_size'];
2436  // Must be cleared. Else a faulty fileref may be inserted if the below code returns an error!
2437  $theDestFile = '';
2438  // a FAL file was added, now resolve the file object and get the absolute path
2439  // @todo in future versions this needs to be modified to handle FAL objects natively
2440  if (!empty($theFile) && ‪MathUtility::canBeInterpretedAsInteger($theFile)) {
2441  $fileObject = ‪ResourceFactory::getInstance()->‪getFileObject($theFile);
2442  $theFile = $fileObject->‪getForLocalProcessing(false);
2443  }
2444  // NEW FILES? If the value contains '/' it indicates, that the file
2445  // is new and should be added to the uploadsdir (whether its absolute or relative does not matter here)
2446  if (strpos(GeneralUtility::fixWindowsFilePath($theFile), '/') !== false) {
2447  // Check various things before copying file:
2448  // File and destination must exist
2449  if (@is_dir($dest) && (@is_file($theFile) || @is_uploaded_file($theFile))) {
2450  // Finding size.
2451  if (is_uploaded_file($theFile) && $theFile == ‪$uploadedFileArray['tmp_name']) {
2452  $fileSize = ‪$uploadedFileArray['size'];
2453  } else {
2454  $fileSize = filesize($theFile);
2455  }
2456  // Check file size:
2457  if (!$maxSize || $fileSize <= $maxSize * 1024) {
2458  // Prepare filename:
2459  $theEndFileName = $this->alternativeFileName[$theFile] ?? $theFile;
2460  $fI = GeneralUtility::split_fileref($theEndFileName);
2461  // Check for allowed extension:
2462  if ($this->fileFunc->checkIfAllowed($fI['fileext'], $dest, $theEndFileName)) {
2463  $theDestFile = $this->fileFunc->getUniqueName($this->fileFunc->cleanFileName($fI['file']), $dest);
2464  // If we have a unique destination filename, then write the file:
2465  if ($theDestFile) {
2466  GeneralUtility::upload_copy_move($theFile, $theDestFile);
2467  // Hook for post-processing the upload action
2468  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processUpload'] ?? [] as $className) {
2469  $hookObject = GeneralUtility::makeInstance($className);
2470  if (!$hookObject instanceof DataHandlerProcessUploadHookInterface) {
2471  throw new \UnexpectedValueException($className . ' must implement interface ' . DataHandlerProcessUploadHookInterface::class, 1279962349);
2472  }
2473  $hookObject->processUpload_postProcessAction($theDestFile, $this);
2474  }
2475  $this->copiedFileMap[$theFile] = $theDestFile;
2476  clearstatcache();
2477  if (!@is_file($theDestFile)) {
2478  $this->‪log($table, $id, 5, 0, 1, 'Copying file \'%s\' failed!: The destination path (%s) may be write protected. Please make it write enabled!. (%s)', 16, [$theFile, ‪PathUtility::dirname($theDestFile), $recFID], $propArr['event_pid']);
2479  }
2480  } else {
2481  $this->‪log($table, $id, 5, 0, 1, 'Copying file \'%s\' failed!: No destination file (%s) possible!. (%s)', 11, [$theFile, $theDestFile, $recFID], $propArr['event_pid']);
2482  }
2483  } else {
2484  $this->‪log($table, $id, 5, 0, 1, 'File extension \'%s\' not allowed. (%s)', 12, [$fI['fileext'], $recFID], $propArr['event_pid']);
2485  }
2486  } else {
2487  $this->‪log($table, $id, 5, 0, 1, 'Filesize (%s) of file \'%s\' exceeds limit (%s). (%s)', 13, [GeneralUtility::formatSize($fileSize), $theFile, GeneralUtility::formatSize($maxSize * 1024), $recFID], $propArr['event_pid']);
2488  }
2489  } else {
2490  $this->‪log($table, $id, 5, 0, 1, 'The destination (%s) or the source file (%s) does not exist. (%s)', 14, [$dest, $theFile, $recFID], $propArr['event_pid']);
2491  }
2492  // If the destination file was created, we will set the new filename in the value array, otherwise unset the entry in the value array!
2493  if (@is_file($theDestFile)) {
2494  $info = GeneralUtility::split_fileref($theDestFile);
2495  // The value is set to the new filename
2496  $valueArray[$key] = $info['file'];
2497  } else {
2498  // The value is set to the new filename
2499  unset($valueArray[$key]);
2500  }
2501  }
2502  }
2503  }
2504  // If MM relations for the files, we will set the relations as MM records and change the valuearray to contain a single entry with a count of the number of files!
2505  if ($tcaFieldConf['MM']) {
2507  $dbAnalysis = $this->‪createRelationHandlerInstance();
2508  // Dummy
2509  $dbAnalysis->tableArray['files'] = [];
2510  foreach ($valueArray as $key => $theFile) {
2511  // Explode files
2512  $dbAnalysis->itemArray[]['id'] = $theFile;
2513  }
2514  if ($status === 'update') {
2515  $dbAnalysis->writeMM($tcaFieldConf['MM'], $id, 0);
2516  $newFiles = implode(',', $dbAnalysis->getValueArray());
2517  list(, , $recFieldName) = explode(':', $recFID);
2518  if ($currentFilesForHistory != $newFiles) {
2519  $this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$recFieldName] = $currentFilesForHistory;
2520  $this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$recFieldName] = $newFiles;
2521  } else {
2522  $this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$recFieldName] = '';
2523  $this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$recFieldName] = '';
2524  }
2525  } else {
2526  $this->dbAnalysisStore[] = [$dbAnalysis, $tcaFieldConf['MM'], $id, 0];
2527  }
2528  $valueArray = $dbAnalysis->countItems();
2529  }
2530  } else {
2531  if (!empty($valueArray)) {
2532  // If filehandling should NOT be bypassed, do processing:
2533  if (!$this->bypassFileHandling) {
2534  // For logging..
2535  $propArr = $this->‪getRecordProperties($table, $id);
2536  foreach ($valueArray as &$theFile) {
2537  // FAL handling: it's a UID, thus it is resolved to the absolute path
2538  if (!empty($theFile) && ‪MathUtility::canBeInterpretedAsInteger($theFile)) {
2539  $fileObject = ‪ResourceFactory::getInstance()->‪getFileObject($theFile);
2540  $theFile = $fileObject->‪getForLocalProcessing(false);
2541  }
2542  if ($this->alternativeFilePath[$theFile]) {
2543  // If alternative File Path is set for the file, then it was an import
2544  // don't import the file if it already exists
2545  if (@is_file(‪Environment::getPublicPath() . '/' . $this->alternativeFilePath[$theFile])) {
2546  $theFile = ‪Environment::getPublicPath() . '/' . $this->alternativeFilePath[$theFile];
2547  } elseif (@is_file($theFile)) {
2548  $dest = ‪PathUtility::dirname(‪Environment::getPublicPath() . '/' . $this->alternativeFilePath[$theFile]);
2549  if (!@is_dir($dest)) {
2550  GeneralUtility::mkdir_deep($dest);
2551  }
2552  // Init:
2553  $maxSize = (int)$tcaFieldConf['max_size'];
2554  // Must be cleared. Else a faulty fileref may be inserted if the below code returns an error!
2555  $theDestFile = '';
2556  $fileSize = filesize($theFile);
2557  // Check file size:
2558  if (!$maxSize || $fileSize <= $maxSize * 1024) {
2559  // Prepare filename:
2560  $theEndFileName = $this->alternativeFileName[$theFile] ?? $theFile;
2561  $fI = GeneralUtility::split_fileref($theEndFileName);
2562  // Check for allowed extension:
2563  if ($this->fileFunc->checkIfAllowed($fI['fileext'], $dest, $theEndFileName)) {
2564  $theDestFile = ‪Environment::getPublicPath() . '/' . $this->alternativeFilePath[$theFile];
2565  // Write the file:
2566  if ($theDestFile) {
2567  GeneralUtility::upload_copy_move($theFile, $theDestFile);
2568  $this->copiedFileMap[$theFile] = $theDestFile;
2569  clearstatcache();
2570  if (!@is_file($theDestFile)) {
2571  $this->‪log($table, $id, 5, 0, 1, 'Copying file \'%s\' failed!: The destination path (%s) may be write protected. Please make it write enabled!. (%s)', 16, [$theFile, ‪PathUtility::dirname($theDestFile), $recFID], $propArr['event_pid']);
2572  }
2573  } else {
2574  $this->‪log($table, $id, 5, 0, 1, 'Copying file \'%s\' failed!: No destination file (%s) possible!. (%s)', 11, [$theFile, $theDestFile, $recFID], $propArr['event_pid']);
2575  }
2576  } else {
2577  $this->‪log($table, $id, 5, 0, 1, 'File extension \'%s\' not allowed. (%s)', 12, [$fI['fileext'], $recFID], $propArr['event_pid']);
2578  }
2579  } else {
2580  $this->‪log($table, $id, 5, 0, 1, 'Filesize (%s) of file \'%s\' exceeds limit (%s). (%s)', 13, [GeneralUtility::formatSize($fileSize), $theFile, GeneralUtility::formatSize($maxSize * 1024), $recFID], $propArr['event_pid']);
2581  }
2582  // If the destination file was created, we will set the new filename in the value array, otherwise unset the entry in the value array!
2583  if (@is_file($theDestFile)) {
2584  // The value is set to the new filename
2585  $theFile = $theDestFile;
2586  } else {
2587  // The value is set to the new filename
2588  unset($theFile);
2589  }
2590  }
2591  }
2592  if (!empty($theFile)) {
2593  $theFile = GeneralUtility::fixWindowsFilePath($theFile);
2594  if (GeneralUtility::isFirstPartOfStr($theFile, ‪Environment::getPublicPath())) {
2595  $theFile = ‪PathUtility::stripPathSitePrefix($theFile);
2596  }
2597  }
2598  }
2599  unset($theFile);
2600  }
2601  }
2602  }
2603  return $valueArray;
2604  }
2605 
2623  protected function ‪checkValueForFlex($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $tscPID, $uploadedFiles, $field)
2624  {
2625  if (is_array($value)) {
2626  // This value is necessary for flex form processing to happen on flexform fields in page records when they are copied.
2627  // Problem: when copying a page, flexform XML comes along in the array for the new record - but since $this->checkValue_currentRecord
2628  // does not have a uid or pid for that sake, the FlexFormTools->getDataStructureIdentifier() function returns no good DS. For new
2629  // records we do know the expected PID so therefore we send that with this special parameter. Only active when larger than zero.
2631  if ($status === 'new') {
2632  $row['pid'] = $realPid;
2633  }
2634 
2635  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
2636 
2637  // Get data structure. The methods may throw various exceptions, with some of them being
2638  // ok in certain scenarios, for instance on new record rows. Those are ok to "eat" here
2639  // and substitute with a dummy DS.
2640  $dataStructureArray = ['sheets' => ['sDEF' => []]];
2641  try {
2642  $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
2643  ['config' => $tcaFieldConf],
2644  $table,
2645  $field,
2646  $row
2647  );
2648 
2649  $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
2650  } catch (‪InvalidParentRowException $e) {
2651  } catch (InvalidParentRowLoopException $e) {
2652  } catch (InvalidParentRowRootException $e) {
2653  } catch (InvalidPointerFieldValueException $e) {
2654  } catch (InvalidIdentifierException $e) {
2655  }
2656 
2657  // Get current value array:
2658  $currentValueArray = (string)$curValue !== '' ? GeneralUtility::xml2array($curValue) : [];
2659  if (!is_array($currentValueArray)) {
2660  $currentValueArray = [];
2661  }
2662  // Remove all old meta for languages...
2663  // Evaluation of input values:
2664  $value['data'] = $this->‪checkValue_flex_procInData($value['data'] ?? [], $currentValueArray['data'] ?? [], $uploadedFiles['data'] ?? [], $dataStructureArray, [$table, $id, $curValue, $status, $realPid, $recFID, $tscPID]);
2665  // Create XML from input value:
2666  $xmlValue = $this->‪checkValue_flexArray2Xml($value, true);
2667 
2668  // Here we convert the currently submitted values BACK to an array, then merge the two and then BACK to XML again. This is needed to ensure the charsets are the same
2669  // (provided that the current value was already stored IN the charset that the new value is converted to).
2670  $arrValue = GeneralUtility::xml2array($xmlValue);
2671 
2672  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['checkFlexFormValue'] ?? [] as $className) {
2673  $hookObject = GeneralUtility::makeInstance($className);
2674  if (method_exists($hookObject, 'checkFlexFormValue_beforeMerge')) {
2675  $hookObject->checkFlexFormValue_beforeMerge($this, $currentValueArray, $arrValue);
2676  }
2677  }
2678 
2679  ‪ArrayUtility::mergeRecursiveWithOverrule($currentValueArray, $arrValue);
2680  $xmlValue = $this->‪checkValue_flexArray2Xml($currentValueArray, true);
2681 
2682  // Action commands (sorting order and removals of elements) for flexform sections,
2683  // see FormEngine for the use of this GP parameter
2684  $actionCMDs = GeneralUtility::_GP('_ACTION_FLEX_FORMdata');
2685  if (is_array($actionCMDs[$table][$id][$field]['data'] ?? null)) {
2686  $arrValue = GeneralUtility::xml2array($xmlValue);
2687  $this->‪_ACTION_FLEX_FORMdata($arrValue['data'], $actionCMDs[$table][$id][$field]['data']);
2688  $xmlValue = $this->‪checkValue_flexArray2Xml($arrValue, true);
2689  }
2690  // Create the value XML:
2691  $res['value'] = '';
2692  $res['value'] .= $xmlValue;
2693  } else {
2694  // Passthrough...:
2695  $res['value'] = $value;
2696  }
2697 
2698  return $res;
2699  }
2700 
2708  public function ‪checkValue_flexArray2Xml($array, $addPrologue = false)
2709  {
2711  $flexObj = GeneralUtility::makeInstance(FlexFormTools::class);
2712  return $flexObj->flexArray2Xml($array, $addPrologue);
2713  }
2714 
2722  protected function ‪_ACTION_FLEX_FORMdata(&$valueArray, $actionCMDs)
2723  {
2724  if (!is_array($valueArray) || !is_array($actionCMDs)) {
2725  return;
2726  }
2727 
2728  foreach ($actionCMDs as $key => $value) {
2729  if ($key === '_ACTION') {
2730  // First, check if there are "commands":
2731  if (current($actionCMDs[$key]) === '') {
2732  continue;
2733  }
2734 
2735  asort($actionCMDs[$key]);
2736  $newValueArray = [];
2737  foreach ($actionCMDs[$key] as $idx => $order) {
2738  // Just one reflection here: It is clear that when removing elements from a flexform, then we will get lost
2739  // files unless we act on this delete operation by traversing and deleting files that were referred to.
2740  if ($order !== 'DELETE') {
2741  $newValueArray[$idx] = $valueArray[$idx];
2742  }
2743  unset($valueArray[$idx]);
2744  }
2745  $valueArray += $newValueArray;
2746  } elseif (is_array($actionCMDs[$key]) && isset($valueArray[$key])) {
2747  $this->‪_ACTION_FLEX_FORMdata($valueArray[$key], $actionCMDs[$key]);
2748  }
2749  }
2750  }
2751 
2763  public function ‪checkValue_inline($res, $value, $tcaFieldConf, $PP, $field, array $additionalData = null)
2764  {
2765  list($table, $id, , $status) = $PP;
2766  $this->‪checkValueForInline($res, $value, $tcaFieldConf, $table, $id, $status, $field, $additionalData);
2767  }
2783  public function ‪checkValueForInline($res, $value, $tcaFieldConf, $table, $id, $status, $field, array $additionalData = null)
2784  {
2785  if (!$tcaFieldConf['foreign_table']) {
2786  // Fatal error, inline fields should always have a foreign_table defined
2787  return false;
2788  }
2789  // When values are sent they come as comma-separated values which are exploded by this function:
2790  $valueArray = GeneralUtility::trimExplode(',', $value);
2791  // Remove duplicates: (should not be needed)
2792  $valueArray = array_unique($valueArray);
2793  // Example for received data:
2794  // $value = 45,NEW4555fdf59d154,12,123
2795  // We need to decide whether we use the stack or can save the relation directly.
2796  if (!empty($value) && (strpos($value, 'NEW') !== false || !‪MathUtility::canBeInterpretedAsInteger($id))) {
2797  $this->remapStackRecords[$table][$id] = ['remapStackIndex' => count($this->remapStack)];
2798  $this->‪addNewValuesToRemapStackChildIds($valueArray);
2799  $this->remapStack[] = [
2800  'func' => 'checkValue_inline_processDBdata',
2801  'args' => [$valueArray, $tcaFieldConf, $id, $status, $table, $field, $additionalData],
2802  'pos' => ['valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 4],
2803  'additionalData' => $additionalData,
2804  'field' => $field,
2805  ];
2806  unset($res['value']);
2807  } elseif ($value || ‪MathUtility::canBeInterpretedAsInteger($id)) {
2808  $res['value'] = $this->‪checkValue_inline_processDBdata($valueArray, $tcaFieldConf, $id, $status, $table, $field, $additionalData);
2809  }
2810  return $res;
2811  }
2812 
2821  public function ‪checkValue_checkMax($tcaFieldConf, $valueArray)
2822  {
2823  // BTW, checking for min and max items here does NOT make any sense when MM is used because the above function
2824  // calls will just return an array with a single item (the count) if MM is used... Why didn't I perform the check
2825  // before? Probably because we could not evaluate the validity of record uids etc... Hmm...
2826  // NOTE to the comment: It's not really possible to check for too few items, because you must then determine first,
2827  // if the field is actual used regarding the CType.
2828  $maxitems = isset($tcaFieldConf['maxitems']) ? (int)$tcaFieldConf['maxitems'] : 99999;
2829  return array_slice($valueArray, 0, $maxitems);
2830  }
2831 
2832  /*********************************************
2833  *
2834  * Helper functions for evaluation functions.
2835  *
2836  ********************************************/
2847  public function ‪getUnique($table, $field, $value, $id, $newPid = 0)
2848  {
2849  if (!is_array(‪$GLOBALS['TCA'][$table]) || !is_array(‪$GLOBALS['TCA'][$table]['columns'][$field])) {
2850  // Field is not configured in TCA
2851  return $value;
2852  }
2853 
2854  if ((string)‪$GLOBALS['TCA'][$table]['columns'][$field]['l10n_mode'] === 'exclude') {
2855  $transOrigPointerField = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
2856  $l10nParent = (int)$this->checkValue_currentRecord[$transOrigPointerField];
2857  if ($l10nParent > 0) {
2858  // Current record is a translation and l10n_mode "exclude" just copies the value from source language
2859  return $value;
2860  }
2861  }
2862 
2863  $newValue = $value;
2864  $statement = $this->‪getUniqueCountStatement($newValue, $table, $field, (int)$id, (int)$newPid);
2865  // For as long as records with the test-value existing, try again (with incremented numbers appended)
2866  if ($statement->fetchColumn()) {
2867  for ($counter = 0; $counter <= 100; $counter++) {
2868  $newValue = $value . $counter;
2869  $statement->bindValue(1, $newValue);
2870  $statement->execute();
2871  if (!$statement->fetchColumn()) {
2872  break;
2873  }
2874  }
2875  }
2876 
2877  return $newValue;
2878  }
2879 
2890  protected function ‪getUniqueCountStatement(
2891  string $value,
2892  string $table,
2893  string $field,
2894  int $uid,
2895  int $pid
2896  ): Statement {
2897  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
2898  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
2899  $queryBuilder
2900  ->count('uid')
2901  ->from($table)
2902  ->where(
2903  $queryBuilder->expr()->eq($field, $queryBuilder->createPositionalParameter($value, \PDO::PARAM_STR)),
2904  $queryBuilder->expr()->neq('uid', $queryBuilder->createPositionalParameter($uid, \PDO::PARAM_INT))
2905  );
2906  if ($pid !== 0) {
2907  $queryBuilder->andWhere(
2908  $queryBuilder->expr()->eq('pid', $queryBuilder->createPositionalParameter($pid, \PDO::PARAM_INT))
2909  );
2910  } else {
2911  // pid>=0 for versioning
2912  $queryBuilder->andWhere(
2913  $queryBuilder->expr()->gte('pid', $queryBuilder->createPositionalParameter(0, \PDO::PARAM_INT))
2914  );
2915  }
2916 
2917  return $queryBuilder->execute();
2918  }
2919 
2931  public function ‪getRecordsWithSameValue($tableName, $uid, $fieldName, $value, $pageId = 0)
2932  {
2933  $result = [];
2934  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
2935  return $result;
2936  }
2937 
2938  $uid = (int)$uid;
2939  $pageId = (int)$pageId;
2940 
2941  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
2942  $queryBuilder->getRestrictions()
2943  ->removeAll()
2944  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2945  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
2946 
2947  $queryBuilder->select('*')
2948  ->from($tableName)
2949  ->where(
2950  $queryBuilder->expr()->eq(
2951  $fieldName,
2952  $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR)
2953  ),
2954  $queryBuilder->expr()->neq(
2955  'uid',
2956  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
2957  )
2958  );
2959 
2960  if ($pageId) {
2961  $queryBuilder->andWhere(
2962  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT))
2963  );
2964  } else {
2965  $queryBuilder->andWhere(
2966  $queryBuilder->expr()->gte('pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
2967  );
2968  }
2969 
2970  $result = $queryBuilder->execute()->fetchAll();
2971 
2972  return $result;
2973  }
2974 
2981  public function ‪checkValue_text_Eval($value, $evalArray, $is_in)
2982  {
2983  $res = [];
2984  $set = true;
2985  foreach ($evalArray as $func) {
2986  switch ($func) {
2987  case 'trim':
2988  $value = trim($value);
2989  break;
2990  case 'required':
2991  if (!$value) {
2992  $set = false;
2993  }
2994  break;
2995  default:
2996  if (isset(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
2997  if (class_exists($func)) {
2998  $evalObj = GeneralUtility::makeInstance($func);
2999  if (method_exists($evalObj, 'evaluateFieldValue')) {
3000  $value = $evalObj->evaluateFieldValue($value, $is_in, $set);
3001  }
3002  }
3003  }
3004  }
3005  }
3006  if ($set) {
3007  $res['value'] = $value;
3008  }
3009  return $res;
3010  }
3011 
3021  public function ‪checkValue_input_Eval($value, $evalArray, $is_in, string $table = ''): array
3022  {
3023  $res = [];
3024  $set = true;
3025  foreach ($evalArray as $func) {
3026  switch ($func) {
3027  case 'int':
3028  case 'year':
3029  $value = (int)$value;
3030  break;
3031  case 'time':
3032  case 'timesec':
3033  // If $value is a pure integer we have the number of seconds, we can store that directly
3034  if ($value !== '' && !‪MathUtility::canBeInterpretedAsInteger($value)) {
3035  // $value is an ISO 8601 date
3036  $value = (new \DateTime($value))->getTimestamp();
3037  }
3038  break;
3039  case 'date':
3040  case 'datetime':
3041  // If $value is a pure integer we have the number of seconds, we can store that directly
3042  if ($value !== null && $value !== '' && !‪MathUtility::canBeInterpretedAsInteger($value)) {
3043  // The value we receive from JS is an ISO 8601 date, which is always in UTC. (the JS code works like that, on purpose!)
3044  // For instance "1999-11-11T11:11:11Z"
3045  // Since the user actually specifies the time in the server's local time, we need to mangle this
3046  // to reflect the server TZ. So we make this 1999-11-11T11:11:11+0200 (assuming Europe/Vienna here)
3047  // In the database we store the date in UTC (1999-11-11T09:11:11Z), hence we take the timestamp of this converted value.
3048  // For achieving this we work with timestamps only (which are UTC) and simply adjust it for the
3049  // TZ difference.
3050  try {
3051  // Make the date from JS a timestamp
3052  $value = (new \DateTime($value))->getTimestamp();
3053  } catch (\Exception $e) {
3054  // set the default timezone value to achieve the value of 0 as a result
3055  $value = (int)date('Z', 0);
3056  }
3057 
3058  // @todo this hacky part is problematic when it comes to times around DST switch! Add test to prove that this is broken.
3059  $value -= date('Z', $value);
3060  }
3061  break;
3062  case 'double2':
3063  $value = preg_replace('/[^0-9,\\.-]/', '', $value);
3064  $negative = $value[0] === '-';
3065  $value = strtr($value, [',' => '.', '-' => '']);
3066  if (strpos($value, '.') === false) {
3067  $value .= '.0';
3068  }
3069  $valueArray = explode('.', $value);
3070  $dec = array_pop($valueArray);
3071  $value = implode('', $valueArray) . '.' . $dec;
3072  if ($negative) {
3073  $value *= -1;
3074  }
3075  $value = number_format($value, 2, '.', '');
3076  break;
3077  case 'md5':
3078  if (strlen($value) !== 32) {
3079  $set = false;
3080  }
3081  break;
3082  case 'trim':
3083  $value = trim($value);
3084  break;
3085  case 'upper':
3086  $value = mb_strtoupper($value, 'utf-8');
3087  break;
3088  case 'lower':
3089  $value = mb_strtolower($value, 'utf-8');
3090  break;
3091  case 'required':
3092  if (!isset($value) || $value === '') {
3093  $set = false;
3094  }
3095  break;
3096  case 'is_in':
3097  $c = mb_strlen($value);
3098  if ($c) {
3099  $newVal = '';
3100  for ($a = 0; $a < $c; $a++) {
3101  $char = mb_substr($value, $a, 1);
3102  if (mb_strpos($is_in, $char) !== false) {
3103  $newVal .= $char;
3104  }
3105  }
3106  $value = $newVal;
3107  }
3108  break;
3109  case 'nospace':
3110  $value = str_replace(' ', '', $value);
3111  break;
3112  case 'alpha':
3113  $value = preg_replace('/[^a-zA-Z]/', '', $value);
3114  break;
3115  case 'num':
3116  $value = preg_replace('/[^0-9]/', '', $value);
3117  break;
3118  case 'alphanum':
3119  $value = preg_replace('/[^a-zA-Z0-9]/', '', $value);
3120  break;
3121  case 'alphanum_x':
3122  $value = preg_replace('/[^a-zA-Z0-9_-]/', '', $value);
3123  break;
3124  case 'domainname':
3125  if (!preg_match('/^[a-z0-9.\\-]*$/i', $value)) {
3126  $value = GeneralUtility::idnaEncode($value);
3127  }
3128  break;
3129  case 'email':
3130  if ((string)$value !== '') {
3131  $this->‪checkValue_input_ValidateEmail($value, $set);
3132  }
3133  break;
3134  case 'saltedPassword':
3135  // An incoming value is either the salted password if the user did not change existing password
3136  // when submitting the form, or a plaintext new password that needs to be turned into a salted password now.
3137  // The strategy is to see if a salt instance can be created from the incoming value. If so,
3138  // no new password was submitted and we keep the value. If no salting instance can be created,
3139  // incoming value must be a new plain text value that needs to be hashed.
3140  $hashMethod = substr($value, 0, 2);
3141  // The old scheduler task turned existing non-salted passwords into salted hashes by taking the simple md5
3142  // and using that as 'password' and make a salted md5 from given hash. Those where then prefixed with 'M'.
3143  // PasswordHashFactory->get($value) only recognizes these salts if we cut off the M again.
3144  // @todo @deprecated: $isDeprecatedSaltedHash should be removed in TYPO3 v10.0 as dedicated breaking patch, similar
3145  // @todo to authUser() of AuthenticationService::class
3146  $isDeprecatedSaltedHash = $hashMethod === 'M$';
3147  $tempValue = $isDeprecatedSaltedHash ? substr($value, 1) : $value;
3148  $hashFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
3149  $mode = $table === 'fe_users' ? 'FE' : 'BE';
3150  try {
3151  $hashFactory->get($tempValue, $mode);
3152  } catch (‪InvalidPasswordHashException $e) {
3153  // We got no salted password instance, incoming value must be a new plaintext password
3154  // Get an instance of the current configured salted password strategy and hash the value
3155  $newHashInstance = $hashFactory->getDefaultHashInstance($mode);
3156  $value = $newHashInstance->getHashedPassword($value);
3157  }
3158  break;
3159  default:
3160  if (isset(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
3161  if (class_exists($func)) {
3162  $evalObj = GeneralUtility::makeInstance($func);
3163  if (method_exists($evalObj, 'evaluateFieldValue')) {
3164  $value = $evalObj->evaluateFieldValue($value, $is_in, $set);
3165  }
3166  }
3167  }
3168  }
3169  }
3170  if ($set) {
3171  $res['value'] = $value;
3172  }
3173  return $res;
3174  }
3175 
3186  protected function ‪checkValue_input_ValidateEmail($value, &$set)
3187  {
3188  if (GeneralUtility::validEmail($value)) {
3189  return;
3190  }
3191 
3192  $set = false;
3194  $message = GeneralUtility::makeInstance(
3195  FlashMessage::class,
3196  sprintf($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.invalidEmail'), $value),
3197  '', // header is optional
3199  true // whether message should be stored in session
3200  );
3202  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
3203  $flashMessageService->getMessageQueueByIdentifier()->enqueue($message);
3204  }
3205 
3218  public function ‪checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, $type, $currentTable, $currentField)
3219  {
3220  if ($type === 'group') {
3221  $tables = $tcaFieldConf['allowed'];
3222  } elseif (!empty($tcaFieldConf['special']) && $tcaFieldConf['special'] === 'languages') {
3223  $tables = 'sys_language';
3224  } else {
3225  $tables = $tcaFieldConf['foreign_table'];
3226  }
3227  $prep = $type === 'group' ? $tcaFieldConf['prepend_tname'] : '';
3228  $newRelations = implode(',', $valueArray);
3230  $dbAnalysis = $this->‪createRelationHandlerInstance();
3231  $dbAnalysis->registerNonTableValues = !empty($tcaFieldConf['allowNonIdValues']);
3232  $dbAnalysis->start($newRelations, $tables, '', 0, $currentTable, $tcaFieldConf);
3233  if ($tcaFieldConf['MM']) {
3234  // convert submitted items to use version ids instead of live ids
3235  // (only required for MM relations in a workspace context)
3236  $dbAnalysis->convertItemArray();
3237  if ($status === 'update') {
3239  $oldRelations_dbAnalysis = $this->‪createRelationHandlerInstance();
3240  $oldRelations_dbAnalysis->registerNonTableValues = !empty($tcaFieldConf['allowNonIdValues']);
3241  // Db analysis with $id will initialize with the existing relations
3242  $oldRelations_dbAnalysis->start('', $tables, $tcaFieldConf['MM'], $id, $currentTable, $tcaFieldConf);
3243  $oldRelations = implode(',', $oldRelations_dbAnalysis->getValueArray());
3244  $dbAnalysis->writeMM($tcaFieldConf['MM'], $id, $prep);
3245  if ($oldRelations != $newRelations) {
3246  $this->mmHistoryRecords[$currentTable . ':' . $id]['oldRecord'][$currentField] = $oldRelations;
3247  $this->mmHistoryRecords[$currentTable . ':' . $id]['newRecord'][$currentField] = $newRelations;
3248  } else {
3249  $this->mmHistoryRecords[$currentTable . ':' . $id]['oldRecord'][$currentField] = '';
3250  $this->mmHistoryRecords[$currentTable . ':' . $id]['newRecord'][$currentField] = '';
3251  }
3252  } else {
3253  $this->dbAnalysisStore[] = [$dbAnalysis, $tcaFieldConf['MM'], $id, $prep, $currentTable];
3254  }
3255  $valueArray = $dbAnalysis->countItems();
3256  } else {
3257  $valueArray = $dbAnalysis->getValueArray($prep);
3258  }
3259  // Here we should see if 1) the records exist anymore, 2) which are new and check if the BE_USER has read-access to the new ones.
3260  return $valueArray;
3261  }
3262 
3270  {
3271  $valueArray = GeneralUtility::trimExplode(',', $value, true);
3272  foreach ($valueArray as &$newVal) {
3273  $temp = explode('|', $newVal, 2);
3274  $newVal = str_replace(['|', ','], '', rawurldecode($temp[0]));
3275  }
3276  unset($newVal);
3277  return $valueArray;
3278  }
3279 
3295  public function ‪checkValue_flex_procInData($dataPart, $dataPart_current, $uploadedFiles, $dataStructure, $pParams, $callBackFunc = '', array $workspaceOptions = [])
3296  {
3297  if (is_array($dataPart)) {
3298  foreach ($dataPart as $sKey => $sheetDef) {
3299  if (isset($dataStructure['sheets'][$sKey]) && is_array($dataStructure['sheets'][$sKey]) && is_array($sheetDef)) {
3300  foreach ($sheetDef as $lKey => $lData) {
3302  $dataPart[$sKey][$lKey],
3303  $dataPart_current[$sKey][$lKey],
3304  $uploadedFiles[$sKey][$lKey],
3305  $dataStructure['sheets'][$sKey]['ROOT']['el'],
3306  $pParams,
3307  $callBackFunc,
3308  $sKey . '/' . $lKey . '/',
3309  $workspaceOptions
3310  );
3311  }
3312  }
3313  }
3314  }
3315  return $dataPart;
3316  }
3317 
3332  public function ‪checkValue_flex_procInData_travDS(&$dataValues, $dataValues_current, $uploadedFiles, $DSelements, $pParams, $callBackFunc, $structurePath, array $workspaceOptions = [])
3333  {
3334  if (!is_array($DSelements)) {
3335  return;
3336  }
3337 
3338  // For each DS element:
3339  foreach ($DSelements as $key => $dsConf) {
3340  // Array/Section:
3341  if ($DSelements[$key]['type'] === 'array') {
3342  if (!is_array($dataValues[$key]['el'])) {
3343  continue;
3344  }
3345 
3346  if ($DSelements[$key]['section']) {
3347  foreach ($dataValues[$key]['el'] as $ik => $el) {
3348  if (!is_array($el)) {
3349  continue;
3350  }
3351 
3352  if (!is_array($dataValues_current[$key]['el'])) {
3353  $dataValues_current[$key]['el'] = [];
3354  }
3355  $theKey = key($el);
3356  if (!is_array($dataValues[$key]['el'][$ik][$theKey]['el'])) {
3357  continue;
3358  }
3359 
3360  $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'] : [], $uploadedFiles[$key]['el'][$ik][$theKey]['el'], $DSelements[$key]['el'][$theKey]['el'], $pParams, $callBackFunc, $structurePath . $key . '/el/' . $ik . '/' . $theKey . '/el/', $workspaceOptions);
3361  }
3362  } else {
3363  if (!isset($dataValues[$key]['el'])) {
3364  $dataValues[$key]['el'] = [];
3365  }
3366  $this->‪checkValue_flex_procInData_travDS($dataValues[$key]['el'], $dataValues_current[$key]['el'], $uploadedFiles[$key]['el'], $DSelements[$key]['el'], $pParams, $callBackFunc, $structurePath . $key . '/el/', $workspaceOptions);
3367  }
3368  } else {
3369  // When having no specific sheets, it's "TCEforms.config", when having a sheet, it's just "config"
3370  $fieldConfiguration = $dsConf['TCEforms']['config'] ?? $dsConf['config'] ?? null;
3371  // init with value from config for passthrough fields
3372  if (!empty($fieldConfiguration['type']) && $fieldConfiguration['type'] === 'passthrough') {
3373  if (!empty($dataValues_current[$key]['vDEF'])) {
3374  // If there is existing value, keep it
3375  $dataValues[$key]['vDEF'] = $dataValues_current[$key]['vDEF'];
3376  } elseif (
3377  !empty($fieldConfiguration['default'])
3378  && isset($pParams[1])
3380  ) {
3381  // If is new record and a default is specified for field, use it.
3382  $dataValues[$key]['vDEF'] = $fieldConfiguration['default'];
3383  }
3384  }
3385  if (!is_array($fieldConfiguration) || !is_array($dataValues[$key])) {
3386  continue;
3387  }
3388 
3389  foreach ($dataValues[$key] as $vKey => $data) {
3390  if ($callBackFunc) {
3391  if (is_object($this->callBackObj)) {
3392  $res = $this->callBackObj->{$callBackFunc}($pParams, $fieldConfiguration, $dataValues[$key][$vKey], $dataValues_current[$key][$vKey], $uploadedFiles[$key][$vKey], $structurePath . $key . '/' . $vKey . '/', $workspaceOptions);
3393  } else {
3394  $res = $this->{$callBackFunc}($pParams, $fieldConfiguration, $dataValues[$key][$vKey], $dataValues_current[$key][$vKey], $uploadedFiles[$key][$vKey], $structurePath . $key . '/' . $vKey . '/', $workspaceOptions);
3395  }
3396  } else {
3397  // Default
3398  list($CVtable, $CVid, $CVcurValue, $CVstatus, $CVrealPid, $CVrecFID, $CVtscPID) = $pParams;
3399 
3400  $additionalData = [
3401  'flexFormId' => $CVrecFID,
3402  'flexFormPath' => trim(rtrim($structurePath, '/') . '/' . $key . '/' . $vKey, '/'),
3403  ];
3404 
3405  $res = $this->‪checkValue_SW([], $dataValues[$key][$vKey], $fieldConfiguration, $CVtable, $CVid, $dataValues_current[$key][$vKey], $CVstatus, $CVrealPid, $CVrecFID, '', $uploadedFiles[$key][$vKey], $CVtscPID, $additionalData);
3406  }
3407  // Adding the value:
3408  if (isset($res['value'])) {
3409  $dataValues[$key][$vKey] = $res['value'];
3410  }
3411  // Finally, check if new and old values are different (or no .vDEFbase value is found) and if so, we record the vDEF value for diff'ing.
3412  // We do this after $dataValues has been updated since I expect that $dataValues_current holds evaluated values from database (so this must be the right value to compare with).
3413  if (mb_substr($vKey, -9) !== '.vDEFbase') {
3414  if (‪$GLOBALS['TYPO3_CONF_VARS']['BE']['flexFormXMLincludeDiffBase'] && $vKey !== 'vDEF' && ((string)$dataValues[$key][$vKey] !== (string)$dataValues_current[$key][$vKey] || !isset($dataValues_current[$key][$vKey . '.vDEFbase']))) {
3415  // Now, check if a vDEF value is submitted in the input data, if so we expect this has been processed prior to this operation (normally the case since those fields are higher in the form) and we can use that:
3416  if (isset($dataValues[$key]['vDEF'])) {
3417  $diffValue = $dataValues[$key]['vDEF'];
3418  } else {
3419  // If not found (for translators with no access to the default language) we use the one from the current-value data set:
3420  $diffValue = $dataValues_current[$key]['vDEF'];
3421  }
3422  // Setting the reference value for vDEF for this translation. This will be used for translation tools to make a diff between the vDEF and vDEFbase to see if an update would be fitting.
3423  $dataValues[$key][$vKey . '.vDEFbase'] = $this->updateModeL10NdiffDataClear ? '' : $diffValue;
3424  }
3425  }
3426  }
3427  }
3428  }
3429  }
3430 
3443  protected function ‪checkValue_inline_processDBdata($valueArray, $tcaFieldConf, $id, $status, $table, $field, array $additionalData = null)
3444  {
3445  $foreignTable = $tcaFieldConf['foreign_table'];
3446  $valueArray = $this->‪applyFiltersToValues($tcaFieldConf, $valueArray);
3447  // Fetch the related child records using \TYPO3\CMS\Core\Database\RelationHandler
3449  $dbAnalysis = $this->‪createRelationHandlerInstance();
3450  $dbAnalysis->start(implode(',', $valueArray), $foreignTable, '', 0, $table, $tcaFieldConf);
3451  // IRRE with a pointer field (database normalization):
3452  if ($tcaFieldConf['foreign_field']) {
3453  // if the record was imported, sorting was also imported, so skip this
3454  $skipSorting = (bool)$this->callFromImpExp;
3455  // update record in intermediate table (sorting & pointer uid to parent record)
3456  $dbAnalysis->writeForeignField($tcaFieldConf, $id, 0, $skipSorting);
3457  $newValue = $dbAnalysis->countItems(false);
3458  } elseif ($this->‪getInlineFieldType($tcaFieldConf) === 'mm') {
3459  // In order to fully support all the MM stuff, directly call checkValue_group_select_processDBdata instead of repeating the needed code here
3460  $valueArray = $this->‪checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, 'select', $table, $field);
3461  $newValue = $valueArray[0];
3462  } else {
3463  $valueArray = $dbAnalysis->getValueArray();
3464  // Checking that the number of items is correct:
3465  $valueArray = $this->‪checkValue_checkMax($tcaFieldConf, $valueArray);
3466  $newValue = $this->‪castReferenceValue(implode(',', $valueArray), $tcaFieldConf);
3467  }
3468  return $newValue;
3469  }
3470 
3471  /*********************************************
3472  *
3473  * PROCESSING COMMANDS
3474  *
3475  ********************************************/
3482  public function ‪process_cmdmap()
3483  {
3484  // Editing frozen:
3485  if ($this->BE_USER->workspace !== 0 && $this->BE_USER->workspaceRec['freeze']) {
3486  $this->‪newlog('All editing in this workspace has been frozen!', 1);
3487  return false;
3488  }
3489  // Hook initialization:
3490  $hookObjectsArr = [];
3491  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'] ?? [] as $className) {
3492  $hookObj = GeneralUtility::makeInstance($className);
3493  if (method_exists($hookObj, 'processCmdmap_beforeStart')) {
3494  $hookObj->processCmdmap_beforeStart($this);
3495  }
3496  $hookObjectsArr[] = $hookObj;
3497  }
3498  $pasteDatamap = [];
3499  // Traverse command map:
3500  foreach ($this->cmdmap as $table => $_) {
3501  // Check if the table may be modified!
3502  $modifyAccessList = $this->‪checkModifyAccessList($table);
3503  if (!$modifyAccessList) {
3504  $this->‪log($table, 0, 2, 0, 1, 'Attempt to modify table \'%s\' without permission', 1, [$table]);
3505  }
3506  // Check basic permissions and circumstances:
3507  if (!isset(‪$GLOBALS['TCA'][$table]) || $this->‪tableReadOnly($table) || !is_array($this->cmdmap[$table]) || !$modifyAccessList) {
3508  continue;
3509  }
3510 
3511  // Traverse the command map:
3512  foreach ($this->cmdmap[$table] as $id => $incomingCmdArray) {
3513  if (!is_array($incomingCmdArray)) {
3514  continue;
3515  }
3516 
3517  if ($table === 'pages') {
3518  // for commands on pages do a pagetree-refresh
3519  $this->pagetreeNeedsRefresh = true;
3520  }
3521 
3522  foreach ($incomingCmdArray as $command => $value) {
3523  $pasteUpdate = false;
3524  if (is_array($value) && isset($value['action']) && $value['action'] === 'paste') {
3525  // Extended paste command: $command is set to "move" or "copy"
3526  // $value['update'] holds field/value pairs which should be updated after copy/move operation
3527  // $value['target'] holds original $value (target of move/copy)
3528  $pasteUpdate = $value['update'];
3529  $value = $value['target'];
3530  }
3531  foreach ($hookObjectsArr as $hookObj) {
3532  if (method_exists($hookObj, 'processCmdmap_preProcess')) {
3533  $hookObj->processCmdmap_preProcess($command, $table, $id, $value, $this, $pasteUpdate);
3534  }
3535  }
3536  // Init copyMapping array:
3537  // Must clear this array before call from here to those functions:
3538  // Contains mapping information between new and old id numbers.
3539  $this->copyMappingArray = [];
3540  // process the command
3541  $commandIsProcessed = false;
3542  foreach ($hookObjectsArr as $hookObj) {
3543  if (method_exists($hookObj, 'processCmdmap')) {
3544  $hookObj->processCmdmap($command, $table, $id, $value, $commandIsProcessed, $this, $pasteUpdate);
3545  }
3546  }
3547  // Only execute default commands if a hook hasn't been processed the command already
3548  if (!$commandIsProcessed) {
3549  $procId = $id;
3550  $backupUseTransOrigPointerField = ‪$this->useTransOrigPointerField;
3551  // Branch, based on command
3552  switch ($command) {
3553  case 'move':
3554  $this->‪moveRecord($table, $id, $value);
3555  break;
3556  case 'copy':
3557  $target = $value['target'] ?? $value;
3558  $ignoreLocalization = (bool)($value['ignoreLocalization'] ?? false);
3559  if ($table === 'pages') {
3560  $this->‪copyPages($id, $target);
3561  } else {
3562  $this->‪copyRecord($table, $id, $target, true, [], '', 0, $ignoreLocalization);
3563  }
3564  $procId = $this->copyMappingArray[$table][$id];
3565  break;
3566  case 'localize':
3567  $this->useTransOrigPointerField = true;
3568  $this->‪localize($table, $id, $value);
3569  break;
3570  case 'copyToLanguage':
3571  $this->useTransOrigPointerField = false;
3572  $this->‪localize($table, $id, $value);
3573  break;
3574  case 'inlineLocalizeSynchronize':
3575  $this->‪inlineLocalizeSynchronize($table, $id, $value);
3576  break;
3577  case 'delete':
3578  $this->‪deleteAction($table, $id);
3579  break;
3580  case 'undelete':
3581  $this->‪undeleteRecord($table, $id);
3582  break;
3583  }
3584  $this->useTransOrigPointerField = $backupUseTransOrigPointerField;
3585  if (is_array($pasteUpdate)) {
3586  $pasteDatamap[$table][$procId] = $pasteUpdate;
3587  }
3588  }
3589  foreach ($hookObjectsArr as $hookObj) {
3590  if (method_exists($hookObj, 'processCmdmap_postProcess')) {
3591  $hookObj->processCmdmap_postProcess($command, $table, $id, $value, $this, $pasteUpdate, $pasteDatamap);
3592  }
3593  }
3594  // Merging the copy-array info together for remapping purposes.
3595  ‪ArrayUtility::mergeRecursiveWithOverrule($this->copyMappingArray_merged, $this->copyMappingArray);
3596  }
3597  }
3598  }
3600  $copyTCE = $this->‪getLocalTCE();
3601  $copyTCE->start($pasteDatamap, [], $this->BE_USER);
3602  $copyTCE->process_datamap();
3603  $this->errorLog = array_merge($this->errorLog, $copyTCE->errorLog);
3604  unset($copyTCE);
3605 
3606  // Finally, before exit, check if there are ID references to remap.
3607  // This might be the case if versioning or copying has taken place!
3608  $this->‪remapListedDBRecords();
3609  $this->‪processRemapStack();
3610  foreach ($hookObjectsArr as $hookObj) {
3611  if (method_exists($hookObj, 'processCmdmap_afterFinish')) {
3612  $hookObj->processCmdmap_afterFinish($this);
3613  }
3614  }
3615  if ($this->‪isOuterMostInstance()) {
3616  $this->‪processClearCacheQueue();
3617  $this->‪resetNestedElementCalls();
3618  }
3619  }
3620 
3621  /*********************************************
3622  *
3623  * Cmd: Copying
3624  *
3625  ********************************************/
3639  public function ‪copyRecord($table, $uid, $destPid, $first = false, ‪$overrideValues = [], $excludeFields = '', $language = 0, $ignoreLocalization = false)
3640  {
3641  $uid = ($origUid = (int)$uid);
3642  // Only copy if the table is defined in $GLOBALS['TCA'], a uid is given and the record wasn't copied before:
3643  if (empty(‪$GLOBALS['TCA'][$table]) || $uid === 0) {
3644  return null;
3645  }
3646  if ($this->‪isRecordCopied($table, $uid)) {
3647  return null;
3648  }
3649 
3650  // Fetch record with permission check
3651  $row = $this->‪recordInfoWithPermissionCheck($table, $uid, 'show');
3652 
3653  // This checks if the record can be selected which is all that a copy action requires.
3654  if ($row === false) {
3655  $this->‪log($table, $uid, 1, 0, 1, 'Attempt to copy record "%s:%s" which does not exist or you do not have permission to read', -1, [$table, $uid]);
3656  return null;
3657  }
3658 
3659  // Check if table is allowed on destination page
3660  if ($destPid >= 0 && !$this->‪isTableAllowedForThisPage($destPid, $table)) {
3661  $this->‪log($table, $uid, 1, 0, 1, 'Attempt to insert record "%s:%s" on a page (%s) that can\'t store record type.', -1, [$table, $uid, $destPid]);
3662  return null;
3663  }
3664 
3665  $fullLanguageCheckNeeded = $table !== 'pages';
3666  // Used to check language and general editing rights
3667  if (!$ignoreLocalization && ($language <= 0 || !$this->BE_USER->checkLanguageAccess($language)) && !$this->BE_USER->recordEditAccessInternals($table, $uid, false, false, $fullLanguageCheckNeeded)) {
3668  $this->‪log($table, $uid, 1, 0, 1, 'Attempt to copy record "%s:%s" without having permissions to do so. [' . $this->BE_USER->errorMsg . '].', -1, [$table, $uid]);
3669  return null;
3670  }
3671 
3672  $data = [];
3673  $nonFields = array_unique(GeneralUtility::trimExplode(',', 'uid,perms_userid,perms_groupid,perms_user,perms_group,perms_everybody,t3ver_oid,t3ver_wsid,t3ver_id,t3ver_label,t3ver_state,t3ver_count,t3ver_stage,t3ver_tstamp,' . $excludeFields, true));
3674  ‪BackendUtility::workspaceOL($table, $row, -99, false);
3676 
3677  // Initializing:
3678  $theNewID = ‪StringUtility::getUniqueId('NEW');
3679  $enableField = isset(‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']) ? ‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'] : '';
3680  $headerField = ‪$GLOBALS['TCA'][$table]['ctrl']['label'];
3681  // Getting default data:
3682  $defaultData = $this->‪newFieldArray($table);
3683  // Getting "copy-after" fields if applicable:
3684  $copyAfterFields = $destPid < 0 ? $this->‪fixCopyAfterDuplFields($table, $uid, abs($destPid), 0) : [];
3685  // Page TSconfig related:
3686  // NOT using \TYPO3\CMS\Backend\Utility\BackendUtility::getTSCpid() because we need the real pid - not the ID of a page, if the input is a page...
3687  $tscPID = ‪BackendUtility::getTSconfig_pidValue($table, $uid, $destPid);
3688  $TSConfig = ‪BackendUtility::getPagesTSconfig($tscPID)['TCEMAIN.'] ?? [];
3689  $tE = $this->‪getTableEntries($table, $TSConfig);
3690  // Traverse ALL fields of the selected record:
3691  $setDefaultOnCopyArray = array_flip(GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][$table]['ctrl']['setToDefaultOnCopy']));
3692  foreach ($row as $field => $value) {
3693  if (!in_array($field, $nonFields, true)) {
3694  // Get TCA configuration for the field:
3695  $conf = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'];
3696  // Preparation/Processing of the value:
3697  // "pid" is hardcoded of course:
3698  // isset() won't work here, since values can be NULL in each of the arrays
3699  // except setDefaultOnCopyArray, since we exploded that from a string
3700  if ($field === 'pid') {
3701  $value = $destPid;
3702  } elseif (array_key_exists($field, ‪$overrideValues)) {
3703  // Override value...
3704  $value = ‪$overrideValues[$field];
3705  } elseif (array_key_exists($field, $copyAfterFields)) {
3706  // Copy-after value if available:
3707  $value = $copyAfterFields[$field];
3708  } elseif (‪$GLOBALS['TCA'][$table]['ctrl']['setToDefaultOnCopy'] && isset($setDefaultOnCopyArray[$field])) {
3709  $value = $defaultData[$field];
3710  } else {
3711  // Hide at copy may override:
3712  if ($first && $field == $enableField && ‪$GLOBALS['TCA'][$table]['ctrl']['hideAtCopy'] && !$this->neverHideAtCopy && !$tE['disableHideAtCopy']) {
3713  $value = 1;
3714  }
3715  // Prepend label on copy:
3716  if ($first && $field == $headerField && ‪$GLOBALS['TCA'][$table]['ctrl']['prependAtCopy'] && !$tE['disablePrependAtCopy']) {
3717  $value = $this->‪getCopyHeader($table, $this->‪resolvePid($table, $destPid), $field, $this->‪clearPrefixFromValue($table, $value), 0);
3718  }
3719  // Processing based on the TCA config field type (files, references, flexforms...)
3720  $value = $this->‪copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $tscPID, $language);
3721  }
3722  // Add value to array.
3723  $data[$table][$theNewID][$field] = $value;
3724  }
3725  }
3726  // Overriding values:
3727  if (‪$GLOBALS['TCA'][$table]['ctrl']['editlock']) {
3728  $data[$table][$theNewID][‪$GLOBALS['TCA'][$table]['ctrl']['editlock']] = 0;
3729  }
3730  // Setting original UID:
3731  if (‪$GLOBALS['TCA'][$table]['ctrl']['origUid']) {
3732  $data[$table][$theNewID][‪$GLOBALS['TCA'][$table]['ctrl']['origUid']] = $uid;
3733  }
3734  // Do the copy by simply submitting the array through DataHandler:
3736  $copyTCE = $this->‪getLocalTCE();
3737  $copyTCE->start($data, [], $this->BE_USER);
3738  $copyTCE->process_datamap();
3739  // Getting the new UID:
3740  $theNewSQLID = $copyTCE->substNEWwithIDs[$theNewID];
3741  if ($theNewSQLID) {
3742  $this->‪copyRecord_fixRTEmagicImages($table, ‪BackendUtility::wsMapId($table, $theNewSQLID));
3743  $this->copyMappingArray[$table][$origUid] = $theNewSQLID;
3744  // Keep automatically versionized record information:
3745  if (isset($copyTCE->autoVersionIdMap[$table][$theNewSQLID])) {
3746  $this->autoVersionIdMap[$table][$theNewSQLID] = $copyTCE->autoVersionIdMap[$table][$theNewSQLID];
3747  }
3748  }
3749  $this->errorLog = array_merge($this->errorLog, $copyTCE->errorLog);
3750  unset($copyTCE);
3751  if (!$ignoreLocalization && $language == 0) {
3752  //repointing the new translation records to the parent record we just created
3753  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $theNewSQLID;
3754  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'])) {
3755  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['translationSource']] = 0;
3756  }
3757  $this->‪copyL10nOverlayRecords($table, $uid, $destPid, $first, ‪$overrideValues, $excludeFields);
3758  }
3759 
3760  return $theNewSQLID;
3761  }
3770  public function ‪copyPages($uid, $destPid)
3771  {
3772  // Initialize:
3773  $uid = (int)$uid;
3774  $destPid = (int)$destPid;
3775 
3776  $copyTablesAlongWithPage = $this->‪getAllowedTablesToCopyWhenCopyingAPage();
3777  // Begin to copy pages if we're allowed to:
3778  if ($this->admin || in_array('pages', $copyTablesAlongWithPage, true)) {
3779  // Copy this page we're on. And set first-flag (this will trigger that the record is hidden if that is configured)
3780  // This method also copies the localizations of a page
3781  $theNewRootID = $this->‪copySpecificPage($uid, $destPid, $copyTablesAlongWithPage, true);
3782  // If we're going to copy recursively
3783  if ($theNewRootID && $this->copyTree) {
3784  // Get ALL subpages to copy (read-permissions are respected!):
3785  $CPtable = $this->‪int_pageTreeInfo([], $uid, (int)$this->copyTree, $theNewRootID);
3786  // Now copying the subpages:
3787  foreach ($CPtable as $thePageUid => $thePagePid) {
3788  $newPid = $this->copyMappingArray['pages'][$thePagePid];
3789  if (isset($newPid)) {
3790  $this->‪copySpecificPage($thePageUid, $newPid, $copyTablesAlongWithPage);
3791  } else {
3792  $this->‪log('pages', $uid, 5, 0, 1, 'Something went wrong during copying branch');
3793  break;
3794  }
3795  }
3796  }
3797  } else {
3798  $this->‪log('pages', $uid, 5, 0, 1, 'Attempt to copy page without permission to this table');
3799  }
3800  }
3801 
3811  protected function ‪getAllowedTablesToCopyWhenCopyingAPage(): array
3812  {
3813  // Finding list of tables to copy.
3814  // These are the tables, the user may modify
3815  $copyTablesArray = $this->admin ? $this->‪compileAdminTables() : explode(',', $this->BE_USER->groupData['tables_modify']);
3816  // If not all tables are allowed then make a list of allowed tables.
3817  // That is the tables that figure in both allowed tables AND the copyTable-list
3818  if (strpos($this->copyWhichTables, '*') === false) {
3819  $definedTablesToCopy = GeneralUtility::trimExplode(',', $this->copyWhichTables, true);
3820  // Pages are always allowed
3821  $definedTablesToCopy[] = 'pages';
3822  $definedTablesToCopy = array_flip($definedTablesToCopy);
3823  foreach ($copyTablesArray as $k => $table) {
3824  if (!$table || !isset($definedTablesToCopy[$table])) {
3825  unset($copyTablesArray[$k]);
3826  }
3827  }
3828  }
3829  $copyTablesArray = array_unique($copyTablesArray);
3830  return $copyTablesArray;
3831  }
3841  public function ‪copySpecificPage($uid, $destPid, $copyTablesArray, $first = false)
3842  {
3843  // Copy the page itself:
3844  $theNewRootID = $this->‪copyRecord('pages', $uid, $destPid, $first);
3845  // If a new page was created upon the copy operation we will proceed with all the tables ON that page:
3846  if ($theNewRootID) {
3847  foreach ($copyTablesArray as $table) {
3848  // All records under the page is copied.
3849  if ($table && is_array(‪$GLOBALS['TCA'][$table]) && $table !== 'pages') {
3850  ‪$fields = ['uid'];
3851  $languageField = null;
3852  $transOrigPointerField = null;
3853  $translationSourceField = null;
3855  $languageField = ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'];
3856  $transOrigPointerField = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
3857  ‪$fields[] = $languageField;
3858  ‪$fields[] = $transOrigPointerField;
3859  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'])) {
3860  $translationSourceField = ‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'];
3861  ‪$fields[] = $translationSourceField;
3862  }
3863  }
3864  $isTableWorkspaceEnabled = ‪BackendUtility::isTableWorkspaceEnabled($table);
3865  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
3866  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
3867  $queryBuilder
3868  ->select(...‪$fields)
3869  ->from($table)
3870  ->where(
3871  $queryBuilder->expr()->eq(
3872  'pid',
3873  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
3874  )
3875  );
3876  if ($isTableWorkspaceEnabled && (int)$this->BE_USER->workspace === 0) {
3877  // Table is workspace enabled, user is in default ws -> add t3ver_wsid=0 restriction
3878  $queryBuilder->andWhere(
3879  $queryBuilder->expr()->eq(
3880  't3ver_wsid',
3881  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
3882  )
3883  );
3884  } elseif ($isTableWorkspaceEnabled) {
3885  // Table is workspace enabled, user has a ws selected -> select wsid=0 and selected wsid rows
3886  $queryBuilder->andWhere($queryBuilder->expr()->in(
3887  't3ver_wsid',
3888  $queryBuilder->createNamedParameter(
3889  [0, $this->BE_USER->workspace],
3890  Connection::PARAM_INT_ARRAY
3891  )
3892  ));
3893  }
3894  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['sortby'])) {
3895  $queryBuilder->orderBy(‪$GLOBALS['TCA'][$table]['ctrl']['sortby'], 'DESC');
3896  }
3897  $queryBuilder->addOrderBy('uid');
3898  try {
3899  $result = $queryBuilder->execute();
3900  $rows = [];
3901  while ($row = $result->fetch()) {
3902  $rows[$row['uid']] = $row;
3903  }
3904  // Resolve placeholders of workspace versions
3905  if (!empty($rows) && (int)$this->BE_USER->workspace !== 0 && $isTableWorkspaceEnabled) {
3906  $rows = array_reverse(
3908  $table,
3909  implode(',', ‪$fields),
3910  ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'],
3911  array_keys($rows)
3912  ),
3913  true
3914  );
3915  }
3916  if (is_array($rows)) {
3917  $languageSourceMap = [];
3918  ‪$overrideValues = $translationSourceField ? [$translationSourceField => 0] : [];
3919  $doRemap = false;
3920  foreach ($rows as $row) {
3921  // Skip localized records that will be processed in
3922  // copyL10nOverlayRecords() on copying the default language record
3923  $transOrigPointer = $row[$transOrigPointerField];
3924  if ($row[$languageField] > 0 && $transOrigPointer > 0 && isset($rows[$transOrigPointer])) {
3925  continue;
3926  }
3927  // Copying each of the underlying records...
3928  $newUid = $this->‪copyRecord($table, $row['uid'], $theNewRootID, false, ‪$overrideValues);
3929  if ($translationSourceField) {
3930  $languageSourceMap[$row['uid']] = $newUid;
3931  if ($row[$languageField] > 0) {
3932  $doRemap = true;
3933  }
3934  }
3935  }
3936  if ($doRemap) {
3937  //remap is needed for records in non-default language records in the "free mode"
3938  $this->‪copy_remapTranslationSourceField($table, $rows, $languageSourceMap);
3939  }
3940  }
3941  } catch (DBALException $e) {
3942  $databaseErrorMessage = $e->getPrevious()->getMessage();
3943  $this->‪log($table, $uid, 5, 0, 1, 'An SQL error occurred: ' . $databaseErrorMessage);
3944  }
3945  }
3946  }
3947  $this->‪processRemapStack();
3948  return $theNewRootID;
3949  }
3950  return null;
3951  }
3952 
3968  public function ‪copyRecord_raw($table, $uid, $pid, $overrideArray = [], array $workspaceOptions = [])
3969  {
3970  $uid = (int)$uid;
3971  // Stop any actions if the record is marked to be deleted:
3972  // (this can occur if IRRE elements are versionized and child elements are removed)
3973  if ($this->‪isElementToBeDeleted($table, $uid)) {
3974  return null;
3975  }
3976  // Only copy if the table is defined in TCA, a uid is given and the record wasn't copied before:
3977  if (!‪$GLOBALS['TCA'][$table] || !$uid || $this->‪isRecordCopied($table, $uid)) {
3978  return null;
3979  }
3980 
3981  // Fetch record with permission check
3982  $row = $this->‪recordInfoWithPermissionCheck($table, $uid, 'show');
3983 
3984  // This checks if the record can be selected which is all that a copy action requires.
3985  if ($row === false) {
3986  $this->‪log(
3987  $table,
3988  $uid,
3989  3,
3990  0,
3991  1,
3992  'Attempt to rawcopy/versionize record which either does not exist or you don\'t have permission to read'
3993  );
3994  return null;
3995  }
3996 
3997  // Set up fields which should not be processed. They are still written - just passed through no-questions-asked!
3998  $nonFields = ['uid', 'pid', 't3ver_id', 't3ver_oid', 't3ver_wsid', 't3ver_label', 't3ver_state', 't3ver_count', 't3ver_stage', 't3ver_tstamp', 'perms_userid', 'perms_groupid', 'perms_user', 'perms_group', 'perms_everybody'];
3999 
4000  // Merge in override array.
4001  $row = array_merge($row, $overrideArray);
4002  // Traverse ALL fields of the selected record:
4003  foreach ($row as $field => $value) {
4004  if (!in_array($field, $nonFields, true)) {
4005  // Get TCA configuration for the field:
4006  $conf = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'];
4007  if (is_array($conf)) {
4008  // Processing based on the TCA config field type (files, references, flexforms...)
4009  $value = $this->‪copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $pid, 0, $workspaceOptions);
4010  }
4011  // Add value to array.
4012  $row[$field] = $value;
4013  }
4014  }
4015  // Force versioning related fields:
4016  $row['pid'] = $pid;
4017  // Setting original UID:
4018  if (‪$GLOBALS['TCA'][$table]['ctrl']['origUid']) {
4019  $row[‪$GLOBALS['TCA'][$table]['ctrl']['origUid']] = $uid;
4020  }
4021  // Do the copy by internal function
4022  $theNewSQLID = $this->‪insertNewCopyVersion($table, $row, $pid);
4023  if ($theNewSQLID) {
4024  $this->‪dbAnalysisStoreExec();
4025  $this->dbAnalysisStore = [];
4026  $this->‪copyRecord_fixRTEmagicImages($table, ‪BackendUtility::wsMapId($table, $theNewSQLID));
4027  return $this->copyMappingArray[$table][$uid] = $theNewSQLID;
4028  }
4029  return null;
4030  }
4031 
4041  public function ‪insertNewCopyVersion($table, $fieldArray, $realPid)
4042  {
4043  $id = ‪StringUtility::getUniqueId('NEW');
4044  // $fieldArray is set as current record.
4045  // The point is that when new records are created as copies with flex type fields there might be a field containing information about which DataStructure to use and without that information the flexforms cannot be correctly processed.... This should be OK since the $checkValueRecord is used by the flexform evaluation only anyways...
4046  $this->checkValue_currentRecord = $fieldArray;
4047  // Makes sure that transformations aren't processed on the copy.
4048  $backupDontProcessTransformations = ‪$this->dontProcessTransformations;
4049  $this->dontProcessTransformations = true;
4050  // Traverse record and input-process each value:
4051  foreach ($fieldArray as $field => $fieldValue) {
4052  if (isset(‪$GLOBALS['TCA'][$table]['columns'][$field])) {
4053  // Evaluating the value.
4054  $res = $this->‪checkValue($table, $field, $fieldValue, $id, 'new', $realPid, 0, $fieldArray);
4055  if (isset($res['value'])) {
4056  $fieldArray[$field] = $res['value'];
4057  }
4058  }
4059  }
4060  // System fields being set:
4061  if (‪$GLOBALS['TCA'][$table]['ctrl']['crdate']) {
4062  $fieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['crdate']] = ‪$GLOBALS['EXEC_TIME'];
4063  }
4064  if (‪$GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
4065  $fieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = ‪$this->userid;
4066  }
4067  if (‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
4068  $fieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = ‪$GLOBALS['EXEC_TIME'];
4069  }
4070  // Finally, insert record:
4071  $this->‪insertDB($table, $id, $fieldArray, true);
4072  // Resets dontProcessTransformations to the previous state.
4073  $this->dontProcessTransformations = $backupDontProcessTransformations;
4074  // Return new id:
4075  return $this->substNEWwithIDs[$id];
4076  }
4077 
4094  public function ‪copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $realDestPid, $language = 0, array $workspaceOptions = [])
4095  {
4096  // Process references and files, currently that means only the files, prepending absolute paths (so the DataHandler engine will detect the file as new and one that should be made into a copy)
4097  $value = $this->‪copyRecord_procFilesRefs($conf, $uid, $value);
4098  $inlineSubType = $this->‪getInlineFieldType($conf);
4099  // Get the localization mode for the current (parent) record (keep|select):
4100  // Register if there are references to take care of or MM is used on an inline field (no change to value):
4101  if ($this->‪isReferenceField($conf) || $inlineSubType === 'mm') {
4102  $value = $this->‪copyRecord_processManyToMany($table, $uid, $field, $value, $conf, $language);
4103  } elseif ($inlineSubType !== false) {
4104  $value = $this->‪copyRecord_processInline($table, $uid, $field, $value, $row, $conf, $realDestPid, $language, $workspaceOptions);
4105  }
4106  // For "flex" fieldtypes we need to traverse the structure for two reasons: If there are file references they have to be prepended with absolute paths and if there are database reference they MIGHT need to be remapped (still done in remapListedDBRecords())
4107  if ($conf['type'] === 'flex') {
4108  // Get current value array:
4109  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
4110  $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
4111  ['config' => $conf],
4112  $table,
4113  $field,
4114  $row
4115  );
4116  $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
4117  $currentValueArray = GeneralUtility::xml2array($value);
4118  // Traversing the XML structure, processing files:
4119  if (is_array($currentValueArray)) {
4120  $currentValueArray['data'] = $this->‪checkValue_flex_procInData($currentValueArray['data'], [], [], $dataStructureArray, [$table, $uid, $field, $realDestPid], 'copyRecord_flexFormCallBack', $workspaceOptions);
4121  // Setting value as an array! -> which means the input will be processed according to the 'flex' type when the new copy is created.
4122  $value = $currentValueArray;
4123  }
4124  }
4125  return $value;
4126  }
4127 
4139  protected function ‪copyRecord_processManyToMany($table, $uid, $field, $value, $conf, $language)
4140  {
4141  $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
4142  $prependName = $conf['type'] === 'group' ? $conf['prepend_tname'] : '';
4143  $mmTable = isset($conf['MM']) && $conf['MM'] ? $conf['MM'] : '';
4144  $localizeForeignTable = isset($conf['foreign_table']) && ‪BackendUtility::isTableLocalizable($conf['foreign_table']);
4145  // Localize referenced records of select fields:
4146  $localizingNonManyToManyFieldReferences = empty($mmTable) && $localizeForeignTable && isset($conf['localizeReferencesAtParentLocalization']) && $conf['localizeReferencesAtParentLocalization'];
4148  $dbAnalysis = $this->‪createRelationHandlerInstance();
4149  $dbAnalysis->start($value, $allowedTables, $mmTable, $uid, $table, $conf);
4150  $purgeItems = false;
4151  if ($language > 0 && $localizingNonManyToManyFieldReferences) {
4152  foreach ($dbAnalysis->itemArray as $index => $item) {
4153  // Since select fields can reference many records, check whether there's already a localization:
4154  $recordLocalization = ‪BackendUtility::getRecordLocalization($item['table'], $item['id'], $language);
4155  if ($recordLocalization) {
4156  $dbAnalysis->itemArray[$index]['id'] = $recordLocalization[0]['uid'];
4157  } elseif ($this->‪isNestedElementCallRegistered($item['table'], $item['id'], 'localize-' . (string)$language) === false) {
4158  $dbAnalysis->itemArray[$index]['id'] = $this->‪localize($item['table'], $item['id'], $language);
4159  }
4160  }
4161  $purgeItems = true;
4162  }
4163 
4164  if ($purgeItems || $mmTable) {
4165  $dbAnalysis->purgeItemArray();
4166  $value = implode(',', $dbAnalysis->getValueArray($prependName));
4167  }
4168  // Setting the value in this array will notify the remapListedDBRecords() function that this field MAY need references to be corrected
4169  if ($value) {
4170  $this->registerDBList[$table][$uid][$field] = $value;
4171  }
4172 
4173  return $value;
4174  }
4175 
4190  protected function ‪copyRecord_processInline(
4191  $table,
4192  $uid,
4193  $field,
4194  $value,
4195  $row,
4196  $conf,
4197  $realDestPid,
4198  $language,
4199  array $workspaceOptions
4200  ) {
4201  // Fetch the related child records using \TYPO3\CMS\Core\Database\RelationHandler
4203  $dbAnalysis = $this->‪createRelationHandlerInstance();
4204  $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
4205  // Walk through the items, copy them and remember the new id:
4206  foreach ($dbAnalysis->itemArray as $k => $v) {
4207  $newId = null;
4208  // If language is set and differs from original record, this isn't a copy action but a localization of our parent/ancestor:
4209  if ($language > 0 && ‪BackendUtility::isTableLocalizable($table) && $language != $row[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]) {
4210  // Children should be localized when the parent gets localized the first time, just do it:
4211  $newId = $this->‪localize($v['table'], $v['id'], $language);
4212  } else {
4213  if (!‪MathUtility::canBeInterpretedAsInteger($realDestPid)) {
4214  $newId = $this->‪copyRecord($v['table'], $v['id'], -$v['id']);
4215  // If the destination page id is a NEW string, keep it on the same page
4216  } elseif ($this->BE_USER->workspace > 0 && ‪BackendUtility::isTableWorkspaceEnabled($v['table'])) {
4217  // A filled $workspaceOptions indicated that this call
4218  // has it's origin in previous versionizeRecord() processing
4219  if (!empty($workspaceOptions)) {
4220  // Versions use live default id, thus the "new"
4221  // id is the original live default child record
4222  $newId = $v['id'];
4223  $this->‪versionizeRecord(
4224  $v['table'],
4225  $v['id'],
4226  $workspaceOptions['label'] ?? 'Auto-created for WS #' . $this->BE_USER->workspace,
4227  $workspaceOptions['delete'] ?? false
4228  );
4229  // Otherwise just use plain copyRecord() to create placeholders etc.
4230  } else {
4231  // If a record has been copied already during this request,
4232  // prevent superfluous duplication and use the existing copy
4233  if (isset($this->copyMappingArray[$v['table']][$v['id']])) {
4234  $newId = $this->copyMappingArray[$v['table']][$v['id']];
4235  } else {
4236  $newId = $this->‪copyRecord($v['table'], $v['id'], $realDestPid);
4237  }
4238  }
4239  } elseif ($this->BE_USER->workspace > 0 && !‪BackendUtility::isTableWorkspaceEnabled($v['table'])) {
4240  // We are in workspace context creating a new parent version and have a child table
4241  // that is not workspace aware. We don't do anything with this child.
4242  continue;
4243  } else {
4244  // If a record has been copied already during this request,
4245  // prevent superfluous duplication and use the existing copy
4246  if (isset($this->copyMappingArray[$v['table']][$v['id']])) {
4247  $newId = $this->copyMappingArray[$v['table']][$v['id']];
4248  } else {
4249  $newId = $this->‪copyRecord_raw($v['table'], $v['id'], $realDestPid, [], $workspaceOptions);
4250  }
4251  }
4252  }
4253  // If the current field is set on a page record, update the pid of related child records:
4254  if ($table === 'pages') {
4255  $this->registerDBPids[$v['table']][$v['id']] = $uid;
4256  } elseif (isset($this->registerDBPids[$table][$uid])) {
4257  $this->registerDBPids[$v['table']][$v['id']] = $this->registerDBPids[$table][$uid];
4258  }
4259  $dbAnalysis->itemArray[$k]['id'] = $newId;
4260  }
4261  // Store the new values, we will set up the uids for the subtype later on (exception keep localization from original record):
4262  $value = implode(',', $dbAnalysis->getValueArray());
4263  $this->registerDBList[$table][$uid][$field] = $value;
4264 
4265  return $value;
4266  }
4267 
4281  public function ‪copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $_1, $_2, $_3, $workspaceOptions)
4282  {
4283  // Extract parameters:
4284  list($table, $uid, $field, $realDestPid) = $pParams;
4285  // Process references and files, currently that means only the files, prepending absolute paths:
4286  $dataValue = $this->‪copyRecord_procFilesRefs($dsConf, $uid, $dataValue);
4287  // If references are set for this field, set flag so they can be corrected later (in ->remapListedDBRecords())
4288  if (($this->‪isReferenceField($dsConf) || $this->‪getInlineFieldType($dsConf) !== false) && (string)$dataValue !== '') {
4289  $dataValue = $this->‪copyRecord_procBasedOnFieldType($table, $uid, $field, $dataValue, [], $dsConf, $realDestPid, 0, $workspaceOptions);
4290  $this->registerDBList[$table][$uid][$field] = 'FlexForm_reference';
4291  }
4292  // Return
4293  return ['value' => $dataValue];
4294  }
4295 
4308  public function ‪copyRecord_procFilesRefs($conf, $uid, $value)
4309  {
4310  if ($conf['type'] !== 'group' || ($conf['internal_type'] !== 'file' && $conf['internal_type'] !== 'file_reference')) {
4311  return $value;
4312  }
4313 
4314  // Get an array with files as values:
4315  if ($conf['MM']) {
4316  $theFileValues = [];
4318  $dbAnalysis = $this->‪createRelationHandlerInstance();
4319  $dbAnalysis->start('', 'files', $conf['MM'], $uid);
4320  foreach ($dbAnalysis->itemArray as $somekey => $someval) {
4321  if ($someval['id']) {
4322  $theFileValues[] = $someval['id'];
4323  }
4324  }
4325  } else {
4326  $theFileValues = GeneralUtility::trimExplode(',', $value, true);
4327  }
4328  // Traverse this array of files:
4329  $uploadFolder = $conf['internal_type'] === 'file' ? $conf['uploadfolder'] : '';
4330  // Prepend absolute paths to files
4331  $dest = ‪Environment::getPublicPath() . '/' . $uploadFolder;
4332  $newValue = [];
4333  foreach ($theFileValues as $file) {
4334  if (trim($file)) {
4335  $realFile = str_replace('//', '/', $dest . '/' . trim($file));
4336  if (@is_file($realFile)) {
4337  $newValue[] = $realFile;
4338  }
4339  }
4340  }
4341  // Implode the new filelist into the new value (all files have absolute paths now which means they will get copied when entering DataHandler as new values...)
4342  $value = implode(',', $newValue);
4343 
4344  // Return the new value:
4345  return $value;
4346  }
4347 
4356  public function ‪copyRecord_fixRTEmagicImages($table, $theNewSQLID)
4357  {
4358  // Creating fileFunc object.
4359  if (!$this->fileFunc) {
4360  $this->fileFunc = GeneralUtility::makeInstance(BasicFileUtility::class);
4361  }
4362  // Select all RTEmagic files in the reference table from the table/ID
4363  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
4364  $queryBuilder->getRestrictions()->removeAll();
4365  $rteFileRecords = $queryBuilder
4366  ->select('*')
4367  ->from('sys_refindex')
4368  ->where(
4369  $queryBuilder->expr()->eq(
4370  'ref_table',
4371  $queryBuilder->createNamedParameter('_FILE', \PDO::PARAM_STR)
4372  ),
4373  $queryBuilder->expr()->like(
4374  'ref_string',
4375  $queryBuilder->createNamedParameter('%/RTEmagic%', \PDO::PARAM_STR)
4376  ),
4377  $queryBuilder->expr()->eq(
4378  'softref_key',
4379  $queryBuilder->createNamedParameter('images', \PDO::PARAM_STR)
4380  ),
4381  $queryBuilder->expr()->eq(
4382  'tablename',
4383  $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)
4384  ),
4385  $queryBuilder->expr()->eq(
4386  'recuid',
4387  $queryBuilder->createNamedParameter($theNewSQLID, \PDO::PARAM_INT)
4388  )
4389  )
4390  ->orderBy('sorting', 'DESC')
4391  ->execute()
4392  ->fetchAll();
4393  // Traverse the files found and copy them:
4394  if (!is_array($rteFileRecords)) {
4395  return;
4396  }
4397  foreach ($rteFileRecords as $rteFileRecord) {
4398  $filename = ‪PathUtility::basename($rteFileRecord['ref_string']);
4399  if (!GeneralUtility::isFirstPartOfStr($filename, 'RTEmagicC_')) {
4400  continue;
4401  }
4402  $fileInfo = [];
4403  $fileInfo['exists'] = @is_file(‪Environment::getPublicPath() . '/' . $rteFileRecord['ref_string']);
4404  $fileInfo['original'] = mb_substr($rteFileRecord['ref_string'], 0, -mb_strlen($filename)) . 'RTEmagicP_' . preg_replace('/\\.[[:alnum:]]+$/', '', mb_substr($filename, 10));
4405  $fileInfo['original_exists'] = @is_file(‪Environment::getPublicPath() . '/' . $fileInfo['original']);
4406  // CODE from tx_impexp and class.rte_images.php adapted for use here:
4407  if (!$fileInfo['exists'] || !$fileInfo['original_exists']) {
4408  $this->‪newlog('Trying to copy RTEmagic files (' . $rteFileRecord['ref_string'] . ' / ' . $fileInfo['original'] . ') but one or both were missing', 1);
4409  continue;
4410  }
4411  // Initialize; Get directory prefix for file and set the original name:
4412  $dirPrefix = ‪PathUtility::dirname($rteFileRecord['ref_string']) . '/';
4413  $rteOrigName = ‪PathUtility::basename($fileInfo['original']);
4414  // If filename looks like an RTE file, and the directory is in "uploads/", then process as a RTE file!
4415  if ($rteOrigName && GeneralUtility::isFirstPartOfStr($dirPrefix, 'uploads/') && @is_dir(‪Environment::getPublicPath() . '/' . $dirPrefix)) {
4416  // RTE:
4417  // From the "original" RTE filename, produce a new "original" destination filename which is unused.
4418  $origDestName = $this->fileFunc->getUniqueName($rteOrigName, ‪Environment::getPublicPath() . '/' . $dirPrefix);
4419  // Create copy file name:
4420  $pI = pathinfo($rteFileRecord['ref_string']);
4421  $copyDestName = ‪PathUtility::dirname($origDestName) . '/RTEmagicC_' . mb_substr(‪PathUtility::basename($origDestName), 10) . '.' . $pI['extension'];
4422  if (!@is_file($copyDestName) && !@is_file($origDestName) && $origDestName === GeneralUtility::getFileAbsFileName($origDestName) && $copyDestName === GeneralUtility::getFileAbsFileName($copyDestName)) {
4423  // Making copies:
4424  GeneralUtility::upload_copy_move(‪Environment::getPublicPath() . '/' . $fileInfo['original'], $origDestName);
4425  GeneralUtility::upload_copy_move(‪Environment::getPublicPath() . '/' . $rteFileRecord['ref_string'], $copyDestName);
4426  clearstatcache();
4427  // Register this:
4428  $this->RTEmagic_copyIndex[$rteFileRecord['tablename']][$rteFileRecord['recuid']][$rteFileRecord['field']][$rteFileRecord['ref_string']] = ‪PathUtility::stripPathSitePrefix($copyDestName);
4429  // Check and update the record using \TYPO3\CMS\Core\Database\ReferenceIndex
4430  if (@is_file($copyDestName)) {
4432  $sysRefObj = GeneralUtility::makeInstance(ReferenceIndex::class);
4433  $sysRefObj->enableRuntimeCache();
4434  $error = $sysRefObj->setReferenceValue($rteFileRecord['hash'], ‪PathUtility::stripPathSitePrefix($copyDestName), false, true);
4435  if ($error) {
4436  $this->‪newlog(ReferenceIndex::class . '::setReferenceValue(): ' . $error, 1);
4437  }
4438  } else {
4439  $this->‪newlog('File "' . $copyDestName . '" was not created!', 1);
4440  }
4441  } else {
4442  $this->‪newlog('Could not construct new unique names for file!', 1);
4443  }
4444  } else {
4445  $this->‪newlog('Maybe directory of file was not within "uploads/"?', 1);
4446  }
4447  }
4448  }
4449 
4460  public function ‪copyL10nOverlayRecords($table, $uid, $destPid, $first = false, ‪$overrideValues = [], $excludeFields = '')
4461  {
4462  // There's no need to perform this for tables that are not localizable
4464  return;
4465  }
4466 
4467  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
4468  $queryBuilder->getRestrictions()
4469  ->removeAll()
4470  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
4471  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
4472 
4473  $queryBuilder->select('*')
4474  ->from($table)
4475  ->where(
4476  $queryBuilder->expr()->eq(
4477  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
4478  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT, ':pointer')
4479  )
4480  );
4481 
4482  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && ‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
4483  $queryBuilder->andWhere(
4484  $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
4485  );
4486  }
4487  // If $destPid is < 0, get the pid of the record with uid equal to abs($destPid)
4488  $tscPID = ‪BackendUtility::getTSconfig_pidValue($table, $uid, $destPid);
4489  // Get the localized records to be copied
4490  $l10nRecords = $queryBuilder->execute()->fetchAll();
4491  if (is_array($l10nRecords)) {
4492  $localizedDestPids = [];
4493  // If $destPid < 0, then it is the uid of the original language record we are inserting after
4494  if ($destPid < 0) {
4495  // Get the localized records of the record we are inserting after
4496  $queryBuilder->setParameter('pointer', abs($destPid), \PDO::PARAM_INT);
4497  $destL10nRecords = $queryBuilder->execute()->fetchAll();
4498  // Index the localized record uids by language
4499  if (is_array($destL10nRecords)) {
4500  foreach ($destL10nRecords as $record) {
4501  $localizedDestPids[$record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]] = -$record['uid'];
4502  }
4503  }
4504  }
4505  $languageSourceMap = [
4506  $uid => ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']]
4507  ];
4508  // Copy the localized records after the corresponding localizations of the destination record
4509  foreach ($l10nRecords as $record) {
4510  $localizedDestPid = (int)$localizedDestPids[$record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]];
4511  if ($localizedDestPid < 0) {
4512  $newUid = $this->‪copyRecord($table, $record['uid'], $localizedDestPid, $first, ‪$overrideValues, $excludeFields, $record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]);
4513  } else {
4514  $newUid = $this->‪copyRecord($table, $record['uid'], $destPid < 0 ? $tscPID : $destPid, $first, ‪$overrideValues, $excludeFields, $record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]);
4515  }
4516  $languageSourceMap[$record['uid']] = $newUid;
4517  }
4518  $this->‪copy_remapTranslationSourceField($table, $l10nRecords, $languageSourceMap);
4519  }
4520  }
4521 
4529  protected function ‪copy_remapTranslationSourceField($table, $l10nRecords, $languageSourceMap)
4530  {
4531  if (empty(‪$GLOBALS['TCA'][$table]['ctrl']['translationSource']) || empty(‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
4532  return;
4533  }
4534  $translationSourceFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'];
4535  $translationParentFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
4536 
4537  //We can avoid running these update queries by sorting the $l10nRecords by languageSource dependency (in copyL10nOverlayRecords)
4538  //and first copy records depending on default record (and map the field).
4539  foreach ($l10nRecords as $record) {
4540  $oldSourceUid = $record[$translationSourceFieldName];
4541  if ($oldSourceUid <= 0 && $record[$translationParentFieldName] > 0) {
4542  //BC fix - in connected mode 'translationSource' field should not be 0
4543  $oldSourceUid = $record[$translationParentFieldName];
4544  }
4545  if ($oldSourceUid > 0) {
4546  if (empty($languageSourceMap[$oldSourceUid])) {
4547  // we don't have mapping information available e.g when copyRecord returned null
4548  continue;
4549  }
4550  $newFieldValue = $languageSourceMap[$oldSourceUid];
4551  $updateFields = [
4552  $translationSourceFieldName => $newFieldValue
4553  ];
4554  GeneralUtility::makeInstance(ConnectionPool::class)
4555  ->getConnectionForTable($table)
4556  ->update($table, $updateFields, ['uid' => (int)$languageSourceMap[$record['uid']]]);
4557  if ($this->BE_USER->workspace > 0) {
4558  GeneralUtility::makeInstance(ConnectionPool::class)
4559  ->getConnectionForTable($table)
4560  ->update($table, $updateFields, ['t3ver_oid' => (int)$languageSourceMap[$record['uid']], 't3ver_wsid' => $this->BE_USER->workspace]);
4561  }
4562  }
4563  }
4564  }
4565 
4566  /*********************************************
4567  *
4568  * Cmd: Moving, Localizing
4569  *
4570  ********************************************/
4578  public function ‪moveRecord($table, $uid, $destPid)
4579  {
4580  if (!‪$GLOBALS['TCA'][$table]) {
4581  return;
4582  }
4583 
4584  // In case the record to be moved turns out to be an offline version,
4585  // we have to find the live version and work on that one.
4586  if ($lookForLiveVersion = ‪BackendUtility::getLiveVersionOfRecord($table, $uid, 'uid')) {
4587  $uid = $lookForLiveVersion['uid'];
4588  }
4589  // Initialize:
4590  $destPid = (int)$destPid;
4591  // Get this before we change the pid (for logging)
4592  $propArr = $this->‪getRecordProperties($table, $uid);
4593  $moveRec = $this->‪getRecordProperties($table, $uid, true);
4594  // This is the actual pid of the moving to destination
4595  $resolvedPid = $this->‪resolvePid($table, $destPid);
4596  // Finding out, if the record may be moved from where it is. If the record is a non-page, then it depends on edit-permissions.
4597  // If the record is a page, then there are two options: If the page is moved within itself,
4598  // (same pid) it's edit-perms of the pid. If moved to another place then its both delete-perms of the pid and new-page perms on the destination.
4599  if ($table !== 'pages' || $resolvedPid == $moveRec['pid']) {
4600  // Edit rights for the record...
4601  $mayMoveAccess = $this->‪checkRecordUpdateAccess($table, $uid);
4602  } else {
4603  $mayMoveAccess = $this->‪doesRecordExist($table, $uid, 'delete');
4604  }
4605  // Finding out, if the record may be moved TO another place. Here we check insert-rights (non-pages = edit, pages = new),
4606  // unless the pages are moved on the same pid, then edit-rights are checked
4607  if ($table !== 'pages' || $resolvedPid != $moveRec['pid']) {
4608  // Insert rights for the record...
4609  $mayInsertAccess = $this->‪checkRecordInsertAccess($table, $resolvedPid, 4);
4610  } else {
4611  $mayInsertAccess = $this->‪checkRecordUpdateAccess($table, $uid);
4612  }
4613  // Checking if there is anything else disallowing moving the record by checking if editing is allowed
4614  $fullLanguageCheckNeeded = $table !== 'pages';
4615  $mayEditAccess = $this->BE_USER->recordEditAccessInternals($table, $uid, false, false, $fullLanguageCheckNeeded);
4616  // If moving is allowed, begin the processing:
4617  if (!$mayEditAccess) {
4618  $this->‪log($table, $uid, 4, 0, 1, 'Attempt to move record "%s" (%s) without having permissions to do so. [' . $this->BE_USER->errorMsg . ']', 14, [$propArr['header'], $table . ':' . $uid], $propArr['event_pid']);
4619  return;
4620  }
4621 
4622  if (!$mayMoveAccess) {
4623  $this->‪log($table, $uid, 4, 0, 1, 'Attempt to move record \'%s\' (%s) without having permissions to do so.', 14, [$propArr['header'], $table . ':' . $uid], $propArr['event_pid']);
4624  return;
4625  }
4626 
4627  if (!$mayInsertAccess) {
4628  $this->‪log($table, $uid, 4, 0, 1, 'Attempt to move record \'%s\' (%s) without having permissions to insert.', 14, [$propArr['header'], $table . ':' . $uid], $propArr['event_pid']);
4629  return;
4630  }
4631 
4632  $recordWasMoved = false;
4633  // Move the record via a hook, used e.g. for versioning
4634  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'] ?? [] as $className) {
4635  $hookObj = GeneralUtility::makeInstance($className);
4636  if (method_exists($hookObj, 'moveRecord')) {
4637  $hookObj->moveRecord($table, $uid, $destPid, $propArr, $moveRec, $resolvedPid, $recordWasMoved, $this);
4638  }
4639  }
4640  // Move the record if a hook hasn't moved it yet
4641  if (!$recordWasMoved) {
4642  $this->‪moveRecord_raw($table, $uid, $destPid);
4643  }
4644  }
4645 
4655  public function ‪moveRecord_raw($table, $uid, $destPid)
4656  {
4657  $sortColumn = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
4658  $origDestPid = $destPid;
4659  // This is the actual pid of the moving to destination
4660  $resolvedPid = $this->‪resolvePid($table, $destPid);
4661  // Checking if the pid is negative, but no sorting row is defined. In that case, find the correct pid. Basically this check make the error message 4-13 meaning less... But you can always remove this check if you prefer the error instead of a no-good action (which is to move the record to its own page...)
4662  // $destPid>=0 because we must correct pid in case of versioning "page" types.
4663  if (($destPid < 0 && !$sortColumn) || $destPid >= 0) {
4664  $destPid = $resolvedPid;
4665  }
4666  // Get this before we change the pid (for logging)
4667  $propArr = $this->‪getRecordProperties($table, $uid);
4668  $moveRec = $this->‪getRecordProperties($table, $uid, true);
4669  // Prepare user defined objects (if any) for hooks which extend this function:
4670  $hookObjectsArr = [];
4671  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'] ?? [] as $className) {
4672  $hookObjectsArr[] = GeneralUtility::makeInstance($className);
4673  }
4674  // Timestamp field:
4675  $updateFields = [];
4676  if (‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
4677  $updateFields[‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = ‪$GLOBALS['EXEC_TIME'];
4678  }
4679 
4680  // Check if this is a translation of a page, if so then it just needs to be kept "sorting" in sync
4681  // Usually called from moveL10nOverlayRecords()
4682  if ($table === 'pages') {
4683  $defaultLanguagePageId = $this->‪getDefaultLanguagePageId((int)$uid);
4684  if ($defaultLanguagePageId !== (int)$uid) {
4685  $originalTranslationRecord = $this->‪recordInfo($table, $defaultLanguagePageId, 'pid,' . $sortColumn);
4686  $updateFields[$sortColumn] = $originalTranslationRecord[$sortColumn];
4687  // Ensure that the PID is always the same as the default language page
4688  $destPid = $originalTranslationRecord['pid'];
4689  }
4690  }
4691 
4692  // Insert as first element on page (where uid = $destPid)
4693  if ($destPid >= 0) {
4694  if ($table !== 'pages' || $this->‪destNotInsideSelf($destPid, $uid)) {
4695  // Clear cache before moving
4696  list($parentUid) = ‪BackendUtility::getTSCpid($table, $uid, '');
4697  $this->‪registerRecordIdForPageCacheClearing($table, $uid, $parentUid);
4698  // Setting PID
4699  $updateFields['pid'] = $destPid;
4700  // Table is sorted by 'sortby'
4701  if ($sortColumn && !isset($updateFields[$sortColumn])) {
4702  $sortNumber = $this->‪getSortNumber($table, $uid, $destPid);
4703  $updateFields[$sortColumn] = $sortNumber;
4704  }
4705  // Check for child records that have also to be moved
4706  $this->‪moveRecord_procFields($table, $uid, $destPid);
4707  // Create query for update:
4708  GeneralUtility::makeInstance(ConnectionPool::class)
4709  ->getConnectionForTable($table)
4710  ->update($table, $updateFields, ['uid' => (int)$uid]);
4711  // Check for the localizations of that element
4712  $this->‪moveL10nOverlayRecords($table, $uid, $destPid, $destPid);
4713  // Call post processing hooks:
4714  foreach ($hookObjectsArr as $hookObj) {
4715  if (method_exists($hookObj, 'moveRecord_firstElementPostProcess')) {
4716  $hookObj->moveRecord_firstElementPostProcess($table, $uid, $destPid, $moveRec, $updateFields, $this);
4717  }
4718  }
4719 
4720  $this->‪getRecordHistoryStore()->‪moveRecord($table, $uid, ['oldPageId' => $propArr['pid'], 'newPageId' => $destPid, 'oldData' => $propArr, 'newData' => $updateFields]);
4721  if ($this->enableLogging) {
4722  // Logging...
4723  $oldpagePropArr = $this->‪getRecordProperties('pages', $propArr['pid']);
4724  if ($destPid != $propArr['pid']) {
4725  // Logged to old page
4726  $newPropArr = $this->‪getRecordProperties($table, $uid);
4727  $newpagePropArr = $this->‪getRecordProperties('pages', $destPid);
4728  $this->‪log($table, $uid, 4, $destPid, 0, 'Moved record \'%s\' (%s) to page \'%s\' (%s)', 2, [$propArr['header'], $table . ':' . $uid, $newpagePropArr['header'], $newPropArr['pid']], $propArr['pid']);
4729  // Logged to new page
4730  $this->‪log($table, $uid, 4, $destPid, 0, 'Moved record \'%s\' (%s) from page \'%s\' (%s)', 3, [$propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']], $destPid);
4731  } else {
4732  // Logged to new page
4733  $this->‪log($table, $uid, 4, $destPid, 0, 'Moved record \'%s\' (%s) on page \'%s\' (%s)', 4, [$propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']], $destPid);
4734  }
4735  }
4736  // Clear cache after moving
4737  $this->‪registerRecordIdForPageCacheClearing($table, $uid);
4738  $this->‪fixUniqueInPid($table, $uid);
4739  $this->‪fixUniqueInSite($table, (int)$uid);
4740  if ($table === 'pages') {
4742  }
4743  } elseif ($this->enableLogging) {
4744  $destPropArr = $this->‪getRecordProperties('pages', $destPid);
4745  $this->‪log($table, $uid, 4, 0, 1, 'Attempt to move page \'%s\' (%s) to inside of its own rootline (at page \'%s\' (%s))', 10, [$propArr['header'], $uid, $destPropArr['header'], $destPid], $propArr['pid']);
4746  }
4747  } elseif ($sortColumn) {
4748  // Put after another record
4749  // Table is being sorted
4750  // Save the position to which the original record is requested to be moved
4751  $originalRecordDestinationPid = $destPid;
4752  $sortInfo = $this->‪getSortNumber($table, $uid, $destPid);
4753  // Setting the destPid to the new pid of the record.
4754  $destPid = $sortInfo['pid'];
4755  // If not an array, there was an error (which is already logged)
4756  if (is_array($sortInfo)) {
4757  if ($table !== 'pages' || $this->‪destNotInsideSelf($destPid, $uid)) {
4758  // clear cache before moving
4759  $this->‪registerRecordIdForPageCacheClearing($table, $uid);
4760  // We now update the pid and sortnumber (if not set for page translations)
4761  $updateFields['pid'] = $destPid;
4762  if (!isset($updateFields[$sortColumn])) {
4763  $updateFields[$sortColumn] = $sortInfo['sortNumber'];
4764  }
4765  // Check for child records that have also to be moved
4766  $this->‪moveRecord_procFields($table, $uid, $destPid);
4767  // Create query for update:
4768  GeneralUtility::makeInstance(ConnectionPool::class)
4769  ->getConnectionForTable($table)
4770  ->update($table, $updateFields, ['uid' => (int)$uid]);
4771  // Check for the localizations of that element
4772  $this->‪moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid);
4773  // Call post processing hooks:
4774  foreach ($hookObjectsArr as $hookObj) {
4775  if (method_exists($hookObj, 'moveRecord_afterAnotherElementPostProcess')) {
4776  $hookObj->moveRecord_afterAnotherElementPostProcess($table, $uid, $destPid, $origDestPid, $moveRec, $updateFields, $this);
4777  }
4778  }
4779  $this->‪getRecordHistoryStore()->‪moveRecord($table, $uid, ['oldPageId' => $propArr['pid'], 'newPageId' => $destPid, 'oldData' => $propArr, 'newData' => $updateFields]);
4780  if ($this->enableLogging) {
4781  // Logging...
4782  $oldpagePropArr = $this->‪getRecordProperties('pages', $propArr['pid']);
4783  if ($destPid != $propArr['pid']) {
4784  // Logged to old page
4785  $newPropArr = $this->‪getRecordProperties($table, $uid);
4786  $newpagePropArr = $this->‪getRecordProperties('pages', $destPid);
4787  $this->‪log($table, $uid, 4, 0, 0, 'Moved record \'%s\' (%s) to page \'%s\' (%s)', 2, [$propArr['header'], $table . ':' . $uid, $newpagePropArr['header'], $newPropArr['pid']], $propArr['pid']);
4788  // Logged to old page
4789  $this->‪log($table, $uid, 4, 0, 0, 'Moved record \'%s\' (%s) from page \'%s\' (%s)', 3, [$propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']], $destPid);
4790  } else {
4791  // Logged to old page
4792  $this->‪log($table, $uid, 4, 0, 0, 'Moved record \'%s\' (%s) on page \'%s\' (%s)', 4, [$propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']], $destPid);
4793  }
4794  }
4795  // Clear cache after moving
4796  $this->‪registerRecordIdForPageCacheClearing($table, $uid);
4797  $this->‪fixUniqueInPid($table, $uid);
4798  $this->‪fixUniqueInSite($table, (int)$uid);
4799  if ($table === 'pages') {
4800  $this->‪fixUniqueInSiteForSubpages((int)$uid);
4801  }
4802  } elseif ($this->enableLogging) {
4803  $destPropArr = $this->‪getRecordProperties('pages', $destPid);
4804  $this->‪log($table, $uid, 4, 0, 1, 'Attempt to move page \'%s\' (%s) to inside of its own rootline (at page \'%s\' (%s))', 10, [$propArr['header'], $uid, $destPropArr['header'], $destPid], $propArr['pid']);
4805  }
4806  } else {
4807  $this->‪log($table, $uid, 4, 0, 1, 'Attempt to move record \'%s\' (%s) to after another record, although the table has no sorting row.', 13, [$propArr['header'], $table . ':' . $uid], $propArr['event_pid']);
4808  }
4809  }
4810  }
4811 
4820  public function ‪moveRecord_procFields($table, $uid, $destPid)
4821  {
4822  $row = ‪BackendUtility::getRecordWSOL($table, $uid);
4823  if (is_array($row) && (int)$destPid !== (int)$row['pid']) {
4824  $conf = ‪$GLOBALS['TCA'][$table]['columns'];
4825  foreach ($row as $field => $value) {
4826  $this->‪moveRecord_procBasedOnFieldType($table, $uid, $destPid, $field, $value, $conf[$field]['config']);
4827  }
4828  }
4829  }
4830 
4841  public function ‪moveRecord_procBasedOnFieldType($table, $uid, $destPid, $field, $value, $conf)
4842  {
4843  if ($conf['type'] === 'inline') {
4844  $foreign_table = $conf['foreign_table'];
4845  $moveChildrenWithParent = !isset($conf['behaviour']['disableMovingChildrenWithParent']) || !$conf['behaviour']['disableMovingChildrenWithParent'];
4846  if ($foreign_table && $moveChildrenWithParent) {
4847  $inlineType = $this->‪getInlineFieldType($conf);
4848  if ($inlineType === 'list' || $inlineType === 'field') {
4849  if ($table === 'pages') {
4850  // If the inline elements are related to a page record,
4851  // make sure they reside at that page and not at its parent
4852  $destPid = $uid;
4853  }
4854  $dbAnalysis = $this->‪createRelationHandlerInstance();
4855  $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
4856  }
4857  }
4858  }
4859  // Move the records
4860  if (isset($dbAnalysis)) {
4861  // Moving records to a positive destination will insert each
4862  // record at the beginning, thus the order is reversed here:
4863  foreach (array_reverse($dbAnalysis->itemArray) as $v) {
4864  $this->‪moveRecord($v['table'], $v['id'], $destPid);
4865  }
4866  }
4867  }
4868 
4877  public function ‪moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid)
4878  {
4879  // There's no need to perform this for non-localizable tables
4881  return;
4882  }
4883 
4884  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
4885  $queryBuilder->getRestrictions()
4886  ->removeAll()
4887  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
4888  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
4889 
4890  $queryBuilder->select('*')
4891  ->from($table)
4892  ->where(
4893  $queryBuilder->expr()->eq(
4894  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
4895  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT, ':pointer')
4896  )
4897  );
4898 
4899  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && ‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
4900  $queryBuilder->andWhere(
4901  $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
4902  );
4903  }
4904 
4905  $l10nRecords = $queryBuilder->execute()->fetchAll();
4906  if (is_array($l10nRecords)) {
4907  $localizedDestPids = [];
4908  // If $$originalRecordDestinationPid < 0, then it is the uid of the original language record we are inserting after
4909  if ($originalRecordDestinationPid < 0) {
4910  // Get the localized records of the record we are inserting after
4911  $queryBuilder->setParameter('pointer', abs($originalRecordDestinationPid), \PDO::PARAM_INT);
4912  $destL10nRecords = $queryBuilder->execute()->fetchAll();
4913  // Index the localized record uids by language
4914  if (is_array($destL10nRecords)) {
4915  foreach ($destL10nRecords as $record) {
4916  $localizedDestPids[$record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]] = -$record['uid'];
4917  }
4918  }
4919  }
4920  // Move the localized records after the corresponding localizations of the destination record
4921  foreach ($l10nRecords as $record) {
4922  $localizedDestPid = (int)$localizedDestPids[$record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]];
4923  if ($localizedDestPid < 0) {
4924  $this->‪moveRecord($table, $record['uid'], $localizedDestPid);
4925  } else {
4926  $this->‪moveRecord($table, $record['uid'], $destPid);
4927  }
4928  }
4929  }
4930  }
4931 
4940  public function ‪localize($table, $uid, $language)
4941  {
4942  $newId = false;
4943  $uid = (int)$uid;
4944  if (!‪$GLOBALS['TCA'][$table] || !$uid || $this->‪isNestedElementCallRegistered($table, $uid, 'localize-' . (string)$language) !== false) {
4945  return false;
4946  }
4947 
4948  $this->‪registerNestedElementCall($table, $uid, 'localize-' . (string)$language);
4949  if (!‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] || !‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
4950  $this->‪newlog('Localization failed; "languageField" and "transOrigPointerField" must be defined for the table ' . $table, 1);
4951  return false;
4952  }
4953  $langRec = ‪BackendUtility::getRecord('sys_language', (int)$language, 'uid,title');
4954  if (!$langRec) {
4955  $this->‪newlog('Sys language UID "' . $language . '" not found valid!', 1);
4956  return false;
4957  }
4958 
4959  if (!$this->‪doesRecordExist($table, $uid, 'show')) {
4960  $this->‪newlog('Attempt to localize record ' . $table . ':' . $uid . ' without permission.', 1);
4961  return false;
4962  }
4963 
4964  // Getting workspace overlay if possible - this will localize versions in workspace if any
4965  $row = ‪BackendUtility::getRecordWSOL($table, $uid);
4966  if (!is_array($row)) {
4967  $this->‪newlog('Attempt to localize record ' . $table . ':' . $uid . ' that did not exist!', 1);
4968  return false;
4969  }
4970 
4971  // Make sure that records which are translated from another language than the default language have a correct
4972  // localization source set themselves, before translating them to another language.
4973  if ((int)$row[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] !== 0
4974  && $row[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0) {
4975  $localizationParentRecord = ‪BackendUtility::getRecord(
4976  $table,
4977  $row[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']]
4978  );
4979  if ((int)$localizationParentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] !== 0) {
4980  $this->‪newlog('Localization failed; Source record ' . $table . ':' . $localizationParentRecord['uid'] . ' contained a reference to an original record that is not a default record (which is strange)!', 1);
4981  return false;
4982  }
4983  }
4984 
4985  // Default language records must never have a localization parent as they are the origin of any translation.
4986  if ((int)$row[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] !== 0
4987  && (int)$row[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] === 0) {
4988  $this->‪newlog('Localization failed; Source record ' . $table . ':' . $row['uid'] . ' contained a reference to an original default record but is a default record itself (which is strange)!', 1);
4989  return false;
4990  }
4991 
4992  $recordLocalizations = ‪BackendUtility::getRecordLocalization($table, $uid, $language, 'AND pid=' . (int)$row['pid']);
4993 
4994  if (!empty($recordLocalizations)) {
4995  $this->‪newlog(sprintf(
4996  'Localization failed: there already are localizations (%s) for language %d of the "%s" record %d!',
4997  implode(', ', array_column($recordLocalizations, 'uid')),
4998  $language,
4999  $table,
5000  $uid
5001  ), 1);
5002  return false;
5003  }
5004 
5005  // Initialize:
5006  ‪$overrideValues = [];
5007  // Set override values:
5008  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $langRec['uid'];
5009  // If the translated record is a default language record, set it's uid as localization parent of the new record.
5010  // If translating from any other language, no override is needed; we just can copy the localization parent of
5011  // the original record (which is pointing to the correspondent default language record) to the new record.
5012  // In copy / free mode the TransOrigPointer field is always set to 0, as no connection to the localization parent is wanted in that case.
5013  // For pages, there is no "copy/free mode".
5014  if (($this->useTransOrigPointerField || $table === 'pages') && (int)$row[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] === 0) {
5015  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $uid;
5016  } elseif (!$this->useTransOrigPointerField) {
5017  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = 0;
5018  }
5019  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'])) {
5020  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['translationSource']] = $uid;
5021  }
5022  // Copy the type (if defined in both tables) from the original record so that translation has same type as original record
5023  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['type'])) {
5024  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['type']] = $row[‪$GLOBALS['TCA'][$table]['ctrl']['type']];
5025  }
5026  // Set exclude Fields:
5027  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $fN => $fCfg) {
5028  $translateToMsg = '';
5029  // Check if we are just prefixing:
5030  if ($fCfg['l10n_mode'] === 'prefixLangTitle') {
5031  if (($fCfg['config']['type'] === 'text' || $fCfg['config']['type'] === 'input') && (string)$row[$fN] !== '') {
5032  list($tscPID) = ‪BackendUtility::getTSCpid($table, $uid, '');
5033  $TSConfig = ‪BackendUtility::getPagesTSconfig($tscPID)['TCEMAIN.'] ?? [];
5034  $tE = $this->‪getTableEntries($table, $TSConfig);
5035  if (!empty($TSConfig['translateToMessage']) && !$tE['disablePrependAtCopy']) {
5036  $translateToMsg = $this->‪getLanguageService()->‪sL($TSConfig['translateToMessage']);
5037  $translateToMsg = @sprintf($translateToMsg, $langRec['title']);
5038  }
5039 
5040  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processTranslateToClass'] ?? [] as $className) {
5041  $hookObj = GeneralUtility::makeInstance($className);
5042  if (method_exists($hookObj, 'processTranslateTo_copyAction')) {
5043  $hookObj->processTranslateTo_copyAction($row[$fN], $langRec, $this, $fN);
5044  }
5045  }
5046  if (!empty($translateToMsg)) {
5047  ‪$overrideValues[$fN] = '[' . $translateToMsg . '] ' . $row[$fN];
5048  } else {
5049  ‪$overrideValues[$fN] = $row[$fN];
5050  }
5051  }
5052  }
5053  }
5054 
5055  if ($table !== 'pages') {
5056  // Get the uid of record after which this localized record should be inserted
5057  $previousUid = $this->‪getPreviousLocalizedRecordUid($table, $uid, $row['pid'], $language);
5058  // Execute the copy:
5059  $newId = $this->‪copyRecord($table, $uid, -$previousUid, true, ‪$overrideValues, '', $language);
5060  $autoVersionNewId = $this->‪getAutoVersionId($table, $newId);
5061  if ($autoVersionNewId !== null) {
5062  $this->‪triggerRemapAction($table, $newId, [$this, 'placeholderShadowing'], [$table, $autoVersionNewId], true);
5063  }
5064  } else {
5065  // Create new page which needs to contain the same pid as the original page
5066  ‪$overrideValues['pid'] = $row['pid'];
5067  // Take over the hidden state of the original language state, this is done due to legacy reasons where-as
5068  // pages_language_overlay was set to "hidden -> default=0" but pages hidden -> default 1"
5069  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])) {
5070  $hiddenFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
5071  ‪$overrideValues[$hiddenFieldName] = $row[$hiddenFieldName] ?? ‪$GLOBALS['TCA'][$table]['columns'][$hiddenFieldName]['config']['default'];
5072  }
5073  $temporaryId = ‪StringUtility::getUniqueId('NEW');
5074  $copyTCE = $this->‪getLocalTCE();
5075  $copyTCE->start([$table => [$temporaryId => ‪$overrideValues]], [], $this->BE_USER);
5076  $copyTCE->process_datamap();
5077  // Getting the new UID as if it had been copied:
5078  $theNewSQLID = $copyTCE->substNEWwithIDs[$temporaryId];
5079  if ($theNewSQLID) {
5080  $this->copyMappingArray[$table][$uid] = $theNewSQLID;
5081  $newId = $theNewSQLID;
5082  }
5083  }
5084 
5085  return $newId;
5086  }
5087 
5104  protected function ‪inlineLocalizeSynchronize($table, $id, $command)
5105  {
5106  $parentRecord = ‪BackendUtility::getRecordWSOL($table, $id);
5107 
5108  // Backward-compatibility handling
5109  if (!is_array($command)) {
5110  // <field>, (localize | synchronize | <uid>):
5111  $parts = GeneralUtility::trimExplode(',', $command);
5112  $command = [
5113  'field' => $parts[0],
5114  // The previous process expected $id to point to the localized record already
5115  'language' => (int)$parentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]
5116  ];
5118  $command['action'] = $parts[1];
5119  } else {
5120  $command['ids'] = [$parts[1]];
5121  }
5122  }
5123 
5124  // In case the parent record is the default language record, fetch the localization
5125  if (empty($parentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']])) {
5126  // Fetch the live record
5127  $parentRecordLocalization = ‪BackendUtility::getRecordLocalization($table, $id, $command['language'], 'AND pid<>-1');
5128  if (empty($parentRecordLocalization)) {
5129  if ($this->enableLogging) {
5130  $this->‪log($table, $id, 0, 0, 0, 'Localization for parent record ' . $table . ':' . $id . '" cannot be fetched', -1, [], $this->‪eventPid($table, $id, $parentRecord['pid']));
5131  }
5132  return;
5133  }
5134  $parentRecord = $parentRecordLocalization[0];
5135  $id = $parentRecord['uid'];
5136  // Process overlay for current selected workspace
5137  ‪BackendUtility::workspaceOL($table, $parentRecord);
5138  }
5139 
5140  $field = $command['field'];
5141  $language = $command['language'];
5142  $action = $command['action'];
5143  $ids = $command['ids'];
5144 
5145  if (!$field || !($action === 'localize' || $action === 'synchronize') && empty($ids) || !isset(‪$GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
5146  return;
5147  }
5148 
5149  $config = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'];
5150  $foreignTable = $config['foreign_table'];
5151 
5152  $transOrigPointer = (int)$parentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
5153  $childTransOrigPointerField = ‪$GLOBALS['TCA'][$foreignTable]['ctrl']['transOrigPointerField'];
5154 
5155  if (!$parentRecord || !is_array($parentRecord) || $language <= 0 || !$transOrigPointer) {
5156  return;
5157  }
5158 
5159  $inlineSubType = $this->‪getInlineFieldType($config);
5160  if ($inlineSubType === false) {
5161  return;
5162  }
5163 
5164  $transOrigRecord = ‪BackendUtility::getRecordWSOL($table, $transOrigPointer);
5165 
5166  $removeArray = [];
5167  $mmTable = $inlineSubType === 'mm' && isset($config['MM']) && $config['MM'] ? $config['MM'] : '';
5168  // Fetch children from original language parent:
5170  $dbAnalysisOriginal = $this->‪createRelationHandlerInstance();
5171  $dbAnalysisOriginal->start($transOrigRecord[$field], $foreignTable, $mmTable, $transOrigRecord['uid'], $table, $config);
5172  $elementsOriginal = [];
5173  foreach ($dbAnalysisOriginal->itemArray as $item) {
5174  $elementsOriginal[$item['id']] = $item;
5175  }
5176  unset($dbAnalysisOriginal);
5177  // Fetch children from current localized parent:
5179  $dbAnalysisCurrent = $this->‪createRelationHandlerInstance();
5180  $dbAnalysisCurrent->start($parentRecord[$field], $foreignTable, $mmTable, $id, $table, $config);
5181  // Perform synchronization: Possibly removal of already localized records:
5182  if ($action === 'synchronize') {
5183  foreach ($dbAnalysisCurrent->itemArray as $index => $item) {
5184  $childRecord = ‪BackendUtility::getRecordWSOL($item['table'], $item['id']);
5185  if (isset($childRecord[$childTransOrigPointerField]) && $childRecord[$childTransOrigPointerField] > 0) {
5186  $childTransOrigPointer = $childRecord[$childTransOrigPointerField];
5187  // If synchronization is requested, child record was translated once, but original record does not exist anymore, remove it:
5188  if (!isset($elementsOriginal[$childTransOrigPointer])) {
5189  unset($dbAnalysisCurrent->itemArray[$index]);
5190  $removeArray[$item['table']][$item['id']]['delete'] = 1;
5191  }
5192  }
5193  }
5194  }
5195  // Perform synchronization/localization: Possibly add unlocalized records for original language:
5196  if ($action === 'localize' || $action === 'synchronize') {
5197  foreach ($elementsOriginal as $originalId => $item) {
5198  $item['id'] = $this->‪localize($item['table'], $item['id'], $language);
5199  $item['id'] = $this->‪overlayAutoVersionId($item['table'], $item['id']);
5200  $dbAnalysisCurrent->itemArray[] = $item;
5201  }
5202  } elseif (!empty($ids)) {
5203  foreach ($ids as $childId) {
5204  if (!‪MathUtility::canBeInterpretedAsInteger($childId) || !isset($elementsOriginal[$childId])) {
5205  continue;
5206  }
5207  $item = $elementsOriginal[$childId];
5208  $item['id'] = $this->‪localize($item['table'], $item['id'], $language);
5209  $item['id'] = $this->‪overlayAutoVersionId($item['table'], $item['id']);
5210  $dbAnalysisCurrent->itemArray[] = $item;
5211  }
5212  }
5213  // Store the new values, we will set up the uids for the subtype later on (exception keep localization from original record):
5214  $value = implode(',', $dbAnalysisCurrent->getValueArray());
5215  $this->registerDBList[$table][$id][$field] = $value;
5216  // Remove child records (if synchronization requested it):
5217  if (is_array($removeArray) && !empty($removeArray)) {
5219  $tce = GeneralUtility::makeInstance(__CLASS__);
5220  $tce->enableLogging = ‪$this->enableLogging;
5221  $tce->start([], $removeArray, $this->BE_USER);
5222  $tce->process_cmdmap();
5223  unset($tce);
5224  }
5225  $updateFields = [];
5226  // Handle, reorder and store relations:
5227  if ($inlineSubType === 'list') {
5228  $updateFields = [$field => $value];
5229  } elseif ($inlineSubType === 'field') {
5230  $dbAnalysisCurrent->writeForeignField($config, $id);
5231  $updateFields = [$field => $dbAnalysisCurrent->countItems(false)];
5232  } elseif ($inlineSubType === 'mm') {
5233  $dbAnalysisCurrent->writeMM($config['MM'], $id);
5234  $updateFields = [$field => $dbAnalysisCurrent->countItems(false)];
5235  }
5236  // Update field referencing to child records of localized parent record:
5237  if (!empty($updateFields)) {
5238  $this->‪updateDB($table, $id, $updateFields);
5239  }
5240  }
5241 
5242  /*********************************************
5243  *
5244  * Cmd: Deleting
5245  *
5246  ********************************************/
5253  public function ‪deleteAction($table, $id)
5254  {
5255  $recordToDelete = ‪BackendUtility::getRecord($table, $id);
5256  // Record asked to be deleted was found:
5257  if (is_array($recordToDelete)) {
5258  $recordWasDeleted = false;
5259  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'] ?? [] as $className) {
5260  $hookObj = GeneralUtility::makeInstance($className);
5261  if (method_exists($hookObj, 'processCmdmap_deleteAction')) {
5262  $hookObj->processCmdmap_deleteAction($table, $id, $recordToDelete, $recordWasDeleted, $this);
5263  }
5264  }
5265  // Delete the record if a hook hasn't deleted it yet
5266  if (!$recordWasDeleted) {
5267  $this->‪deleteEl($table, $id);
5268  }
5269  }
5270  }
5271 
5282  public function ‪deleteEl($table, $uid, $noRecordCheck = false, $forceHardDelete = false, bool $deleteRecordsOnPage = true)
5283  {
5284  if ($table === 'pages') {
5285  $this->‪deletePages($uid, $noRecordCheck, $forceHardDelete, $deleteRecordsOnPage);
5286  } else {
5287  $this->‪deleteVersionsForRecord($table, $uid, $forceHardDelete);
5288  $this->‪deleteRecord($table, $uid, $noRecordCheck, $forceHardDelete);
5289  }
5290  }
5291 
5299  public function ‪deleteVersionsForRecord($table, $uid, $forceHardDelete)
5300  {
5301  $versions = ‪BackendUtility::selectVersionsOfRecord($table, $uid, 'uid,pid,t3ver_wsid,t3ver_state', $this->BE_USER->workspace ?: null);
5302  if (is_array($versions)) {
5303  foreach ($versions as $verRec) {
5304  if (!$verRec['_CURRENT_VERSION']) {
5305  if ($table === 'pages') {
5306  $this->‪deletePages($verRec['uid'], true, $forceHardDelete);
5307  } else {
5308  $this->‪deleteRecord($table, $verRec['uid'], true, $forceHardDelete);
5309  }
5310 
5311  // Delete move-placeholder
5312  $versionState = ‪VersionState::cast($verRec['t3ver_state']);
5313  if ($versionState->equals(‪VersionState::MOVE_POINTER)) {
5314  $versionMovePlaceholder = ‪BackendUtility::getMovePlaceholder($table, $uid, 'uid', $verRec['t3ver_wsid']);
5315  if (!empty($versionMovePlaceholder)) {
5316  $this->‪deleteEl($table, $versionMovePlaceholder['uid'], true, $forceHardDelete);
5317  }
5318  }
5319  }
5320  }
5321  }
5322  }
5323 
5330  public function ‪undeleteRecord($table, $uid)
5331  {
5332  if ($this->‪isRecordUndeletable($table, $uid)) {
5333  $this->‪deleteRecord($table, $uid, true, false, true);
5334  }
5335  }
5336 
5349  public function ‪deleteRecord($table, $uid, $noRecordCheck = false, $forceHardDelete = false, $undeleteRecord = false)
5350  {
5351  $uid = (int)$uid;
5352  if (!‪$GLOBALS['TCA'][$table] || !$uid) {
5353  $this->‪log($table, $uid, 3, 0, 1, 'Attempt to delete record without delete-permissions. [' . $this->BE_USER->errorMsg . ']');
5354  return;
5355  }
5356  // Skip processing already deleted records
5357  if (!$forceHardDelete && !$undeleteRecord && $this->‪hasDeletedRecord($table, $uid)) {
5358  return;
5359  }
5360 
5361  // Checking if there is anything else disallowing deleting the record by checking if editing is allowed
5362  $deletedRecord = $forceHardDelete || $undeleteRecord;
5363  $fullLanguageAccessCheck = true;
5364  if ($table === 'pages') {
5365  // If this is a page translation, the full language access check should not be done
5366  $defaultLanguagePageId = $this->‪getDefaultLanguagePageId($uid);
5367  if ($defaultLanguagePageId !== $uid) {
5368  $fullLanguageAccessCheck = false;
5369  }
5370  }
5371  $hasEditAccess = $this->BE_USER->recordEditAccessInternals($table, $uid, false, $deletedRecord, $fullLanguageAccessCheck);
5372  if (!$hasEditAccess) {
5373  $this->‪log($table, $uid, 3, 0, 1, 'Attempt to delete record without delete-permissions');
5374  return;
5375  }
5376  if (!$noRecordCheck && !$this->‪doesRecordExist($table, $uid, 'delete')) {
5377  return;
5378  }
5379 
5380  // Clear cache before deleting the record, else the correct page cannot be identified by clear_cache
5381  list($parentUid) = ‪BackendUtility::getTSCpid($table, $uid, '');
5382  $this->‪registerRecordIdForPageCacheClearing($table, $uid, $parentUid);
5383  $deleteField = ‪$GLOBALS['TCA'][$table]['ctrl']['delete'];
5384  $databaseErrorMessage = '';
5385  if ($deleteField && !$forceHardDelete) {
5386  $updateFields = [
5387  $deleteField => $undeleteRecord ? 0 : 1
5388  ];
5389  if (‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
5390  $updateFields[‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = ‪$GLOBALS['EXEC_TIME'];
5391  }
5392  // before (un-)deleting this record, check for child records or references
5393  $this->‪deleteRecord_procFields($table, $uid, $undeleteRecord);
5394  try {
5395  // Delete all l10n records as well, impossible during undelete because it might bring too many records back to life
5396  if (!$undeleteRecord) {
5397  $this->deletedRecords[$table][] = (int)$uid;
5398  $this->‪deleteL10nOverlayRecords($table, $uid);
5399  }
5400  GeneralUtility::makeInstance(ConnectionPool::class)
5401  ->getConnectionForTable($table)
5402  ->update($table, $updateFields, ['uid' => (int)$uid]);
5403  } catch (DBALException $e) {
5404  $databaseErrorMessage = $e->getPrevious()->getMessage();
5405  }
5406  } else {
5407  // Fetches all fields with flexforms and look for files to delete:
5408  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $fieldName => $cfg) {
5409  $conf = $cfg['config'];
5410  switch ($conf['type']) {
5411  case 'flex':
5412  $flexObj = GeneralUtility::makeInstance(FlexFormTools::class);
5413 
5414  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
5415  ->getQueryBuilderForTable($table);
5416  $queryBuilder->getRestrictions()->removeAll();
5417 
5418  $files = $queryBuilder
5419  ->select('*')
5420  ->from($table)
5421  ->where(
5422  $queryBuilder->expr()->eq(
5423  'uid',
5424  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
5425  )
5426  )
5427  ->execute()
5428  ->fetch();
5429 
5430  $flexObj->traverseFlexFormXMLData($table, $fieldName, $files, $this, 'deleteRecord_flexFormCallBack');
5431  break;
5432  }
5433  }
5434  // Fetches all fields that holds references to files
5435  $fileFieldArr = $this->‪extFileFields($table);
5436  if (!empty($fileFieldArr)) {
5437  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
5438  $queryBuilder->getRestrictions()->removeAll();
5439  $result = $queryBuilder
5440  ->select(...$fileFieldArr)
5441  ->from($table)
5442  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)))
5443  ->execute();
5444  if ($row = $result->fetch()) {
5445  $fArray = $fileFieldArr;
5446  // MISSING: Support for MM file relations!
5447  foreach ($fArray as $theField) {
5448  // This deletes files that belonged to this record.
5449  $this->‪extFileFunctions($table, $theField, $row[$theField]);
5450  }
5451  } else {
5452  $this->‪log($table, $uid, 3, 0, 100, 'Delete: Zero rows in result when trying to read filenames from record which should be deleted');
5453  }
5454  }
5455  // Delete the hard way...:
5456  try {
5457  GeneralUtility::makeInstance(ConnectionPool::class)
5458  ->getConnectionForTable($table)
5459  ->delete($table, ['uid' => (int)$uid]);
5460  $this->deletedRecords[$table][] = (int)$uid;
5461  $this->‪deleteL10nOverlayRecords($table, $uid);
5462  } catch (DBALException $e) {
5463  $databaseErrorMessage = $e->getPrevious()->getMessage();
5464  }
5465  }
5466  if ($this->enableLogging) {
5467  // 1 means insert, 3 means delete
5468  $state = $undeleteRecord ? 1 : 3;
5469  if ($databaseErrorMessage === '') {
5470  if ($forceHardDelete) {
5471  $message = 'Record \'%s\' (%s) was deleted unrecoverable from page \'%s\' (%s)';
5472  } else {
5473  $message = $state === 1 ? 'Record \'%s\' (%s) was restored on page \'%s\' (%s)' : 'Record \'%s\' (%s) was deleted from page \'%s\' (%s)';
5474  }
5475  $propArr = $this->‪getRecordProperties($table, $uid);
5476  $pagePropArr = $this->‪getRecordProperties('pages', $propArr['pid']);
5477 
5478  $this->‪log($table, $uid, $state, 0, 0, $message, 0, [
5479  $propArr['header'],
5480  $table . ':' . $uid,
5481  $pagePropArr['header'],
5482  $propArr['pid']
5483  ], $propArr['event_pid']);
5484  } else {
5485  $this->‪log($table, $uid, $state, 0, 100, $databaseErrorMessage);
5486  }
5487  }
5488 
5489  // Add history entry
5490  if ($undeleteRecord) {
5491  $this->‪getRecordHistoryStore()->‪undeleteRecord($table, $uid);
5492  } else {
5493  $this->‪getRecordHistoryStore()->‪deleteRecord($table, $uid);
5494  }
5495 
5496  // Update reference index:
5497  $this->‪updateRefIndex($table, $uid);
5498 
5499  // We track calls to update the reference index as to avoid calling it twice
5500  // with the same arguments. This is done because reference indexing is quite
5501  // costly and the update reference index stack usually contain duplicates.
5502  // NB: also filled and checked in loop below. The initialisation prevents
5503  // running the "root" record twice if it appears in the stack twice.
5504  $updateReferenceIndexCalls = [[$table, $uid]];
5505 
5506  // If there are entries in the updateRefIndexStack
5507  if (is_array($this->updateRefIndexStack[$table]) && is_array($this->updateRefIndexStack[$table][$uid])) {
5508  while (‪$args = array_pop($this->updateRefIndexStack[$table][$uid])) {
5509  if (!in_array(‪$args, $updateReferenceIndexCalls, true)) {
5510  // $args[0]: table, $args[1]: uid
5511  $this->‪updateRefIndex(‪$args[0], ‪$args[1]);
5512  $updateReferenceIndexCalls[] = ‪$args;
5513  }
5514  }
5515  unset($this->updateRefIndexStack[$table][$uid]);
5516  }
5517  }
5518 
5528  public function ‪deleteRecord_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, $pObj)
5529  {
5530  // Use reference index object to find files in fields:
5532  $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
5533  $refIndexObj->enableRuntimeCache();
5534  $files = $refIndexObj->getRelations_procFiles($dataValue, $dsArr['TCEforms']['config'], $PA['uid']);
5535  // Traverse files and delete them if the field is a regular file field (and not a file_reference field)
5536  if (is_array($files) && $dsArr['TCEforms']['config']['internal_type'] === 'file') {
5537  // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Deprecation logged by TcaMigration class.
5538  foreach ($files as $dat) {
5539  if (@is_file($dat['ID_absFile'])) {
5540  $file = $this->‪getResourceFactory()->‪retrieveFileOrFolderObject($dat['ID_absFile']);
5541  $file->‪delete();
5542  } else {
5543  $this->‪log('', 0, 3, 0, 100, 'Delete: Referenced file \'' . $dat['ID_absFile'] . '\' that was supposed to be deleted together with its record which didn\'t exist');
5544  }
5545  }
5546  }
5547  }
5548 
5558  public function ‪deletePages($uid, $force = false, $forceHardDelete = false, bool $deleteRecordsOnPage = true)
5559  {
5560  $uid = (int)$uid;
5561  if ($uid === 0) {
5562  if ($this->enableLogging) {
5563  $this->‪log('pages', $uid, 0, 0, 2, 'Deleting all pages starting from the root-page is disabled.', -1, [], 0);
5564  }
5565  return;
5566  }
5567  // Getting list of pages to delete:
5568  if ($force) {
5569  // Returns the branch WITHOUT permission checks (0 secures that), so it cannot return -1
5570  $pageIdsInBranch = $this->‪doesBranchExist('', $uid, 0, true);
5571  $res = GeneralUtility::intExplode(',', $pageIdsInBranch . $uid, true);
5572  } else {
5573  $res = $this->‪canDeletePage($uid);
5574  }
5575  // Perform deletion if not error:
5576  if (is_array($res)) {
5577  foreach ($res as $deleteId) {
5578  $this->‪deleteSpecificPage($deleteId, $forceHardDelete, $deleteRecordsOnPage);
5579  }
5580  } else {
5582  $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $res, '', ‪FlashMessage::ERROR, true);
5584  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
5585  $flashMessageService->getMessageQueueByIdentifier()->addMessage($flashMessage);
5586  $this->‪newlog($res, 1);
5587  }
5588  }
5589 
5599  public function ‪deleteSpecificPage($uid, $forceHardDelete = false, bool $deleteRecordsOnPage = true)
5600  {
5601  $uid = (int)$uid;
5602  if (!$uid) {
5603  // Early void return on invalid uid
5604  return;
5605  }
5606  $forceHardDelete = (bool)$forceHardDelete;
5607 
5608  // Delete either a default language page or a translated page
5609  $pageIdInDefaultLanguage = $this->‪getDefaultLanguagePageId($uid);
5610  $isPageTranslation = false;
5611  $pageLanguageId = 0;
5612  if ($pageIdInDefaultLanguage !== $uid) {
5613  // For translated pages, translated records in other tables (eg. tt_content) for the
5614  // to-delete translated page have their pid field set to the uid of the default language record,
5615  // NOT the uid of the translated page record.
5616  // If a translated page is deleted, only translations of records in other tables of this language
5617  // should be deleted. The code checks if the to-delete page is a translated page and
5618  // adapts the query for other tables to use the uid of the default language page as pid together
5619  // with the language id of the translated page.
5620  $isPageTranslation = true;
5621  $pageLanguageId = $this->‪pageInfo($uid, ‪$GLOBALS['TCA']['pages']['ctrl']['languageField']);
5622  }
5623 
5624  if ($deleteRecordsOnPage) {
5625  $tableNames = $this->‪compileAdminTables();
5626  foreach ($tableNames as $table) {
5627  if ($table === 'pages' || ($isPageTranslation && !‪BackendUtility::isTableLocalizable($table))) {
5628  // Skip pages table. And skip table if not translatable, but a translated page is deleted
5629  continue;
5630  }
5631 
5632  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
5633  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
5634  $queryBuilder
5635  ->select('uid')
5636  ->from($table);
5637 
5638  if ($isPageTranslation) {
5639  // Only delete records in the specified language
5640  $queryBuilder->where(
5641  $queryBuilder->expr()->eq(
5642  'pid',
5643  $queryBuilder->createNamedParameter($pageIdInDefaultLanguage, \PDO::PARAM_INT)
5644  ),
5645  $queryBuilder->expr()->eq(
5646  ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'],
5647  $queryBuilder->createNamedParameter($pageLanguageId, \PDO::PARAM_INT)
5648  )
5649  );
5650  } else {
5651  // Delete all records on this page
5652  $queryBuilder->where(
5653  $queryBuilder->expr()->eq(
5654  'pid',
5655  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
5656  )
5657  );
5658  }
5659  $statement = $queryBuilder->execute();
5660 
5661  while ($row = $statement->fetch()) {
5662  // Handle a detail related to workspace placeholder records, delete any
5663  // further workspace overlays for the record in question, then delete the record.
5664  $this->‪copyMovedRecordToNewLocation($table, $row['uid']);
5665  $this->‪deleteVersionsForRecord($table, $row['uid'], $forceHardDelete);
5666  $this->‪deleteRecord($table, $row['uid'], true, $forceHardDelete);
5667  }
5668  }
5669  }
5670 
5671  // Handle a detail related to workspace placeholder records, delete any
5672  // further workspace overlays for the page in question, then delete the page.
5673  $this->‪copyMovedRecordToNewLocation('pages', $uid);
5674  $this->‪deleteVersionsForRecord('pages', $uid, $forceHardDelete);
5675  $this->‪deleteRecord('pages', $uid, true, $forceHardDelete);
5676  }
5677 
5690  protected function ‪copyMovedRecordToNewLocation($table, $uid)
5691  {
5692  if ($this->BE_USER->workspace > 0) {
5693  $originalRecord = ‪BackendUtility::getRecord($table, $uid);
5694  $movePlaceholder = ‪BackendUtility::getMovePlaceholder($table, $uid);
5695  // Check whether target page to copied to is different to current page
5696  // Cloning on the same page is superfluous and does not help at all
5697  if (!empty($originalRecord) && !empty($movePlaceholder) && (int)$originalRecord['pid'] !== (int)$movePlaceholder['pid']) {
5698  // If move placeholder exists, copy to new location
5699  // This will create a New placeholder on the new location
5700  // and a version for this new placeholder
5701  $command = [
5702  $table => [
5703  $uid => [
5704  'copy' => '-' . $movePlaceholder['uid']
5705  ]
5706  ]
5707  ];
5709  $dataHandler = GeneralUtility::makeInstance(__CLASS__);
5710  $dataHandler->enableLogging = ‪$this->enableLogging;
5711  $dataHandler->neverHideAtCopy = true;
5712  $dataHandler->start([], $command, $this->BE_USER);
5713  $dataHandler->process_cmdmap();
5714  unset($dataHandler);
5715 
5716  // Delete move placeholder
5717  $this->‪deleteRecord($table, $movePlaceholder['uid'], true, true);
5718  }
5719  }
5720  }
5721 
5728  public function ‪canDeletePage($uid)
5729  {
5730  $uid = (int)$uid;
5731  $isTranslatedPage = null;
5732 
5733  // If we may at all delete this page
5734  // If this is a page translation, do the check against the perms_* of the default page
5735  // Because it is currently only deleting the translation
5736  $defaultLanguagePageId = $this->‪getDefaultLanguagePageId($uid);
5737  if ($defaultLanguagePageId !== $uid) {
5738  if ($this->‪doesRecordExist('pages', (int)$defaultLanguagePageId, 'delete')) {
5739  $isTranslatedPage = true;
5740  } else {
5741  return 'Attempt to delete page without permissions';
5742  }
5743  } elseif (!$this->‪doesRecordExist('pages', $uid, 'delete')) {
5744  return 'Attempt to delete page without permissions';
5745  }
5746 
5747  $pageIdsInBranch = $this->‪doesBranchExist('', $uid, $this->pMap['delete'], true);
5748 
5749  if ($this->deleteTree) {
5750  if ($pageIdsInBranch === -1) {
5751  return 'Attempt to delete pages in branch without permissions';
5752  }
5753 
5754  $pagesInBranch = GeneralUtility::intExplode(',', $pageIdsInBranch . $uid, true);
5755  } else {
5756  if ($pageIdsInBranch === -1) {
5757  return 'Attempt to delete page without permissions';
5758  }
5759  if ($pageIdsInBranch !== '') {
5760  return 'Attempt to delete page which has subpages';
5761  }
5762 
5763  $pagesInBranch = [$uid];
5764  }
5765 
5766  if (!$this->‪checkForRecordsFromDisallowedTables($pagesInBranch)) {
5767  return 'Attempt to delete records from disallowed tables';
5768  }
5769 
5770  foreach ($pagesInBranch as $pageInBranch) {
5771  if (!$this->BE_USER->recordEditAccessInternals('pages', $pageInBranch, false, false, $isTranslatedPage ? false : true)) {
5772  return 'Attempt to delete page which has prohibited localizations.';
5773  }
5774  }
5775  return $pagesInBranch;
5776  }
5777 
5785  public function ‪cannotDeleteRecord($table, $id)
5786  {
5787  if ($table === 'pages') {
5788  $res = $this->‪canDeletePage($id);
5789  return is_array($res) ? false : $res;
5790  }
5791  return $this->‪doesRecordExist($table, $id, 'delete') ? false : 'No permission to delete record';
5792  }
5793 
5801  public function ‪isRecordUndeletable($table, $uid)
5802  {
5803  $result = false;
5804  $record = ‪BackendUtility::getRecord($table, $uid, 'pid', '', false);
5805  if ($record['pid']) {
5806  $page = ‪BackendUtility::getRecord('pages', $record['pid'], 'deleted, title, uid', '', false);
5807  // The page containing the record is not deleted, thus the record can be undeleted:
5808  if (!$page['deleted']) {
5809  $result = true;
5810  } else {
5811  $this->‪log($table, $uid, 'isRecordUndeletable', '', 1, 'Record cannot be undeleted since the page containing it is deleted! Undelete page "' . $page['title'] . ' (UID: ' . $page['uid'] . ')" first');
5812  }
5813  } else {
5814  // The page containing the record is on rootlevel, so there is no parent record to check, and the record can be undeleted:
5815  $result = true;
5816  }
5817  return $result;
5818  }
5819 
5829  public function ‪deleteRecord_procFields($table, $uid, $undeleteRecord = false)
5830  {
5831  $conf = ‪$GLOBALS['TCA'][$table]['columns'];
5832  $row = ‪BackendUtility::getRecord($table, $uid, '*', '', false);
5833  if (empty($row)) {
5834  return;
5835  }
5836  foreach ($row as $field => $value) {
5837  $this->‪deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf[$field]['config'], $undeleteRecord);
5838  }
5839  }
5840 
5853  public function ‪deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf, $undeleteRecord = false)
5854  {
5855  if ($conf['type'] === 'inline') {
5856  $foreign_table = $conf['foreign_table'];
5857  if ($foreign_table) {
5858  $inlineType = $this->‪getInlineFieldType($conf);
5859  if ($inlineType === 'list' || $inlineType === 'field') {
5861  $dbAnalysis = $this->‪createRelationHandlerInstance();
5862  $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
5863  $dbAnalysis->undeleteRecord = true;
5864 
5865  $enableCascadingDelete = true;
5866  // non type save comparison is intended!
5867  if (isset($conf['behaviour']['enableCascadingDelete']) && $conf['behaviour']['enableCascadingDelete'] == false) {
5868  $enableCascadingDelete = false;
5869  }
5870 
5871  // Walk through the items and remove them
5872  foreach ($dbAnalysis->itemArray as $v) {
5873  if (!$undeleteRecord) {
5874  if ($enableCascadingDelete) {
5875  $this->‪deleteAction($v['table'], $v['id']);
5876  }
5877  } else {
5878  $this->‪undeleteRecord($v['table'], $v['id']);
5879  }
5880  }
5881  }
5882  }
5883  } elseif ($this->‪isReferenceField($conf)) {
5884  $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
5885  $dbAnalysis = $this->‪createRelationHandlerInstance();
5886  $dbAnalysis->start($value, $allowedTables, $conf['MM'], $uid, $table, $conf);
5887  foreach ($dbAnalysis->itemArray as $v) {
5888  $this->updateRefIndexStack[$table][$uid][] = [$v['table'], $v['id']];
5889  }
5890  }
5891  }
5892 
5899  public function ‪deleteL10nOverlayRecords($table, $uid)
5900  {
5901  // Check whether table can be localized
5903  return;
5904  }
5905 
5906  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
5907  $queryBuilder->getRestrictions()
5908  ->removeAll()
5909  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
5910  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
5911 
5912  $queryBuilder->select('*')
5913  ->from($table)
5914  ->where(
5915  $queryBuilder->expr()->eq(
5916  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
5917  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
5918  )
5919  );
5920 
5921  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && ‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
5922  $queryBuilder->andWhere(
5923  $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
5924  );
5925  }
5926 
5927  $result = $queryBuilder->execute();
5928  while ($record = $result->fetch()) {
5929  // Ignore workspace delete placeholders. Those records have been marked for
5930  // deletion before - deleting them again in a workspace would revert that state.
5931  if ($this->BE_USER->workspace > 0 && ‪BackendUtility::isTableWorkspaceEnabled($table)) {
5932  ‪BackendUtility::workspaceOL($table, $record);
5933  if (‪VersionState::cast($record['t3ver_state'])->equals(‪VersionState::DELETE_PLACEHOLDER)) {
5934  continue;
5935  }
5936  }
5937  $this->‪deleteAction($table, (int)$record['t3ver_oid'] > 0 ? (int)$record['t3ver_oid'] : (int)$record['uid']);
5938  }
5939  }
5940 
5941  /*********************************************
5942  *
5943  * Cmd: Versioning
5944  *
5945  ********************************************/
5957  public function ‪versionizeRecord($table, $id, $label, $delete = false)
5958  {
5959  $id = (int)$id;
5960  // Stop any actions if the record is marked to be deleted:
5961  // (this can occur if IRRE elements are versionized and child elements are removed)
5962  if ($this->‪isElementToBeDeleted($table, $id)) {
5963  return null;
5964  }
5965  if (!‪$GLOBALS['TCA'][$table] || !‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS'] || $id <= 0) {
5966  $this->‪newlog('Versioning is not supported for this table "' . $table . '" / ' . $id, 1);
5967  return null;
5968  }
5969 
5970  // Fetch record with permission check
5971  $row = $this->‪recordInfoWithPermissionCheck($table, $id, 'show');
5972 
5973  // This checks if the record can be selected which is all that a copy action requires.
5974  if ($row === false) {
5975  $this->‪newlog(
5976  'The record does not exist or you don\'t have correct permissions to make a new version (copy) of this record "' . $table . ':' . $id . '"',
5977  1
5978  );
5979  return null;
5980  }
5981 
5982  // Record must be online record
5983  if ($row['pid'] < 0) {
5984  $this->‪newlog('Record "' . $table . ':' . $id . '" you wanted to versionize was already a version in archive (pid=-1)!', 1);
5985  return null;
5986  }
5987 
5988  // Record must not be placeholder for moving.
5989  if (‪VersionState::cast($row['t3ver_state'])->equals(‪VersionState::MOVE_PLACEHOLDER)) {
5990  $this->‪newlog('Record cannot be versioned because it is a placeholder for a moving operation', 1);
5991  return null;
5992  }
5993 
5994  if ($delete && $this->‪cannotDeleteRecord($table, $id)) {
5995  $this->‪newlog('Record cannot be deleted: ' . $this->‪cannotDeleteRecord($table, $id), 1);
5996  return null;
5997  }
5998 
5999  // Look for next version number:
6000  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
6001  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
6002  $highestVerNumber = $queryBuilder
6003  ->select('t3ver_id')
6004  ->from($table)
6005  ->where($queryBuilder->expr()->orX(
6006  $queryBuilder->expr()->andX(
6007  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)),
6008  $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT))
6009  ),
6010  $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT))
6011  ))
6012  ->orderBy('t3ver_id', 'DESC')
6013  ->setMaxResults(1)
6014  ->execute()
6015  ->fetchColumn();
6016  // Look for version number of the current:
6017  $subVer = $row['t3ver_id'] . '.' . ($highestVerNumber + 1);
6018  // Set up the values to override when making a raw-copy:
6019  $overrideArray = [
6020  't3ver_id' => $highestVerNumber + 1,
6021  't3ver_oid' => $id,
6022  't3ver_label' => $label ?: $subVer . ' / ' . date('d-m-Y H:m:s'),
6023  't3ver_wsid' => $this->BE_USER->workspace,
6024  't3ver_state' => (string)($delete ? new VersionState(‪VersionState::DELETE_PLACEHOLDER) : new VersionState(‪VersionState::DEFAULT_STATE)),
6025  't3ver_count' => 0,
6026  't3ver_stage' => 0,
6027  't3ver_tstamp' => 0
6028  ];
6029  if (‪$GLOBALS['TCA'][$table]['ctrl']['editlock']) {
6030  $overrideArray[‪$GLOBALS['TCA'][$table]['ctrl']['editlock']] = 0;
6031  }
6032  // Checking if the record already has a version in the current workspace of the backend user
6033  if ($this->BE_USER->workspace !== 0) {
6034  // Look for version already in workspace:
6035  $versionRecord = ‪BackendUtility::getWorkspaceVersionOfRecord($this->BE_USER->workspace, $table, $id, 'uid');
6036  }
6037  // Create new version of the record and return the new uid
6038  if (empty($versionRecord['uid'])) {
6039  // Create raw-copy and return result:
6040  // The information of the label to be used for the workspace record
6041  // as well as the information whether the record shall be removed
6042  // must be forwarded (creating remove placeholders on a workspace are
6043  // done by copying the record and override several fields).
6044  $workspaceOptions = [
6045  'delete' => $delete,
6046  'label' => $label,
6047  ];
6048  return $this->‪copyRecord_raw($table, $id, -1, $overrideArray, $workspaceOptions);
6049  }
6050  // Reuse the existing record and return its uid
6051  // (prior to TYPO3 CMS 6.2, an error was thrown here, which
6052  // did not make much sense since the information is available)
6053  return $versionRecord['uid'];
6054  }
6055 
6064  public function ‪version_remapMMForVersionSwap($table, $id, $swapWith)
6065  {
6066  // Actually, selecting the records fully is only need if flexforms are found inside... This could be optimized ...
6067  $currentRec = ‪BackendUtility::getRecord($table, $id);
6068  $swapRec = ‪BackendUtility::getRecord($table, $swapWith);
6069  $this->version_remapMMForVersionSwap_reg = [];
6070  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
6071  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $fConf) {
6072  $conf = $fConf['config'];
6073  if ($this->‪isReferenceField($conf)) {
6074  $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
6075  $prependName = $conf['type'] === 'group' ? $conf['prepend_tname'] : '';
6076  if ($conf['MM']) {
6078  $dbAnalysis = $this->‪createRelationHandlerInstance();
6079  $dbAnalysis->start('', $allowedTables, $conf['MM'], $id, $table, $conf);
6080  if (!empty($dbAnalysis->getValueArray($prependName))) {
6081  $this->version_remapMMForVersionSwap_reg[$id][$field] = [$dbAnalysis, $conf['MM'], $prependName];
6082  }
6084  $dbAnalysis = $this->‪createRelationHandlerInstance();
6085  $dbAnalysis->start('', $allowedTables, $conf['MM'], $swapWith, $table, $conf);
6086  if (!empty($dbAnalysis->getValueArray($prependName))) {
6087  $this->version_remapMMForVersionSwap_reg[$swapWith][$field] = [$dbAnalysis, $conf['MM'], $prependName];
6088  }
6089  }
6090  } elseif ($conf['type'] === 'flex') {
6091  // Current record
6092  $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
6093  $fConf,
6094  $table,
6095  $field,
6096  $currentRec
6097  );
6098  $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
6099  $currentValueArray = GeneralUtility::xml2array($currentRec[$field]);
6100  if (is_array($currentValueArray)) {
6101  $this->‪checkValue_flex_procInData($currentValueArray['data'], [], [], $dataStructureArray, [$table, $id, $field], 'version_remapMMForVersionSwap_flexFormCallBack');
6102  }
6103  // Swap record
6104  $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
6105  $fConf,
6106  $table,
6107  $field,
6108  $swapRec
6109  );
6110  $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
6111  $currentValueArray = GeneralUtility::xml2array($swapRec[$field]);
6112  if (is_array($currentValueArray)) {
6113  $this->‪checkValue_flex_procInData($currentValueArray['data'], [], [], $dataStructureArray, [$table, $swapWith, $field], 'version_remapMMForVersionSwap_flexFormCallBack');
6114  }
6115  }
6116  }
6117  // Execute:
6118  $this->‪version_remapMMForVersionSwap_execSwap($table, $id, $swapWith);
6119  }
6120 
6132  public function ‪version_remapMMForVersionSwap_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)
6133  {
6134  // Extract parameters:
6135  list($table, $uid, $field) = $pParams;
6136  if ($this->‪isReferenceField($dsConf)) {
6137  $allowedTables = $dsConf['type'] === 'group' ? $dsConf['allowed'] : $dsConf['foreign_table'];
6138  $prependName = $dsConf['type'] === 'group' ? $dsConf['prepend_tname'] : '';
6139  if ($dsConf['MM']) {
6141  $dbAnalysis = $this->‪createRelationHandlerInstance();
6142  $dbAnalysis->start('', $allowedTables, $dsConf['MM'], $uid, $table, $dsConf);
6143  $this->version_remapMMForVersionSwap_reg[$uid][$field . '/' . $path] = [$dbAnalysis, $dsConf['MM'], $prependName];
6144  }
6145  }
6146  }
6147 
6157  public function ‪version_remapMMForVersionSwap_execSwap($table, $id, $swapWith)
6158  {
6159  if (is_array($this->version_remapMMForVersionSwap_reg[$id])) {
6160  foreach ($this->version_remapMMForVersionSwap_reg[$id] as $field => $str) {
6161  $str[0]->remapMM($str[1], $id, -$id, $str[2]);
6162  }
6163  }
6164  if (is_array($this->version_remapMMForVersionSwap_reg[$swapWith])) {
6165  foreach ($this->version_remapMMForVersionSwap_reg[$swapWith] as $field => $str) {
6166  $str[0]->remapMM($str[1], $swapWith, $id, $str[2]);
6167  }
6168  }
6169  if (is_array($this->version_remapMMForVersionSwap_reg[$id])) {
6170  foreach ($this->version_remapMMForVersionSwap_reg[$id] as $field => $str) {
6171  $str[0]->remapMM($str[1], -$id, $swapWith, $str[2]);
6172  }
6173  }
6174  }
6175 
6176  /*********************************************
6177  *
6178  * Cmd: Helper functions
6179  *
6180  ********************************************/
6181 
6187  protected function ‪getLocalTCE()
6188  {
6189  $copyTCE = GeneralUtility::makeInstance(DataHandler::class);
6190  $copyTCE->copyTree = ‪$this->copyTree;
6191  $copyTCE->enableLogging = ‪$this->enableLogging;
6192  // Transformations should NOT be carried out during copy
6193  $copyTCE->dontProcessTransformations = true;
6194  // make sure the isImporting flag is transferred, so all hooks know if
6195  // the current process is an import process
6196  $copyTCE->isImporting = ‪$this->isImporting;
6197  $copyTCE->bypassAccessCheckForRecords = ‪$this->bypassAccessCheckForRecords;
6198  $copyTCE->bypassWorkspaceRestrictions = ‪$this->bypassWorkspaceRestrictions;
6199  return $copyTCE;
6200  }
6201 
6205  public function ‪remapListedDBRecords()
6206  {
6207  if (!empty($this->registerDBList)) {
6208  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
6209  foreach ($this->registerDBList as $table => $records) {
6210  foreach ($records as $uid => ‪$fields) {
6211  $newData = [];
6212  $theUidToUpdate = $this->copyMappingArray_merged[$table][$uid];
6213  $theUidToUpdate_saveTo = ‪BackendUtility::wsMapId($table, $theUidToUpdate);
6214  foreach (‪$fields as $fieldName => $value) {
6215  $conf = ‪$GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
6216  switch ($conf['type']) {
6217  case 'group':
6218  case 'select':
6219  $vArray = $this->‪remapListedDBRecords_procDBRefs($conf, $value, $theUidToUpdate, $table);
6220  if (is_array($vArray)) {
6221  $newData[$fieldName] = implode(',', $vArray);
6222  }
6223  break;
6224  case 'flex':
6225  if ($value === 'FlexForm_reference') {
6226  // This will fetch the new row for the element
6227  $origRecordRow = $this->‪recordInfo($table, $theUidToUpdate, '*');
6228  if (is_array($origRecordRow)) {
6229  ‪BackendUtility::workspaceOL($table, $origRecordRow);
6230  // Get current data structure and value array:
6231  $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
6232  ['config' => $conf],
6233  $table,
6234  $fieldName,
6235  $origRecordRow
6236  );
6237  $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
6238  $currentValueArray = GeneralUtility::xml2array($origRecordRow[$fieldName]);
6239  // Do recursive processing of the XML data:
6240  $currentValueArray['data'] = $this->‪checkValue_flex_procInData($currentValueArray['data'], [], [], $dataStructureArray, [$table, $theUidToUpdate, $fieldName], 'remapListedDBRecords_flexFormCallBack');
6241  // The return value should be compiled back into XML, ready to insert directly in the field (as we call updateDB() directly later):
6242  if (is_array($currentValueArray['data'])) {
6243  $newData[$fieldName] = $this->‪checkValue_flexArray2Xml($currentValueArray, true);
6244  }
6245  }
6246  }
6247  break;
6248  case 'inline':
6249  $this->‪remapListedDBRecords_procInline($conf, $value, $uid, $table);
6250  break;
6251  default:
6252  $this->logger->debug('Field type should not appear here: ' . $conf['type']);
6253  }
6254  }
6255  // If any fields were changed, those fields are updated!
6256  if (!empty($newData)) {
6257  $this->‪updateDB($table, $theUidToUpdate_saveTo, $newData);
6258  }
6259  }
6260  }
6261  }
6262  }
6263 
6275  public function ‪remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
6276  {
6277  // Extract parameters:
6278  list($table, $uid, $field) = $pParams;
6279  // If references are set for this field, set flag so they can be corrected later:
6280  if ($this->‪isReferenceField($dsConf) && (string)$dataValue !== '') {
6281  $vArray = $this->‪remapListedDBRecords_procDBRefs($dsConf, $dataValue, $uid, $table);
6282  if (is_array($vArray)) {
6283  $dataValue = implode(',', $vArray);
6284  }
6285  }
6286  // Return
6287  return ['value' => $dataValue];
6288  }
6289 
6300  public function ‪remapListedDBRecords_procDBRefs($conf, $value, $MM_localUid, $table)
6301  {
6302  // Initialize variables
6303  // Will be set TRUE if an upgrade should be done...
6304  $set = false;
6305  // Allowed tables for references.
6306  $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
6307  // Table name to prepend the UID
6308  $prependName = $conf['type'] === 'group' ? $conf['prepend_tname'] : '';
6309  // Which tables that should possibly not be remapped
6310  $dontRemapTables = GeneralUtility::trimExplode(',', $conf['dontRemapTablesOnCopy'], true);
6311  // Convert value to list of references:
6312  $dbAnalysis = $this->‪createRelationHandlerInstance();
6313  $dbAnalysis->registerNonTableValues = $conf['type'] === 'select' && $conf['allowNonIdValues'];
6314  $dbAnalysis->start($value, $allowedTables, $conf['MM'], $MM_localUid, $table, $conf);
6315  // Traverse those references and map IDs:
6316  foreach ($dbAnalysis->itemArray as $k => $v) {
6317  $mapID = $this->copyMappingArray_merged[$v['table']][$v['id']];
6318  if ($mapID && !in_array($v['table'], $dontRemapTables, true)) {
6319  $dbAnalysis->itemArray[$k]['id'] = $mapID;
6320  $set = true;
6321  }
6322  }
6323  if (!empty($conf['MM'])) {
6324  // Purge invalid items (live/version)
6325  $dbAnalysis->purgeItemArray();
6326  if ($dbAnalysis->isPurged()) {
6327  $set = true;
6328  }
6329 
6330  // If record has been versioned/copied in this process, handle invalid relations of the live record
6331  $liveId = ‪BackendUtility::getLiveVersionIdOfRecord($table, $MM_localUid);
6332  $originalId = 0;
6333  if (!empty($this->copyMappingArray_merged[$table])) {
6334  $originalId = array_search($MM_localUid, $this->copyMappingArray_merged[$table]);
6335  }
6336  if (!empty($liveId) && !empty($originalId) && (int)$liveId === (int)$originalId) {
6337  $liveRelations = $this->‪createRelationHandlerInstance();
6338  $liveRelations->setWorkspaceId(0);
6339  $liveRelations->start('', $allowedTables, $conf['MM'], $liveId, $table, $conf);
6340  // Purge invalid relations in the live workspace ("0")
6341  $liveRelations->purgeItemArray(0);
6342  if ($liveRelations->isPurged()) {
6343  $liveRelations->writeMM($conf['MM'], $liveId, $prependName);
6344  }
6345  }
6346  }
6347  // If a change has been done, set the new value(s)
6348  if ($set) {
6349  if ($conf['MM']) {
6350  $dbAnalysis->writeMM($conf['MM'], $MM_localUid, $prependName);
6351  } else {
6352  return $dbAnalysis->getValueArray($prependName);
6353  }
6354  }
6355  return null;
6356  }
6357 
6366  public function ‪remapListedDBRecords_procInline($conf, $value, $uid, $table)
6367  {
6368  $theUidToUpdate = $this->copyMappingArray_merged[$table][$uid];
6369  if ($conf['foreign_table']) {
6370  $inlineType = $this->‪getInlineFieldType($conf);
6371  if ($inlineType === 'mm') {
6372  $this->‪remapListedDBRecords_procDBRefs($conf, $value, $theUidToUpdate, $table);
6373  } elseif ($inlineType !== false) {
6375  $dbAnalysis = $this->‪createRelationHandlerInstance();
6376  $dbAnalysis->start($value, $conf['foreign_table'], '', 0, $table, $conf);
6377 
6378  // Keep original (live) item array and update values for specific versioned records
6379  $originalItemArray = $dbAnalysis->itemArray;
6380  foreach ($dbAnalysis->itemArray as &$item) {
6381  $versionedId = $this->‪getAutoVersionId($item['table'], $item['id']);
6382  if (!empty($versionedId)) {
6383  $item['id'] = $versionedId;
6384  }
6385  }
6386 
6387  // Update child records if using pointer fields ('foreign_field'):
6388  if ($inlineType === 'field') {
6389  $dbAnalysis->writeForeignField($conf, $uid, $theUidToUpdate);
6390  }
6391  $thePidToUpdate = null;
6392  // If the current field is set on a page record, update the pid of related child records:
6393  if ($table === 'pages') {
6394  $thePidToUpdate = $theUidToUpdate;
6395  } elseif (isset($this->registerDBPids[$table][$uid])) {
6396  $thePidToUpdate = $this->registerDBPids[$table][$uid];
6397  $thePidToUpdate = $this->copyMappingArray_merged['pages'][$thePidToUpdate];
6398  }
6399 
6400  // Update child records if change to pid is required (only if the current record is not on a workspace):
6401  if ($thePidToUpdate) {
6402  // Ensure that only the default language page is used as PID
6403  $thePidToUpdate = $this->‪getDefaultLanguagePageId($thePidToUpdate);
6404  // ensure, only live page ids are used as 'pid' values
6405  $liveId = ‪BackendUtility::getLiveVersionIdOfRecord('pages', $theUidToUpdate);
6406  if ($liveId !== null) {
6407  $thePidToUpdate = $liveId;
6408  }
6409  $updateValues = ['pid' => $thePidToUpdate];
6410  foreach ($originalItemArray as $v) {
6411  if ($v['id'] && $v['table'] && ‪BackendUtility::getLiveVersionIdOfRecord($v['table'], $v['id']) === null) {
6412  GeneralUtility::makeInstance(ConnectionPool::class)
6413  ->getConnectionForTable($v['table'])
6414  ->update($v['table'], $updateValues, ['uid' => (int)$v['id']]);
6415  }
6416  }
6417  }
6418  }
6419  }
6420  }
6421 
6426  public function ‪processRemapStack()
6427  {
6428  // Processes the remap stack:
6429  if (is_array($this->remapStack)) {
6430  $remapFlexForms = [];
6431  $hookPayload = [];
6432 
6433  foreach ($this->remapStack as $remapAction) {
6434  // If no position index for the arguments was set, skip this remap action:
6435  if (!is_array($remapAction['pos'])) {
6436  continue;
6437  }
6438  // Load values from the argument array in remapAction:
6439  $field = $remapAction['field'];
6440  $id = $remapAction['args'][$remapAction['pos']['id']];
6441  $rawId = $id;
6442  $table = $remapAction['args'][$remapAction['pos']['table']];
6443  $valueArray = $remapAction['args'][$remapAction['pos']['valueArray']];
6444  $tcaFieldConf = $remapAction['args'][$remapAction['pos']['tcaFieldConf']];
6445  $additionalData = $remapAction['additionalData'];
6446  // The record is new and has one or more new ids (in case of versioning/workspaces):
6447  if (strpos($id, 'NEW') !== false) {
6448  // Replace NEW...-ID with real uid:
6449  $id = $this->substNEWwithIDs[$id];
6450  // If the new parent record is on a non-live workspace or versionized, it has another new id:
6451  if (isset($this->autoVersionIdMap[$table][$id])) {
6452  $id = $this->autoVersionIdMap[$table][$id];
6453  }
6454  $remapAction['args'][$remapAction['pos']['id']] = $id;
6455  }
6456  // Replace relations to NEW...-IDs in field value (uids of child records):
6457  if (is_array($valueArray)) {
6458  foreach ($valueArray as $key => $value) {
6459  if (strpos($value, 'NEW') !== false) {
6460  if (strpos($value, '_') === false) {
6461  $affectedTable = $tcaFieldConf['foreign_table'];
6462  $prependTable = false;
6463  } else {
6464  $parts = explode('_', $value);
6465  $value = array_pop($parts);
6466  $affectedTable = implode('_', $parts);
6467  $prependTable = true;
6468  }
6469  $value = $this->substNEWwithIDs[$value];
6470  // The record is new, but was also auto-versionized and has another new id:
6471  if (isset($this->autoVersionIdMap[$affectedTable][$value])) {
6472  $value = $this->autoVersionIdMap[$affectedTable][$value];
6473  }
6474  if ($prependTable) {
6475  $value = $affectedTable . '_' . $value;
6476  }
6477  // Set a hint that this was a new child record:
6478  $this->newRelatedIDs[$affectedTable][] = $value;
6479  $valueArray[$key] = $value;
6480  }
6481  }
6482  $remapAction['args'][$remapAction['pos']['valueArray']] = $valueArray;
6483  }
6484  // Process the arguments with the defined function:
6485  if (!empty($remapAction['func'])) {
6486  $newValue = call_user_func_array([$this, $remapAction['func']], $remapAction['args']);
6487  }
6488  // If array is returned, check for maxitems condition, if string is returned this was already done:
6489  if (is_array($newValue)) {
6490  $newValue = implode(',', $this->‪checkValue_checkMax($tcaFieldConf, $newValue));
6491  // The reference casting is only required if
6492  // checkValue_group_select_processDBdata() returns an array
6493  $newValue = $this->‪castReferenceValue($newValue, $tcaFieldConf);
6494  }
6495  // Update in database (list of children (csv) or number of relations (foreign_field)):
6496  if (!empty($field)) {
6497  $fieldArray = [$field => $newValue];
6498  if (‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
6499  $fieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = ‪$GLOBALS['EXEC_TIME'];
6500  }
6501  $this->‪updateDB($table, $id, $fieldArray);
6502  } elseif (!empty($additionalData['flexFormId']) && !empty($additionalData['flexFormPath'])) {
6503  // Collect data to update FlexForms
6504  $flexFormId = $additionalData['flexFormId'];
6505  $flexFormPath = $additionalData['flexFormPath'];
6506 
6507  if (!isset($remapFlexForms[$flexFormId])) {
6508  $remapFlexForms[$flexFormId] = [];
6509  }
6510 
6511  $remapFlexForms[$flexFormId][$flexFormPath] = $newValue;
6512  }
6513 
6514  // Collect elements that shall trigger processDatamap_afterDatabaseOperations
6515  if (isset($this->remapStackRecords[$table][$rawId]['processDatamap_afterDatabaseOperations'])) {
6516  $hookArgs = $this->remapStackRecords[$table][$rawId]['processDatamap_afterDatabaseOperations'];
6517  if (!isset($hookPayload[$table][$rawId])) {
6518  $hookPayload[$table][$rawId] = [
6519  'status' => $hookArgs['status'],
6520  'fieldArray' => $hookArgs['fieldArray'],
6521  'hookObjects' => $hookArgs['hookObjectsArr'],
6522  ];
6523  }
6524  $hookPayload[$table][$rawId]['fieldArray'][$field] = $newValue;
6525  }
6526  }
6527 
6528  if ($remapFlexForms) {
6529  foreach ($remapFlexForms as $flexFormId => $modifications) {
6530  $this->‪updateFlexFormData($flexFormId, $modifications);
6531  }
6532  }
6533 
6534  foreach ($hookPayload as $tableName => $rawIdPayload) {
6535  foreach ($rawIdPayload as $rawId => $payload) {
6536  foreach ($payload['hookObjects'] as $hookObject) {
6537  if (!method_exists($hookObject, 'processDatamap_afterDatabaseOperations')) {
6538  continue;
6539  }
6540  $hookObject->processDatamap_afterDatabaseOperations(
6541  $payload['status'],
6542  $tableName,
6543  $rawId,
6544  $payload['fieldArray'],
6545  $this
6546  );
6547  }
6548  }
6549  }
6550  }
6551  // Processes the remap stack actions:
6552  if ($this->remapStackActions) {
6553  foreach ($this->remapStackActions as $action) {
6554  if (isset($action['callback'], $action['arguments'])) {
6555  call_user_func_array($action['callback'], $action['arguments']);
6556  }
6557  }
6558  }
6559  // Processes the reference index updates of the remap stack:
6560  foreach ($this->remapStackRefIndex as $table => $idArray) {
6561  foreach ($idArray as $id) {
6562  $this->‪updateRefIndex($table, $id);
6563  unset($this->remapStackRefIndex[$table][$id]);
6564  }
6565  }
6566  // Reset:
6567  $this->remapStack = [];
6568  $this->remapStackRecords = [];
6569  $this->remapStackActions = [];
6570  $this->remapStackRefIndex = [];
6571  }
6572 
6579  protected function ‪updateFlexFormData($flexFormId, array $modifications)
6580  {
6581  list($table, $uid, $field) = explode(':', $flexFormId, 3);
6582 
6583  if (!‪MathUtility::canBeInterpretedAsInteger($uid) && !empty($this->substNEWwithIDs[$uid])) {
6584  $uid = $this->substNEWwithIDs[$uid];
6585  }
6586 
6587  $record = $this->‪recordInfo($table, $uid, '*');
6589  if (!$table || !$uid || !$field || !is_array($record)) {
6590  return;
6591  }
6592 
6593  ‪BackendUtility::workspaceOL($table, $record);
6594 
6595  // Get current data structure and value array:
6596  $valueStructure = GeneralUtility::xml2array($record[$field]);
6597 
6598  // Do recursive processing of the XML data:
6599  foreach ($modifications as $path => $value) {
6600  $valueStructure['data'] = ‪ArrayUtility::setValueByPath(
6601  $valueStructure['data'],
6602  $path,
6603  $value
6604  );
6605  }
6606 
6607  if (is_array($valueStructure['data'])) {
6608  // The return value should be compiled back into XML
6609  $values = [
6610  $field => $this->‪checkValue_flexArray2Xml($valueStructure, true),
6611  ];
6612 
6613  $this->‪updateDB($table, $uid, $values);
6614  }
6615  }
6616 
6631  protected function ‪triggerRemapAction($table, $id, array $callback, array $arguments, $forceRemapStackActions = false)
6632  {
6633  // Check whether the affected record is marked to be remapped:
6634  if (!$forceRemapStackActions && !isset($this->remapStackRecords[$table][$id]) && !isset($this->remapStackChildIds[$id])) {
6635  call_user_func_array($callback, $arguments);
6636  } else {
6637  $this->‪addRemapAction($table, $id, $callback, $arguments);
6638  }
6639  }
6640 
6649  public function ‪addRemapAction($table, $id, array $callback, array $arguments)
6650  {
6651  $this->remapStackActions[] = [
6652  'affects' => [
6653  'table' => $table,
6654  'id' => $id
6655  ],
6656  'callback' => $callback,
6657  'arguments' => $arguments
6658  ];
6659  }
6660 
6667  public function ‪addRemapStackRefIndex($table, $id)
6668  {
6669  $this->remapStackRefIndex[$table][$id] = $id;
6670  }
6671 
6683  public function ‪getVersionizedIncomingFieldArray($table, $id, &$incomingFieldArray, &‪$registerDBList)
6684  {
6685  if (is_array(‪$registerDBList[$table][$id])) {
6686  foreach ($incomingFieldArray as $field => $value) {
6687  $fieldConf = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'];
6688  if (‪$registerDBList[$table][$id][$field] && ($foreignTable = $fieldConf['foreign_table'])) {
6689  $newValueArray = [];
6690  $origValueArray = is_array($value) ? $value : explode(',', $value);
6691  // Update the uids of the copied records, but also take care about new records:
6692  foreach ($origValueArray as $childId) {
6693  $newValueArray[] = $this->autoVersionIdMap[$foreignTable][$childId] ? $this->autoVersionIdMap[$foreignTable][$childId] : $childId;
6694  }
6695  // Set the changed value to the $incomingFieldArray
6696  $incomingFieldArray[$field] = implode(',', $newValueArray);
6697  }
6698  }
6699  // Clean up the $registerDBList array:
6700  unset(‪$registerDBList[$table][$id]);
6701  if (empty(‪$registerDBList[$table])) {
6702  unset(‪$registerDBList[$table]);
6703  }
6704  }
6705  }
6706 
6707  /*****************************
6708  *
6709  * Access control / Checking functions
6710  *
6711  *****************************/
6718  public function ‪checkModifyAccessList($table)
6719  {
6720  $res = $this->admin || (!$this->‪tableAdminOnly($table) && isset($this->BE_USER->groupData['tables_modify']) && GeneralUtility::inList($this->BE_USER->groupData['tables_modify'], $table));
6721  // Hook 'checkModifyAccessList': Post-processing of the state of access
6722  foreach ($this->‪getCheckModifyAccessListHookObjects() as $hookObject) {
6724  $hookObject->checkModifyAccessList($res, $table, $this);
6725  }
6726  return $res;
6727  }
6728 
6736  public function ‪isRecordInWebMount($table, $id)
6737  {
6738  if (!isset($this->isRecordInWebMount_Cache[$table . ':' . $id])) {
6739  $recP = $this->‪getRecordProperties($table, $id);
6740  $this->isRecordInWebMount_Cache[$table . ':' . $id] = $this->‪isInWebMount($recP['event_pid']);
6741  }
6742  return $this->isRecordInWebMount_Cache[$table . ':' . $id];
6743  }
6744 
6751  public function ‪isInWebMount($pid)
6752  {
6753  if (!isset($this->isInWebMount_Cache[$pid])) {
6754  $this->isInWebMount_Cache[$pid] = $this->BE_USER->isInWebMount($pid);
6755  }
6756  return $this->isInWebMount_Cache[$pid];
6757  }
6758 
6768  public function ‪checkRecordUpdateAccess($table, $id, $data = false, $hookObjectsArr = null)
6769  {
6770  $res = null;
6771  if (is_array($hookObjectsArr)) {
6772  foreach ($hookObjectsArr as $hookObj) {
6773  if (method_exists($hookObj, 'checkRecordUpdateAccess')) {
6774  $res = $hookObj->checkRecordUpdateAccess($table, $id, $data, $res, $this);
6775  }
6776  }
6777  if (isset($res)) {
6778  return (bool)$res;
6779  }
6780  }
6781  $res = false;
6782 
6783  if (‪$GLOBALS['TCA'][$table] && (int)$id > 0) {
6784  $cacheId = 'checkRecordUpdateAccess' . '_' . $table . '_' . $id;
6785 
6786  // If information is cached, return it
6787  $cachedValue = $this->runtimeCache->get($cacheId);
6788  if (!empty($cachedValue)) {
6789  return $cachedValue;
6790  }
6791 
6792  if ($this->‪doesRecordExist($table, $id, 'edit')) {
6793  $res = 1;
6794  }
6795  // Cache the result
6796  $this->runtimeCache->set($cacheId, $res);
6797  }
6798  return $res;
6799  }
6800 
6810  public function ‪checkRecordInsertAccess($insertTable, $pid, $action = 1)
6811  {
6812  $pid = (int)$pid;
6813  if ($pid < 0) {
6814  return false;
6815  }
6816  // If information is cached, return it
6817  if (isset($this->recInsertAccessCache[$insertTable][$pid])) {
6818  return $this->recInsertAccessCache[$insertTable][$pid];
6819  }
6820 
6821  $res = false;
6822  if ($insertTable === 'pages') {
6823  $perms = $this->pMap['new'];
6824  } elseif (($insertTable === 'sys_file_reference') && array_key_exists('pages', $this->datamap)) {
6825  // @todo: find a more generic way to handle content relations of a page (without needing content editing access to that page)
6826  $perms = $this->pMap['edit'];
6827  } else {
6828  $perms = $this->pMap['editcontent'];
6829  }
6830  $pageExists = (bool)$this->‪doesRecordExist('pages', $pid, $perms);
6831  // If either admin and root-level or if page record exists and 1) if 'pages' you may create new ones 2) if page-content, new content items may be inserted on the $pid page
6832  if ($pageExists || $pid === 0 && ($this->admin || ‪BackendUtility::isRootLevelRestrictionIgnored($insertTable))) {
6833  // Check permissions
6834  if ($this->‪isTableAllowedForThisPage($pid, $insertTable)) {
6835  $res = true;
6836  // Cache the result
6837  $this->recInsertAccessCache[$insertTable][$pid] = $res;
6838  } elseif ($this->enableLogging) {
6839  $propArr = $this->‪getRecordProperties('pages', $pid);
6840  $this->‪log($insertTable, $pid, $action, 0, 1, 'Attempt to insert record on page \'%s\' (%s) where this table, %s, is not allowed', 11, [$propArr['header'], $pid, $insertTable], $propArr['event_pid']);
6841  }
6842  } elseif ($this->enableLogging) {
6843  $propArr = $this->‪getRecordProperties('pages', $pid);
6844  $this->‪log($insertTable, $pid, $action, 0, 1, 'Attempt to insert a record on page \'%s\' (%s) from table \'%s\' without permissions. Or non-existing page.', 12, [$propArr['header'], $pid, $insertTable], $propArr['event_pid']);
6845  }
6846  return $res;
6847  }
6848 
6856  public function ‪isTableAllowedForThisPage($page_uid, $checkTable)
6857  {
6858  $page_uid = (int)$page_uid;
6859  $rootLevelSetting = (int)‪$GLOBALS['TCA'][$checkTable]['ctrl']['rootLevel'];
6860  // Check if rootLevel flag is set and we're trying to insert on rootLevel - and reversed - and that the table is not "pages" which are allowed anywhere.
6861  if ($checkTable !== 'pages' && $rootLevelSetting !== -1 && ($rootLevelSetting xor !$page_uid)) {
6862  return false;
6863  }
6864  $allowed = false;
6865  // Check root-level
6866  if (!$page_uid) {
6867  if ($this->admin || ‪BackendUtility::isRootLevelRestrictionIgnored($checkTable)) {
6868  $allowed = true;
6869  }
6870  } else {
6871  // Check non-root-level
6872  $doktype = $this->‪pageInfo($page_uid, 'doktype');
6873  $allowedTableList = ‪$GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'] ?? ‪$GLOBALS['PAGES_TYPES']['default']['allowedTables'];
6874  $allowedArray = GeneralUtility::trimExplode(',', $allowedTableList, true);
6875  // If all tables or the table is listed as an allowed type, return TRUE
6876  if (strpos($allowedTableList, '*') !== false || in_array($checkTable, $allowedArray, true)) {
6877  $allowed = true;
6878  }
6879  }
6880  return $allowed;
6881  }
6882 
6893  public function ‪doesRecordExist($table, $id, $perms)
6894  {
6895  return $this->‪recordInfoWithPermissionCheck($table, $id, $perms, 'uid, pid') !== false;
6896  }
6897 
6908  protected function ‪doesRecordExist_pageLookUp($id, $perms, $columns = ['uid'])
6909  {
6910  $cacheId = md5('doesRecordExist_pageLookUp' . '_' . $id . '_' . $perms . '_' . implode(
6911  '_',
6912  $columns
6913  ) . '_' . (string)$this->admin);
6914 
6915  // If result is cached, return it
6916  $cachedResult = $this->runtimeCache->get($cacheId);
6917  if (!empty($cachedResult)) {
6918  return $cachedResult;
6919  }
6921  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
6922  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
6923  $queryBuilder
6924  ->select(...$columns)
6925  ->from('pages')
6926  ->where($queryBuilder->expr()->eq(
6927  'uid',
6928  $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
6929  ));
6930  if ($perms && !$this->admin) {
6931  $queryBuilder->andWhere($this->BE_USER->getPagePermsClause($perms));
6932  }
6933  if (!$this->admin && ‪$GLOBALS['TCA']['pages']['ctrl']['editlock'] &&
6935  ) {
6936  $queryBuilder->andWhere($queryBuilder->expr()->eq(
6937  ‪$GLOBALS['TCA']['pages']['ctrl']['editlock'],
6938  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
6939  ));
6940  }
6941 
6942  $row = $queryBuilder->execute()->fetch();
6943  $this->runtimeCache->set($cacheId, $row);
6944 
6945  return $row;
6946  }
6947 
6960  public function ‪doesBranchExist($inList, $pid, $perms, $recurse)
6961  {
6962  $pid = (int)$pid;
6963  $perms = (int)$perms;
6964  if ($pid >= 0) {
6965  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
6966  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
6967  $result = $queryBuilder
6968  ->select('uid', 'perms_userid', 'perms_groupid', 'perms_user', 'perms_group', 'perms_everybody')
6969  ->from('pages')
6970  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))
6971  ->orderBy('sorting')
6972  ->execute();
6973  while ($row = $result->fetch()) {
6974  // IF admin, then it's OK
6975  if ($this->admin || $this->BE_USER->doesUserHaveAccess($row, $perms)) {
6976  $inList .= $row['uid'] . ',';
6977  if ($recurse) {
6978  // Follow the subpages recursively...
6979  $inList = $this->‪doesBranchExist($inList, $row['uid'], $perms, $recurse);
6980  if ($inList === -1) {
6981  return -1;
6982  }
6983  }
6984  } else {
6985  // No permissions
6986  return -1;
6987  }
6988  }
6989  }
6990  return $inList;
6991  }
6992 
6999  public function ‪tableReadOnly($table)
7000  {
7001  // Returns TRUE if table is readonly
7002  return (bool)‪$GLOBALS['TCA'][$table]['ctrl']['readOnly'];
7003  }
7004 
7011  public function ‪tableAdminOnly($table)
7012  {
7013  // Returns TRUE if table is admin-only
7014  return !empty(‪$GLOBALS['TCA'][$table]['ctrl']['adminOnly']);
7015  }
7016 
7025  public function ‪destNotInsideSelf($destinationId, $id)
7026  {
7027  $loopCheck = 100;
7028  $destinationId = (int)$destinationId;
7029  $id = (int)$id;
7030  if ($destinationId === $id) {
7031  return false;
7032  }
7033  while ($destinationId !== 0 && $loopCheck > 0) {
7034  $loopCheck--;
7035  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
7036  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
7037  $result = $queryBuilder
7038  ->select('pid', 'uid', 't3ver_oid', 't3ver_wsid')
7039  ->from('pages')
7040  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($destinationId, \PDO::PARAM_INT)))
7041  ->execute();
7042  if ($row = $result->fetch()) {
7043  ‪BackendUtility::fixVersioningPid('pages', $row);
7044  if ($row['pid'] == $id) {
7045  return false;
7046  }
7047  $destinationId = (int)$row['pid'];
7048  } else {
7049  return false;
7050  }
7051  }
7052  return true;
7053  }
7054 
7061  public function ‪getExcludeListArray()
7062  {
7063  $list = [];
7064  if (isset($this->BE_USER->groupData['non_exclude_fields'])) {
7065  $nonExcludeFieldsArray = array_flip(GeneralUtility::trimExplode(',', $this->BE_USER->groupData['non_exclude_fields']));
7066  foreach (‪$GLOBALS['TCA'] as $table => $tableConfiguration) {
7067  if (isset($tableConfiguration['columns'])) {
7068  foreach ($tableConfiguration['columns'] as $field => $config) {
7069  if ($config['exclude'] && !isset($nonExcludeFieldsArray[$table . ':' . $field])) {
7070  $list[] = $table . '-' . $field;
7071  }
7072  }
7073  }
7074  }
7075  }
7076 
7077  return $list;
7078  }
7079 
7087  public function ‪doesPageHaveUnallowedTables($page_uid, $doktype)
7088  {
7089  $page_uid = (int)$page_uid;
7090  if (!$page_uid) {
7091  // Not a number. Probably a new page
7092  return false;
7093  }
7094  $allowedTableList = ‪$GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'] ?? ‪$GLOBALS['PAGES_TYPES']['default']['allowedTables'];
7095  // If all tables are allowed, return early
7096  if (strpos($allowedTableList, '*') !== false) {
7097  return false;
7098  }
7099  $allowedArray = GeneralUtility::trimExplode(',', $allowedTableList, true);
7100  $tableList = [];
7101  $allTableNames = $this->‪compileAdminTables();
7102  foreach ($allTableNames as $table) {
7103  // If the table is not in the allowed list, check if there are records...
7104  if (in_array($table, $allowedArray, true)) {
7105  continue;
7106  }
7107  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7108  $queryBuilder->getRestrictions()->removeAll();
7109  $count = $queryBuilder
7110  ->count('uid')
7111  ->from($table)
7112  ->where($queryBuilder->expr()->eq(
7113  'pid',
7114  $queryBuilder->createNamedParameter($page_uid, \PDO::PARAM_INT)
7115  ))
7116  ->execute()
7117  ->fetchColumn(0);
7118  if ($count) {
7119  $tableList[] = $table;
7120  }
7121  }
7122  return implode(',', $tableList);
7123  }
7124 
7125  /*****************************
7126  *
7127  * Information lookup
7128  *
7129  *****************************/
7138  public function ‪pageInfo($id, $field)
7139  {
7140  if (!isset($this->pageCache[$id])) {
7141  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
7142  $queryBuilder->getRestrictions()->removeAll();
7143  $row = $queryBuilder
7144  ->select('*')
7145  ->from('pages')
7146  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
7147  ->execute()
7148  ->fetch();
7149  if ($row) {
7150  $this->pageCache[$id] = $row;
7151  }
7152  }
7153  return $this->pageCache[$id][$field];
7154  }
7155 
7165  public function ‪recordInfo($table, $id, $fieldList)
7166  {
7167  // Skip, if searching for NEW records or there's no TCA table definition
7168  if ((int)$id === 0 || !isset(‪$GLOBALS['TCA'][$table])) {
7169  return null;
7170  }
7171  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7172  $queryBuilder->getRestrictions()->removeAll();
7173  $result = $queryBuilder
7174  ->select(...GeneralUtility::trimExplode(',', $fieldList))
7175  ->from($table)
7176  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
7177  ->execute()
7178  ->fetch();
7179  return $result ?: null;
7180  }
7181 
7192  protected function ‪recordInfoWithPermissionCheck(string $table, int $id, $perms, string $fieldList = '*')
7193  {
7194  if ($this->bypassAccessCheckForRecords) {
7195  $columns = GeneralUtility::trimExplode(',', $fieldList, true);
7196 
7197  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7198  $queryBuilder->getRestrictions()->removeAll();
7199 
7200  $record = $queryBuilder->select(...$columns)
7201  ->from($table)
7202  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
7203  ->execute()
7204  ->fetch();
7205 
7206  return $record ?: false;
7207  }
7208  // Processing the incoming $perms (from possible string to integer that can be AND'ed)
7210  if ($table !== 'pages') {
7211  switch ($perms) {
7212  case 'edit':
7213 
7214  case 'delete':
7215 
7216  case 'new':
7217  // This holds it all in case the record is not page!!
7218  if ($table === 'sys_file_reference' && array_key_exists('pages', $this->datamap)) {
7219  $perms = 'edit';
7220  } else {
7221  $perms = 'editcontent';
7222  }
7223  break;
7224  }
7225  }
7226  $perms = (int)$this->pMap[$perms];
7227  } else {
7228  $perms = (int)$perms;
7229  }
7230  if (!$perms) {
7231  throw new \RuntimeException('Internal ERROR: no permissions to check for non-admin user', 1270853920);
7232  }
7233  // For all tables: Check if record exists:
7234  $isWebMountRestrictionIgnored = ‪BackendUtility::isWebMountRestrictionIgnored($table);
7235  if (is_array(‪$GLOBALS['TCA'][$table]) && $id > 0 && ($this->admin || $isWebMountRestrictionIgnored || $this->‪isRecordInWebMount($table, $id))) {
7236  $columns = GeneralUtility::trimExplode(',', $fieldList, true);
7237  if ($table !== 'pages') {
7238  // Find record without checking page
7239  // @todo: This should probably check for editlock
7240  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7241  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
7242  ‪$output = $queryBuilder
7243  ->select(...$columns)
7244  ->from($table)
7245  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
7246  ->execute()
7247  ->fetch();
7249  // If record found, check page as well:
7250  if (is_array(‪$output)) {
7251  // Looking up the page for record:
7252  $pageRec = $this->‪doesRecordExist_pageLookUp(‪$output['pid'], $perms);
7253  // Return TRUE if either a page was found OR if the PID is zero AND the user is ADMIN (in which case the record is at root-level):
7254  $isRootLevelRestrictionIgnored = ‪BackendUtility::isRootLevelRestrictionIgnored($table);
7255  if (is_array($pageRec) || !‪$output['pid'] && ($this->admin || $isRootLevelRestrictionIgnored)) {
7256  return ‪$output;
7257  }
7258  }
7259  return false;
7260  }
7261  return $this->‪doesRecordExist_pageLookUp($id, $perms, $columns);
7262  }
7263  return false;
7264  }
7265 
7277  public function ‪getRecordProperties($table, $id, $noWSOL = false)
7278  {
7279  $row = $table === 'pages' && !$id ? ['title' => '[root-level]', 'uid' => 0, 'pid' => 0] : $this->‪recordInfo($table, $id, '*');
7280  if (!$noWSOL) {
7281  ‪BackendUtility::workspaceOL($table, $row);
7282  }
7283  return $this->‪getRecordPropertiesFromRow($table, $row);
7284  }
7285 
7293  public function ‪getRecordPropertiesFromRow($table, $row)
7294  {
7295  if (‪$GLOBALS['TCA'][$table]) {
7297  return [
7298  'header' => ‪BackendUtility::getRecordTitle($table, $row),
7299  'pid' => $row['pid'],
7300  'event_pid' => $this->‪eventPid($table, isset($row['_ORIG_pid']) ? $row['t3ver_oid'] : $row['uid'], $row['pid']),
7301  't3ver_state' => ‪$GLOBALS['TCA'][$table]['ctrl']['versioningWS'] ? $row['t3ver_state'] : '',
7302  '_ORIG_pid' => $row['_ORIG_pid']
7303  ];
7304  }
7305  return null;
7306  }
7307 
7314  public function ‪eventPid($table, $uid, $pid)
7315  {
7316  return $table === 'pages' ? $uid : $pid;
7317  }
7318 
7319  /*********************************************
7320  *
7321  * Storing data to Database Layer
7322  *
7323  ********************************************/
7332  public function ‪updateDB($table, $id, $fieldArray)
7333  {
7334  if (is_array($fieldArray) && is_array(‪$GLOBALS['TCA'][$table]) && (int)$id) {
7335  // Do NOT update the UID field, ever!
7336  unset($fieldArray['uid']);
7337  if (!empty($fieldArray)) {
7338  $fieldArray = $this->‪insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray);
7339 
7340  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
7341 
7342  $types = [];
7343  $platform = $connection->getDatabasePlatform();
7344  if ($platform instanceof SQLServerPlatform) {
7345  // mssql needs to set proper PARAM_LOB and others to update fields
7346  $tableDetails = $connection->getSchemaManager()->listTableDetails($table);
7347  foreach ($fieldArray as $columnName => $columnValue) {
7348  $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
7349  }
7350  }
7351 
7352  // Execute the UPDATE query:
7353  $updateErrorMessage = '';
7354  try {
7355  $connection->‪update($table, $fieldArray, ['uid' => (int)$id], $types);
7356  } catch (DBALException $e) {
7357  $updateErrorMessage = $e->getPrevious()->getMessage();
7358  }
7359  // If succeeds, do...:
7360  if ($updateErrorMessage === '') {
7361  // Update reference index:
7362  $this->‪updateRefIndex($table, $id);
7363  // Set History data
7364  $historyEntryId = 0;
7365  if (isset($this->historyRecords[$table . ':' . $id])) {
7366  $historyEntryId = $this->‪getRecordHistoryStore()->‪modifyRecord($table, $id, $this->historyRecords[$table . ':' . $id]);
7367  }
7368  if ($this->enableLogging) {
7369  if ($this->checkStoredRecords) {
7370  $newRow = $this->‪checkStoredRecord($table, $id, $fieldArray, 2);
7371  } else {
7372  $newRow = $fieldArray;
7373  $newRow['uid'] = $id;
7374  }
7375  // Set log entry:
7376  $propArr = $this->‪getRecordPropertiesFromRow($table, $newRow);
7377  $this->‪log($table, $id, 2, $propArr['pid'], 0, 'Record \'%s\' (%s) was updated.' . ($propArr['_ORIG_pid'] == -1 ? ' (Offline version).' : ' (Online).'), 10, [$propArr['header'], $table . ':' . $id, 'history' => $historyEntryId], $propArr['event_pid']);
7378  }
7379  // Clear cache for relevant pages:
7380  $this->‪registerRecordIdForPageCacheClearing($table, $id);
7381  // Unset the pageCache for the id if table was page.
7382  if ($table === 'pages') {
7383  unset($this->pageCache[$id]);
7384  }
7385  } else {
7386  $this->‪log($table, $id, 2, 0, 2, 'SQL error: \'%s\' (%s)', 12, [$updateErrorMessage, $table . ':' . $id]);
7387  }
7388  }
7389  }
7390  }
7391 
7404  public function ‪insertDB($table, $id, $fieldArray, $newVersion = false, $suggestedUid = 0, $dontSetNewIdIndex = false)
7405  {
7406  if (is_array($fieldArray) && is_array(‪$GLOBALS['TCA'][$table]) && isset($fieldArray['pid'])) {
7407  // Do NOT insert the UID field, ever!
7408  unset($fieldArray['uid']);
7409  if (!empty($fieldArray)) {
7410  // Check for "suggestedUid".
7411  // This feature is used by the import functionality to force a new record to have a certain UID value.
7412  // This is only recommended for use when the destination server is a passive mirror of another server.
7413  // As a security measure this feature is available only for Admin Users (for now)
7414  $suggestedUid = (int)$suggestedUid;
7415  if ($this->BE_USER->isAdmin() && $suggestedUid && $this->suggestedInsertUids[$table . ':' . $suggestedUid]) {
7416  // When the value of ->suggestedInsertUids[...] is "DELETE" it will try to remove the previous record
7417  if ($this->suggestedInsertUids[$table . ':' . $suggestedUid] === 'DELETE') {
7418  // DELETE:
7419  GeneralUtility::makeInstance(ConnectionPool::class)
7420  ->getConnectionForTable($table)
7421  ->delete($table, ['uid' => (int)$suggestedUid]);
7422  }
7423  $fieldArray['uid'] = $suggestedUid;
7424  }
7425  $fieldArray = $this->‪insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray);
7426  $typeArray = [];
7427  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'])
7428  && array_key_exists(‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'], $fieldArray)
7429  ) {
7430  $typeArray[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']] = ‪Connection::PARAM_LOB;
7431  }
7432  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
7433  $insertErrorMessage = '';
7434  try {
7435  // Execute the INSERT query:
7436  $connection->‪insert(
7437  $table,
7438  $fieldArray,
7439  $typeArray
7440  );
7441  } catch (DBALException $e) {
7442  $insertErrorMessage = $e->getPrevious()->getMessage();
7443  }
7444  // If succees, do...:
7445  if ($insertErrorMessage === '') {
7446  // Set mapping for NEW... -> real uid:
7447  // the NEW_id now holds the 'NEW....' -id
7448  $NEW_id = $id;
7449  $id = $this->‪postProcessDatabaseInsert($connection, $table, $suggestedUid);
7450 
7451  if (!$dontSetNewIdIndex) {
7452  $this->substNEWwithIDs[$NEW_id] = $id;
7453  $this->substNEWwithIDs_table[$NEW_id] = $table;
7454  }
7455  $newRow = [];
7456  if ($this->enableLogging) {
7457  // Checking the record is properly saved if configured
7458  if ($this->checkStoredRecords) {
7459  $newRow = $this->‪checkStoredRecord($table, $id, $fieldArray, 1);
7460  } else {
7461  $newRow = $fieldArray;
7462  $newRow['uid'] = $id;
7463  }
7464  }
7465  // Update reference index:
7466  $this->‪updateRefIndex($table, $id);
7467 
7468  // Store in history
7469  $this->‪getRecordHistoryStore()->‪addRecord($table, $id, $newRow);
7470 
7471  if ($newVersion) {
7472  if ($this->enableLogging) {
7473  $propArr = $this->‪getRecordPropertiesFromRow($table, $newRow);
7474  $this->‪log($table, $id, 1, 0, 0, 'New version created of table \'%s\', uid \'%s\'. UID of new version is \'%s\'', 10, [$table, $fieldArray['t3ver_oid'], $id], $propArr['event_pid'], $NEW_id);
7475  }
7476  } else {
7477  if ($this->enableLogging) {
7478  $propArr = $this->‪getRecordPropertiesFromRow($table, $newRow);
7479  $page_propArr = $this->‪getRecordProperties('pages', $propArr['pid']);
7480  $this->‪log($table, $id, 1, 0, 0, 'Record \'%s\' (%s) was inserted on page \'%s\' (%s)', 10, [$propArr['header'], $table . ':' . $id, $page_propArr['header'], $newRow['pid']], $newRow['pid'], $NEW_id);
7481  }
7482  // Clear cache for relevant pages:
7483  $this->‪registerRecordIdForPageCacheClearing($table, $id);
7484  }
7485  return $id;
7486  }
7487  if ($this->enableLogging) {
7488  $this->‪log($table, $id, 1, 0, 2, 'SQL error: \'%s\' (%s)', 12, [$insertErrorMessage, $table . ':' . $id]);
7489  }
7490  }
7491  }
7492  return null;
7493  }
7494 
7505  public function ‪checkStoredRecord($table, $id, $fieldArray, $action)
7506  {
7507  $id = (int)$id;
7508  if (is_array(‪$GLOBALS['TCA'][$table]) && $id) {
7509  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7510  $queryBuilder->getRestrictions()->removeAll();
7511 
7512  $row = $queryBuilder
7513  ->select('*')
7514  ->from($table)
7515  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
7516  ->execute()
7517  ->fetch();
7518 
7519  if (!empty($row)) {
7520  // Traverse array of values that was inserted into the database and compare with the actually stored value:
7521  ‪$errors = [];
7522  foreach ($fieldArray as $key => $value) {
7523  if (!$this->checkStoredRecords_loose || $value || $row[$key]) {
7524  if (is_float($row[$key])) {
7525  // if the database returns the value as double, compare it as double
7526  if ((double)$value !== (double)$row[$key]) {
7527  ‪$errors[] = $key;
7528  }
7529  } else {
7530  $dbType = ‪$GLOBALS['TCA'][$table]['columns'][$key]['config']['dbType'] ?? false;
7531  if ($dbType === 'datetime' || $dbType === 'time') {
7532  $row[$key] = $this->‪normalizeTimeFormat($table, $row[$key], $dbType);
7533  }
7534  if ((string)$value !== (string)$row[$key]) {
7535  // The is_numeric check catches cases where we want to store a float/double value
7536  // and database returns the field as a string with the least required amount of
7537  // significant digits, i.e. "0.00" being saved and "0" being read back.
7538  if (is_numeric($value) && is_numeric($row[$key])) {
7539  if ((double)$value === (double)$row[$key]) {
7540  continue;
7541  }
7542  }
7543  ‪$errors[] = $key;
7544  }
7545  }
7546  }
7547  }
7548  // Set log message if there were fields with unmatching values:
7549  if (!empty(‪$errors)) {
7550  $message = sprintf(
7551  'These fields of record %d in table "%s" have not been saved correctly: %s! The values might have changed due to type casting of the database.',
7552  $id,
7553  $table,
7554  implode(', ', ‪$errors)
7555  );
7556  $this->‪log($table, $id, $action, 0, 1, $message);
7557  }
7558  // Return selected rows:
7559  return $row;
7560  }
7561  }
7562  return null;
7563  }
7564 
7574  public function ‪setHistory($table, $id, $logId)
7575  {
7576  if (isset($this->historyRecords[$table . ':' . $id])) {
7578  $table,
7579  $id,
7580  $this->historyRecords[$table . ':' . $id]
7581  );
7582  }
7583  }
7584 
7588  protected function ‪getRecordHistoryStore(): ‪RecordHistoryStore
7589  {
7590  return GeneralUtility::makeInstance(
7591  RecordHistoryStore::class,
7593  $this->BE_USER->user['uid'],
7594  $this->BE_USER->user['ses_backuserid'] ?? null,
7595  ‪$GLOBALS['EXEC_TIME'],
7596  $this->BE_USER->workspace
7597  );
7598  }
7599 
7607  public function ‪updateRefIndex($table, $id)
7608  {
7610  $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
7612  $refIndexObj->setWorkspaceId($this->BE_USER->workspace);
7613  }
7614  $refIndexObj->enableRuntimeCache();
7615  $refIndexObj->updateRefIndexTable($table, $id);
7616  }
7617 
7618  /*********************************************
7619  *
7620  * Misc functions
7621  *
7622  ********************************************/
7653  public function ‪getSortNumber($table, $uid, $pid)
7654  {
7655  $sortColumn = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
7656  if (!$sortColumn) {
7657  return null;
7658  }
7659 
7660  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
7661  $queryBuilder = $connectionPool->getQueryBuilderForTable($table);
7662  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
7663 
7664  $queryBuilder
7665  ->select($sortColumn, 'pid', 'uid')
7666  ->from($table);
7667 
7668  // find and return the sorting value for the first record on that pid
7669  if ($pid >= 0) {
7670  // Fetches the first record (lowest sorting) under this pid
7671  $row = $queryBuilder
7672  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))
7673  ->orderBy($sortColumn, 'ASC')
7674  ->addOrderBy('uid', 'ASC')
7675  ->setMaxResults(1)
7676  ->execute()
7677  ->fetch();
7678 
7679  if (!empty($row)) {
7680  // The top record was the record itself, so we return its current sorting value
7681  if ($row['uid'] == $uid) {
7682  return $row[$sortColumn];
7683  }
7684  // If the record sorting value < 1 we must resort all the records under this pid
7685  if ($row[$sortColumn] < 1) {
7686  $this->‪increaseSortingOfFollowingRecords($table, (int)$pid);
7687  // Lowest sorting value after full resorting is $sortIntervals
7688  return ‪$this->sortIntervals;
7689  }
7690  // Sorting number between current top element and zero
7691  return floor($row[$sortColumn] / 2);
7692  }
7693  // No records, so we choose the default value as sorting-number
7694  return ‪$this->sortIntervals;
7695  }
7696 
7697  // Find and return first possible sorting value AFTER record with given uid ($pid)
7698  // Fetches the record which is supposed to be the prev record
7699  $row = $queryBuilder
7700  ->where($queryBuilder->expr()->eq(
7701  'uid',
7702  $queryBuilder->createNamedParameter(abs($pid), \PDO::PARAM_INT)
7703  ))
7704  ->execute()
7705  ->fetch();
7707  // There is a previous record
7708  if (!empty($row)) {
7709  // Look, if the record UID happens to be an offline record. If so, find its live version.
7710  // Offline uids will be used when a page is versionized as "branch" so this is when we must correct
7711  // - otherwise a pid of "-1" and a wrong sort-row number is returned which we don't want.
7712  if ($lookForLiveVersion = ‪BackendUtility::getLiveVersionOfRecord($table, $row['uid'], $sortColumn . ',pid,uid')) {
7713  $row = $lookForLiveVersion;
7714  }
7715  // Fetch move placeholder, since it might point to a new page in the current workspace
7716  if ($movePlaceholder = ‪BackendUtility::getMovePlaceholder($table, $row['uid'], 'uid,pid,' . $sortColumn)) {
7717  $row = $movePlaceholder;
7718  }
7719  // If the record should be inserted after itself, keep the current sorting information:
7720  if ((int)$row['uid'] === (int)$uid) {
7721  $sortNumber = $row[$sortColumn];
7722  } else {
7723  $queryBuilder = $connectionPool->getQueryBuilderForTable($table);
7724  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
7725 
7726  $subResults = $queryBuilder
7727  ->select($sortColumn, 'pid', 'uid')
7728  ->from($table)
7729  ->where(
7730  $queryBuilder->expr()->eq(
7731  'pid',
7732  $queryBuilder->createNamedParameter($row['pid'], \PDO::PARAM_INT)
7733  ),
7734  $queryBuilder->expr()->gte(
7735  $sortColumn,
7736  $queryBuilder->createNamedParameter($row[$sortColumn], \PDO::PARAM_INT)
7737  )
7738  )
7739  ->orderBy($sortColumn, 'ASC')
7740  ->addOrderBy('uid', 'DESC')
7741  ->setMaxResults(2)
7742  ->execute()
7743  ->fetchAll();
7744  // Fetches the next record in order to calculate the in-between sortNumber
7745  // There was a record afterwards
7746  if (count($subResults) === 2) {
7747  // There was a record afterwards, fetch that
7748  $subrow = array_pop($subResults);
7749  // The sortNumber is found in between these values
7750  $sortNumber = $row[$sortColumn] + floor(($subrow[$sortColumn] - $row[$sortColumn]) / 2);
7751  // The sortNumber happened NOT to be between the two surrounding numbers, so we'll have to resort the list
7752  if ($sortNumber <= $row[$sortColumn] || $sortNumber >= $subrow[$sortColumn]) {
7753  $this->‪increaseSortingOfFollowingRecords($table, (int)$row['pid'], (int)$row[$sortColumn]);
7754  $sortNumber = $row[$sortColumn] + ‪$this->sortIntervals;
7755  }
7756  } else {
7757  // If after the last record in the list, we just add the sortInterval to the last sortvalue
7758  $sortNumber = $row[$sortColumn] + ‪$this->sortIntervals;
7759  }
7760  }
7761  return ['pid' => $row['pid'], 'sortNumber' => $sortNumber];
7762  }
7763  if ($this->enableLogging) {
7764  $propArr = $this->‪getRecordProperties($table, $uid);
7765  // OK, don't insert $propArr['event_pid'] here...
7766  $this->‪log($table, $uid, 4, 0, 1, 'Attempt to move record \'%s\' (%s) to after a non-existing record (uid=%s)', 1, [$propArr['header'], $table . ':' . $uid, abs($pid)], $propArr['pid']);
7767  }
7768  // There MUST be a previous record or else this cannot work
7769  return false;
7770  }
7771 
7785  public function ‪resorting($table, $pid, $sortColumn, $return_SortNumber_After_This_Uid)
7786  {
7787  trigger_error('DataHandler->resorting() will be removed in TYPO3 v10.0, use the increaseSortingOfFollowingRecords() function instead.', E_USER_DEPRECATED);
7788 
7789  $sortBy = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
7790  if ($sortBy && $sortBy === $sortColumn) {
7791  $returnVal = 0;
7792  $intervals = ‪$this->sortIntervals;
7793  $i = $intervals * 2;
7794  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
7795  $queryBuilder = $connection->‪createQueryBuilder();
7796  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
7797 
7798  $result = $queryBuilder
7799  ->select('uid')
7800  ->from($table)
7801  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))
7802  ->orderBy($sortColumn, 'ASC')
7803  ->addOrderBy('uid', 'ASC')
7804  ->execute();
7805  if ($connection->getDatabasePlatform() instanceof SqlitePlatform) {
7806  // The default iteration behavior "fetch single row and update it" below can fail on sqlite.
7807  // See https://bugs.php.net/bug.php?id=72267 and https://www.sqlite.org/isolation.html for details, money quote:
7808  // "If changes occur on the same database connection after a query starts running but before the query completes,
7809  // then the query might return a changed row more than once, or it might return a row that was previously deleted."
7810  // In this resorting case, sqlite tends to run into infinite loops by returning the same rows over and over again
7811  // with their already updated sorting values. As less memory efficient but safe solution, we just fetchAll() all
7812  // rows into an array and update them in a second step.
7813  $result = $result->fetchAll();
7814  foreach ($result as $row) {
7815  $uid = (int)$row['uid'];
7816  if ($uid) {
7817  $connection->‪update($table, [$sortColumn => $i], ['uid' => (int)$uid]);
7818  // This is used to return a sortingValue if the list is resorted because of inserting records inside the list and not in the top
7819  if ($uid == $return_SortNumber_After_This_Uid) {
7820  $i += $intervals;
7821  $returnVal = $i;
7822  }
7823  } else {
7824  die('Fatal ERROR!! No Uid at resorting.');
7825  }
7826  $i += $intervals;
7827  }
7828  } else {
7829  while ($row = $result->fetch()) {
7830  // fetch() and update() single rows in one go
7831  $uid = (int)$row['uid'];
7832  if ($uid) {
7833  $connection->‪update($table, [$sortColumn => $i], ['uid' => (int)$uid]);
7834  // This is used to return a sortingValue if the list is resorted because of inserting records inside the list and not in the top
7835  if ($uid == $return_SortNumber_After_This_Uid) {
7836  $i += $intervals;
7837  $returnVal = $i;
7838  }
7839  } else {
7840  die('Fatal ERROR!! No Uid at resorting.');
7841  }
7842  $i += $intervals;
7843  }
7844  }
7845  return $returnVal;
7846  }
7847  return null;
7848  }
7849 
7860  protected function ‪increaseSortingOfFollowingRecords(string $table, int $pid, int $sortingValue = null): void
7861  {
7862  $sortBy = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
7863  if ($sortBy) {
7864  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7865 
7866  $queryBuilder
7867  ->update($table)
7868  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))
7869  ->set($sortBy, $queryBuilder->quoteIdentifier($sortBy) . ' + ' . $this->sortIntervals . ' + ' . $this->sortIntervals, false);
7870  if ($sortingValue !== null) {
7871  $queryBuilder->andWhere($queryBuilder->expr()->gt($sortBy, $sortingValue));
7872  }
7873 
7874  $deleteColumn = ‪$GLOBALS['TCA'][$table]['ctrl']['delete'] ?? '';
7875  if ($deleteColumn) {
7876  $queryBuilder->andWhere($queryBuilder->expr()->eq($deleteColumn, 0));
7877  }
7878 
7879  $queryBuilder->execute();
7880  }
7881  }
7882 
7900  protected function ‪getPreviousLocalizedRecordUid($table, $uid, $pid, $language)
7901  {
7902  $previousLocalizedRecordUid = $uid;
7903  $sortColumn = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
7904  if ($sortColumn) {
7905  $select = [$sortColumn, 'pid', 'uid'];
7906  // For content elements, we also need the colPos
7907  if ($table === 'tt_content') {
7908  $select[] = 'colPos';
7909  }
7910  // Get the sort value of the default language record
7911  $row = ‪BackendUtility::getRecord($table, $uid, implode(',', $select));
7912  if (is_array($row)) {
7913  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7914  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
7915 
7916  $queryBuilder
7917  ->select(...$select)
7918  ->from($table)
7919  ->where(
7920  $queryBuilder->expr()->eq(
7921  'pid',
7922  $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)
7923  ),
7924  $queryBuilder->expr()->eq(
7925  ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'],
7926  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
7927  ),
7928  $queryBuilder->expr()->lt(
7929  $sortColumn,
7930  $queryBuilder->createNamedParameter($row[$sortColumn], \PDO::PARAM_INT)
7931  )
7932  )
7933  ->orderBy($sortColumn, 'DESC')
7934  ->addOrderBy('uid', 'DESC')
7935  ->setMaxResults(1);
7936  if ($table === 'tt_content') {
7937  $queryBuilder
7938  ->andWhere(
7939  $queryBuilder->expr()->eq(
7940  'colPos',
7941  $queryBuilder->createNamedParameter($row['colPos'], \PDO::PARAM_INT)
7942  )
7943  );
7944  }
7945  // If there is an element, find its localized record in specified localization language
7946  if ($previousRow = $queryBuilder->execute()->fetch()) {
7947  $previousLocalizedRecord = ‪BackendUtility::getRecordLocalization($table, $previousRow['uid'], $language);
7948  if (is_array($previousLocalizedRecord[0])) {
7949  $previousLocalizedRecordUid = $previousLocalizedRecord[0]['uid'];
7950  }
7951  }
7952  }
7953  }
7954  return $previousLocalizedRecordUid;
7955  }
7956 
7965  public function ‪setTSconfigPermissions($fieldArray, $TSConfig_p)
7966  {
7967  if ((string)$TSConfig_p['userid'] !== '') {
7968  $fieldArray['perms_userid'] = (int)$TSConfig_p['userid'];
7969  }
7970  if ((string)$TSConfig_p['groupid'] !== '') {
7971  $fieldArray['perms_groupid'] = (int)$TSConfig_p['groupid'];
7972  }
7973  if ((string)$TSConfig_p['user'] !== '') {
7974  $fieldArray['perms_user'] = ‪MathUtility::canBeInterpretedAsInteger($TSConfig_p['user']) ? $TSConfig_p['user'] : $this->‪assemblePermissions($TSConfig_p['user']);
7975  }
7976  if ((string)$TSConfig_p['group'] !== '') {
7977  $fieldArray['perms_group'] = ‪MathUtility::canBeInterpretedAsInteger($TSConfig_p['group']) ? $TSConfig_p['group'] : $this->‪assemblePermissions($TSConfig_p['group']);
7978  }
7979  if ((string)$TSConfig_p['everybody'] !== '') {
7980  $fieldArray['perms_everybody'] = ‪MathUtility::canBeInterpretedAsInteger($TSConfig_p['everybody']) ? $TSConfig_p['everybody'] : $this->‪assemblePermissions($TSConfig_p['everybody']);
7981  }
7982  return $fieldArray;
7983  }
7984 
7992  public function ‪newFieldArray($table)
7993  {
7994  $fieldArray = [];
7995  if (is_array(‪$GLOBALS['TCA'][$table]['columns'])) {
7996  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $content) {
7997  if (isset($this->defaultValues[$table][$field])) {
7998  $fieldArray[$field] = $this->defaultValues[$table][$field];
7999  } elseif (isset($content['config']['default'])) {
8000  $fieldArray[$field] = $content['config']['default'];
8001  }
8002  }
8003  }
8004  // Set default permissions for a page.
8005  if ($table === 'pages') {
8006  $fieldArray['perms_userid'] = ‪$this->userid;
8007  $fieldArray['perms_groupid'] = (int)$this->BE_USER->firstMainGroup;
8008  $fieldArray['perms_user'] = $this->assemblePermissions($this->defaultPermissions['user']);
8009  $fieldArray['perms_group'] = $this->‪assemblePermissions($this->defaultPermissions['group']);
8010  $fieldArray['perms_everybody'] = $this->‪assemblePermissions($this->defaultPermissions['everybody']);
8011  }
8012  return $fieldArray;
8013  }
8014 
8021  public function ‪addDefaultPermittedLanguageIfNotSet($table, &$incomingFieldArray)
8022  {
8023  // Checking languages:
8024  if (‪$GLOBALS['TCA'][$table]['ctrl']['languageField']) {
8025  if (!isset($incomingFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']])) {
8026  // Language field must be found in input row - otherwise it does not make sense.
8027  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
8028  ->getQueryBuilderForTable('sys_language');
8029  $queryBuilder->getRestrictions()
8030  ->removeAll()
8031  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8032  $queryBuilder
8033  ->select('uid')
8034  ->from('sys_language')
8035  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)));
8036  $rows = array_merge([['uid' => 0]], $queryBuilder->execute()->fetchAll(), [['uid' => -1]]);
8037  foreach ($rows as $r) {
8038  if ($this->BE_USER->checkLanguageAccess($r['uid'])) {
8039  $incomingFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $r['uid'];
8040  break;
8041  }
8042  }
8043  }
8044  }
8045  }
8046 
8054  public function ‪overrideFieldArray($table, $data)
8055  {
8056  if (is_array($this->overrideValues[$table])) {
8057  $data = array_merge($data, $this->overrideValues[$table]);
8058  }
8059  return $data;
8060  }
8061 
8071  public function ‪compareFieldArrayWithCurrentAndUnset($table, $id, $fieldArray)
8072  {
8073  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
8074  $queryBuilder = $connection->‪createQueryBuilder();
8075  $queryBuilder->‪getRestrictions()->‪removeAll();
8076  $currentRecord = $queryBuilder->select('*')
8077  ->from($table)
8078  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
8079  ->execute()
8080  ->fetch();
8081  // If the current record exists (which it should...), begin comparison:
8082  if (is_array($currentRecord)) {
8083  $tableDetails = $connection->getSchemaManager()->listTableDetails($table);
8084  $columnRecordTypes = [];
8085  foreach ($currentRecord as $columnName => $_) {
8086  $columnRecordTypes[$columnName] = '';
8087  $type = $tableDetails->getColumn($columnName)->getType();
8088  if ($type instanceof IntegerType) {
8089  $columnRecordTypes[$columnName] = 'int';
8090  }
8091  }
8092  // Unset the fields which are similar:
8093  foreach ($fieldArray as $col => $val) {
8094  $fieldConfiguration = ‪$GLOBALS['TCA'][$table]['columns'][$col]['config'];
8095  $isNullField = (!empty($fieldConfiguration['eval']) && GeneralUtility::inList($fieldConfiguration['eval'], 'null'));
8096 
8097  // Unset fields if stored and submitted values are equal - except the current field holds MM relations.
8098  // In general this avoids to store superfluous data which also will be visualized in the editing history.
8099  if (!$fieldConfiguration['MM'] && $this->‪isSubmittedValueEqualToStoredValue($val, $currentRecord[$col], $columnRecordTypes[$col], $isNullField)) {
8100  unset($fieldArray[$col]);
8101  } else {
8102  if (!isset($this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$col])) {
8103  $this->historyRecords[$table . ':' . $id]['oldRecord'][$col] = $currentRecord[$col];
8104  } elseif ($this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$col] != $this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$col]) {
8105  $this->historyRecords[$table . ':' . $id]['oldRecord'][$col] = $this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$col];
8106  }
8107  if (!isset($this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$col])) {
8108  $this->historyRecords[$table . ':' . $id]['newRecord'][$col] = $fieldArray[$col];
8109  } elseif ($this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$col] != $this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$col]) {
8110  $this->historyRecords[$table . ':' . $id]['newRecord'][$col] = $this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$col];
8111  }
8112  }
8113  }
8114  } else {
8115  // If the current record does not exist this is an error anyways and we just return an empty array here.
8116  $fieldArray = [];
8117  }
8118  return $fieldArray;
8119  }
8120 
8133  protected function ‪isSubmittedValueEqualToStoredValue($submittedValue, $storedValue, $storedType, $allowNull = false)
8134  {
8135  // No NULL values are allowed, this is the regular behaviour.
8136  // Thus, check whether strings are the same or whether integer values are empty ("0" or "").
8137  if (!$allowNull) {
8138  $result = (string)$submittedValue === (string)$storedValue || $storedType === 'int' && (int)$storedValue === (int)$submittedValue;
8139  // Null values are allowed, but currently there's a real (not NULL) value.
8140  // Thus, ensure no NULL value was submitted and fallback to the regular behaviour.
8141  } elseif ($storedValue !== null) {
8142  $result = (
8143  $submittedValue !== null
8144  && $this->‪isSubmittedValueEqualToStoredValue($submittedValue, $storedValue, $storedType, false)
8145  );
8146  // Null values are allowed, and currently there's a NULL value.
8147  // Thus, check whether a NULL value was submitted.
8148  } else {
8149  $result = ($submittedValue === null);
8150  }
8151 
8152  return $result;
8153  }
8154 
8162  public function ‪assemblePermissions($string)
8163  {
8164  $keyArr = GeneralUtility::trimExplode(',', $string, true);
8165  $value = 0;
8166  foreach ($keyArr as $key) {
8167  if ($key && isset($this->pMap[$key])) {
8168  $value |= $this->pMap[$key];
8169  }
8170  }
8171  return $value;
8172  }
8173 
8180  public function ‪convNumEntityToByteValue($input)
8181  {
8182  $token = md5(microtime());
8183  $parts = explode($token, preg_replace('/(&#([0-9]+);)/', $token . '\\2' . $token, $input));
8184  foreach ($parts as $k => $v) {
8185  if ($k % 2) {
8186  $v = (int)$v;
8187  // Just to make sure that control bytes are not converted.
8188  if ($v > 32) {
8189  $parts[$k] = chr($v);
8190  }
8191  }
8192  }
8193  return implode('', $parts);
8194  }
8195 
8201  public function ‪disableDeleteClause()
8202  {
8203  $this->‪disableDeleteClause = true;
8204  }
8205 
8212  public function ‪deleteClause($table)
8213  {
8214  // Returns the proper delete-clause if any for a table from TCA
8215  if (!$this->‪disableDeleteClause && $GLOBALS['TCA'][$table]['ctrl']['delete']) {
8216  return ' AND ' . $table . '.' . ‪$GLOBALS['TCA'][$table]['ctrl']['delete'] . '=0';
8217  }
8218  return '';
8219  }
8220 
8226  protected function ‪addDeleteRestriction(QueryRestrictionContainerInterface $restrictions)
8227  {
8228  if (!$this->‪disableDeleteClause) {
8229  $restrictions->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8230  }
8231  }
8232 
8241  protected function ‪getOriginalParentOfRecord($table, $uid)
8242  {
8243  if (isset(self::$recordPidsForDeletedRecords[$table][$uid])) {
8244  return self::$recordPidsForDeletedRecords[$table][$uid];
8245  }
8246  list($parentUid) = ‪BackendUtility::getTSCpid($table, $uid, '');
8247  return [$parentUid];
8248  }
8249 
8257  public function ‪getTCEMAIN_TSconfig($tscPID)
8258  {
8259  trigger_error('Method getTCEMAIN_TSconfig() will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
8260  return ‪BackendUtility::getPagesTSconfig($tscPID)['TCEMAIN.'] ?? [];
8261  }
8262 
8270  public function ‪getTableEntries($table, $TSconfig)
8271  {
8272  $tA = is_array($TSconfig['table.'][$table . '.']) ? $TSconfig['table.'][$table . '.'] : [];
8273  $dA = is_array($TSconfig['default.']) ? $TSconfig['default.'] : [];
8275  return $dA;
8276  }
8277 
8285  public function ‪getPID($table, $uid)
8286  {
8287  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
8288  $queryBuilder->getRestrictions()
8289  ->removeAll();
8290  $queryBuilder->select('pid')
8291  ->from($table)
8292  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)));
8293  if ($row = $queryBuilder->execute()->fetch()) {
8294  return $row['pid'];
8295  }
8296  return false;
8297  }
8298 
8303  public function ‪dbAnalysisStoreExec()
8304  {
8305  foreach ($this->dbAnalysisStore as $action) {
8306  $id = ‪BackendUtility::wsMapId($action[4], ‪MathUtility::canBeInterpretedAsInteger($action[2]) ? $action[2] : $this->substNEWwithIDs[$action[2]]);
8307  if ($id) {
8308  $action[0]->writeMM($action[1], $id, $action[3]);
8309  }
8310  }
8311  }
8312 
8316  public function ‪removeRegisteredFiles()
8317  {
8318  foreach ($this->removeFilesStore as $file) {
8319  if (@is_file($file)) {
8320  $file = $this->‪getResourceFactory()->‪retrieveFileOrFolderObject($file);
8321  $file->‪delete();
8322  }
8323  }
8324  }
8325 
8336  public function ‪int_pageTreeInfo($CPtable, $pid, $counter, $rootID)
8337  {
8338  if ($counter) {
8339  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
8340  $restrictions = $queryBuilder->getRestrictions()->removeAll();
8341  $this->‪addDeleteRestriction($restrictions);
8342  $queryBuilder
8343  ->select('uid')
8344  ->from('pages')
8345  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))
8346  ->orderBy('sorting', 'DESC');
8347  if (!$this->admin) {
8348  $queryBuilder->andWhere($this->BE_USER->getPagePermsClause($this->pMap['show']));
8349  }
8350  if ((int)$this->BE_USER->workspace === 0) {
8351  $queryBuilder->andWhere(
8352  $queryBuilder->expr()->eq('t3ver_wsid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
8353  );
8354  } else {
8355  $queryBuilder->andWhere($queryBuilder->expr()->in(
8356  't3ver_wsid',
8357  $queryBuilder->createNamedParameter([0, $this->BE_USER->workspace], Connection::PARAM_INT_ARRAY)
8358  ));
8359  }
8360  $result = $queryBuilder->execute();
8361 
8362  $pages = [];
8363  while ($row = $result->fetch()) {
8364  $pages[$row['uid']] = $row;
8365  }
8366 
8367  // Resolve placeholders of workspace versions
8368  if (!empty($pages) && (int)$this->BE_USER->workspace !== 0) {
8369  $pages = array_reverse(
8371  'pages',
8372  'uid',
8373  'sorting',
8374  array_keys($pages)
8375  ),
8376  true
8377  );
8378  }
8379 
8380  foreach ($pages as $page) {
8381  if ($page['uid'] != $rootID) {
8382  $CPtable[$page['uid']] = $pid;
8383  // If the uid is NOT the rootID of the copyaction and if we are supposed to walk further down
8384  if ($counter - 1) {
8385  $CPtable = $this->‪int_pageTreeInfo($CPtable, $page['uid'], $counter - 1, $rootID);
8386  }
8387  }
8388  }
8389  }
8390  return $CPtable;
8391  }
8392 
8398  public function ‪compileAdminTables()
8399  {
8400  return array_keys(‪$GLOBALS['TCA']);
8401  }
8402 
8409  public function ‪fixUniqueInPid($table, $uid)
8410  {
8411  if (empty(‪$GLOBALS['TCA'][$table])) {
8412  return;
8413  }
8414 
8415  $curData = $this->‪recordInfo($table, $uid, '*');
8416  $newData = [];
8417  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $conf) {
8418  if ($conf['config']['type'] === 'input' && (string)$curData[$field] !== '') {
8419  $evalCodesArray = GeneralUtility::trimExplode(',', $conf['config']['eval'], true);
8420  if (in_array('uniqueInPid', $evalCodesArray, true)) {
8421  $newV = $this->‪getUnique($table, $field, $curData[$field], $uid, $curData['pid']);
8422  if ((string)$newV !== (string)$curData[$field]) {
8423  $newData[$field] = $newV;
8424  }
8425  }
8426  }
8427  }
8428  // IF there are changed fields, then update the database
8429  if (!empty($newData)) {
8430  $this->‪updateDB($table, $uid, $newData);
8431  }
8432  }
8433 
8441  protected function ‪fixUniqueInSite(string $table, int $uid): bool
8442  {
8443  $curData = $this->‪recordInfo($table, $uid, '*');
8444  $workspaceId = $this->BE_USER->workspace;
8445  $newData = [];
8446  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $conf) {
8447  if ($conf['config']['type'] === 'slug' && (string)$curData[$field] !== '') {
8448  $evalCodesArray = GeneralUtility::trimExplode(',', $conf['config']['eval'], true);
8449  if (in_array('uniqueInSite', $evalCodesArray, true)) {
8450  $helper = GeneralUtility::makeInstance(SlugHelper::class, $table, $field, $conf['config'], $workspaceId);
8451  $state = ‪RecordStateFactory::forName($table)->fromArray($curData);
8452  $newValue = $helper->buildSlugForUniqueInSite($curData[$field], $state);
8453  if ((string)$newValue !== (string)$curData[$field]) {
8454  $newData[$field] = $newValue;
8455  }
8456  }
8457  }
8458  }
8459  // IF there are changed fields, then update the database
8460  if (!empty($newData)) {
8461  $this->‪updateDB($table, $uid, $newData);
8462  return true;
8463  }
8464  return false;
8465  }
8472  protected function ‪fixUniqueInSiteForSubpages(int $pageId)
8473  {
8474  // Get ALL subpages to update - read-permissions are respected
8475  $subPages = $this->‪int_pageTreeInfo([], $pageId, 99, $pageId);
8476  // Now fix uniqueInSite for subpages
8477  foreach ($subPages as $thePageUid => $thePagePid) {
8478  $recordWasModified = $this->‪fixUniqueInSite('pages', $thePageUid);
8479  if ($recordWasModified) {
8480  // @todo: Add logging and history - but how? we don't know the data that was in the system before
8481  }
8482  }
8483  }
8484 
8496  public function ‪fixCopyAfterDuplFields($table, $uid, $prevUid, $update, $newData = [])
8497  {
8498  if (‪$GLOBALS['TCA'][$table] && ‪$GLOBALS['TCA'][$table]['ctrl']['copyAfterDuplFields']) {
8499  $prevData = $this->‪recordInfo($table, $prevUid, '*');
8500  $theFields = GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][$table]['ctrl']['copyAfterDuplFields'], true);
8501  foreach ($theFields as $field) {
8502  if (‪$GLOBALS['TCA'][$table]['columns'][$field] && ($update || !isset($newData[$field]))) {
8503  $newData[$field] = $prevData[$field];
8504  }
8505  }
8506  if ($update && !empty($newData)) {
8507  $this->‪updateDB($table, $uid, $newData);
8508  }
8509  }
8510  return $newData;
8511  }
8512 
8520  public function ‪extFileFields($table)
8521  {
8522  $listArr = [];
8523  if (isset(‪$GLOBALS['TCA'][$table]['columns'])) {
8524  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $configArr) {
8525  if ($configArr['config']['type'] === 'group' && ($configArr['config']['internal_type'] === 'file' || $configArr['config']['internal_type'] === 'file_reference')) {
8526  $listArr[] = $field;
8527  }
8528  }
8529  }
8530  return $listArr;
8531  }
8532 
8545  protected function ‪castReferenceValue($value, array $configuration)
8546  {
8547  if ((string)$value !== '') {
8548  return $value;
8549  }
8550 
8551  if (!empty($configuration['MM']) || !empty($configuration['foreign_field'])) {
8552  return 0;
8553  }
8554 
8555  if (array_key_exists('default', $configuration)) {
8556  return $configuration['default'];
8557  }
8558 
8559  return $value;
8560  }
8561 
8568  public function ‪isReferenceField($conf)
8569  {
8570  return $conf['type'] === 'group' && $conf['internal_type'] === 'db' || $conf['type'] === 'select' && $conf['foreign_table'];
8571  }
8572 
8580  public function ‪getInlineFieldType($conf)
8581  {
8582  if ($conf['type'] !== 'inline' || !$conf['foreign_table']) {
8583  return false;
8584  }
8585  if ($conf['foreign_field']) {
8586  // The reference to the parent is stored in a pointer field in the child record
8587  return 'field';
8588  }
8589  if ($conf['MM']) {
8590  // Regular MM intermediate table is used to store data
8591  return 'mm';
8592  }
8593  // An item list (separated by comma) is stored (like select type is doing)
8594  return 'list';
8595  }
8596 
8608  public function ‪getCopyHeader($table, $pid, $field, $value, $count, $prevTitle = '')
8609  {
8610  // Set title value to check for:
8611  $checkTitle = $value;
8612  if ($count > 0) {
8613  $checkTitle = $value . rtrim(' ' . sprintf($this->‪prependLabel($table), $count));
8614  }
8615  // Do check:
8616  if ($prevTitle != $checkTitle || $count < 100) {
8617  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
8618  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
8619  $rowCount = $queryBuilder
8620  ->count('uid')
8621  ->from($table)
8622  ->where(
8623  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)),
8624  $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($checkTitle, \PDO::PARAM_STR))
8625  )
8626  ->execute()
8627  ->fetchColumn(0);
8628  if ($rowCount) {
8629  return $this->‪getCopyHeader($table, $pid, $field, $value, $count + 1, $checkTitle);
8630  }
8631  }
8632  // Default is to just return the current input title if no other was returned before:
8633  return $checkTitle;
8634  }
8635 
8643  public function ‪prependLabel($table)
8644  {
8645  return $this->‪getLanguageService()->‪sL($GLOBALS['TCA'][$table]['ctrl']['prependAtCopy']);
8646  }
8647 
8655  public function ‪resolvePid($table, $pid)
8656  {
8657  $pid = (int)$pid;
8658  if ($pid < 0) {
8659  $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
8660  $query->getRestrictions()
8661  ->removeAll();
8662  $row = $query
8663  ->select('pid')
8664  ->from($table)
8665  ->where($query->expr()->eq('uid', $query->createNamedParameter(abs($pid), \PDO::PARAM_INT)))
8666  ->execute()
8667  ->fetch();
8668  // Look, if the record UID happens to be an offline record. If so, find its live version.
8669  if ($lookForLiveVersion = ‪BackendUtility::getLiveVersionOfRecord($table, abs($pid), 'pid')) {
8670  $row = $lookForLiveVersion;
8671  }
8672  $pid = (int)$row['pid'];
8673  }
8674  return $pid;
8675  }
8676 
8684  public function ‪clearPrefixFromValue($table, $value)
8685  {
8686  $regex = '/\s' . sprintf(preg_quote($this->‪prependLabel($table)), '[0-9]*') . '$/';
8687  return @preg_replace($regex, '', $value);
8688  }
8689 
8698  public function ‪extFileFunctions($table, $field, $filelist)
8699  {
8700  $uploadFolder = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config']['uploadfolder'];
8701  if ($uploadFolder && trim($filelist) && ‪$GLOBALS['TCA'][$table]['columns'][$field]['config']['internal_type'] === 'file') {
8702  $uploadPath = ‪Environment::getPublicPath() . '/' . $uploadFolder;
8703  $fileArray = GeneralUtility::trimExplode(',', $filelist, true);
8704  foreach ($fileArray as $theFile) {
8705  $theFileFullPath = $uploadPath . '/' . $theFile;
8706  if (@is_file($theFileFullPath)) {
8708  } else {
8709  $this->‪log($table, 0, 3, 0, 100, 'Delete: Referenced file that was supposed to be deleted together with it\'s record didn\'t exist');
8710  }
8711  }
8712  }
8713  }
8714 
8722  protected function ‪checkForRecordsFromDisallowedTables(array $pageIds)
8723  {
8724  if ($this->admin) {
8725  return true;
8726  }
8727 
8728  if (!empty($pageIds)) {
8729  $tableNames = $this->‪compileAdminTables();
8730  foreach ($tableNames as $table) {
8731  $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
8732  $query->getRestrictions()
8733  ->removeAll()
8734  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8735  $count = $query->count('uid')
8736  ->from($table)
8737  ->where($query->expr()->in(
8738  'pid',
8739  $query->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
8740  ))
8741  ->execute()
8742  ->fetchColumn(0);
8743  if ($count && ($this->‪tableReadOnly($table) || !$this->‪checkModifyAccessList($table))) {
8744  return false;
8745  }
8746  }
8747  }
8748  return true;
8749  }
8750 
8758  public function ‪isRecordCopied($table, $uid)
8759  {
8760  // If the record was copied:
8761  if (isset($this->copyMappingArray[$table][$uid])) {
8762  return true;
8763  }
8764  if (isset($this->copyMappingArray[$table]) && in_array($uid, array_values($this->copyMappingArray[$table]))) {
8765  return true;
8766  }
8767  return false;
8768  }
8769 
8770  /******************************
8771  *
8772  * Clearing cache
8773  *
8774  ******************************/
8775 
8786  public function ‪registerRecordIdForPageCacheClearing($table, $uid, $pid = null)
8787  {
8788  if (!is_array(static::$recordsToClearCacheFor[$table])) {
8789  static::$recordsToClearCacheFor[$table] = [];
8790  }
8791  static::$recordsToClearCacheFor[$table][] = (int)$uid;
8792  if ($pid !== null) {
8793  if (!is_array(static::$recordPidsForDeletedRecords[$table])) {
8794  static::$recordPidsForDeletedRecords[$table] = [];
8795  }
8796  static::$recordPidsForDeletedRecords[$table][$uid][] = (int)$pid;
8797  }
8798  }
8799 
8803  protected function ‪processClearCacheQueue()
8804  {
8805  $tagsToClear = [];
8806  $clearCacheCommands = [];
8807 
8808  foreach (static::$recordsToClearCacheFor as $table => $uids) {
8809  foreach (array_unique($uids) as $uid) {
8810  if (!isset(‪$GLOBALS['TCA'][$table]) || $uid <= 0) {
8811  return;
8812  }
8813  // For move commands we may get more then 1 parent.
8814  $pageUids = $this->‪getOriginalParentOfRecord($table, $uid);
8815  foreach ($pageUids as $originalParent) {
8816  list($tagsToClearFromPrepare, $clearCacheCommandsFromPrepare)
8817  = $this->‪prepareCacheFlush($table, $uid, $originalParent);
8818  $tagsToClear = array_merge($tagsToClear, $tagsToClearFromPrepare);
8819  $clearCacheCommands = array_merge($clearCacheCommands, $clearCacheCommandsFromPrepare);
8820  }
8821  }
8822  }
8823 
8825  $cacheManager = $this->‪getCacheManager();
8826  $cacheManager->flushCachesInGroupByTags('pages', array_keys($tagsToClear));
8827 
8828  // Filter duplicate cache commands from cacheQueue
8829  $clearCacheCommands = array_unique($clearCacheCommands);
8830  // Execute collected clear cache commands from page TSConfig
8831  foreach ($clearCacheCommands as $command) {
8832  $this->‪clear_cacheCmd($command);
8833  }
8834 
8835  // Reset the cache clearing array
8836  static::$recordsToClearCacheFor = [];
8837 
8838  // Reset the original pid array
8839  static::$recordPidsForDeletedRecords = [];
8840  }
8841 
8851  protected function ‪prepareCacheFlush($table, $uid, $pid)
8852  {
8853  $tagsToClear = [];
8854  $clearCacheCommands = [];
8855  $pageUid = 0;
8856  // Get Page TSconfig relevant:
8857  $TSConfig = ‪BackendUtility::getPagesTSconfig($pid)['TCEMAIN.'] ?? [];
8858  if (empty($TSConfig['clearCache_disable'])) {
8859  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
8860  // If table is "pages":
8861  $pageIdsThatNeedCacheFlush = [];
8862  if ($table === 'pages') {
8863  // Find out if the record is a get the original page
8864  $pageUid = $this->‪getDefaultLanguagePageId($uid);
8865 
8866  // Builds list of pages on the SAME level as this page (siblings)
8867  $queryBuilder = $connectionPool->getQueryBuilderForTable('pages');
8868  $queryBuilder->getRestrictions()
8869  ->removeAll()
8870  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8871  $siblings = $queryBuilder
8872  ->select('A.pid AS pid', 'B.uid AS uid')
8873  ->from('pages', 'A')
8874  ->from('pages', 'B')
8875  ->where(
8876  $queryBuilder->expr()->eq('A.uid', $queryBuilder->createNamedParameter($pageUid, \PDO::PARAM_INT)),
8877  $queryBuilder->expr()->eq('B.pid', $queryBuilder->quoteIdentifier('A.pid')),
8878  $queryBuilder->expr()->gte('A.pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
8879  )
8880  ->execute();
8881 
8882  $parentPageId = 0;
8883  while ($row_tmp = $siblings->fetch()) {
8884  $pageIdsThatNeedCacheFlush[] = (int)$row_tmp['uid'];
8885  $parentPageId = (int)$row_tmp['pid'];
8886  // Add children as well:
8887  if ($TSConfig['clearCache_pageSiblingChildren']) {
8888  $siblingChildrenQuery = $connectionPool->getQueryBuilderForTable('pages');
8889  $siblingChildrenQuery->getRestrictions()
8890  ->removeAll()
8891  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8892  $siblingChildren = $siblingChildrenQuery
8893  ->select('uid')
8894  ->from('pages')
8895  ->where($siblingChildrenQuery->expr()->eq(
8896  'pid',
8897  $siblingChildrenQuery->createNamedParameter($row_tmp['uid'], \PDO::PARAM_INT)
8898  ))
8899  ->execute();
8900  while ($row_tmp2 = $siblingChildren->fetch()) {
8901  $pageIdsThatNeedCacheFlush[] = (int)$row_tmp2['uid'];
8902  }
8903  }
8904  }
8905  // Finally, add the parent page as well when clearing a specific page
8906  if ($parentPageId > 0) {
8907  $pageIdsThatNeedCacheFlush[] = $parentPageId;
8908  }
8909  // Add grand-parent as well if configured
8910  if ($TSConfig['clearCache_pageGrandParent']) {
8911  $parentQuery = $connectionPool->getQueryBuilderForTable('pages');
8912  $parentQuery->getRestrictions()
8913  ->removeAll()
8914  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8915  $row_tmp = $parentQuery
8916  ->select('pid')
8917  ->from('pages')
8918  ->where($parentQuery->expr()->eq(
8919  'uid',
8920  $parentQuery->createNamedParameter($parentPageId, \PDO::PARAM_INT)
8921  ))
8922  ->execute()
8923  ->fetch();
8924  if (!empty($row_tmp)) {
8925  $pageIdsThatNeedCacheFlush[] = (int)$row_tmp['pid'];
8926  }
8927  }
8928  } else {
8929  // For other tables than "pages", delete cache for the records "parent page".
8930  $pageIdsThatNeedCacheFlush[] = $pageUid = (int)$this->‪getPID($table, $uid);
8931  // Add the parent page as well
8932  if ($TSConfig['clearCache_pageGrandParent']) {
8933  $parentQuery = $connectionPool->getQueryBuilderForTable('pages');
8934  $parentQuery->getRestrictions()
8935  ->removeAll()
8936  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8937  $parentPageRecord = $parentQuery
8938  ->select('pid')
8939  ->from('pages')
8940  ->where($parentQuery->expr()->eq(
8941  'uid',
8942  $parentQuery->createNamedParameter($pageUid, \PDO::PARAM_INT)
8943  ))
8944  ->execute()
8945  ->fetch();
8946  if (!empty($parentPageRecord)) {
8947  $pageIdsThatNeedCacheFlush[] = (int)$parentPageRecord['pid'];
8948  }
8949  }
8950  }
8951  // Call pre-processing function for clearing of cache for page ids:
8952  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval'] ?? [] as $funcName) {
8953  $_params = ['pageIdArray' => &$pageIdsThatNeedCacheFlush, 'table' => $table, 'uid' => $uid, 'functionID' => 'clear_cache()'];
8954  // Returns the array of ids to clear, FALSE if nothing should be cleared! Never an empty array!
8955  GeneralUtility::callUserFunction($funcName, $_params, $this);
8956  }
8957  // Delete cache for selected pages:
8958  foreach ($pageIdsThatNeedCacheFlush as $pageId) {
8959  // Workspaces always use "-1" as the page id which do not
8960  // point to real pages and caches at all. Flushing caches for
8961  // those records does not make sense and decreases performance
8962  if ($pageId >= 0) {
8963  $tagsToClear['pageId_' . $pageId] = true;
8964  }
8965  }
8966  // Queue delete cache for current table and record
8967  $tagsToClear[$table] = true;
8968  $tagsToClear[$table . '_' . $uid] = true;
8969  }
8970  // Clear cache for pages entered in TSconfig:
8971  if (!empty($TSConfig['clearCacheCmd'])) {
8972  $commands = GeneralUtility::trimExplode(',', $TSConfig['clearCacheCmd'], true);
8973  $clearCacheCommands = array_unique($commands);
8974  }
8975  // Call post processing function for clear-cache:
8976  $_params = ['table' => $table, 'uid' => $uid, 'uid_page' => $pageUid, 'TSConfig' => $TSConfig, 'tags' => $tagsToClear];
8977  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'] ?? [] as $_funcRef) {
8978  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
8979  }
8980  return [
8981  $tagsToClear,
8982  $clearCacheCommands
8983  ];
8984  }
8985 
9023  public function ‪clear_cacheCmd($cacheCmd)
9024  {
9025  if (is_object($this->BE_USER)) {
9026  $this->BE_USER->writelog(3, 1, 0, 0, 'User %s has cleared the cache (cacheCmd=%s)', [$this->BE_USER->user['username'], $cacheCmd]);
9027  }
9028  $userTsConfig = $this->BE_USER->getTSConfig();
9029  switch (strtolower($cacheCmd)) {
9030  case 'pages':
9031  if ($this->admin || ($userTsConfig['options.']['clearCache.']['pages'] ?? false)) {
9032  $this->‪getCacheManager()->‪flushCachesInGroup('pages');
9033  }
9034  break;
9035  case 'all':
9036  // allow to clear all caches if the TS config option is enabled or the option is not explicitly
9037  // disabled for admins (which could clear all caches by default). The latter option is useful
9038  // for big production sites where it should be possible to restrict the cache clearing for some admins.
9039  if (($userTsConfig['options.']['clearCache.']['all'] ?? false)
9040  || ($this->admin && (bool)($userTsConfig['options.']['clearCache.']['all'] ?? true))
9041  ) {
9042  $this->‪getCacheManager()->‪flushCaches();
9043  GeneralUtility::makeInstance(ConnectionPool::class)
9044  ->getConnectionForTable('cache_treelist')
9045  ->truncate('cache_treelist');
9046 
9047  // Delete Opcode Cache
9048  GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
9049  }
9050  break;
9051  case 'temp_cached':
9052  case 'system':
9053  trigger_error(
9054  'Calling clear_cacheCmd() with arguments "temp_cached" or "system", using'
9055  . ' the TSconfig option "options.clearCache.system" will be removed in TYPO3 v10.0, use "all"'
9056  . ' instead or call the group cache clearing of "system" group directly via a custom extension.',
9057  E_USER_DEPRECATED
9058  );
9059  if ($this->admin || $userTsConfig['options.']['clearCache.']['system'] ?? false) {
9060  $this->‪getCacheManager()->‪flushCachesInGroup('system');
9061  }
9062  break;
9063  }
9064 
9065  $tagsToFlush = [];
9066  // Clear cache for a page ID!
9068  $list_cache = [$cacheCmd];
9069  // Call pre-processing function for clearing of cache for page ids:
9070  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval'] ?? [] as $funcName) {
9071  $_params = ['pageIdArray' => &$list_cache, 'cacheCmd' => $cacheCmd, 'functionID' => 'clear_cacheCmd()'];
9072  // Returns the array of ids to clear, FALSE if nothing should be cleared! Never an empty array!
9073  GeneralUtility::callUserFunction($funcName, $_params, $this);
9074  }
9075  // Delete cache for selected pages:
9076  if (is_array($list_cache)) {
9077  foreach ($list_cache as $pageId) {
9078  $tagsToFlush[] = 'pageId_' . (int)$pageId;
9079  }
9080  }
9081  }
9082  // flush cache by tag
9083  if (GeneralUtility::isFirstPartOfStr(strtolower($cacheCmd), 'cachetag:')) {
9084  $cacheTag = substr($cacheCmd, 9);
9085  $tagsToFlush[] = $cacheTag;
9086  }
9087  // process caching framwork operations
9088  if (!empty($tagsToFlush)) {
9089  $this->‪getCacheManager()->‪flushCachesInGroupByTags('pages', $tagsToFlush);
9090  }
9091 
9092  // Call post processing function for clear-cache:
9093  $_params = ['cacheCmd' => strtolower($cacheCmd), 'tags' => $tagsToFlush];
9094  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'] ?? [] as $_funcRef) {
9095  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
9096  }
9097  }
9098 
9099  /*****************************
9100  *
9101  * Logging
9102  *
9103  *****************************/
9119  public function ‪log($table, $recuid, $action, $recpid, $error, $details, $details_nr = -1, $data = [], $event_pid = -1, $NEWid = '')
9120  {
9121  if (!$this->enableLogging) {
9122  return 0;
9123  }
9124  // Type value for DataHandler
9125  $type = 1;
9126  if (!$this->storeLogMessages) {
9127  $details = '';
9128  }
9129  if ($error > 0) {
9130  $detailMessage = $details;
9131  if (is_array($data)) {
9132  $detailMessage = vsprintf($details, $data);
9133  }
9134  $this->errorLog[] = '[' . $type . '.' . $action . '.' . $details_nr . ']: ' . $detailMessage;
9135  }
9136  return $this->BE_USER->writelog($type, $action, $error, $details_nr, $details, $data, $table, $recuid, $recpid, $event_pid, $NEWid);
9137  }
9138 
9147  public function ‪newlog($message, $error = 0)
9148  {
9149  return $this->‪log('', 0, 0, 0, $error, $message, -1);
9150  }
9151 
9164  public function ‪newlog2($message, $table, $uid, $pid = null, $error = 0)
9165  {
9166  trigger_error('DataHandler->newlog2() will be removed in TYPO3 v10.0, use the generic log() function instead.', E_USER_DEPRECATED);
9167  if (!$this->enableLogging) {
9168  return 0;
9169  }
9170  if ($pid === null) {
9171  $propArr = $this->‪getRecordProperties($table, $uid);
9172  $pid = $propArr['pid'];
9173  }
9174  return $this->‪log($table, $uid, 0, 0, $error, $message, -1, [], $this->‪eventPid($table, $uid, $pid));
9175  }
9176 
9180  public function ‪printLogErrorMessages()
9181  {
9182  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
9183  $queryBuilder->getRestrictions()->removeAll();
9184  $result = $queryBuilder
9185  ->select('*')
9186  ->from('sys_log')
9187  ->where(
9188  $queryBuilder->expr()->eq('type', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
9189  $queryBuilder->expr()->lt('action', $queryBuilder->createNamedParameter(256, \PDO::PARAM_INT)),
9190  $queryBuilder->expr()->eq(
9191  'userid',
9192  $queryBuilder->createNamedParameter($this->BE_USER->user['uid'], \PDO::PARAM_INT)
9193  ),
9194  $queryBuilder->expr()->eq(
9195  'tstamp',
9196  $queryBuilder->createNamedParameter(‪$GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
9197  ),
9198  $queryBuilder->expr()->neq('error', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
9199  )
9200  ->execute();
9201 
9202  while ($row = $result->fetch()) {
9203  $log_data = unserialize($row['log_data']);
9204  $msg = $row['error'] . ': ' . sprintf($row['details'], $log_data[0], $log_data[1], $log_data[2], $log_data[3], $log_data[4]);
9206  $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, '', ‪FlashMessage::ERROR, true);
9208  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
9209  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
9210  $defaultFlashMessageQueue->enqueue($flashMessage);
9211  }
9212  }
9213 
9214  /*****************************
9215  *
9216  * Internal (do not use outside Core!)
9217  *
9218  *****************************/
9226  protected function ‪getDefaultLanguagePageId(int $pageId): int
9227  {
9228  $localizationParentFieldName = ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'];
9229  $row = $this->‪recordInfo('pages', $pageId, $localizationParentFieldName);
9230  $localizationParent = (int)$row[$localizationParentFieldName];
9231  if ($localizationParent > 0) {
9232  return $localizationParent;
9233  }
9234  return $pageId;
9235  }
9236 
9246  public function ‪insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray)
9247  {
9248  $result = $fieldArray;
9249  foreach ($fieldArray as $field => $value) {
9251  && ‪$GLOBALS['TCA'][$table]['columns'][$field]['config']['type'] === 'inline'
9252  && ‪$GLOBALS['TCA'][$table]['columns'][$field]['config']['foreign_field']) {
9253  $result[$field] = count(GeneralUtility::trimExplode(',', $value, true));
9254  }
9255  }
9256  return $result;
9257  }
9258 
9267  public function ‪hasDeletedRecord($tableName, $uid)
9268  {
9269  return
9270  !empty($this->deletedRecords[$tableName])
9271  && in_array($uid, $this->deletedRecords[$tableName])
9272  ;
9273  }
9274 
9282  public function ‪getAutoVersionId($table, $id)
9283  {
9284  $result = null;
9285  if (isset($this->autoVersionIdMap[$table][$id])) {
9286  $result = $this->autoVersionIdMap[$table][$id];
9287  }
9288  return $result;
9289  }
9290 
9298  protected function ‪overlayAutoVersionId($table, $id)
9299  {
9300  $autoVersionId = $this->‪getAutoVersionId($table, $id);
9301  if ($autoVersionId !== null) {
9302  $id = $autoVersionId;
9303  }
9304  return $id;
9305  }
9306 
9312  protected function ‪addNewValuesToRemapStackChildIds(array $idValues)
9313  {
9314  foreach ($idValues as $idValue) {
9315  if (strpos($idValue, 'NEW') === 0) {
9316  $this->remapStackChildIds[$idValue] = true;
9317  }
9318  }
9319  }
9320 
9331  protected function ‪resolveVersionedRecords($tableName, $fieldNames, $sortingField, array $liveIds)
9332  {
9333  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
9334  ->getConnectionForTable($tableName);
9335  $sortingStatement = !empty($sortingField)
9336  ? [$connection->‪quoteIdentifier($sortingField)]
9337  : null;
9339  $resolver = GeneralUtility::makeInstance(
9340  PlainDataResolver::class,
9341  $tableName,
9342  $liveIds,
9343  $sortingStatement
9344  );
9345 
9346  $resolver->setWorkspaceId($this->BE_USER->workspace);
9347  $resolver->setKeepDeletePlaceholder(false);
9348  $resolver->setKeepMovePlaceholder(false);
9349  $resolver->setKeepLiveIds(true);
9350  $recordIds = $resolver->get();
9351 
9352  $records = [];
9353  foreach ($recordIds as $recordId) {
9354  $records[$recordId] = ‪BackendUtility::getRecord($tableName, $recordId, $fieldNames);
9355  }
9357  return $records;
9358  }
9359 
9367  protected function ‪getOuterMostInstance()
9368  {
9369  if (!isset($this->outerMostInstance)) {
9370  $stack = array_reverse(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS));
9371  foreach ($stack as $stackItem) {
9372  if (isset($stackItem['object']) && $stackItem['object'] instanceof self) {
9373  $this->outerMostInstance = $stackItem['object'];
9374  break;
9375  }
9376  }
9377  }
9379  }
9380 
9388  public function ‪isOuterMostInstance()
9389  {
9390  return $this->‪getOuterMostInstance() === $this;
9391  }
9392 
9398  protected function ‪getRuntimeCache()
9399  {
9400  return $this->‪getCacheManager()->‪getCache('cache_runtime');
9401  }
9402 
9411  protected function ‪isNestedElementCallRegistered($table, $id, $identifier)
9412  {
9413  $nestedElementCalls = (array)$this->runtimeCache->get($this->cachePrefixNestedElementCalls);
9414  return isset($nestedElementCalls[$identifier][$table][$id]);
9415  }
9416 
9425  protected function ‪registerNestedElementCall($table, $id, $identifier)
9426  {
9427  $nestedElementCalls = (array)$this->runtimeCache->get($this->cachePrefixNestedElementCalls);
9428  $nestedElementCalls[$identifier][$table][$id] = true;
9429  $this->runtimeCache->set($this->cachePrefixNestedElementCalls, $nestedElementCalls);
9430  }
9431 
9435  protected function ‪resetNestedElementCalls()
9436  {
9437  $this->runtimeCache->remove($this->cachePrefixNestedElementCalls);
9438  }
9439 
9451  protected function ‪isElementToBeDeleted($table, $id)
9452  {
9453  $elementsToBeDeleted = (array)$this->runtimeCache->get('core-datahandler-elementsToBeDeleted');
9454  return isset($elementsToBeDeleted[$table][$id]);
9455  }
9456 
9462  protected function ‪registerElementsToBeDeleted()
9463  {
9464  $elementsToBeDeleted = (array)$this->runtimeCache->get('core-datahandler-elementsToBeDeleted');
9465  $this->runtimeCache->set('core-datahandler-elementsToBeDeleted', array_merge($elementsToBeDeleted, $this->‪getCommandMapElements('delete')));
9466  }
9467 
9473  protected function ‪resetElementsToBeDeleted()
9474  {
9475  $this->runtimeCache->remove('core-datahandler-elementsToBeDeleted');
9476  }
9477 
9485  protected function ‪unsetElementsToBeDeleted(array $elements)
9486  {
9487  $elements = ‪ArrayUtility::arrayDiffAssocRecursive($elements, $this->‪getCommandMapElements('delete'));
9488  foreach ($elements as $key => $value) {
9489  if (empty($value)) {
9490  unset($elements[$key]);
9491  }
9492  }
9493  return $elements;
9494  }
9495 
9502  protected function ‪getCommandMapElements($needle)
9503  {
9504  $elements = [];
9505  foreach ($this->cmdmap as $tableName => $idArray) {
9506  foreach ($idArray as $id => $commandArray) {
9507  foreach ($commandArray as $command => $value) {
9508  if ($value && $command == $needle) {
9509  $elements[$tableName][$id] = true;
9510  }
9511  }
9512  }
9513  }
9514  return $elements;
9515  }
9516 
9521  protected function ‪controlActiveElements()
9522  {
9523  if (!empty($this->control['active'])) {
9524  $this->‪setNullValues(
9525  $this->control['active'],
9526  $this->datamap
9527  );
9528  }
9529  }
9530 
9539  protected function ‪setNullValues(array $active, array &$haystack)
9540  {
9541  foreach ($active as $key => $value) {
9542  // Nested data is processes recursively
9543  if (is_array($value)) {
9544  $this->‪setNullValues(
9545  $value,
9546  $haystack[$key]
9547  );
9548  } elseif ($value == 0) {
9549  // Field has not been activated in the user interface,
9550  // thus a NULL value shall be stored in the database
9551  $haystack[$key] = null;
9552  }
9553  }
9554  }
9555 
9565  protected function ‪postProcessDatabaseInsert(Connection $connection, string $tableName, int $suggestedUid): int
9566  {
9567  if ($suggestedUid !== 0 && $connection->getDatabasePlatform() instanceof PostgreSqlPlatform) {
9568  $this->‪postProcessPostgresqlInsert($connection, $tableName);
9569  // The last inserted id on postgresql is actually the last value generated by the sequence.
9570  // On a forced UID insert this might not be the actual value or the sequence might not even
9571  // have generated a value yet.
9572  // Return the actual ID we forced on insert as a surrogate.
9573  return $suggestedUid;
9574  }
9575  if ($connection->getDatabasePlatform() instanceof SQLServerPlatform) {
9576  return $this->‪postProcessSqlServerInsert($connection, $tableName);
9577  }
9578  $id = $connection->lastInsertId($tableName);
9579  return (int)$id;
9580  }
9581 
9596  protected function ‪postProcessSqlServerInsert(‪Connection $connection, string $tableName): int
9597  {
9598  $id = $connection->‪lastInsertId($tableName);
9599  if (!((int)$id > 0)) {
9600  $table = $connection->‪quoteIdentifier($tableName);
9601  $result = $connection->executeQuery('SELECT IDENT_CURRENT(\'' . $table . '\') AS id')->fetch();
9602  if (isset($result['id']) && $result['id'] > 0) {
9603  $id = $result['id'];
9604  }
9605  }
9606  return (int)$id;
9607  }
9608 
9617  protected function postProcessPostgresqlInsert(Connection $connection, string $tableName)
9618  {
9619  $queryBuilder = $connection->createQueryBuilder();
9620  $queryBuilder->getRestrictions()->removeAll();
9621  $row = $queryBuilder->select('PGT.schemaname', 'S.relname', 'C.attname', 'T.relname AS tablename')
9622  ->from('pg_class', 'S')
9623  ->from('pg_depend', 'D')
9624  ->from('pg_class', 'T')
9625  ->from('pg_attribute', 'C')
9626  ->from('pg_tables', 'PGT')
9627  ->where(
9628  $queryBuilder->expr()->eq('S.relkind', $queryBuilder->quote('S')),
9629  $queryBuilder->expr()->eq('S.oid', $queryBuilder->quoteIdentifier('D.objid')),
9630  $queryBuilder->expr()->eq('D.refobjid', $queryBuilder->quoteIdentifier('T.oid')),
9631  $queryBuilder->expr()->eq('D.refobjid', $queryBuilder->quoteIdentifier('C.attrelid')),
9632  $queryBuilder->expr()->eq('D.refobjsubid', $queryBuilder->quoteIdentifier('C.attnum')),
9633  $queryBuilder->expr()->eq('T.relname', $queryBuilder->quoteIdentifier('PGT.tablename')),
9634  $queryBuilder->expr()->eq('PGT.tablename', $queryBuilder->quote($tableName))
9635  )
9636  ->setMaxResults(1)
9637  ->execute()
9638  ->fetch();
9639 
9640  if ($row !== false) {
9641  $connection->exec(
9642  sprintf(
9643  'SELECT SETVAL(%s, COALESCE(MAX(%s), 0)+1, FALSE) FROM %s',
9644  $connection->quote($row['schemaname'] . '.' . $row['relname']),
9645  $connection->quoteIdentifier($row['attname']),
9646  $connection->quoteIdentifier($row['schemaname'] . '.' . $row['tablename'])
9647  )
9648  );
9649  }
9650  }
9651 
9658  protected function getFieldEvalCacheIdentifier($additionalIdentifier)
9659  {
9660  return 'core-datahandler-eval-' . md5($additionalIdentifier);
9661  }
9662 
9666  protected function createRelationHandlerInstance()
9667  {
9668  $isWorkspacesLoaded = ExtensionManagementUtility::isLoaded('workspaces');
9669  $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
9670  $relationHandler->setWorkspaceId($this->BE_USER->workspace);
9671  $relationHandler->setUseLiveReferenceIds($isWorkspacesLoaded);
9672  $relationHandler->setUseLiveParentIds($isWorkspacesLoaded);
9673  return $relationHandler;
9674  }
9675 
9681  protected function getCacheManager()
9682  {
9683  return GeneralUtility::makeInstance(CacheManager::class);
9684  }
9685 
9691  protected function getResourceFactory()
9692  {
9693  return ResourceFactory::getInstance();
9694  }
9695 
9699  protected function getLanguageService()
9700  {
9701  return $GLOBALS['LANG'];
9702  }
9703 
9704  public function getHistoryRecords(): array
9705  {
9706  return $this->historyRecords;
9707  }
9708 }
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord_procBasedOnFieldType
‪array string copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $realDestPid, $language=0, array $workspaceOptions=[])
Definition: DataHandler.php:4015
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:81
‪TYPO3\CMS\Core\DataHandling\DataHandler\tableAdminOnly
‪bool tableAdminOnly($table)
Definition: DataHandler.php:6932
‪TYPO3\CMS\Core\DataHandling\DataHandler\version_remapMMForVersionSwap_flexFormCallBack
‪version_remapMMForVersionSwap_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)
Definition: DataHandler.php:6053
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_group_select_explodeSelectGroupValue
‪array checkValue_group_select_explodeSelectGroupValue($value)
Definition: DataHandler.php:3190
‪TYPO3\CMS\Core\DataHandling\DataHandler\resolveFieldConfigurationAndRespectColumnsOverrides
‪array resolveFieldConfigurationAndRespectColumnsOverrides(string $table, string $field)
Definition: DataHandler.php:1689
‪TYPO3\CMS\Core\DataHandling\DataHandler\fixUniqueInSiteForSubpages
‪fixUniqueInSiteForSubpages(int $pageId)
Definition: DataHandler.php:8393
‪TYPO3\CMS\Core\DataHandling\DataHandler\copy_remapTranslationSourceField
‪copy_remapTranslationSourceField($table, $l10nRecords, $languageSourceMap)
Definition: DataHandler.php:4450
‪TYPO3\CMS\Core\DataHandling\DataHandler\$recordsToClearCacheFor
‪static array $recordsToClearCacheFor
Definition: DataHandler.php:608
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordsWithSameValue
‪array getRecordsWithSameValue($tableName, $uid, $fieldName, $value, $pageId=0)
Definition: DataHandler.php:2852
‪TYPO3\CMS\Core\DataHandling\DataHandler\remapListedDBRecords_flexFormCallBack
‪array remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
Definition: DataHandler.php:6196
‪TYPO3\CMS\Core\DataHandling\DataHandler\setHistory
‪setHistory($table, $id, $logId)
Definition: DataHandler.php:7495
‪TYPO3\CMS\Core\DataHandling\DataHandler\getPlaceholderTitleForTableLabel
‪string getPlaceholderTitleForTableLabel($table, $placeholderContent=null)
Definition: DataHandler.php:1393
‪TYPO3\CMS\Core\DataHandling\DataHandler\getDefaultLanguagePageId
‪int getDefaultLanguagePageId(int $pageId)
Definition: DataHandler.php:9147
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\addRecord
‪string addRecord(string $table, int $uid, array $payload)
Definition: RecordHistoryStore.php:81
‪TYPO3\CMS\Core\Database\Query\QueryHelper\getDateTimeFormats
‪static array getDateTimeFormats()
Definition: QueryHelper.php:175
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord
‪moveRecord($table, $uid, $destPid)
Definition: DataHandler.php:4499
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord
‪int null copyRecord($table, $uid, $destPid, $first=false, $overrideValues=[], $excludeFields='', $language=0, $ignoreLocalization=false)
Definition: DataHandler.php:3560
‪TYPO3\CMS\Core\DataHandling\DataHandler\$reverseOrder
‪bool $reverseOrder
Definition: DataHandler.php:113
‪TYPO3\CMS\Core\DataHandling\DataHandler\$substNEWwithIDs
‪array $substNEWwithIDs
Definition: DataHandler.php:288
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidParentRowException
Definition: InvalidParentRowException.php:22
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\modifyRecord
‪string modifyRecord(string $table, int $uid, array $payload)
Definition: RecordHistoryStore.php:104
‪TYPO3\CMS\Core\DataHandling\DataHandler\$isInWebMount_Cache
‪array $isInWebMount_Cache
Definition: DataHandler.php:469
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory
Definition: PasswordHashFactory.php:25
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\USER_BACKEND
‪const USER_BACKEND
Definition: RecordHistoryStore.php:35
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerNestedElementCall
‪registerNestedElementCall($table, $id, $identifier)
Definition: DataHandler.php:9346
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyL10nOverlayRecords
‪copyL10nOverlayRecords($table, $uid, $destPid, $first=false, $overrideValues=[], $excludeFields='')
Definition: DataHandler.php:4381
‪TYPO3\CMS\Core\DataHandling\DataHandler\getResourceFactory
‪ResourceFactory getResourceFactory()
Definition: DataHandler.php:9612
‪TYPO3\CMS\Core\DataHandling\DataHandler\clearPrefixFromValue
‪string clearPrefixFromValue($table, $value)
Definition: DataHandler.php:8605
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_text_Eval
‪array checkValue_text_Eval($value, $evalArray, $is_in)
Definition: DataHandler.php:2902
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:23
‪TYPO3\CMS\Core\DataHandling\DataHandler\$isRecordInWebMount_Cache
‪array $isRecordInWebMount_Cache
Definition: DataHandler.php:463
‪TYPO3\CMS\Core\DataHandling\DataHandler\updateRefIndex
‪updateRefIndex($table, $id)
Definition: DataHandler.php:7528
‪TYPO3\CMS\Core\DataHandling\DataHandler\increaseSortingOfFollowingRecords
‪increaseSortingOfFollowingRecords(string $table, int $pid, int $sortingValue=null)
Definition: DataHandler.php:7781
‪TYPO3\CMS\Core\DataHandling\DataHandler\fixUniqueInPid
‪fixUniqueInPid($table, $uid)
Definition: DataHandler.php:8330
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:73
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord_procFilesRefs
‪string copyRecord_procFilesRefs($conf, $uid, $value)
Definition: DataHandler.php:4229
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForCheck
‪array checkValueForCheck($res, $value, $tcaFieldConf, $table, $id, $realPid, $field)
Definition: DataHandler.php:2023
‪TYPO3\CMS\Core\Cache\CacheManager\getCache
‪FrontendInterface getCache($identifier)
Definition: CacheManager.php:129
‪TYPO3\CMS\Core\DataHandling\DataHandler\$remapStack
‪array $remapStack
Definition: DataHandler.php:523
‪TYPO3\CMS\Core\DataHandling\DataHandler\$pageCache
‪array $pageCache
Definition: DataHandler.php:475
‪TYPO3\CMS\Core\DataHandling\DataHandler\$storeLogMessages
‪bool $storeLogMessages
Definition: DataHandler.php:100
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordLocalization
‪static mixed getRecordLocalization($table, $uid, $language, $andWhereClause='')
Definition: BackendUtility.php:316
‪TYPO3\CMS\Core\DataHandling\DataHandler\getUnique
‪string getUnique($table, $field, $value, $id, $newPid=0)
Definition: DataHandler.php:2768
‪TYPO3\CMS\Core\DataHandling\DataHandler\$updateRefIndexStack
‪array $updateRefIndexStack
Definition: DataHandler.php:554
‪TYPO3\CMS\Core\DataHandling\DataHandler\doesRecordExist_pageLookUp
‪bool array doesRecordExist_pageLookUp($id, $perms, $columns=['uid'])
Definition: DataHandler.php:6829
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static string getPublicPath()
Definition: Environment.php:153
‪TYPO3\CMS\Core\Versioning\VersionState\NEW_PLACEHOLDER
‪const NEW_PLACEHOLDER
Definition: VersionState.php:46
‪TYPO3\CMS\Core\DataHandling\DataHandler\canDeletePage
‪int[] string canDeletePage($uid)
Definition: DataHandler.php:5649
‪TYPO3\CMS\Core\DataHandling\DataHandler\getInlineFieldType
‪string bool getInlineFieldType($conf)
Definition: DataHandler.php:8501
‪TYPO3\CMS\Core\DataHandling\DataHandler\overrideFieldArray
‪array overrideFieldArray($table, $data)
Definition: DataHandler.php:7975
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerRecordIdForPageCacheClearing
‪registerRecordIdForPageCacheClearing($table, $uid, $pid=null)
Definition: DataHandler.php:8707
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkRecordInsertAccess
‪bool checkRecordInsertAccess($insertTable, $pid, $action=1)
Definition: DataHandler.php:6731
‪TYPO3\CMS\Core\DataHandling\DataHandler\$cmdmap
‪array $cmdmap
Definition: DataHandler.php:407
‪TYPO3\CMS\Core\DataHandling\DataHandler\printLogErrorMessages
‪printLogErrorMessages()
Definition: DataHandler.php:9101
‪TYPO3\CMS\Core\DataHandling\DataHandler\getOriginalParentOfRecord
‪int[] getOriginalParentOfRecord($table, $uid)
Definition: DataHandler.php:8162
‪$args
‪$args
Definition: checkIntegrityCsvFixtures.php:230
‪TYPO3\CMS\Core\DataHandling\DataHandler\isElementToBeDeleted
‪bool isElementToBeDeleted($table, $id)
Definition: DataHandler.php:9372
‪TYPO3\CMS\Core\Resource\AbstractFile\getForLocalProcessing
‪string getForLocalProcessing($writable=true)
Definition: AbstractFile.php:548
‪TYPO3\CMS\Core\DataHandling\DataHandler\isInWebMount
‪bool isInWebMount($pid)
Definition: DataHandler.php:6672
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteEl
‪deleteEl($table, $uid, $noRecordCheck=false, $forceHardDelete=false, bool $deleteRecordsOnPage=true)
Definition: DataHandler.php:5203
‪TYPO3\CMS\Core\DataHandling\DataHandler\disableDeleteClause
‪disableDeleteClause()
Definition: DataHandler.php:8122
‪TYPO3\CMS\Core\Utility\File\BasicFileUtility
Definition: BasicFileUtility.php:32
‪TYPO3\CMS\Core\Database\Connection\lastInsertId
‪string lastInsertId($tableName=null, string $fieldName='uid')
Definition: Connection.php:439
‪TYPO3\CMS\Core\DataHandling\DataHandler\addRemapStackRefIndex
‪addRemapStackRefIndex($table, $id)
Definition: DataHandler.php:6588
‪TYPO3\CMS\Core\Utility\PathUtility\dirname
‪static string dirname($path)
Definition: PathUtility.php:185
‪TYPO3\CMS\Core\Exception
Definition: Exception.php:21
‪TYPO3\CMS\Core\DataHandling\DataHandler\_ACTION_FLEX_FORMdata
‪_ACTION_FLEX_FORMdata(&$valueArray, $actionCMDs)
Definition: DataHandler.php:2643
‪TYPO3\CMS\Core\DataHandling\DataHandler\hook_processDatamap_afterDatabaseOperations
‪hook_processDatamap_afterDatabaseOperations(&$hookObjectsArr, &$status, &$table, &$id, &$fieldArray)
Definition: DataHandler.php:851
‪TYPO3\CMS\Core\DataHandling\DataHandler\isSubmittedValueEqualToStoredValue
‪bool isSubmittedValueEqualToStoredValue($submittedValue, $storedValue, $storedType, $allowNull=false)
Definition: DataHandler.php:8054
‪TYPO3\CMS\Core\DataHandling\DataHandler\compareFieldArrayWithCurrentAndUnset
‪array compareFieldArrayWithCurrentAndUnset($table, $id, $fieldArray)
Definition: DataHandler.php:7992
‪TYPO3\CMS\Core\DataHandling\DataHandler\assemblePermissions
‪int assemblePermissions($string)
Definition: DataHandler.php:8083
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForGroupSelect
‪array checkValueForGroupSelect($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $recFID, $uploadedFiles, $field)
Definition: DataHandler.php:2144
‪TYPO3\CMS\Core\Cache\CacheManager\flushCachesInGroupByTags
‪flushCachesInGroupByTags($groupIdentifier, array $tags)
Definition: CacheManager.php:213
‪TYPO3\CMS\Core\DataHandling\DataHandler\getFieldEvalCacheIdentifier
‪string getFieldEvalCacheIdentifier($additionalIdentifier)
Definition: DataHandler.php:9579
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForFlex
‪array checkValueForFlex($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $tscPID, $uploadedFiles, $field)
Definition: DataHandler.php:2544
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyPages
‪copyPages($uid, $destPid)
Definition: DataHandler.php:3691
‪TYPO3\CMS\Core\Utility\PathUtility\stripPathSitePrefix
‪static string stripPathSitePrefix($path)
Definition: PathUtility.php:371
‪TYPO3\CMS\Core\DataHandling\DataHandler\getCopyHeader
‪string getCopyHeader($table, $pid, $field, $value, $count, $prevTitle='')
Definition: DataHandler.php:8529
‪TYPO3\CMS\Core\DataHandling\DataHandler\getAutoVersionId
‪int getAutoVersionId($table, $id)
Definition: DataHandler.php:9203
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:32
‪TYPO3\CMS\Core\DataHandling\DataHandler\getTableEntries
‪array getTableEntries($table, $TSconfig)
Definition: DataHandler.php:8191
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\moveRecord
‪string moveRecord(string $table, int $uid, array $payload)
Definition: RecordHistoryStore.php:169
‪TYPO3\CMS\Core\DataHandling\DataHandler\$errorLog
‪array $errorLog
Definition: DataHandler.php:330
‪TYPO3\CMS\Core\DataHandling\DataHandler\remapListedDBRecords_procInline
‪remapListedDBRecords_procInline($conf, $value, $uid, $table)
Definition: DataHandler.php:6287
‪TYPO3\CMS\Core\Database\ReferenceIndex
Definition: ReferenceIndex.php:50
‪TYPO3\CMS\Core\DataHandling\DataHandler\destNotInsideSelf
‪bool destNotInsideSelf($destinationId, $id)
Definition: DataHandler.php:6946
‪TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException
Definition: InvalidPasswordHashException.php:22
‪TYPO3\CMS\Core\DataHandling\DataHandler\getOuterMostInstance
‪DataHandler getOuterMostInstance()
Definition: DataHandler.php:9288
‪TYPO3\CMS\Core\DataHandling\DataHandler\getExcludeListArray
‪array getExcludeListArray()
Definition: DataHandler.php:6982
‪TYPO3\CMS\Core\DataHandling\DataHandler\$updateModeL10NdiffDataClear
‪bool $updateModeL10NdiffDataClear
Definition: DataHandler.php:181
‪TYPO3\CMS\Core\DataHandling\DataHandler\$callBackObj
‪object $callBackObj
Definition: DataHandler.php:273
‪TYPO3\CMS\Core\DataHandling\DataHandler\$BE_USER
‪BackendUserAuthentication $BE_USER
Definition: DataHandler.php:354
‪TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface\removeAll
‪QueryRestrictionContainerInterface removeAll()
‪TYPO3\CMS\Core\DataHandling
Definition: DataHandler.php:2
‪TYPO3\CMS\Core\DataHandling\DataHandler\$alternativeFileName
‪array $alternativeFileName
Definition: DataHandler.php:242
‪TYPO3\CMS\Core\DataHandling\DataHandler\resetNestedElementCalls
‪resetNestedElementCalls()
Definition: DataHandler.php:9356
‪TYPO3\CMS\Core\DataHandling\DataHandler\resolveSortingAndPidForNewRecord
‪array resolveSortingAndPidForNewRecord(string $table, int $pid, array $fieldArray)
Definition: DataHandler.php:1302
‪TYPO3\CMS\Core\DataHandling\DataHandler\isRecordCopied
‪bool isRecordCopied($table, $uid)
Definition: DataHandler.php:8679
‪TYPO3\CMS\Core\DataHandling\DataHandler\dbAnalysisStoreExec
‪dbAnalysisStoreExec()
Definition: DataHandler.php:8224
‪TYPO3\CMS\Core\Database\Connection\insert
‪int insert($tableName, array $data, array $types=[])
Definition: Connection.php:193
‪TYPO3\CMS\Core\DataHandling\DataHandler\$excludedTablesAndFields
‪array $excludedTablesAndFields
Definition: DataHandler.php:388
‪TYPO3\CMS\Core\DataHandling\DataHandler\getPreviousLocalizedRecordUid
‪int getPreviousLocalizedRecordUid($table, $uid, $pid, $language)
Definition: DataHandler.php:7821
‪TYPO3\CMS\Core\DataHandling\DataHandler\$enableLogging
‪bool $enableLogging
Definition: DataHandler.php:106
‪TYPO3\CMS\Backend\Utility\BackendUtility\getItemLabel
‪static string getItemLabel($table, $col)
Definition: BackendUtility.php:1791
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForRadio
‪array checkValueForRadio($res, $value, $tcaFieldConf, $table, $id, $pid, $field)
Definition: DataHandler.php:2095
‪TYPO3\CMS\Core\DataHandling\DataHandler\getUniqueCountStatement
‪Doctrine DBAL Driver Statement int getUniqueCountStatement(string $value, string $table, string $field, int $uid, int $pid)
Definition: DataHandler.php:2811
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForInline
‪array bool checkValueForInline($res, $value, $tcaFieldConf, $table, $id, $status, $field, array $additionalData=null)
Definition: DataHandler.php:2704
‪TYPO3\CMS\Core\Resource\ResourceFactory\getInstance
‪static ResourceFactory getInstance()
Definition: ResourceFactory.php:39
‪TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction
Definition: BackendWorkspaceRestriction.php:28
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_checkMax
‪array checkValue_checkMax($tcaFieldConf, $valueArray)
Definition: DataHandler.php:2742
‪TYPO3\CMS\Core\DataHandling\DataHandler\getLanguageService
‪LanguageService getLanguageService()
Definition: DataHandler.php:9620
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\getRestrictions
‪QueryRestrictionContainerInterface getRestrictions()
Definition: QueryBuilder.php:89
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_group_select_file
‪array checkValue_group_select_file($valueArray, $tcaFieldConf, $curValue, $uploadedFileArray, $status, $table, $id, $recFID)
Definition: DataHandler.php:2265
‪TYPO3\CMS\Core\DataHandling\DataHandler\getSortNumber
‪int array bool null getSortNumber($table, $uid, $pid)
Definition: DataHandler.php:7574
‪TYPO3\CMS\Core\DataHandling\DataHandler\addNewValuesToRemapStackChildIds
‪addNewValuesToRemapStackChildIds(array $idValues)
Definition: DataHandler.php:9233
‪TYPO3\CMS\Core\DataHandling\DataHandler\$substNEWwithIDs_table
‪array $substNEWwithIDs_table
Definition: DataHandler.php:294
‪TYPO3\CMS\Core\DataHandling\DataHandler\tableReadOnly
‪bool tableReadOnly($table)
Definition: DataHandler.php:6920
‪TYPO3\CMS\Core\DataHandling\DataHandler\isRecordUndeletable
‪bool isRecordUndeletable($table, $uid)
Definition: DataHandler.php:5722
‪TYPO3\CMS\Core\Utility\ArrayUtility\mergeRecursiveWithOverrule
‪static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
Definition: ArrayUtility.php:614
‪TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface
Definition: QueryRestrictionContainerInterface.php:23
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteRecord
‪deleteRecord($table, $uid, $noRecordCheck=false, $forceHardDelete=false, $undeleteRecord=false)
Definition: DataHandler.php:5270
‪TYPO3\CMS\Core\Database\Connection\quoteIdentifier
‪string quoteIdentifier($identifier)
Definition: Connection.php:126
‪TYPO3\CMS\Core\DataHandling\DataHandler\$deprecatedPublicProperties
‪array $deprecatedPublicProperties
Definition: DataHandler.php:87
‪TYPO3\CMS\Core\Versioning\VersionState\DELETE_PLACEHOLDER
‪const DELETE_PLACEHOLDER
Definition: VersionState.php:54
‪TYPO3\CMS\Core\DataHandling\DataHandler\postProcessSqlServerInsert
‪int postProcessSqlServerInsert(Connection $connection, string $tableName)
Definition: DataHandler.php:9517
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:158
‪TYPO3\CMS\Core\DataHandling\DataHandler\compileAdminTables
‪array compileAdminTables()
Definition: DataHandler.php:8319
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_SW
‪array checkValue_SW($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $field, $uploadedFiles, $tscPID, array $additionalData=null)
Definition: DataHandler.php:1719
‪TYPO3\CMS\Core\DataHandling\DataHandler\postProcessPostgresqlInsert
‪postProcessPostgresqlInsert(Connection $connection, string $tableName)
Definition: DataHandler.php:9538
‪TYPO3\CMS\Core\DataHandling\DataHandler\extFileFields
‪array extFileFields($table)
Definition: DataHandler.php:8441
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_POINTER
‪const MOVE_POINTER
Definition: VersionState.php:72
‪TYPO3\CMS\Backend\Utility\BackendUtility\isRootLevelRestrictionIgnored
‪static bool isRootLevelRestrictionIgnored($table)
Definition: BackendUtility.php:4535
‪TYPO3\CMS\Core\DataHandling\DataHandler\$remapStackActions
‪array $remapStackActions
Definition: DataHandler.php:542
‪TYPO3\CMS\Core\DataHandling\DataHandler\$runtimeCache
‪TYPO3 CMS Core Cache Frontend FrontendInterface $runtimeCache
Definition: DataHandler.php:621
‪$fields
‪$fields
Definition: pages.php:4
‪TYPO3\CMS\Core\DataHandling\DataHandler\$copiedFileMap
‪array $copiedFileMap
Definition: DataHandler.php:318
‪TYPO3\CMS\Core\DataHandling\DataHandler\undeleteRecord
‪undeleteRecord($table, $uid)
Definition: DataHandler.php:5251
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteAction
‪deleteAction($table, $id)
Definition: DataHandler.php:5174
‪TYPO3\CMS\Core\Database\Connection\update
‪int update($tableName, array $data, array $identifier, array $types=[])
Definition: Connection.php:287
‪TYPO3\CMS\Core\DataHandling\DataHandler\recordInfo
‪array null recordInfo($table, $id, $fieldList)
Definition: DataHandler.php:7086
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkModifyAccessList
‪bool checkModifyAccessList($table)
Definition: DataHandler.php:6639
‪TYPO3\CMS\Core\DataHandling\DataHandler\resolvePid
‪int resolvePid($table, $pid)
Definition: DataHandler.php:8576
‪TYPO3\CMS\Core\DataHandling\DataHandler\applyDefaultsForFieldArray
‪array applyDefaultsForFieldArray(string $table, ?array $tcaDefaults, array $prepopulatedFieldArray)
Definition: DataHandler.php:766
‪TYPO3\CMS\Core\DataHandling\DataHandler\$recInsertAccessCache
‪array $recInsertAccessCache
Definition: DataHandler.php:457
‪TYPO3\CMS\Core\DataHandling\DataHandler\isOuterMostInstance
‪bool isOuterMostInstance()
Definition: DataHandler.php:9309
‪TYPO3\CMS\Core\DataHandling\DataHandler\$remapStackChildIds
‪array $remapStackChildIds
Definition: DataHandler.php:536
‪TYPO3\CMS\Core\Utility\PathUtility\basename
‪static string basename($path)
Definition: PathUtility.php:164
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:23
‪TYPO3\CMS\Core\DataHandling\DataHandler\getCheckModifyAccessListHookObjects
‪array getCheckModifyAccessListHookObjects()
Definition: DataHandler.php:876
‪TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory
Definition: RecordStateFactory.php:25
‪TYPO3\CMS\Backend\Utility\BackendUtility\isWebMountRestrictionIgnored
‪static bool isWebMountRestrictionIgnored($table)
Definition: BackendUtility.php:4522
‪TYPO3\CMS\Core\DataHandling\DataHandler\overlayAutoVersionId
‪int overlayAutoVersionId($table, $id)
Definition: DataHandler.php:9219
‪TYPO3\CMS\Core\DataHandling\DataHandler\$bypassWorkspaceRestrictions
‪bool $bypassWorkspaceRestrictions
Definition: DataHandler.php:188
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:36
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableWorkspaceEnabled
‪static bool isTableWorkspaceEnabled($table)
Definition: BackendUtility.php:4493
‪TYPO3\CMS\Core\DataHandling\DataHandler\setControl
‪setControl(array $control)
Definition: DataHandler.php:642
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore
Definition: RecordHistoryStore.php:28
‪TYPO3\CMS\Core\DataHandling\DataHandler\getLocalTCE
‪DataHandler getLocalTCE()
Definition: DataHandler.php:6108
‪TYPO3\CMS\Core\DataHandling\DataHandler\$checkStoredRecords
‪bool $checkStoredRecords
Definition: DataHandler.php:127
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidParentRowRootException
Definition: InvalidParentRowRootException.php:21
‪TYPO3\CMS\Core\DataHandling\DataHandler\hasDeletedRecord
‪bool hasDeletedRecord($tableName, $uid)
Definition: DataHandler.php:9188
‪TYPO3\CMS\Core\DataHandling\DataHandler\triggerRemapAction
‪triggerRemapAction($table, $id, array $callback, array $arguments, $forceRemapStackActions=false)
Definition: DataHandler.php:6552
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor
Definition: DataMapProcessor.php:48
‪TYPO3\CMS\Core\DataHandling\DataHandler\updateDB
‪updateDB($table, $id, $fieldArray)
Definition: DataHandler.php:7253
‪TYPO3\CMS\Core\Type\Enumeration\cast
‪static static cast($value)
Definition: Enumeration.php:182
‪TYPO3\CMS\Core\DataHandling\DataHandler\doesBranchExist
‪string int doesBranchExist($inList, $pid, $perms, $recurse)
Definition: DataHandler.php:6881
‪TYPO3\CMS\Core\DataHandling\DataHandler\prepareCacheFlush
‪array prepareCacheFlush($table, $uid, $pid)
Definition: DataHandler.php:8772
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_input_Eval
‪array checkValue_input_Eval($value, $evalArray, $is_in, string $table='')
Definition: DataHandler.php:2942
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveL10nOverlayRecords
‪moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid)
Definition: DataHandler.php:4798
‪TYPO3\CMS\Core\DataHandling\DataHandler\setDefaultsFromUserTS
‪setDefaultsFromUserTS($userTS)
Definition: DataHandler.php:734
‪TYPO3\CMS\Core\DataHandling\DataHandler\remapListedDBRecords
‪remapListedDBRecords()
Definition: DataHandler.php:6126
‪TYPO3\CMS\Backend\Utility\BackendUtility\fixVersioningPid
‪static fixVersioningPid($table, &$rr, $ignoreWorkspaceMatch=false)
Definition: BackendUtility.php:3986
‪TYPO3\CMS\Core\DataHandling\DataHandler\$pMap
‪array $pMap
Definition: DataHandler.php:426
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord_raw
‪int copyRecord_raw($table, $uid, $pid, $overrideArray=[], array $workspaceOptions=[])
Definition: DataHandler.php:3889
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue
‪array checkValue($table, $field, $value, $id, $status, $realPid, $tscPID, $incomingFieldArray=[])
Definition: DataHandler.php:1589
‪TYPO3\CMS\Core\DataHandling\DataHandler\getVersionizedIncomingFieldArray
‪getVersionizedIncomingFieldArray($table, $id, &$incomingFieldArray, &$registerDBList)
Definition: DataHandler.php:6604
‪TYPO3\CMS\Core\DataHandling\DataHandler\$bypassFileHandling
‪bool $bypassFileHandling
Definition: DataHandler.php:195
‪TYPO3\CMS\Core\DataHandling\DataHandler\$cachePrefixNestedElementCalls
‪string $cachePrefixNestedElementCalls
Definition: DataHandler.php:627
‪TYPO3\CMS\Core\DataHandling\DataHandler\localize
‪int bool localize($table, $uid, $language)
Definition: DataHandler.php:4861
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord_procFields
‪moveRecord_procFields($table, $uid, $destPid)
Definition: DataHandler.php:4741
‪TYPO3\CMS\Core\DataHandling\DataHandler\$datamap
‪array $datamap
Definition: DataHandler.php:401
‪TYPO3\CMS\Core\DataHandling\DataHandler\recordInfoWithPermissionCheck
‪array bool recordInfoWithPermissionCheck(string $table, int $id, $perms, string $fieldList=' *')
Definition: DataHandler.php:7113
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForText
‪array checkValueForText($value, $tcaFieldConf, $table, $id, $realPid, $field)
Definition: DataHandler.php:1830
‪TYPO3\CMS\Core\DataHandling\DataHandler\newlog2
‪int newlog2($message, $table, $uid, $pid=null, $error=0)
Definition: DataHandler.php:9085
‪TYPO3\CMS\Core\DataHandling\DataHandler\deletePages
‪deletePages($uid, $force=false, $forceHardDelete=false, bool $deleteRecordsOnPage=true)
Definition: DataHandler.php:5479
‪TYPO3\CMS\Core\DataHandling\DataHandler\$registerDBPids
‪array $registerDBPids
Definition: DataHandler.php:506
‪TYPO3\CMS\Core\DataHandling\DataHandler\$outerMostInstance
‪TYPO3 CMS Core DataHandling DataHandler $outerMostInstance
Definition: DataHandler.php:602
‪TYPO3\CMS\Core\DataHandling\DataHandler\$fileFunc
‪BasicFileUtility $fileFunc
Definition: DataHandler.php:569
‪TYPO3\CMS\Core\DataHandling\DataHandler\isTableAllowedForThisPage
‪bool isTableAllowedForThisPage($page_uid, $checkTable)
Definition: DataHandler.php:6777
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidIdentifierException
Definition: InvalidIdentifierException.php:21
‪TYPO3\CMS\Core\Resource\AbstractFile\delete
‪bool delete()
Definition: AbstractFile.php:433
‪TYPO3\CMS\Core\DataHandling\DataHandler\isReferenceField
‪bool isReferenceField($conf)
Definition: DataHandler.php:8489
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:30
‪TYPO3\CMS\Core\DataHandling\DataHandler\$userid
‪int $userid
Definition: DataHandler.php:360
‪TYPO3\CMS\Core\DataHandling\DataHandler\$remapStackRecords
‪array $remapStackRecords
Definition: DataHandler.php:530
‪TYPO3\CMS\Core\DataHandling\DataHandler\$username
‪string $username
Definition: DataHandler.php:366
‪TYPO3\CMS\Core\DataHandling\DataHandler\$copyMappingArray
‪array $copyMappingArray
Definition: DataHandler.php:517
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteRecord_procBasedOnFieldType
‪deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf, $undeleteRecord=false)
Definition: DataHandler.php:5774
‪TYPO3\CMS\Core\DataHandling\DataHandler\$newRelatedIDs
‪array $newRelatedIDs
Definition: DataHandler.php:300
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\process
‪array process()
Definition: DataMapProcessor.php:106
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkForRecordsFromDisallowedTables
‪bool checkForRecordsFromDisallowedTables(array $pageIds)
Definition: DataHandler.php:8643
‪TYPO3\CMS\Core\DataHandling\DataHandler\$suggestedInsertUids
‪array $suggestedInsertUids
Definition: DataHandler.php:266
‪TYPO3\CMS\Core\DataHandling\DataHandler\$checkValue_currentRecord
‪array $checkValue_currentRecord
Definition: DataHandler.php:575
‪TYPO3\CMS\Core\Cache\CacheManager\flushCaches
‪flushCaches()
Definition: CacheManager.php:154
‪TYPO3\CMS\Core\Database\Query\QueryHelper\getDateTimeTypes
‪static array getDateTimeTypes()
Definition: QueryHelper.php:200
‪TYPO3\CMS\Core\DataHandling\DataHandler\$disableDeleteClause
‪bool $disableDeleteClause
Definition: DataHandler.php:587
‪TYPO3\CMS\Core\Utility\ArrayUtility\removeArrayEntryByValue
‪static array removeArrayEntryByValue(array $array, $cmpValue)
Definition: ArrayUtility.php:643
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForInput
‪array checkValueForInput($value, $tcaFieldConf, $table, $id, $realPid, $field)
Definition: DataHandler.php:1871
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools
Definition: FlexFormTools.php:36
‪TYPO3\CMS\Core\Resource\ResourceFactory
Definition: ResourceFactory.php:33
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkStoredRecord
‪array null checkStoredRecord($table, $id, $fieldArray, $action)
Definition: DataHandler.php:7426
‪TYPO3\CMS\Core\DataHandling\DataHandler\$defaultValues
‪array $defaultValues
Definition: DataHandler.php:226
‪TYPO3\CMS\Backend\Utility\BackendUtility\getLiveVersionOfRecord
‪static array null getLiveVersionOfRecord($table, $uid, $fields=' *')
Definition: BackendUtility.php:4213
‪TYPO3\CMS\Core\DataHandling\DataHandler\$pagetreeNeedsRefresh
‪bool $pagetreeNeedsRefresh
Definition: DataHandler.php:342
‪TYPO3\CMS\Core\DataHandling\DataHandler\extFileFunctions
‪extFileFunctions($table, $field, $filelist)
Definition: DataHandler.php:8619
‪TYPO3\CMS\Core\DataHandling\DataHandler\$overrideValues
‪array $overrideValues
Definition: DataHandler.php:234
‪TYPO3\CMS\Core\DataHandling\DataHandler\clear_cacheCmd
‪clear_cacheCmd($cacheCmd)
Definition: DataHandler.php:8944
‪TYPO3\CMS\Core\DataHandling\DataHandler\$sortIntervals
‪int $sortIntervals
Definition: DataHandler.php:444
‪TYPO3\CMS\Core\DataHandling\DataHandler\versionizeRecord
‪int null versionizeRecord($table, $id, $label, $delete=false)
Definition: DataHandler.php:5878
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordTitle
‪static string getRecordTitle($table, $row, $prep=false, $forceResult=true)
Definition: BackendUtility.php:1811
‪TYPO3\CMS\Core\DataHandling\DataHandler\resorting
‪int null resorting($table, $pid, $sortColumn, $return_SortNumber_After_This_Uid)
Definition: DataHandler.php:7706
‪TYPO3\CMS\Core\Html\RteHtmlParser
Definition: RteHtmlParser.php:40
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord_raw
‪moveRecord_raw($table, $uid, $destPid)
Definition: DataHandler.php:4576
‪TYPO3\CMS\Core\DataHandling\DataHandler\$dbAnalysisStore
‪array $dbAnalysisStore
Definition: DataHandler.php:482
‪TYPO3\CMS\Core\DataHandling\DataHandler\eventPid
‪int eventPid($table, $uid, $pid)
Definition: DataHandler.php:7235
‪TYPO3\CMS\Core\DataHandling\DataHandler\insertUpdateDB_preprocessBasedOnFieldType
‪array insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray)
Definition: DataHandler.php:9167
‪TYPO3\CMS\Backend\Utility\BackendUtility\getMovePlaceholder
‪static array bool getMovePlaceholder($table, $uid, $fields=' *', $workspace=null)
Definition: BackendUtility.php:4311
‪TYPO3\CMS\Core\DataHandling\DataHandler\normalizeTimeFormat
‪string normalizeTimeFormat(string $table, string $value, string $dbType)
Definition: DataHandler.php:1276
‪TYPO3\CMS\Core\DataHandling\DataHandler\$removeFilesStore
‪array $removeFilesStore
Definition: DataHandler.php:488
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:34
‪TYPO3\CMS\Core\Cache\CacheManager\flushCachesInGroup
‪flushCachesInGroup($groupIdentifier)
Definition: CacheManager.php:168
‪TYPO3\CMS\Backend\Utility\BackendUtility\purgeComputedPropertiesFromRecord
‪static array purgeComputedPropertiesFromRecord(array $record)
Definition: BackendUtility.php:206
‪TYPO3\CMS\Core\DataHandling\DataHandler\getPID
‪int false getPID($table, $uid)
Definition: DataHandler.php:8206
‪TYPO3\CMS\Core\DataHandling\DataHandler\$defaultPermissions
‪array $defaultPermissions
Definition: DataHandler.php:378
‪TYPO3\CMS\Core\DataHandling\DataHandler\$remapStackRefIndex
‪array $remapStackRefIndex
Definition: DataHandler.php:548
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:45
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteL10nOverlayRecords
‪deleteL10nOverlayRecords($table, $uid)
Definition: DataHandler.php:5820
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkRecordUpdateAccess
‪bool checkRecordUpdateAccess($table, $id, $data=false, $hookObjectsArr=null)
Definition: DataHandler.php:6689
‪TYPO3\CMS\Core\DataHandling\DataHandler\int_pageTreeInfo
‪array int_pageTreeInfo($CPtable, $pid, $counter, $rootID)
Definition: DataHandler.php:8257
‪TYPO3\CMS\Core\DataHandling\DataHandler\fixCopyAfterDuplFields
‪array fixCopyAfterDuplFields($table, $uid, $prevUid, $update, $newData=[])
Definition: DataHandler.php:8417
‪TYPO3\CMS\Core\DataHandling\DataHandler\$isImporting
‪bool $isImporting
Definition: DataHandler.php:152
‪TYPO3\CMS\Core\DataHandling\DataHandler\$alternativeFilePath
‪array $alternativeFilePath
Definition: DataHandler.php:248
‪TYPO3\CMS\Core\DataHandling\DataHandler\doesRecordExist
‪bool doesRecordExist($table, $id, $perms)
Definition: DataHandler.php:6814
‪TYPO3\CMS\Core\DataHandling\DataHandler\createRelationHandlerInstance
‪RelationHandler createRelationHandlerInstance()
Definition: DataHandler.php:9587
‪TYPO3\CMS\Core\Service\OpcodeCacheService
Definition: OpcodeCacheService.php:24
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForSlug
‪array checkValueForSlug(string $value, array $tcaFieldConf, string $table, $id, int $realPid, string $field, array $incomingFieldArray=[])
Definition: DataHandler.php:1967
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteVersionsForRecord
‪deleteVersionsForRecord($table, $uid, $forceHardDelete)
Definition: DataHandler.php:5220
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_group_select_processDBdata
‪array checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, $type, $currentTable, $currentField)
Definition: DataHandler.php:3139
‪TYPO3\CMS\Core\DataHandling\DataHandler\getCacheManager
‪CacheManager getCacheManager()
Definition: DataHandler.php:9602
‪TYPO3\CMS\Core\DataHandling\DataHandler\$copyWhichTables
‪string $copyWhichTables
Definition: DataHandler.php:209
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyMovedRecordToNewLocation
‪copyMovedRecordToNewLocation($table, $uid)
Definition: DataHandler.php:5611
‪TYPO3\CMS\Core\DataHandling\DataHandler\copySpecificPage
‪int null copySpecificPage($uid, $destPid, $copyTablesArray, $first=false)
Definition: DataHandler.php:3762
‪TYPO3\CMS\Core\DataHandling\DataHandler\$checkSimilar
‪bool $checkSimilar
Definition: DataHandler.php:120
‪TYPO3\CMS\Core\DataHandling\DataHandler\$historyRecords
‪array $historyRecords
Definition: DataHandler.php:419
‪TYPO3\CMS\Core\DataHandling\DataHandler\process_cmdmap
‪void bool process_cmdmap()
Definition: DataHandler.php:3403
‪TYPO3\CMS\Core\DataHandling\DataHandler\$updateModeL10NdiffData
‪bool string $updateModeL10NdiffData
Definition: DataHandler.php:174
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:72
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_flex_procInData
‪array checkValue_flex_procInData($dataPart, $dataPart_current, $uploadedFiles, $dataStructure, $pParams, $callBackFunc='', array $workspaceOptions=[])
Definition: DataHandler.php:3216
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerElementsToBeDeleted
‪registerElementsToBeDeleted()
Definition: DataHandler.php:9383
‪TYPO3\CMS\Backend\Utility\BackendUtility\getWorkspaceVersionOfRecord
‪static array bool getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields=' *')
Definition: BackendUtility.php:4166
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordWSOL
‪static array getRecordWSOL( $table, $uid, $fields=' *', $where='', $useDeleteClause=true, $unsetMovePointers=false)
Definition: BackendUtility.php:174
‪TYPO3\CMS\Core\DataHandling\DataHandler\processClearCacheQueue
‪processClearCacheQueue()
Definition: DataHandler.php:8724
‪TYPO3\CMS\Core\DataHandling\DataHandler\getAllowedTablesToCopyWhenCopyingAPage
‪array getAllowedTablesToCopyWhenCopyingAPage()
Definition: DataHandler.php:3732
‪TYPO3\CMS\Core\DataHandling\DataHandler\convNumEntityToByteValue
‪string convNumEntityToByteValue($input)
Definition: DataHandler.php:8101
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:130
‪TYPO3\CMS\Core\DataHandling\DataHandler\__construct
‪__construct()
Definition: DataHandler.php:632
‪$errors
‪$errors
Definition: annotationChecker.php:115
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:23
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_input_ValidateEmail
‪checkValue_input_ValidateEmail($value, &$set)
Definition: DataHandler.php:3107
‪$output
‪$output
Definition: annotationChecker.php:113
‪TYPO3\CMS\Core\Utility\ArrayUtility\setValueByPath
‪static array setValueByPath(array $array, $path, $value, $delimiter='/')
Definition: ArrayUtility.php:271
‪TYPO3\CMS\Core\DataHandling\DataHandler\version_remapMMForVersionSwap
‪version_remapMMForVersionSwap($table, $id, $swapWith)
Definition: DataHandler.php:5985
‪TYPO3\CMS\Core\DataHandling\DataHandler\setMirror
‪setMirror($mirror)
Definition: DataHandler.php:705
‪TYPO3\CMS\Core\DataHandling\DataHandler\unsetElementsToBeDeleted
‪array unsetElementsToBeDeleted(array $elements)
Definition: DataHandler.php:9406
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidPointerFieldValueException
Definition: InvalidPointerFieldValueException.php:21
‪TYPO3\CMS\Core\DataHandling\DataHandler\$version_remapMMForVersionSwap_reg
‪array $version_remapMMForVersionSwap_reg
Definition: DataHandler.php:595
‪TYPO3\CMS\Core\DataHandling\DataHandler\$bypassAccessCheckForRecords
‪bool $bypassAccessCheckForRecords
Definition: DataHandler.php:202
‪TYPO3\CMS\Core\DataHandling\DataHandler\controlActiveElements
‪controlActiveElements()
Definition: DataHandler.php:9442
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord_procBasedOnFieldType
‪moveRecord_procBasedOnFieldType($table, $uid, $destPid, $field, $value, $conf)
Definition: DataHandler.php:4762
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:31
‪TYPO3\CMS\Core\Versioning\VersionState\NEW_PLACEHOLDER_VERSION
‪const NEW_PLACEHOLDER_VERSION
Definition: VersionState.php:32
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:21
‪TYPO3\CMS\Core\DataHandling\DataHandler\processRemapStack
‪processRemapStack()
Definition: DataHandler.php:6347
‪TYPO3\CMS\Core\DataHandling\DataHandler\prependLabel
‪string prependLabel($table)
Definition: DataHandler.php:8564
‪TYPO3\CMS\Core\DataHandling\DataHandler\$copyTree
‪int $copyTree
Definition: DataHandler.php:217
‪TYPO3\CMS\Core\DataHandling\DataHandler\insertNewCopyVersion
‪int insertNewCopyVersion($table, $fieldArray, $realPid)
Definition: DataHandler.php:3962
‪TYPO3\CMS\Core\DataHandling\DataHandler\addRemapAction
‪addRemapAction($table, $id, array $callback, array $arguments)
Definition: DataHandler.php:6570
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_inline
‪checkValue_inline($res, $value, $tcaFieldConf, $PP, $field, array $additionalData=null)
Definition: DataHandler.php:2684
‪TYPO3\CMS\Core\DataHandling\DataHandler\$admin
‪bool $admin
Definition: DataHandler.php:372
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord_processInline
‪string copyRecord_processInline( $table, $uid, $field, $value, $row, $conf, $realDestPid, $language, array $workspaceOptions)
Definition: DataHandler.php:4111
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static string getUniqueId($prefix='')
Definition: StringUtility.php:91
‪TYPO3\CMS\Core\DataHandling\DataHandler\postProcessDatabaseInsert
‪int postProcessDatabaseInsert(Connection $connection, string $tableName, int $suggestedUid)
Definition: DataHandler.php:9486
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:22
‪TYPO3\CMS\Backend\Utility\BackendUtility\getLiveVersionIdOfRecord
‪static int getLiveVersionIdOfRecord($table, $uid)
Definition: BackendUtility.php:4229
‪TYPO3\CMS\Core\DataHandling\DataHandler\version_remapMMForVersionSwap_execSwap
‪version_remapMMForVersionSwap_execSwap($table, $id, $swapWith)
Definition: DataHandler.php:6078
‪TYPO3\CMS\Core\DataHandling\DataHandler\start
‪start($data, $cmd, $altUserObject=null)
Definition: DataHandler.php:656
‪TYPO3\CMS\Backend\Utility\BackendUtility\getTSCpid
‪static array getTSCpid($table, $uid, $pid)
Definition: BackendUtility.php:3607
‪TYPO3\CMS\Core\DataHandling\DataHandler\applyFiltersToValues
‪array mixed applyFiltersToValues(array $tcaFieldConfiguration, array $values)
Definition: DataHandler.php:2229
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableLocalizable
‪static bool isTableLocalizable($table)
Definition: BackendUtility.php:616
‪TYPO3\CMS\Core\DataHandling\DataHandler\getCommandMapElements
‪array getCommandMapElements($needle)
Definition: DataHandler.php:9423
‪TYPO3\CMS\Core\Type\Bitmask\Permission\CONTENT_EDIT
‪const CONTENT_EDIT
Definition: Permission.php:52
‪TYPO3\CMS\Core\DataHandling\DataHandler\getTCEMAIN_TSconfig
‪array getTCEMAIN_TSconfig($tscPID)
Definition: DataHandler.php:8178
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordHistoryStore
‪RecordHistoryStore getRecordHistoryStore()
Definition: DataHandler.php:7509
‪TYPO3\CMS\Backend\Utility\BackendUtility\getPagesTSconfig
‪static array getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
Definition: BackendUtility.php:864
‪TYPO3\CMS\Backend\Utility\BackendUtility\getTSconfig_pidValue
‪static int getTSconfig_pidValue($table, $uid, $pid)
Definition: BackendUtility.php:3522
‪TYPO3\CMS\Core\Versioning\VersionState\DEFAULT_STATE
‪const DEFAULT_STATE
Definition: VersionState.php:38
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\undeleteRecord
‪string undeleteRecord(string $table, int $uid)
Definition: RecordHistoryStore.php:147
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:23
‪TYPO3\CMS\Core\DataHandling\DataHandler\remapListedDBRecords_procDBRefs
‪array null remapListedDBRecords_procDBRefs($conf, $value, $MM_localUid, $table)
Definition: DataHandler.php:6221
‪TYPO3\CMS\Core\DataHandling\DataHandler\$autoVersionIdMap
‪array $autoVersionIdMap
Definition: DataHandler.php:282
‪TYPO3\CMS\Core\DataHandling\DataHandler\fillInFieldArray
‪array fillInFieldArray($table, $id, $fieldArray, $incomingFieldArray, $realPid, $status, $tscPID)
Definition: DataHandler.php:1422
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\deleteRecord
‪string deleteRecord(string $table, int $uid)
Definition: RecordHistoryStore.php:126
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_flexArray2Xml
‪string checkValue_flexArray2Xml($array, $addPrologue=false)
Definition: DataHandler.php:2629
‪TYPO3\CMS\Core\DataHandling\DataHandler\process_uploads_traverseArray
‪process_uploads_traverseArray(&$outputArr, $inputArr, $keyToSet)
Definition: DataHandler.php:822
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteClause
‪string deleteClause($table)
Definition: DataHandler.php:8133
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Backend\Utility\BackendUtility\workspaceOL
‪static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=false)
Definition: BackendUtility.php:4048
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:26
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\instance
‪static DataMapProcessor instance(array $dataMap, BackendUserAuthentication $backendUser)
Definition: DataMapProcessor.php:80
‪TYPO3\CMS\Core\DataHandling\DataHandler\$recUpdateAccessCache
‪array $recUpdateAccessCache
Definition: DataHandler.php:451
‪TYPO3\CMS\Core\DataHandling\DataHandler\addDefaultPermittedLanguageIfNotSet
‪addDefaultPermittedLanguageIfNotSet($table, &$incomingFieldArray)
Definition: DataHandler.php:7942
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:39
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_EDIT
‪const PAGE_EDIT
Definition: Permission.php:37
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord_fixRTEmagicImages
‪copyRecord_fixRTEmagicImages($table, $theNewSQLID)
Definition: DataHandler.php:4277
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_DELETE
‪const PAGE_DELETE
Definition: Permission.php:42
‪TYPO3\CMS\Core\DataHandling\DataHandler\isRecordInWebMount
‪bool isRecordInWebMount($table, $id)
Definition: DataHandler.php:6657
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord_processManyToMany
‪mixed copyRecord_processManyToMany($table, $uid, $field, $value, $conf, $language)
Definition: DataHandler.php:4060
‪TYPO3\CMS\Backend\Utility\BackendUtility\selectVersionsOfRecord
‪static array null selectVersionsOfRecord( $table, $uid, $fields=' *', $workspace=0, $includeDeletedRecords=false, $row=null)
Definition: BackendUtility.php:3891
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidParentRowLoopException
Definition: InvalidParentRowLoopException.php:21
‪TYPO3\CMS\Core\DataHandling\DataHandler\placeholderShadowing
‪placeholderShadowing($table, $id)
Definition: DataHandler.php:1337
‪TYPO3\CMS\Core\DataHandling\DataHandler\$checkModifyAccessListHookObjects
‪array $checkModifyAccessListHookObjects
Definition: DataHandler.php:591
‪TYPO3\CMS\Core\DataHandling\DataHandler\$useTransOrigPointerField
‪bool $useTransOrigPointerField
Definition: DataHandler.php:166
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteSpecificPage
‪deleteSpecificPage($uid, $forceHardDelete=false, bool $deleteRecordsOnPage=true)
Definition: DataHandler.php:5520
‪TYPO3\CMS\Core\DataHandling\DataHandler\setNullValues
‪setNullValues(array $active, array &$haystack)
Definition: DataHandler.php:9460
‪TYPO3\CMS\Core\DataHandling\DataHandler\process_uploads
‪process_uploads($postFiles)
Definition: DataHandler.php:787
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:21
‪TYPO3\CMS\Core\DataHandling\DataHandler\$dontProcessTransformations
‪bool $dontProcessTransformations
Definition: DataHandler.php:158
‪TYPO3\CMS\Core\Resource\ResourceFactory\getFileObject
‪File getFileObject($uid, array $fileData=[])
Definition: ResourceFactory.php:399
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForInternalReferences
‪array checkValueForInternalReferences(array $res, $value, $tcaFieldConf, $table, $id, $field)
Definition: DataHandler.php:1786
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_flex_procInData_travDS
‪checkValue_flex_procInData_travDS(&$dataValues, $dataValues_current, $uploadedFiles, $DSelements, $pParams, $callBackFunc, $structurePath, array $workspaceOptions=[])
Definition: DataHandler.php:3253
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteRecord_procFields
‪deleteRecord_procFields($table, $uid, $undeleteRecord=false)
Definition: DataHandler.php:5750
‪TYPO3\CMS\Core\Database\Connection\createQueryBuilder
‪TYPO3 CMS Core Database Query QueryBuilder createQueryBuilder()
Definition: Connection.php:110
‪TYPO3\CMS\Core\DataHandling\DataHandler\$RTEmagic_copyIndex
‪array $RTEmagic_copyIndex
Definition: DataHandler.php:324
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:29
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordProperties
‪array getRecordProperties($table, $id, $noWSOL=false)
Definition: DataHandler.php:7198
‪TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait
Definition: PublicPropertyDeprecationTrait.php:66
‪TYPO3\CMS\Core\DataHandling\DataHandler\resolveVersionedRecords
‪array resolveVersionedRecords($tableName, $fieldNames, $sortingField, array $liveIds)
Definition: DataHandler.php:9252
‪TYPO3\CMS\Core\DataHandling\DataHandler\addDeleteRestriction
‪addDeleteRestriction(QueryRestrictionContainerInterface $restrictions)
Definition: DataHandler.php:8147
‪TYPO3\CMS\Core\DataHandling\DataHandler\$callFromImpExp
‪array $callFromImpExp
Definition: DataHandler.php:561
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:44
‪TYPO3\CMS\Core\DataHandling\DataHandlerCheckModifyAccessListHookInterface
Definition: DataHandlerCheckModifyAccessListHookInterface.php:21
‪TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface\add
‪QueryRestrictionContainerInterface add(QueryRestrictionInterface $restriction)
‪TYPO3\CMS\Core\DataHandling\DataHandler\castReferenceValue
‪int string castReferenceValue($value, array $configuration)
Definition: DataHandler.php:8466
‪TYPO3\CMS\Core\DataHandling\DataHandler\$autoVersioningUpdate
‪bool $autoVersioningUpdate
Definition: DataHandler.php:581
‪TYPO3\CMS\Core\DataHandling\DataHandler\process_datamap
‪bool void process_datamap()
Definition: DataHandler.php:902
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\DataHandling\DataHandler\$deleteTree
‪bool $deleteTree
Definition: DataHandler.php:140
‪TYPO3\CMS\Core\DataHandling\DataHandler\removeRegisteredFiles
‪removeRegisteredFiles()
Definition: DataHandler.php:8237
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:21
‪TYPO3\CMS\Core\DataHandling\DataHandler\doesPageHaveUnallowedTables
‪bool array doesPageHaveUnallowedTables($page_uid, $doktype)
Definition: DataHandler.php:7008
‪TYPO3\CMS\Core\DataHandling\DataHandler\pageInfo
‪string pageInfo($id, $field)
Definition: DataHandler.php:7059
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord_flexFormCallBack
‪array copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $_1, $_2, $_3, $workspaceOptions)
Definition: DataHandler.php:4202
‪TYPO3\CMS\Core\DataHandling\DataHandler\setTSconfigPermissions
‪array setTSconfigPermissions($fieldArray, $TSConfig_p)
Definition: DataHandler.php:7886
‪TYPO3\CMS\Core\DataHandling\DataHandler\$uploadedFileArray
‪array $uploadedFileArray
Definition: DataHandler.php:494
‪TYPO3\CMS\Core\Configuration\Richtext
Definition: Richtext.php:31
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteRecord_flexFormCallBack
‪deleteRecord_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, $pObj)
Definition: DataHandler.php:5449
‪TYPO3\CMS\Core\DataHandling\DataHandler\$neverHideAtCopy
‪bool $neverHideAtCopy
Definition: DataHandler.php:146
‪TYPO3\CMS\Core\Utility\ArrayUtility\arrayDiffAssocRecursive
‪static array arrayDiffAssocRecursive(array $array1, array $array2)
Definition: ArrayUtility.php:728
‪TYPO3\CMS\Core\DataHandling\DataHandler\log
‪int log($table, $recuid, $action, $recpid, $error, $details, $details_nr=-1, $data=[], $event_pid=-1, $NEWid='')
Definition: DataHandler.php:9040
‪TYPO3\CMS\Core\DataHandling\DataHandler\isNestedElementCallRegistered
‪bool isNestedElementCallRegistered($table, $id, $identifier)
Definition: DataHandler.php:9332
‪TYPO3\CMS\Core\DataHandling\DataHandler\updateFlexFormData
‪updateFlexFormData($flexFormId, array $modifications)
Definition: DataHandler.php:6500
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:25
‪TYPO3\CMS\Backend\Utility\BackendUtility\wsMapId
‪static int wsMapId($table, $uid)
Definition: BackendUtility.php:4288
‪TYPO3\CMS\Core\DataHandling\DataHandler\$pagetreeRefreshFieldsFromPages
‪array $pagetreeRefreshFieldsFromPages
Definition: DataHandler.php:336
‪TYPO3\CMS\Core\DataHandling\DataHandler\fixUniqueInSite
‪bool fixUniqueInSite(string $table, int $uid)
Definition: DataHandler.php:8362
‪TYPO3\CMS\Core\DataHandling\DataHandler\resetElementsToBeDeleted
‪resetElementsToBeDeleted()
Definition: DataHandler.php:9394
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordPropertiesFromRow
‪array null getRecordPropertiesFromRow($table, $row)
Definition: DataHandler.php:7214
‪TYPO3\CMS\Core\Resource\ResourceFactory\retrieveFileOrFolderObject
‪File Folder null retrieveFileOrFolderObject($input)
Definition: ResourceFactory.php:491
‪TYPO3\CMS\Core\Messaging\AbstractMessage\ERROR
‪const ERROR
Definition: AbstractMessage.php:29
‪TYPO3\CMS\Core\DataHandling\DataHandler\cannotDeleteRecord
‪string cannotDeleteRecord($table, $id)
Definition: DataHandler.php:5706
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_inline_processDBdata
‪string checkValue_inline_processDBdata($valueArray, $tcaFieldConf, $id, $status, $table, $field, array $additionalData=null)
Definition: DataHandler.php:3364
‪TYPO3\CMS\Core\DataHandling\DataHandler\insertDB
‪int null insertDB($table, $id, $fieldArray, $newVersion=false, $suggestedUid=0, $dontSetNewIdIndex=false)
Definition: DataHandler.php:7325
‪TYPO3\CMS\Core\DataHandling\DataHandler\$mmHistoryRecords
‪array $mmHistoryRecords
Definition: DataHandler.php:413
‪TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory\forName
‪static static forName(string $name)
Definition: RecordStateFactory.php:34
‪TYPO3\CMS\Core\DataHandling\DataHandlerProcessUploadHookInterface
Definition: DataHandlerProcessUploadHookInterface.php:22
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRuntimeCache
‪FrontendInterface getRuntimeCache()
Definition: DataHandler.php:9319
‪TYPO3\CMS\Core\DataHandling\DataHandler\$control
‪array $control
Definition: DataHandler.php:395
‪TYPO3\CMS\Core\DataHandling\DataHandler\newlog
‪int newlog($message, $error=0)
Definition: DataHandler.php:9068
‪TYPO3\CMS\Core\DataHandling\DataHandler\$registerDBList
‪array $registerDBList
Definition: DataHandler.php:500
‪TYPO3\CMS\Core\DataHandling\DataHandler\$recordPidsForDeletedRecords
‪static array $recordPidsForDeletedRecords
Definition: DataHandler.php:615
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_PLACEHOLDER
‪const MOVE_PLACEHOLDER
Definition: VersionState.php:71
‪TYPO3\CMS\Core\DataHandling\DataHandler\$copyMappingArray_merged
‪array $copyMappingArray_merged
Definition: DataHandler.php:306
‪TYPO3\CMS\Core\Database\Connection\PARAM_LOB
‪const PARAM_LOB
Definition: Connection.php:52
‪TYPO3\CMS\Core\DataHandling\DataHandler\$data_disableFields
‪array $data_disableFields
Definition: DataHandler.php:256
‪TYPO3\CMS\Core\DataHandling\DataHandler\newFieldArray
‪array newFieldArray($table)
Definition: DataHandler.php:7913
‪TYPO3\CMS\Core\DataHandling\DataHandler\inlineLocalizeSynchronize
‪inlineLocalizeSynchronize($table, $id, $command)
Definition: DataHandler.php:5025
‪TYPO3\CMS\Core\DataHandling\DataHandler\$deletedRecords
‪array $deletedRecords
Definition: DataHandler.php:312
‪TYPO3\CMS\Core\DataHandling\DataHandler\$checkStoredRecords_loose
‪bool $checkStoredRecords_loose
Definition: DataHandler.php:133
‪TYPO3\CMS\Backend\Utility\BackendUtility\getTCAtypeValue
‪static string getTCAtypeValue($table, $row)
Definition: BackendUtility.php:759