‪TYPO3CMS  10.4
DataHandler.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use Doctrine\DBAL\DBALException;
19 use Doctrine\DBAL\Driver\Statement;
20 use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
21 use Doctrine\DBAL\Platforms\SQLServerPlatform;
22 use Doctrine\DBAL\Types\IntegerType;
23 use Psr\Log\LoggerAwareInterface;
24 use Psr\Log\LoggerAwareTrait;
57 use ‪TYPO3\CMS\Core\SysLog\Action\Cache as SystemLogCacheAction;
58 use ‪TYPO3\CMS\Core\SysLog\Action\Database as SystemLogDatabaseAction;
59 use ‪TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
60 use ‪TYPO3\CMS\Core\SysLog\Type as SystemLogType;
69 
83 class ‪DataHandler implements LoggerAwareInterface
84 {
85  use LoggerAwareTrait;
86 
87  // *********************
88  // Public variables you can configure before using the class:
89  // *********************
96  public ‪$storeLogMessages = true;
97 
103  public ‪$enableLogging = true;
104 
111  public ‪$reverseOrder = false;
112 
120  public ‪$checkSimilar = true;
121 
128  public ‪$checkStoredRecords = true;
129 
135  public ‪$checkStoredRecords_loose = true;
136 
143  public ‪$deleteTree = false;
144 
150  public ‪$neverHideAtCopy = false;
151 
157  public ‪$isImporting = false;
158 
164  public ‪$dontProcessTransformations = false;
165 
173  protected ‪$useTransOrigPointerField = true;
174 
182  public ‪$bypassWorkspaceRestrictions = false;
183 
190  public ‪$bypassAccessCheckForRecords = false;
191 
199  public ‪$copyWhichTables = '*';
200 
208  public ‪$copyTree = 0;
209 
219  public ‪$defaultValues = [];
220 
229  public ‪$overrideValues = [];
230 
240 
250  public ‪$suggestedInsertUids = [];
251 
259  public ‪$callBackObj;
260 
267  protected ‪$correlationId;
268 
269  // *********************
270  // Internal variables (mapping arrays) which can be used (read-only) from outside
271  // *********************
278  public ‪$autoVersionIdMap = [];
279 
285  public ‪$substNEWwithIDs = [];
286 
293  public ‪$substNEWwithIDs_table = [];
294 
301  public ‪$newRelatedIDs = [];
302 
310 
316  protected ‪$deletedRecords = [];
317 
324  public ‪$errorLog = [];
325 
331  public ‪$pagetreeRefreshFieldsFromPages = ['pid', 'sorting', 'deleted', 'hidden', 'title', 'doktype', 'is_siteroot', 'fe_group', 'nav_hide', 'nav_title', 'module', 'starttime', 'endtime', 'content_from_pid', 'extendToSubpages'];
332 
339  public ‪$pagetreeNeedsRefresh = false;
340 
341  // *********************
342  // Internal Variables, do not touch.
343  // *********************
344 
345  // Variables set in init() function:
346 
352  public ‪$BE_USER;
353 
360  public ‪$userid;
361 
368  public ‪$username;
369 
376  public ‪$admin;
377 
384  public ‪$defaultPermissions = [
385  'user' => 'show,edit,delete,new,editcontent',
386  'group' => 'show,edit,new,editcontent',
387  'everybody' => ''
388  ];
389 
393  protected ‪$pagePermissionAssembler;
394 
400  protected ‪$excludedTablesAndFields = [];
401 
408  protected ‪$control = [];
409 
415  public ‪$datamap = [];
416 
422  public ‪$cmdmap = [];
423 
429  protected ‪$mmHistoryRecords = [];
430 
436  protected ‪$historyRecords = [];
437 
438  // Internal static:
445  public ‪$pMap = [
446  'show' => 1,
447  // 1st bit
448  'edit' => 2,
449  // 2nd bit
450  'delete' => 4,
451  // 3rd bit
452  'new' => 8,
453  // 4th bit
454  'editcontent' => 16
455  ];
456 
465  public ‪$sortIntervals = 256;
466 
467  // Internal caching arrays
473  protected ‪$recInsertAccessCache = [];
474 
481 
487  protected ‪$isInWebMount_Cache = [];
488 
494  protected ‪$pageCache = [];
495 
496  // Other arrays:
503  public ‪$dbAnalysisStore = [];
504 
511  public ‪$registerDBList = [];
512 
519  public ‪$registerDBPids = [];
520 
532  public ‪$copyMappingArray = [];
533 
540  public ‪$remapStack = [];
541 
549  public ‪$remapStackRecords = [];
550 
556  protected ‪$remapStackChildIds = [];
557 
563  protected ‪$remapStackActions = [];
564 
575 
583  public ‪$callFromImpExp = false;
584 
585  // Various
586 
593  public ‪$checkValue_currentRecord = [];
594 
600  protected ‪$disableDeleteClause = false;
601 
606 
611 
618  protected ‪$outerMostInstance;
619 
625  protected static ‪$recordsToClearCacheFor = [];
626 
633  protected static ‪$recordPidsForDeletedRecords = [];
634 
640  protected ‪$runtimeCache;
641 
647  protected ‪$cachePrefixNestedElementCalls = 'core-datahandler-nestedElementCalls-';
648 
655  {
656  $this->checkStoredRecords = (bool)‪$GLOBALS['TYPO3_CONF_VARS']['BE']['checkStoredRecords'];
657  $this->checkStoredRecords_loose = (bool)‪$GLOBALS['TYPO3_CONF_VARS']['BE']['checkStoredRecordsLoose'];
658  $this->runtimeCache = $this->‪getRuntimeCache();
659  $this->pagePermissionAssembler = GeneralUtility::makeInstance(PagePermissionAssembler::class, ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPermissions']);
660  if (‪$referenceIndexUpdater === null) {
661  // Create ReferenceIndexUpdater object. This should only happen on outer most instance,
662  // sub instances should receive the reference index updater from a parent.
663  ‪$referenceIndexUpdater = GeneralUtility::makeInstance(ReferenceIndexUpdater::class);
664  }
665  $this->referenceIndexUpdater = ‪$referenceIndexUpdater;
666  }
667 
672  public function ‪setControl(array ‪$control)
673  {
674  $this->control = ‪$control;
675  }
676 
686  public function ‪start($data, $cmd, $altUserObject = null)
687  {
688  // Initializing BE_USER
689  $this->BE_USER = is_object($altUserObject) ? $altUserObject : ‪$GLOBALS['BE_USER'];
690  $this->userid = $this->BE_USER->user['uid'] ?? 0;
691  $this->username = $this->BE_USER->user['username'] ?? '';
692  $this->admin = $this->BE_USER->user['admin'] ?? false;
693  if ($this->BE_USER->uc['recursiveDelete'] ?? false) {
694  $this->deleteTree = 1;
695  }
696 
697  // set correlation id for each new set of data or commands
698  $this->correlationId = ‪CorrelationId::forScope(
699  md5(‪StringUtility::getUniqueId(self::class))
700  );
701 
702  // Get default values from user TSconfig
703  $tcaDefaultOverride = $this->BE_USER->getTSConfig()['TCAdefaults.'] ?? null;
704  if (is_array($tcaDefaultOverride)) {
705  $this->‪setDefaultsFromUserTS($tcaDefaultOverride);
706  }
707 
708  // Initializing default permissions for pages
709  ‪$defaultPermissions = ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPermissions'];
710  if (isset(‪$defaultPermissions['user'])) {
711  $this->defaultPermissions['user'] = ‪$defaultPermissions['user'];
712  }
713  if (isset(‪$defaultPermissions['group'])) {
714  $this->defaultPermissions['group'] = ‪$defaultPermissions['group'];
715  }
716  if (isset(‪$defaultPermissions['everybody'])) {
717  $this->defaultPermissions['everybody'] = ‪$defaultPermissions['everybody'];
718  }
719  // generates the excludelist, based on TCA/exclude-flag and non_exclude_fields for the user:
720  if (!$this->admin) {
721  $this->excludedTablesAndFields = array_flip($this->‪getExcludeListArray());
722  }
723  // Setting the data and cmd arrays
724  if (is_array($data)) {
725  reset($data);
726  $this->datamap = $data;
727  }
728  if (is_array($cmd)) {
729  reset($cmd);
730  $this->cmdmap = $cmd;
731  }
732  }
733 
741  public function ‪setMirror($mirror)
742  {
743  if (!is_array($mirror)) {
744  return;
745  }
746 
747  foreach ($mirror as $table => $uid_array) {
748  if (!isset($this->datamap[$table])) {
749  continue;
750  }
751 
752  foreach ($uid_array as $id => $uidList) {
753  if (!isset($this->datamap[$table][$id])) {
754  continue;
755  }
756 
757  $theIdsInArray = ‪GeneralUtility::trimExplode(',', $uidList, true);
758  foreach ($theIdsInArray as $copyToUid) {
759  $this->datamap[$table][$copyToUid] = $this->datamap[$table][$id];
760  }
761  }
762  }
763  }
764 
771  public function ‪setDefaultsFromUserTS($userTS)
772  {
773  if (!is_array($userTS)) {
774  return;
775  }
776 
777  foreach ($userTS as $k => $v) {
778  $k = mb_substr($k, 0, -1);
779  if (!$k || !is_array($v) || !isset(‪$GLOBALS['TCA'][$k])) {
780  continue;
781  }
782 
783  if (is_array($this->defaultValues[$k])) {
784  $this->defaultValues[$k] = array_merge($this->defaultValues[$k], $v);
785  } else {
786  $this->defaultValues[$k] = $v;
787  }
788  }
789  }
790 
803  protected function ‪applyDefaultsForFieldArray(string $table, int $pageId, array $prepopulatedFieldArray): array
804  {
805  // First set TCAdefaults respecting the given PageID
806  $tcaDefaults = ‪BackendUtility::getPagesTSconfig($pageId)['TCAdefaults.'] ?? null;
807  // Re-apply $this->defaultValues settings
808  $this->‪setDefaultsFromUserTS($tcaDefaults);
809  $cleanFieldArray = $this->‪newFieldArray($table);
810  if (isset($prepopulatedFieldArray['pid'])) {
811  $cleanFieldArray['pid'] = $prepopulatedFieldArray['pid'];
812  }
813  $sortColumn = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? null;
814  if ($sortColumn !== null && isset($prepopulatedFieldArray[$sortColumn])) {
815  $cleanFieldArray[$sortColumn] = $prepopulatedFieldArray[$sortColumn];
816  }
817  return $cleanFieldArray;
818  }
819 
825  public function ‪process_uploads()
826  {
827  trigger_error('DataHandler->process_uploads() will be removed in TYPO3 v11.0.', E_USER_DEPRECATED);
828  }
829 
830  /*********************************************
831  *
832  * HOOKS
833  *
834  *********************************************/
849  public function ‪hook_processDatamap_afterDatabaseOperations(&$hookObjectsArr, &$status, &$table, &$id, &$fieldArray)
850  {
851  // Process hook directly:
852  if (!isset($this->remapStackRecords[$table][$id])) {
853  foreach ($hookObjectsArr as $hookObj) {
854  if (method_exists($hookObj, 'processDatamap_afterDatabaseOperations')) {
855  $hookObj->processDatamap_afterDatabaseOperations($status, $table, $id, $fieldArray, $this);
856  }
857  }
858  } else {
859  $this->remapStackRecords[$table][$id]['processDatamap_afterDatabaseOperations'] = [
860  'status' => $status,
861  'fieldArray' => $fieldArray,
862  'hookObjectsArr' => $hookObjectsArr
863  ];
864  }
865  }
866 
874  protected function ‪getCheckModifyAccessListHookObjects()
875  {
876  if (!isset($this->checkModifyAccessListHookObjects)) {
877  $this->checkModifyAccessListHookObjects = [];
878  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['checkModifyAccessList'] ?? [] as $className) {
879  $hookObject = GeneralUtility::makeInstance($className);
880  if (!$hookObject instanceof ‪DataHandlerCheckModifyAccessListHookInterface) {
881  throw new \UnexpectedValueException($className . ' must implement interface ' . DataHandlerCheckModifyAccessListHookInterface::class, 1251892472);
882  }
883  $this->checkModifyAccessListHookObjects[] = $hookObject;
884  }
885  }
887  }
888 
889  /*********************************************
890  *
891  * PROCESSING DATA
892  *
893  *********************************************/
900  public function ‪process_datamap()
901  {
902  $this->‪controlActiveElements();
903 
904  // Keep versionized(!) relations here locally:
905  $registerDBList = [];
907  $this->datamap = $this->‪unsetElementsToBeDeleted($this->datamap);
908  // Editing frozen:
909  if ($this->BE_USER->workspace !== 0 && $this->BE_USER->workspaceRec['freeze']) {
910  $this->‪newlog('All editing in this workspace has been frozen!', SystemLogErrorClassification::USER_ERROR);
911  return false;
912  }
913  // First prepare user defined objects (if any) for hooks which extend this function:
914  $hookObjectsArr = [];
915  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'] ?? [] as $className) {
916  $hookObject = GeneralUtility::makeInstance($className);
917  if (method_exists($hookObject, 'processDatamap_beforeStart')) {
918  $hookObject->processDatamap_beforeStart($this);
919  }
920  $hookObjectsArr[] = $hookObject;
921  }
922  // Pre-process data-map and synchronize localization states
923  $this->datamap = GeneralUtility::makeInstance(SlugEnricher::class)->enrichDataMap($this->datamap);
924  $this->datamap = ‪DataMapProcessor::instance($this->datamap, $this->BE_USER, $this->referenceIndexUpdater)->‪process();
925  // 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.
926  $orderOfTables = [];
927  // Set pages first.
928  if (isset($this->datamap['pages'])) {
929  $orderOfTables[] = 'pages';
930  }
931  $orderOfTables = array_unique(array_merge($orderOfTables, array_keys($this->datamap)));
932  // Process the tables...
933  foreach ($orderOfTables as $table) {
934  // Check if
935  // - table is set in $GLOBALS['TCA'],
936  // - table is NOT readOnly
937  // - the table is set with content in the data-array (if not, there's nothing to process...)
938  // - permissions for tableaccess OK
939  $modifyAccessList = $this->‪checkModifyAccessList($table);
940  if (!$modifyAccessList) {
941  $this->‪log($table, 0, SystemLogDatabaseAction::UPDATE, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to modify table \'%s\' without permission', 1, [$table]);
942  }
943  if (!isset(‪$GLOBALS['TCA'][$table]) || $this->‪tableReadOnly($table) || !is_array($this->datamap[$table]) || !$modifyAccessList) {
944  continue;
945  }
946 
947  if ($this->reverseOrder) {
948  $this->datamap[$table] = array_reverse($this->datamap[$table], 1);
949  }
950  // For each record from the table, do:
951  // $id is the record uid, may be a string if new records...
952  // $incomingFieldArray is the array of fields
953  foreach ($this->datamap[$table] as $id => $incomingFieldArray) {
954  if (!is_array($incomingFieldArray)) {
955  continue;
956  }
957  $theRealPid = null;
958 
959  // Hook: processDatamap_preProcessFieldArray
960  foreach ($hookObjectsArr as $hookObj) {
961  if (method_exists($hookObj, 'processDatamap_preProcessFieldArray')) {
962  $hookObj->processDatamap_preProcessFieldArray($incomingFieldArray, $table, $id, $this);
963  }
964  }
965  // ******************************
966  // Checking access to the record
967  // ******************************
968  $createNewVersion = false;
969  $recordAccess = false;
970  $old_pid_value = '';
971  // Is it a new record? (Then Id is a string)
973  // Get a fieldArray with tca default values
974  $fieldArray = $this->‪newFieldArray($table);
975  // A pid must be set for new records.
976  if (isset($incomingFieldArray['pid'])) {
977  $pid_value = $incomingFieldArray['pid'];
978  // Checking and finding numerical pid, it may be a string-reference to another value
979  $canProceed = true;
980  // If a NEW... id
981  if (strpos($pid_value, 'NEW') !== false) {
982  if ($pid_value[0] === '-') {
983  $negFlag = -1;
984  $pid_value = substr($pid_value, 1);
985  } else {
986  $negFlag = 1;
987  }
988  // Trying to find the correct numerical value as it should be mapped by earlier processing of another new record.
989  if (isset($this->substNEWwithIDs[$pid_value])) {
990  if ($negFlag === 1) {
991  $old_pid_value = $this->substNEWwithIDs[$pid_value];
992  }
993  $pid_value = (int)($negFlag * $this->substNEWwithIDs[$pid_value]);
994  } else {
995  $canProceed = false;
996  }
997  }
998  $pid_value = (int)$pid_value;
999  if ($canProceed) {
1000  $fieldArray = $this->‪resolveSortingAndPidForNewRecord($table, $pid_value, $fieldArray);
1001  }
1002  }
1003  $theRealPid = $fieldArray['pid'];
1004  // Checks if records can be inserted on this $pid.
1005  // If this is a page translation, the check needs to be done for the l10n_parent record
1006  if ($table === 'pages' && $incomingFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0 && $incomingFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] > 0) {
1007  $recordAccess = $this->‪checkRecordInsertAccess($table, $incomingFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']]);
1008  } else {
1009  $recordAccess = $this->‪checkRecordInsertAccess($table, $theRealPid);
1010  }
1011  if ($recordAccess) {
1012  $this->‪addDefaultPermittedLanguageIfNotSet($table, $incomingFieldArray);
1013  $recordAccess = $this->BE_USER->recordEditAccessInternals($table, $incomingFieldArray, true);
1014  if (!$recordAccess) {
1015  $this->‪newlog('recordEditAccessInternals() check failed. [' . $this->BE_USER->errorMsg . ']', SystemLogErrorClassification::USER_ERROR);
1016  } elseif (!$this->bypassWorkspaceRestrictions && !$this->BE_USER->workspaceAllowsLiveEditingInTable($table)) {
1017  // If LIVE records cannot be created due to workspace restrictions, prepare creation of placeholder-record
1018  // So, if no live records were allowed in the current workspace, we have to create a new version of this record
1020  $createNewVersion = true;
1021  } else {
1022  $recordAccess = false;
1023  $this->‪newlog('Record could not be created in this workspace', SystemLogErrorClassification::USER_ERROR);
1024  }
1025  }
1026  }
1027  // Yes new record, change $record_status to 'insert'
1028  $status = 'new';
1029  } else {
1030  // Nope... $id is a number
1031  $fieldArray = [];
1032  $recordAccess = $this->‪checkRecordUpdateAccess($table, $id, $incomingFieldArray, $hookObjectsArr);
1033  if (!$recordAccess) {
1034  if ($this->enableLogging) {
1035  $propArr = $this->‪getRecordProperties($table, $id);
1036  $this->‪log($table, $id, SystemLogDatabaseAction::UPDATE, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to modify record \'%s\' (%s) without permission. Or non-existing page.', 2, [$propArr['header'], $table . ':' . $id], $propArr['event_pid']);
1037  }
1038  continue;
1039  }
1040  // Next check of the record permissions (internals)
1041  $recordAccess = $this->BE_USER->recordEditAccessInternals($table, $id);
1042  if (!$recordAccess) {
1043  $this->‪newlog('recordEditAccessInternals() check failed. [' . $this->BE_USER->errorMsg . ']', SystemLogErrorClassification::USER_ERROR);
1044  } else {
1045  // Here we fetch the PID of the record that we point to...
1046  $tempdata = $this->‪recordInfo($table, $id, 'pid' . (‪BackendUtility::isTableWorkspaceEnabled($table) ? ',t3ver_oid,t3ver_wsid,t3ver_stage' : ''));
1047  $theRealPid = $tempdata['pid'] ?? null;
1048  // Use the new id of the versionized record we're trying to write to:
1049  // (This record is a child record of a parent and has already been versionized.)
1050  if (!empty($this->autoVersionIdMap[$table][$id])) {
1051  // For the reason that creating a new version of this record, automatically
1052  // created related child records (e.g. "IRRE"), update the accordant field:
1053  $this->‪getVersionizedIncomingFieldArray($table, $id, $incomingFieldArray, ‪$registerDBList);
1054  // Use the new id of the copied/versionized record:
1055  $id = $this->autoVersionIdMap[$table][$id];
1056  $recordAccess = true;
1057  } elseif (!$this->bypassWorkspaceRestrictions && ($errorCode = $this->BE_USER->workspaceCannotEditRecord($table, $tempdata))) {
1058  $recordAccess = false;
1059  // Versioning is required and it must be offline version!
1060  // Check if there already is a workspace version
1061  $workspaceVersion = ‪BackendUtility::getWorkspaceVersionOfRecord($this->BE_USER->workspace, $table, $id, 'uid,t3ver_oid');
1062  if ($workspaceVersion) {
1063  $id = $workspaceVersion['uid'];
1064  $recordAccess = true;
1065  } elseif ($this->BE_USER->workspaceAllowAutoCreation($table, $id, $theRealPid)) {
1066  // new version of a record created in a workspace - so always refresh pagetree to indicate there is a change in the workspace
1067  $this->pagetreeNeedsRefresh = true;
1068 
1070  $tce = GeneralUtility::makeInstance(__CLASS__, $this->referenceIndexUpdater);
1071  $tce->enableLogging = ‪$this->enableLogging;
1072  // Setting up command for creating a new version of the record:
1073  $cmd = [];
1074  $cmd[$table][$id]['version'] = [
1075  'action' => 'new',
1076  // Default is to create a version of the individual records
1077  'label' => 'Auto-created for WS #' . $this->BE_USER->workspace
1078  ];
1079  $tce->start([], $cmd, $this->BE_USER);
1080  $tce->process_cmdmap();
1081  $this->errorLog = array_merge($this->errorLog, $tce->errorLog);
1082  // If copying was successful, share the new uids (also of related children):
1083  if (!empty($tce->copyMappingArray[$table][$id])) {
1084  foreach ($tce->copyMappingArray as $origTable => $origIdArray) {
1085  foreach ($origIdArray as $origId => $newId) {
1086  $this->autoVersionIdMap[$origTable][$origId] = $newId;
1087  }
1088  }
1089  // Update registerDBList, that holds the copied relations to child records:
1090  ‪$registerDBList = array_merge(‪$registerDBList, $tce->registerDBList);
1091  // For the reason that creating a new version of this record, automatically
1092  // created related child records (e.g. "IRRE"), update the accordant field:
1093  $this->‪getVersionizedIncomingFieldArray($table, $id, $incomingFieldArray, ‪$registerDBList);
1094  // Use the new id of the copied/versionized record:
1095  $id = $this->autoVersionIdMap[$table][$id];
1096  $recordAccess = true;
1097  } else {
1098  $this->‪newlog('Could not be edited in offline workspace in the branch where found (failure state: \'' . $errorCode . '\'). Auto-creation of version failed!', SystemLogErrorClassification::USER_ERROR);
1099  }
1100  } else {
1101  $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!', SystemLogErrorClassification::USER_ERROR);
1102  }
1103  }
1104  }
1105  // The default is 'update'
1106  $status = 'update';
1107  }
1108  // If access was granted above, proceed to create or update record:
1109  if (!$recordAccess) {
1110  continue;
1111  }
1112 
1113  // Here the "pid" is set IF NOT the old pid was a string pointing to a place in the subst-id array.
1114  [$tscPID] = BackendUtility::getTSCpid($table, $id, $old_pid_value ?: $fieldArray['pid']);
1115  if ($status === 'new') {
1116  // Apply TCAdefaults from pageTS
1117  $fieldArray = $this->applyDefaultsForFieldArray($table, (int)$tscPID, $fieldArray);
1118  // Apply page permissions as well
1119  if ($table === 'pages') {
1120  $fieldArray = $this->pagePermissionAssembler->applyDefaults(
1121  $fieldArray,
1122  (int)$tscPID,
1123  (int)$this->userid,
1124  (int)$this->BE_USER->firstMainGroup
1125  );
1126  }
1127  // Ensure that the default values, that are stored in the $fieldArray (built from internal default values)
1128  // Are also placed inside the incomingFieldArray, so this is checked in "fillInFieldArray" and
1129  // all default values are also checked for validity
1130  // This allows to set TCAdefaults (for example) without having to use FormEngine to have the fields available first.
1131  $incomingFieldArray = array_replace_recursive($fieldArray, $incomingFieldArray);
1132  }
1133  // Processing of all fields in incomingFieldArray and setting them in $fieldArray
1134  $fieldArray = $this->fillInFieldArray($table, $id, $fieldArray, $incomingFieldArray, $theRealPid, $status, $tscPID);
1135  $newVersion_placeholderFieldArray = [];
1136  if ($createNewVersion) {
1137  // create a placeholder array with already processed field content
1138  $newVersion_placeholderFieldArray = $fieldArray;
1139  }
1140  // NOTICE! All manipulation beyond this point bypasses both "excludeFields" AND possible "MM" relations to field!
1141  // Forcing some values unto field array:
1142  // NOTICE: This overriding is potentially dangerous; permissions per field is not checked!!!
1143  $fieldArray = $this->overrideFieldArray($table, $fieldArray);
1144  if ($createNewVersion) {
1145  $newVersion_placeholderFieldArray = $this->overrideFieldArray($table, $newVersion_placeholderFieldArray);
1146  }
1147  // Setting system fields
1148  if ($status === 'new') {
1149  if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1150  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
1151  if ($createNewVersion) {
1152  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
1153  }
1154  }
1155  if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
1156  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $this->userid;
1157  if ($createNewVersion) {
1158  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $this->userid;
1159  }
1160  }
1161  } elseif ($this->checkSimilar) {
1162  // Removing fields which are equal to the current value:
1163  $fieldArray = $this->compareFieldArrayWithCurrentAndUnset($table, $id, $fieldArray);
1164  }
1165  if ($GLOBALS['TCA'][$table]['ctrl']['tstamp'] && !empty($fieldArray)) {
1166  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
1167  if ($createNewVersion) {
1168  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
1169  }
1170  }
1171  // Set stage to "Editing" to make sure we restart the workflow
1172  if (BackendUtility::isTableWorkspaceEnabled($table)) {
1173  $fieldArray['t3ver_stage'] = 0;
1174  }
1175  // Hook: processDatamap_postProcessFieldArray
1176  foreach ($hookObjectsArr as $hookObj) {
1177  if (method_exists($hookObj, 'processDatamap_postProcessFieldArray')) {
1178  $hookObj->processDatamap_postProcessFieldArray($status, $table, $id, $fieldArray, $this);
1179  }
1180  }
1181  // Performing insert/update. If fieldArray has been unset by some userfunction (see hook above), don't do anything
1182  // Kasper: Unsetting the fieldArray is dangerous; MM relations might be saved already
1183  if (is_array($fieldArray)) {
1184  if ($status === 'new') {
1185  if ($table === 'pages') {
1186  // for new pages always a refresh is needed
1187  $this->pagetreeNeedsRefresh = true;
1188  }
1189 
1190  // This creates a new version of the record with online placeholder and offline version
1191  if ($createNewVersion) {
1192  // new record created in a workspace - so always refresh pagetree to indicate there is a change in the workspace
1193  $this->pagetreeNeedsRefresh = true;
1194 
1195  // Setting placeholder state value for temporary record
1196  $newVersion_placeholderFieldArray['t3ver_state'] = (string)new VersionState(‪VersionState::NEW_PLACEHOLDER);
1197  // Setting workspace - only so display of placeholders can filter out those from other workspaces.
1198  $newVersion_placeholderFieldArray['t3ver_wsid'] = $this->BE_USER->workspace;
1199  // Only set a label if it is an input field
1200  $labelField = ‪$GLOBALS['TCA'][$table]['ctrl']['label'];
1201  if (‪$GLOBALS['TCA'][$table]['columns'][$labelField]['config']['type'] === 'input') {
1202  $newVersion_placeholderFieldArray[$labelField] = $this->‪getPlaceholderTitleForTableLabel($table);
1203  }
1204  // Saving placeholder as 'original'
1205  $this->‪insertDB($table, $id, $newVersion_placeholderFieldArray, false, (int)($incomingFieldArray['uid'] ?? 0));
1206  // For the actual new offline version, set versioning values to point to placeholder
1207  $fieldArray['pid'] = $theRealPid;
1208  $fieldArray['t3ver_oid'] = $this->substNEWwithIDs[$id];
1209  // Setting placeholder state value for version (so it can know it is currently a new version...)
1210  $fieldArray['t3ver_state'] = (string)new ‪VersionState(‪VersionState::NEW_PLACEHOLDER_VERSION);
1211  $fieldArray['t3ver_wsid'] = $this->BE_USER->workspace;
1212  // When inserted, $this->substNEWwithIDs[$id] will be changed to the uid of THIS version and so the interface will pick it up just nice!
1213  $phShadowId = $this->‪insertDB($table, $id, $fieldArray, true, 0, true);
1214  if ($phShadowId) {
1215  // Processes fields of the placeholder record:
1216  $this->‪triggerRemapAction($table, $id, [$this, 'placeholderShadowing'], [$table, $phShadowId]);
1217  // Hold auto-versionized ids of placeholders:
1218  $this->autoVersionIdMap[$table][$this->substNEWwithIDs[$id]] = $phShadowId;
1219  }
1220  } else {
1221  $this->‪insertDB($table, $id, $fieldArray, false, (int)($incomingFieldArray['uid'] ?? 0));
1222  }
1223  } else {
1224  if ($table === 'pages') {
1225  // only a certain number of fields needs to be checked for updates
1226  // if $this->checkSimilar is TRUE, fields with unchanged values are already removed here
1227  $fieldsToCheck = array_intersect($this->pagetreeRefreshFieldsFromPages, array_keys($fieldArray));
1228  if (!empty($fieldsToCheck)) {
1229  $this->pagetreeNeedsRefresh = true;
1230  }
1231  }
1232  $this->‪updateDB($table, $id, $fieldArray);
1233  $this->‪placeholderShadowing($table, $id);
1234  }
1235  }
1236  // Hook: processDatamap_afterDatabaseOperations
1237  // Note: When using the hook after INSERT operations, you will only get the temporary NEW... id passed to your hook as $id,
1238  // but you can easily translate it to the real uid of the inserted record using the $this->substNEWwithIDs array.
1239  $this->‪hook_processDatamap_afterDatabaseOperations($hookObjectsArr, $status, $table, $id, $fieldArray);
1240  }
1241  }
1242  // Process the stack of relations to remap/correct
1243  $this->‪processRemapStack();
1244  $this->‪dbAnalysisStoreExec();
1245  // Hook: processDatamap_afterAllOperations
1246  // Note: When this hook gets called, all operations on the submitted data have been finished.
1247  foreach ($hookObjectsArr as $hookObj) {
1248  if (method_exists($hookObj, 'processDatamap_afterAllOperations')) {
1249  $hookObj->processDatamap_afterAllOperations($this);
1250  }
1251  }
1252 
1253  if ($this->‪isOuterMostInstance()) {
1254  $this->referenceIndexUpdater->update();
1255  $this->‪processClearCacheQueue();
1256  $this->‪resetElementsToBeDeleted();
1257  }
1258  }
1259 
1266  protected function ‪normalizeTimeFormat(string $table, string $value, string $dbType): string
1267  {
1268  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
1269  $platform = $connection->getDatabasePlatform();
1270  if ($platform instanceof SQLServerPlatform) {
1271  $defaultLength = ‪QueryHelper::getDateTimeFormats()[$dbType]['empty'];
1272  $value = substr(
1273  $value,
1274  0,
1275  strlen($defaultLength)
1276  );
1277  }
1278  return $value;
1279  }
1280 
1292  protected function ‪resolveSortingAndPidForNewRecord(string $table, int $pid, array $fieldArray): array
1293  {
1294  $sortColumn = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
1295  // Points to a page on which to insert the element, possibly in the top of the page
1296  if ($pid >= 0) {
1297  // Ensure that the "pid" is not a translated page ID, but the default page ID
1298  $pid = $this->‪getDefaultLanguagePageId($pid);
1299  // The numerical pid is inserted in the data array
1300  $fieldArray['pid'] = $pid;
1301  // If this table is sorted we better find the top sorting number
1302  if ($sortColumn) {
1303  $fieldArray[$sortColumn] = $this->‪getSortNumber($table, 0, $pid);
1304  }
1305  } elseif ($sortColumn) {
1306  // Points to another record before itself
1307  // If this table is sorted we better find the top sorting number
1308  // Because $pid is < 0, getSortNumber() returns an array
1309  $sortingInfo = $this->‪getSortNumber($table, 0, $pid);
1310  $fieldArray['pid'] = $sortingInfo['pid'];
1311  $fieldArray[$sortColumn] = $sortingInfo['sortNumber'];
1312  } else {
1313  // Here we fetch the PID of the record that we point to
1314  $record = $this->‪recordInfo($table, abs($pid), 'pid');
1315  // Ensure that the "pid" is not a translated page ID, but the default page ID
1316  $fieldArray['pid'] = $this->‪getDefaultLanguagePageId($record['pid']);
1317  }
1318  return $fieldArray;
1319  }
1320 
1328  public function ‪placeholderShadowing($table, $id)
1329  {
1330  $liveRecord = ‪BackendUtility::getLiveVersionOfRecord($table, $id, '*');
1331  if (empty($liveRecord)) {
1332  return;
1333  }
1334 
1335  $liveState = ‪VersionState::cast($liveRecord['t3ver_state']);
1336  $versionRecord = ‪BackendUtility::getRecord($table, $id);
1337  $versionState = ‪VersionState::cast($versionRecord['t3ver_state']);
1338 
1339  if (!$liveState->indicatesPlaceholder() && !$versionState->indicatesPlaceholder()) {
1340  return;
1341  }
1342  $factory = GeneralUtility::makeInstance(
1343  PlaceholderShadowColumnsResolver::class,
1344  $table,
1345  ‪$GLOBALS['TCA'][$table] ?? []
1346  );
1347 
1348  if ($versionState->equals(‪VersionState::MOVE_POINTER)) {
1349  $placeholderRecord = ‪BackendUtility::getMovePlaceholder($table, $liveRecord['uid'], '*', $versionRecord['t3ver_wsid']);
1350  $shadowColumns = $factory->forMovePlaceholder();
1351  } elseif ($liveState->indicatesPlaceholder()) {
1352  $placeholderRecord = $liveRecord;
1353  $shadowColumns = $factory->forNewPlaceholder();
1354  } else {
1355  return;
1356  }
1357  if (empty($shadowColumns)) {
1358  return;
1359  }
1360 
1361  $placeholderValues = [];
1362  foreach ($shadowColumns as $fieldName) {
1363  if ((string)$versionRecord[$fieldName] !== (string)$placeholderRecord[$fieldName]) {
1364  $placeholderValues[$fieldName] = $versionRecord[$fieldName];
1365  }
1366  }
1367  if (empty($placeholderValues)) {
1368  return;
1369  }
1370 
1371  if ($this->enableLogging) {
1372  $this->‪log($table, $placeholderRecord['uid'], SystemLogGenericAction::UNDEFINED, 0, SystemLogErrorClassification::MESSAGE, 'Shadowing done on fields <i>' . implode(',', array_keys($placeholderValues)) . '</i> in placeholder record ' . $table . ':' . $liveRecord['uid'] . ' (offline version UID=' . $id . ')', -1, [], $this->‪eventPid($table, $liveRecord['uid'], $liveRecord['pid']));
1373  }
1374  $this->‪updateDB($table, $placeholderRecord['uid'], $placeholderValues);
1375  }
1376 
1385  public function ‪getPlaceholderTitleForTableLabel($table, $placeholderContent = null)
1386  {
1387  if ($placeholderContent === null) {
1388  $placeholderContent = 'PLACEHOLDER';
1389  }
1390 
1391  $labelPlaceholder = '[' . $placeholderContent . ', WS#' . $this->BE_USER->workspace . ']';
1392  $labelField = ‪$GLOBALS['TCA'][$table]['ctrl']['label'];
1393  if (!isset(‪$GLOBALS['TCA'][$table]['columns'][$labelField]['config']['eval'])) {
1394  return $labelPlaceholder;
1395  }
1396  $evalCodesArray = ‪GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][$table]['columns'][$labelField]['config']['eval'], true);
1397  $transformedLabel = $this->‪checkValue_input_Eval($labelPlaceholder, $evalCodesArray, '', $table);
1398  return $transformedLabel['value'] ?? $labelPlaceholder;
1399  }
1400 
1415  public function ‪fillInFieldArray($table, $id, $fieldArray, $incomingFieldArray, $realPid, $status, $tscPID)
1416  {
1417  // Initialize:
1418  $originalLanguageRecord = null;
1419  $originalLanguage_diffStorage = null;
1420  $diffStorageFlag = false;
1421  // Setting 'currentRecord' and 'checkValueRecord':
1422  if (strpos($id, 'NEW') !== false) {
1423  // Must have the 'current' array - not the values after processing below...
1424  $checkValueRecord = $fieldArray;
1425  // IF $incomingFieldArray is an array, overlay it.
1426  // 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...
1427  if (is_array($incomingFieldArray) && is_array($checkValueRecord)) {
1428  ‪ArrayUtility::mergeRecursiveWithOverrule($checkValueRecord, $incomingFieldArray);
1429  }
1430  $currentRecord = $checkValueRecord;
1431  } else {
1432  // We must use the current values as basis for this!
1433  $currentRecord = ($checkValueRecord = $this->‪recordInfo($table, $id, '*'));
1434  // This is done to make the pid positive for offline versions; Necessary to have diff-view for page translations in workspaces.
1435  ‪BackendUtility::fixVersioningPid($table, $currentRecord);
1436  }
1437 
1438  // Get original language record if available:
1439  if (is_array($currentRecord)
1440  && ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']
1441  && ‪$GLOBALS['TCA'][$table]['ctrl']['languageField']
1442  && $currentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0
1443  && ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1444  && (int)$currentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] > 0
1445  ) {
1446  $originalLanguageRecord = $this->‪recordInfo($table, $currentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']], '*');
1447  ‪BackendUtility::workspaceOL($table, $originalLanguageRecord);
1448  $originalLanguage_diffStorage = unserialize(
1449  $currentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']],
1450  ['allowed_classes' => false]
1451  );
1452  }
1453 
1454  $this->checkValue_currentRecord = $checkValueRecord;
1455  // In the following all incoming value-fields are tested:
1456  // - Are the user allowed to change the field?
1457  // - Is the field uid/pid (which are already set)
1458  // - perms-fields for pages-table, then do special things...
1459  // - If the field is nothing of the above and the field is configured in TCA, the fieldvalues are evaluated by ->checkValue
1460  // If everything is OK, the field is entered into $fieldArray[]
1461  foreach ($incomingFieldArray as $field => $fieldValue) {
1462  if (isset($this->excludedTablesAndFields[$table . '-' . $field]) || $this->data_disableFields[$table][$id][$field]) {
1463  continue;
1464  }
1465 
1466  // The field must be editable.
1467  // Checking if a value for language can be changed:
1468  $languageDeny = ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] && (string)‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] === (string)$field && !$this->BE_USER->checkLanguageAccess($fieldValue);
1469  if ($languageDeny) {
1470  continue;
1471  }
1472 
1473  switch ($field) {
1474  case 'uid':
1475  case 'pid':
1476  // Nothing happens, already set
1477  break;
1478  case 'perms_userid':
1479  case 'perms_groupid':
1480  case 'perms_user':
1481  case 'perms_group':
1482  case 'perms_everybody':
1483  // Permissions can be edited by the owner or the administrator
1484  if ($table === 'pages' && ($this->admin || $status === 'new' || $this->‪pageInfo($id, 'perms_userid') == $this->userid)) {
1485  $value = (int)$fieldValue;
1486  switch ($field) {
1487  case 'perms_userid':
1488  case 'perms_groupid':
1489  $fieldArray[$field] = $value;
1490  break;
1491  default:
1492  if ($value >= 0 && $value < (2 ** 5)) {
1493  $fieldArray[$field] = $value;
1494  }
1495  }
1496  }
1497  break;
1498  case 't3ver_oid':
1499  case 't3ver_wsid':
1500  case 't3ver_state':
1501  case 't3ver_count':
1502  case 't3ver_stage':
1503  case 't3ver_tstamp':
1504  break;
1505  case 'l10n_state':
1506  $fieldArray[$field] = $fieldValue;
1507  break;
1508  default:
1509  if (isset(‪$GLOBALS['TCA'][$table]['columns'][$field])) {
1510  // Evaluating the value
1511  $res = $this->‪checkValue($table, $field, $fieldValue, $id, $status, $realPid, $tscPID, $incomingFieldArray);
1512  if (array_key_exists('value', $res)) {
1513  $fieldArray[$field] = $res['value'];
1514  }
1515  // Add the value of the original record to the diff-storage content:
1516  if (‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']) {
1517  $originalLanguage_diffStorage[$field] = $originalLanguageRecord[$field];
1518  $diffStorageFlag = true;
1519  }
1520  } elseif (‪$GLOBALS['TCA'][$table]['ctrl']['origUid'] === $field) {
1521  // Allow value for original UID to pass by...
1522  $fieldArray[$field] = $fieldValue;
1523  }
1524  }
1525  }
1526 
1527  // Dealing with a page translation, setting "sorting", "pid", "perms_*" to the same values as the original record
1528  if ($table === 'pages' && is_array($originalLanguageRecord)) {
1529  $fieldArray['sorting'] = $originalLanguageRecord['sorting'];
1530  $fieldArray['perms_userid'] = $originalLanguageRecord['perms_userid'];
1531  $fieldArray['perms_groupid'] = $originalLanguageRecord['perms_groupid'];
1532  $fieldArray['perms_user'] = $originalLanguageRecord['perms_user'];
1533  $fieldArray['perms_group'] = $originalLanguageRecord['perms_group'];
1534  $fieldArray['perms_everybody'] = $originalLanguageRecord['perms_everybody'];
1535  }
1536 
1537  // Add diff-storage information:
1538  if ($diffStorageFlag
1539  && !array_key_exists(‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'], $fieldArray)
1540  ) {
1541  // 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...
1542  $fieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']] = serialize($originalLanguage_diffStorage);
1543  }
1544  // Return fieldArray
1545  return $fieldArray;
1546  }
1547 
1548  /*********************************************
1549  *
1550  * Evaluation of input values
1551  *
1552  ********************************************/
1569  public function ‪checkValue($table, $field, $value, $id, $status, $realPid, $tscPID, $incomingFieldArray = [])
1570  {
1571  $curValueRec = null;
1572  // Result array
1573  $res = [];
1574 
1575  // Processing special case of field pages.doktype
1576  if ($table === 'pages' && $field === 'doktype') {
1577  // If the user may not use this specific doktype, we issue a warning
1578  if (!($this->admin || GeneralUtility::inList($this->BE_USER->groupData['pagetypes_select'], $value))) {
1579  if ($this->enableLogging) {
1580  $propArr = $this->‪getRecordProperties($table, $id);
1581  $this->‪log($table, $id, SystemLogDatabaseAction::CHECK, 0, SystemLogErrorClassification::USER_ERROR, 'You cannot change the \'doktype\' of page \'%s\' to the desired value.', 1, [$propArr['header']], $propArr['event_pid']);
1582  }
1583  return $res;
1584  }
1585  if ($status === 'update') {
1586  // This checks 1) if we should check for disallowed tables and 2) if there are records from disallowed tables on the current page
1587  $onlyAllowedTables = ‪$GLOBALS['PAGES_TYPES'][$value]['onlyAllowedTables'] ?? ‪$GLOBALS['PAGES_TYPES']['default']['onlyAllowedTables'];
1588  if ($onlyAllowedTables) {
1589  // use the real page id (default language)
1590  $recordId = $this->‪getDefaultLanguagePageId($id);
1591  $theWrongTables = $this->‪doesPageHaveUnallowedTables($recordId, $value);
1592  if ($theWrongTables) {
1593  if ($this->enableLogging) {
1594  $propArr = $this->‪getRecordProperties($table, $id);
1595  $this->‪log($table, $id, SystemLogDatabaseAction::CHECK, 0, SystemLogErrorClassification::USER_ERROR, '\'doktype\' of page \'%s\' could not be changed because the page contains records from disallowed tables; %s', 2, [$propArr['header'], $theWrongTables], $propArr['event_pid']);
1596  }
1597  return $res;
1598  }
1599  }
1600  }
1601  }
1602 
1603  $curValue = null;
1604  if ((int)$id !== 0) {
1605  // Get current value:
1606  $curValueRec = $this->‪recordInfo($table, $id, $field);
1607  // isset() won't work here, since values can be NULL
1608  if ($curValueRec !== null && array_key_exists($field, $curValueRec)) {
1609  $curValue = $curValueRec[$field];
1610  }
1611  }
1612 
1613  if ($table === 'be_users'
1614  && ($field === 'admin' || $field === 'password')
1615  && $status === 'update'
1616  ) {
1617  // Do not allow a non system maintainer admin to change admin flag and password of system maintainers
1618  $systemMaintainers = array_map('intval', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? []);
1619  // False if current user is not in system maintainer list or if switch to user mode is active
1620  $isCurrentUserSystemMaintainer = $this->BE_USER->isSystemMaintainer();
1621  $isTargetUserInSystemMaintainerList = in_array((int)$id, $systemMaintainers, true);
1622  if ($field === 'admin') {
1623  $isFieldChanged = (int)$curValueRec[$field] !== (int)$value;
1624  } else {
1625  $isFieldChanged = $curValueRec[$field] !== $value;
1626  }
1627  if (!$isCurrentUserSystemMaintainer && $isTargetUserInSystemMaintainerList && $isFieldChanged) {
1628  $value = $curValueRec[$field];
1629  $this->‪log(
1630  $table,
1631  (int)$id,
1632  SystemLogDatabaseAction::UPDATE,
1633  0,
1634  SystemLogErrorClassification::SECURITY_NOTICE,
1635  'Only system maintainers can change the admin flag and password of other system maintainers. The value has not been updated.',
1636  -1,
1637  [$this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.adminCanNotChangeSystemMaintainer')]
1638  );
1639  }
1640  }
1641 
1642  // Getting config for the field
1643  $tcaFieldConf = $this->‪resolveFieldConfigurationAndRespectColumnsOverrides($table, $field);
1644 
1645  // Create $recFID only for those types that need it
1646  if ($tcaFieldConf['type'] === 'flex') {
1647  $recFID = $table . ':' . $id . ':' . $field;
1648  } else {
1649  $recFID = null;
1650  }
1651 
1652  // Perform processing:
1653  $res = $this->‪checkValue_SW($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $field, [], $tscPID, ['incomingFieldArray' => $incomingFieldArray]);
1654  return $res;
1655  }
1656 
1667  protected function ‪resolveFieldConfigurationAndRespectColumnsOverrides(string $table, string $field): array
1668  {
1669  $tcaFieldConf = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'];
1670  $recordType = ‪BackendUtility::getTCAtypeValue($table, $this->checkValue_currentRecord);
1671  $columnsOverridesConfigOfField = ‪$GLOBALS['TCA'][$table]['types'][$recordType]['columnsOverrides'][$field]['config'] ?? null;
1672  if ($columnsOverridesConfigOfField) {
1673  ‪ArrayUtility::mergeRecursiveWithOverrule($tcaFieldConf, $columnsOverridesConfigOfField);
1674  }
1675  return $tcaFieldConf;
1676  }
1677 
1698  public function ‪checkValue_SW($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $field, $uploadedFiles, $tscPID, array $additionalData = null)
1699  {
1700  // Convert to NULL value if defined in TCA
1701  if ($value === null && !empty($tcaFieldConf['eval']) && GeneralUtility::inList($tcaFieldConf['eval'], 'null')) {
1702  $res = ['value' => null];
1703  return $res;
1704  }
1705 
1706  switch ($tcaFieldConf['type']) {
1707  case 'text':
1708  $res = $this->‪checkValueForText($value, $tcaFieldConf, $table, $id, $realPid, $field);
1709  break;
1710  case 'passthrough':
1711  case 'imageManipulation':
1712  case 'user':
1713  $res['value'] = $value;
1714  break;
1715  case 'input':
1716  $res = $this->‪checkValueForInput($value, $tcaFieldConf, $table, $id, $realPid, $field);
1717  break;
1718  case 'slug':
1719  $res = $this->‪checkValueForSlug((string)$value, $tcaFieldConf, $table, $id, (int)$realPid, $field, $additionalData['incomingFieldArray'] ?? []);
1720  break;
1721  case 'check':
1722  $res = $this->‪checkValueForCheck($res, $value, $tcaFieldConf, $table, $id, $realPid, $field);
1723  break;
1724  case 'radio':
1725  $res = $this->‪checkValueForRadio($res, $value, $tcaFieldConf, $table, $id, $realPid, $field);
1726  break;
1727  case 'group':
1728  case 'select':
1729  $res = $this->‪checkValueForGroupSelect($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $recFID, $uploadedFiles, $field);
1730  break;
1731  case 'inline':
1732  $res = $this->‪checkValueForInline($res, $value, $tcaFieldConf, $table, $id, $status, $field, $additionalData);
1733  break;
1734  case 'flex':
1735  // FlexForms are only allowed for real fields.
1736  if ($field) {
1737  $res = $this->‪checkValueForFlex($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $tscPID, $uploadedFiles, $field);
1738  }
1739  break;
1740  default:
1741  // Do nothing
1742  }
1743  $res = $this->‪checkValueForInternalReferences($res, $value, $tcaFieldConf, $table, $id, $field);
1744  return $res;
1745  }
1746 
1765  protected function ‪checkValueForInternalReferences(array $res, $value, $tcaFieldConf, $table, $id, $field)
1766  {
1767  $relevantFieldNames = [
1768  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null,
1769  ‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'] ?? null,
1770  ];
1771 
1772  if (
1773  // in case field is empty
1774  empty($field)
1775  // in case the field is not relevant
1776  || !in_array($field, $relevantFieldNames)
1777  // in case the 'value' index has been unset already
1778  || !array_key_exists('value', $res)
1779  // in case it's not a NEW-identifier
1780  || strpos($value, 'NEW') === false
1781  ) {
1782  return $res;
1783  }
1784 
1785  $valueArray = [$value];
1786  $this->remapStackRecords[$table][$id] = ['remapStackIndex' => count($this->remapStack)];
1787  $this->‪addNewValuesToRemapStackChildIds($valueArray);
1788  $this->remapStack[] = [
1789  'args' => [$valueArray, $tcaFieldConf, $id, $table, $field],
1790  'pos' => ['valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 3],
1791  'field' => $field
1792  ];
1793  unset($res['value']);
1794 
1795  return $res;
1796  }
1797 
1809  protected function ‪checkValueForText($value, $tcaFieldConf, $table, $id, $realPid, $field)
1810  {
1811  if (isset($tcaFieldConf['eval']) && $tcaFieldConf['eval'] !== '') {
1812  $cacheId = $this->‪getFieldEvalCacheIdentifier($tcaFieldConf['eval']);
1813  $evalCodesArray = $this->runtimeCache->get($cacheId);
1814  if (!is_array($evalCodesArray)) {
1815  $evalCodesArray = ‪GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
1816  $this->runtimeCache->set($cacheId, $evalCodesArray);
1817  }
1818  $valueArray = $this->‪checkValue_text_Eval($value, $evalCodesArray, $tcaFieldConf['is_in']);
1819  } else {
1820  $valueArray = ['value' => $value];
1821  }
1822 
1823  // Handle richtext transformations
1824  if ($this->dontProcessTransformations) {
1825  return $valueArray;
1826  }
1827  // Keep null as value
1828  if ($value === null) {
1829  return $valueArray;
1830  }
1831  if (isset($tcaFieldConf['enableRichtext']) && (bool)$tcaFieldConf['enableRichtext'] === true) {
1832  $recordType = ‪BackendUtility::getTCAtypeValue($table, $this->checkValue_currentRecord);
1833  $richtextConfigurationProvider = GeneralUtility::makeInstance(Richtext::class);
1834  $richtextConfiguration = $richtextConfigurationProvider->getConfiguration($table, $field, $realPid, $recordType, $tcaFieldConf);
1835  $rteParser = GeneralUtility::makeInstance(RteHtmlParser::class);
1836  $valueArray['value'] = $rteParser->transformTextForPersistence((string)$value, $richtextConfiguration['proc.'] ?? []);
1837  }
1838 
1839  return $valueArray;
1840  }
1841 
1853  protected function ‪checkValueForInput($value, $tcaFieldConf, $table, $id, $realPid, $field)
1854  {
1855  // Handle native date/time fields
1856  $isDateOrDateTimeField = false;
1857  $format = '';
1858  $emptyValue = '';
1859  $dateTimeTypes = ‪QueryHelper::getDateTimeTypes();
1860  // normal integer "date" fields (timestamps) are handled in checkValue_input_Eval
1861  if (isset($tcaFieldConf['dbType']) && in_array($tcaFieldConf['dbType'], $dateTimeTypes, true)) {
1862  if (empty($value)) {
1863  $value = null;
1864  } else {
1865  $isDateOrDateTimeField = true;
1866  $dateTimeFormats = ‪QueryHelper::getDateTimeFormats();
1867  $format = $dateTimeFormats[$tcaFieldConf['dbType']]['format'];
1868 
1869  // Convert the date/time into a timestamp for the sake of the checks
1870  $emptyValue = $dateTimeFormats[$tcaFieldConf['dbType']]['empty'];
1871  // We expect the ISO 8601 $value to contain a UTC timezone specifier.
1872  // We explicitly fallback to UTC if no timezone specifier is given (e.g. for copy operations).
1873  $dateTime = new \DateTime($value, new \DateTimeZone('UTC'));
1874  // The timestamp (UTC) returned by getTimestamp() will be converted to
1875  // a local time string by gmdate() later.
1876  $value = $value === $emptyValue ? null : $dateTime->getTimestamp();
1877  }
1878  }
1879  // Secures the string-length to be less than max.
1880  if (isset($tcaFieldConf['max']) && (int)$tcaFieldConf['max'] > 0) {
1881  $value = mb_substr((string)$value, 0, (int)$tcaFieldConf['max'], 'utf-8');
1882  }
1883 
1884  if (empty($tcaFieldConf['eval'])) {
1885  $res = ['value' => $value];
1886  } else {
1887  // Process evaluation settings:
1888  $cacheId = $this->‪getFieldEvalCacheIdentifier($tcaFieldConf['eval']);
1889  $evalCodesArray = $this->runtimeCache->get($cacheId);
1890  if (!is_array($evalCodesArray)) {
1891  $evalCodesArray = ‪GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
1892  $this->runtimeCache->set($cacheId, $evalCodesArray);
1893  }
1894 
1895  $res = $this->‪checkValue_input_Eval((string)$value, $evalCodesArray, $tcaFieldConf['is_in'] ?? '', $table, $id);
1896  if (isset($tcaFieldConf['dbType']) && isset($res['value']) && !$res['value']) {
1897  // set the value to null if we have an empty value for a native field
1898  $res['value'] = null;
1899  }
1900 
1901  // Process UNIQUE settings:
1902  // 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
1903  if ($field && !empty($res['value'])) {
1904  if (in_array('uniqueInPid', $evalCodesArray, true)) {
1905  $res['value'] = $this->‪getUnique($table, $field, $res['value'], $id, $realPid);
1906  }
1907  if ($res['value'] && in_array('unique', $evalCodesArray, true)) {
1908  $res['value'] = $this->‪getUnique($table, $field, $res['value'], $id);
1909  }
1910  }
1911  }
1912 
1913  // Skip range validation, if the default value equals 0 and the input value is 0, "0" or an empty string.
1914  // This is needed for timestamp date fields with ['range']['lower'] set.
1915  $skipRangeValidation =
1916  isset($tcaFieldConf['default'])
1917  && (int)$tcaFieldConf['default'] === 0
1918  && ($res['value'] === '' || $res['value'] === '0' || $res['value'] === 0);
1919 
1920  // Checking range of value:
1921  if (!$skipRangeValidation && isset($tcaFieldConf['range']) && is_array($tcaFieldConf['range'])) {
1922  if (isset($tcaFieldConf['range']['upper']) && ceil($res['value']) > (int)$tcaFieldConf['range']['upper']) {
1923  $res['value'] = (int)$tcaFieldConf['range']['upper'];
1924  }
1925  if (isset($tcaFieldConf['range']['lower']) && floor($res['value']) < (int)$tcaFieldConf['range']['lower']) {
1926  $res['value'] = (int)$tcaFieldConf['range']['lower'];
1927  }
1928  }
1929 
1930  // Handle native date/time fields
1931  if ($isDateOrDateTimeField) {
1932  // Convert the timestamp back to a date/time
1933  $res['value'] = $res['value'] ? gmdate($format, $res['value']) : $emptyValue;
1934  }
1935  return $res;
1936  }
1937 
1952  protected function ‪checkValueForSlug(string $value, array $tcaFieldConf, string $table, $id, int $realPid, string $field, array $incomingFieldArray = []): array
1953  {
1954  $workspaceId = $this->BE_USER->workspace;
1955  $helper = GeneralUtility::makeInstance(SlugHelper::class, $table, $field, $tcaFieldConf, $workspaceId);
1956  $fullRecord = array_replace_recursive($this->checkValue_currentRecord, $incomingFieldArray ?? []);
1957  // Generate a value if there is none, otherwise ensure that all characters are cleaned up
1958  if ($value === '') {
1959  $value = $helper->generate($fullRecord, $realPid);
1960  } else {
1961  $value = $helper->sanitize($value);
1962  }
1963 
1964  // Return directly in case no evaluations are defined
1965  if (empty($tcaFieldConf['eval'])) {
1966  return ['value' => $value];
1967  }
1968 
1969  $state = ‪RecordStateFactory::forName($table)
1970  ->fromArray($fullRecord, $realPid, $id);
1971  $evalCodesArray = ‪GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
1972  if (in_array('unique', $evalCodesArray, true)) {
1973  $value = $helper->buildSlugForUniqueInTable($value, $state);
1974  }
1975  if (in_array('uniqueInSite', $evalCodesArray, true)) {
1976  $value = $helper->buildSlugForUniqueInSite($value, $state);
1977  }
1978  if (in_array('uniqueInPid', $evalCodesArray, true)) {
1979  $value = $helper->buildSlugForUniqueInPid($value, $state);
1980  }
1981 
1982  return ['value' => $value];
1983  }
1984 
1997  protected function ‪checkValueForCheck($res, $value, $tcaFieldConf, $table, $id, $realPid, $field)
1998  {
1999  $items = $tcaFieldConf['items'];
2000  if (!empty($tcaFieldConf['itemsProcFunc'])) {
2002  $processingService = GeneralUtility::makeInstance(ItemProcessingService::class);
2003  $items = $processingService->getProcessingItems(
2004  $table,
2005  $realPid,
2006  $field,
2007  $this->checkValue_currentRecord,
2008  $tcaFieldConf,
2009  $tcaFieldConf['items']
2010  );
2011  }
2012 
2013  $itemC = 0;
2014  if ($items !== null) {
2015  $itemC = count($items);
2016  }
2017  if (!$itemC) {
2018  $itemC = 1;
2019  }
2020  $maxV = (2 ** $itemC) - 1;
2021  if ($value < 0) {
2022  // @todo: throw LogicException here? Negative values for checkbox items do not make sense and indicate a coding error.
2023  $value = 0;
2024  }
2025  if ($value > $maxV) {
2026  // @todo: This case is pretty ugly: If there is an itemsProcFunc registered, and if it returns a dynamic,
2027  // @todo: changing list of items, then it may happen that a value is transformed and vanished checkboxes
2028  // @todo: are permanently removed from the value.
2029  // @todo: Suggestion: Throw an exception instead? Maybe a specific, catchable exception that generates a
2030  // @todo: error message to the user - dynamic item sets via itemProcFunc on check would be a bad idea anyway.
2031  $value = $value & $maxV;
2032  }
2033  if ($field && $value > 0 && !empty($tcaFieldConf['eval'])) {
2034  $evalCodesArray = ‪GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
2035  $otherRecordsWithSameValue = [];
2036  $maxCheckedRecords = 0;
2037  if (in_array('maximumRecordsCheckedInPid', $evalCodesArray, true)) {
2038  $otherRecordsWithSameValue = $this->‪getRecordsWithSameValue($table, $id, $field, $value, $realPid);
2039  $maxCheckedRecords = (int)$tcaFieldConf['validation']['maximumRecordsCheckedInPid'];
2040  }
2041  if (in_array('maximumRecordsChecked', $evalCodesArray, true)) {
2042  $otherRecordsWithSameValue = $this->‪getRecordsWithSameValue($table, $id, $field, $value);
2043  $maxCheckedRecords = (int)$tcaFieldConf['validation']['maximumRecordsChecked'];
2044  }
2045 
2046  // there are more than enough records with value "1" in the DB
2047  // if so, set this value to "0" again
2048  if ($maxCheckedRecords && count($otherRecordsWithSameValue) >= $maxCheckedRecords) {
2049  $value = 0;
2050  $this->‪log($table, $id, SystemLogDatabaseAction::CHECK, 0, SystemLogErrorClassification::USER_ERROR, '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]);
2051  }
2052  }
2053  $res['value'] = $value;
2054  return $res;
2055  }
2056 
2069  protected function ‪checkValueForRadio($res, $value, $tcaFieldConf, $table, $id, $pid, $field)
2070  {
2071  if (is_array($tcaFieldConf['items'])) {
2072  foreach ($tcaFieldConf['items'] as $set) {
2073  if ((string)$set[1] === (string)$value) {
2074  $res['value'] = $value;
2075  break;
2076  }
2077  }
2078  }
2079 
2080  // if no value was found and an itemsProcFunc is defined, check that for the value
2081  if ($tcaFieldConf['itemsProcFunc'] && empty($res['value'])) {
2082  $processingService = GeneralUtility::makeInstance(ItemProcessingService::class);
2083  $processedItems = $processingService->getProcessingItems(
2084  $table,
2085  $pid,
2086  $field,
2087  $this->checkValue_currentRecord,
2088  $tcaFieldConf,
2089  $tcaFieldConf['items']
2090  );
2091 
2092  foreach ($processedItems as $set) {
2093  if ((string)$set[1] === (string)$value) {
2094  $res['value'] = $value;
2095  break;
2096  }
2097  }
2098  }
2099 
2100  return $res;
2101  }
2102 
2118  protected function ‪checkValueForGroupSelect($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $recFID, $uploadedFiles, $field)
2119  {
2120  // Detecting if value sent is an array and if so, implode it around a comma:
2121  if (is_array($value)) {
2122  $value = implode(',', $value);
2123  }
2124  // 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.
2125  // Anyway, this should NOT disturb anything else:
2126  $value = $this->‪convNumEntityToByteValue($value);
2127  // When values are sent as group or select they come as comma-separated values which are exploded by this function:
2128  $valueArray = $this->‪checkValue_group_select_explodeSelectGroupValue($value);
2129  // If multiple is not set, remove duplicates:
2130  if (!$tcaFieldConf['multiple']) {
2131  $valueArray = array_unique($valueArray);
2132  }
2133  // If an exclusive key is found, discard all others:
2134  if ($tcaFieldConf['type'] === 'select' && $tcaFieldConf['exclusiveKeys']) {
2135  $exclusiveKeys = ‪GeneralUtility::trimExplode(',', $tcaFieldConf['exclusiveKeys']);
2136  foreach ($valueArray as $index => $key) {
2137  if (in_array($key, $exclusiveKeys, true)) {
2138  $valueArray = [$index => $key];
2139  break;
2140  }
2141  }
2142  }
2143  // 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?)
2144  // 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!!
2145  $valueArray = $this->‪applyFiltersToValues($tcaFieldConf, $valueArray);
2146  // Checking for select / authMode, removing elements from $valueArray if any of them is not allowed!
2147  if ($tcaFieldConf['type'] === 'select' && $tcaFieldConf['authMode']) {
2148  $preCount = count($valueArray);
2149  foreach ($valueArray as $index => $key) {
2150  if (!$this->BE_USER->checkAuthMode($table, $field, $key, $tcaFieldConf['authMode'])) {
2151  unset($valueArray[$index]);
2152  }
2153  }
2154  // 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.
2155  if ($preCount && empty($valueArray)) {
2156  return [];
2157  }
2158  }
2159  // For select types which has a foreign table attached:
2160  $unsetResult = false;
2161  if (
2162  $tcaFieldConf['type'] === 'group' && $tcaFieldConf['internal_type'] === 'db'
2163  || $tcaFieldConf['type'] === 'select' && ($tcaFieldConf['foreign_table'] || isset($tcaFieldConf['special']) && $tcaFieldConf['special'] === 'languages')
2164  ) {
2165  // check, if there is a NEW... id in the value, that should be substituted later
2166  if (strpos($value, 'NEW') !== false) {
2167  $this->remapStackRecords[$table][$id] = ['remapStackIndex' => count($this->remapStack)];
2168  $this->‪addNewValuesToRemapStackChildIds($valueArray);
2169  $this->remapStack[] = [
2170  'func' => 'checkValue_group_select_processDBdata',
2171  'args' => [$valueArray, $tcaFieldConf, $id, $status, $tcaFieldConf['type'], $table, $field],
2172  'pos' => ['valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 5],
2173  'field' => $field
2174  ];
2175  $unsetResult = true;
2176  } else {
2177  $valueArray = $this->‪checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, $tcaFieldConf['type'], $table, $field);
2178  }
2179  }
2180  if (!$unsetResult) {
2181  $newVal = $this->‪checkValue_checkMax($tcaFieldConf, $valueArray);
2182  $res['value'] = $this->‪castReferenceValue(implode(',', $newVal), $tcaFieldConf);
2183  } else {
2184  unset($res['value']);
2185  }
2186  return $res;
2187  }
2188 
2197  protected function ‪applyFiltersToValues(array $tcaFieldConfiguration, array $values)
2198  {
2199  if (empty($tcaFieldConfiguration['filter']) || !is_array($tcaFieldConfiguration['filter'])) {
2200  return $values;
2201  }
2202  foreach ($tcaFieldConfiguration['filter'] as $filter) {
2203  if (empty($filter['userFunc'])) {
2204  continue;
2205  }
2206  $parameters = $filter['parameters'] ?: [];
2207  $parameters['values'] = $values;
2208  $parameters['tcaFieldConfig'] = $tcaFieldConfiguration;
2209  $values = GeneralUtility::callUserFunction($filter['userFunc'], $parameters, $this);
2210  if (!is_array($values)) {
2211  throw new \RuntimeException('Failed calling filter userFunc.', 1336051942);
2212  }
2213  }
2214  return $values;
2215  }
2216 
2234  protected function ‪checkValueForFlex($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $tscPID, $uploadedFiles, $field)
2235  {
2236  if (is_array($value)) {
2237  // This value is necessary for flex form processing to happen on flexform fields in page records when they are copied.
2238  // Problem: when copying a page, flexform XML comes along in the array for the new record - but since $this->checkValue_currentRecord
2239  // does not have a uid or pid for that sake, the FlexFormTools->getDataStructureIdentifier() function returns no good DS. For new
2240  // records we do know the expected PID so therefore we send that with this special parameter. Only active when larger than zero.
2242  if ($status === 'new') {
2243  $row['pid'] = $realPid;
2244  }
2245 
2246  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
2247 
2248  // Get data structure. The methods may throw various exceptions, with some of them being
2249  // ok in certain scenarios, for instance on new record rows. Those are ok to "eat" here
2250  // and substitute with a dummy DS.
2251  $dataStructureArray = ['sheets' => ['sDEF' => []]];
2252  try {
2253  $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
2254  ['config' => $tcaFieldConf],
2255  $table,
2256  $field,
2257  $row
2258  );
2259 
2260  $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
2261  } catch (InvalidParentRowException|InvalidParentRowLoopException|InvalidParentRowRootException|InvalidPointerFieldValueException|InvalidIdentifierException $e) {
2262  }
2263 
2264  // Get current value array:
2265  $currentValueArray = (string)$curValue !== '' ? ‪GeneralUtility::xml2array($curValue) : [];
2266  if (!is_array($currentValueArray)) {
2267  $currentValueArray = [];
2268  }
2269  // Remove all old meta for languages...
2270  // Evaluation of input values:
2271  $value['data'] = $this->‪checkValue_flex_procInData($value['data'] ?? [], $currentValueArray['data'] ?? [], $uploadedFiles['data'] ?? [], $dataStructureArray, [$table, $id, $curValue, $status, $realPid, $recFID, $tscPID]);
2272  // Create XML from input value:
2273  $xmlValue = $this->‪checkValue_flexArray2Xml($value, true);
2274 
2275  // 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
2276  // (provided that the current value was already stored IN the charset that the new value is converted to).
2277  $arrValue = ‪GeneralUtility::xml2array($xmlValue);
2278 
2279  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['checkFlexFormValue'] ?? [] as $className) {
2280  $hookObject = GeneralUtility::makeInstance($className);
2281  if (method_exists($hookObject, 'checkFlexFormValue_beforeMerge')) {
2282  $hookObject->checkFlexFormValue_beforeMerge($this, $currentValueArray, $arrValue);
2283  }
2284  }
2285 
2286  ‪ArrayUtility::mergeRecursiveWithOverrule($currentValueArray, $arrValue);
2287  $xmlValue = $this->‪checkValue_flexArray2Xml($currentValueArray, true);
2288 
2289  // Action commands (sorting order and removals of elements) for flexform sections,
2290  // see FormEngine for the use of this GP parameter
2291  $actionCMDs = GeneralUtility::_GP('_ACTION_FLEX_FORMdata');
2292  $relevantId = $id;
2293  if ($status === 'update'
2295  && (int)($row['t3ver_wsid'] ?? 0) > 0
2296  && (int)($row['t3ver_oid'] ?? 0) > 0
2297  && !is_array($actionCMDs[$table][$id][$field] ?? false)
2298  && is_array($actionCMDs[$table][(int)$row['t3ver_oid']][$field] ?? false)
2299  ) {
2300  // Scenario: A record with multiple container sections exists in live. The record has no workspace overlay, yet.
2301  // It is then edited in workspaces and sections are resorted or deleted, which should create the version overlay
2302  // plus the resorting or deleting of sections in the version overlay record.
2303  // FormEngine creates this '_ACTION_FLEX_FORMdata' data array with the uid of the live record, since FormEngine
2304  // does not know the uid of the overlay record, yet.
2305  // DataHandler first creates the new overlay record via copyRecord_raw(), which calls this method. At this point,
2306  // we leave the new version record untouched, sorting and deletions of flex sections are not applied.
2307  // DataHandler then calls this method a second time to apply modifications to the just created overlay record. The
2308  // incoming $row is now the version row, and $row['uid'] und incoming $id are the versione'd record uid.
2309  // The '_ACTION_FLEX_FORMdata' POST data however is still the uid of the live record!
2310  // Actions are then not applied since the uid lookups don't match.
2311  // To solve this situation we check for this scenario in the above if conditions and use the live version
2312  // uid (t3ver_oid) to access data from the '_ACTION_FLEX_FORMdata' array.
2313  $relevantId = (int)$row['t3ver_oid'];
2314  }
2315  if (is_array($actionCMDs[$table][$relevantId][$field]['data'] ?? false)) {
2316  $arrValue = ‪GeneralUtility::xml2array($xmlValue);
2317  $this->‪_ACTION_FLEX_FORMdata($arrValue['data'], $actionCMDs[$table][$relevantId][$field]['data']);
2318  $xmlValue = $this->‪checkValue_flexArray2Xml($arrValue, true);
2319  }
2320  // Create the value XML:
2321  $res['value'] = '';
2322  $res['value'] .= $xmlValue;
2323  } else {
2324  // Passthrough...:
2325  $res['value'] = $value;
2326  }
2327 
2328  return $res;
2329  }
2330 
2339  public function ‪checkValue_flexArray2Xml($array, $addPrologue = false)
2340  {
2342  $flexObj = GeneralUtility::makeInstance(FlexFormTools::class);
2343  return $flexObj->flexArray2Xml($array, $addPrologue);
2344  }
2345 
2353  protected function ‪_ACTION_FLEX_FORMdata(&$valueArray, $actionCMDs)
2354  {
2355  if (!is_array($valueArray) || !is_array($actionCMDs)) {
2356  return;
2357  }
2358 
2359  foreach ($actionCMDs as $key => $value) {
2360  if ($key === '_ACTION') {
2361  // First, check if there are "commands":
2362  if (empty(array_filter($actionCMDs[$key]))) {
2363  continue;
2364  }
2365 
2366  asort($actionCMDs[$key]);
2367  $newValueArray = [];
2368  foreach ($actionCMDs[$key] as $idx => $order) {
2369  // Just one reflection here: It is clear that when removing elements from a flexform, then we will get lost
2370  // files unless we act on this delete operation by traversing and deleting files that were referred to.
2371  if ($order !== 'DELETE') {
2372  $newValueArray[$idx] = $valueArray[$idx];
2373  }
2374  unset($valueArray[$idx]);
2375  }
2376  $valueArray += $newValueArray;
2377  } elseif (is_array($actionCMDs[$key]) && isset($valueArray[$key])) {
2378  $this->‪_ACTION_FLEX_FORMdata($valueArray[$key], $actionCMDs[$key]);
2379  }
2380  }
2381  }
2382 
2395  public function ‪checkValue_inline($res, $value, $tcaFieldConf, $PP, $field, array $additionalData = null)
2396  {
2397  [$table, $id, , $status] = $PP;
2398  $this->‪checkValueForInline($res, $value, $tcaFieldConf, $table, $id, $status, $field, $additionalData);
2399  }
2400 
2416  public function ‪checkValueForInline($res, $value, $tcaFieldConf, $table, $id, $status, $field, array $additionalData = null)
2417  {
2418  if (!$tcaFieldConf['foreign_table']) {
2419  // Fatal error, inline fields should always have a foreign_table defined
2420  return false;
2421  }
2422  // When values are sent they come as comma-separated values which are exploded by this function:
2423  $valueArray = ‪GeneralUtility::trimExplode(',', $value);
2424  // Remove duplicates: (should not be needed)
2425  $valueArray = array_unique($valueArray);
2426  // Example for received data:
2427  // $value = 45,NEW4555fdf59d154,12,123
2428  // We need to decide whether we use the stack or can save the relation directly.
2429  if (!empty($value) && (strpos($value, 'NEW') !== false || !‪MathUtility::canBeInterpretedAsInteger($id))) {
2430  $this->remapStackRecords[$table][$id] = ['remapStackIndex' => count($this->remapStack)];
2431  $this->‪addNewValuesToRemapStackChildIds($valueArray);
2432  $this->remapStack[] = [
2433  'func' => 'checkValue_inline_processDBdata',
2434  'args' => [$valueArray, $tcaFieldConf, $id, $status, $table, $field, $additionalData],
2435  'pos' => ['valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 4],
2436  'additionalData' => $additionalData,
2437  'field' => $field,
2438  ];
2439  unset($res['value']);
2440  } elseif ($value || ‪MathUtility::canBeInterpretedAsInteger($id)) {
2441  $res['value'] = $this->‪checkValue_inline_processDBdata($valueArray, $tcaFieldConf, $id, $status, $table, $field, $additionalData);
2442  }
2443  return $res;
2444  }
2445 
2455  public function ‪checkValue_checkMax($tcaFieldConf, $valueArray)
2456  {
2457  // BTW, checking for min and max items here does NOT make any sense when MM is used because the above function
2458  // calls will just return an array with a single item (the count) if MM is used... Why didn't I perform the check
2459  // before? Probably because we could not evaluate the validity of record uids etc... Hmm...
2460  // NOTE to the comment: It's not really possible to check for too few items, because you must then determine first,
2461  // if the field is actual used regarding the CType.
2462  $maxitems = isset($tcaFieldConf['maxitems']) ? (int)$tcaFieldConf['maxitems'] : 99999;
2463  return array_slice($valueArray, 0, $maxitems);
2464  }
2465 
2466  /*********************************************
2467  *
2468  * Helper functions for evaluation functions.
2469  *
2470  ********************************************/
2483  public function ‪getUnique($table, $field, $value, $id, $newPid = 0)
2484  {
2485  if (!is_array(‪$GLOBALS['TCA'][$table]) || !is_array(‪$GLOBALS['TCA'][$table]['columns'][$field])) {
2486  // Field is not configured in TCA
2487  return $value;
2488  }
2489 
2490  if ((string)‪$GLOBALS['TCA'][$table]['columns'][$field]['l10n_mode'] === 'exclude') {
2491  $transOrigPointerField = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
2492  $l10nParent = (int)$this->checkValue_currentRecord[$transOrigPointerField];
2493  if ($l10nParent > 0) {
2494  // Current record is a translation and l10n_mode "exclude" just copies the value from source language
2495  return $value;
2496  }
2497  }
2498 
2499  $newValue = $originalValue = $value;
2500  $queryBuilder = $this->‪getUniqueCountStatement($newValue, $table, $field, (int)$id, (int)$newPid);
2501  // For as long as records with the test-value existing, try again (with incremented numbers appended)
2502  $statement = $queryBuilder->execute();
2503  if ($statement->fetchColumn()) {
2504  for ($counter = 0; $counter <= 100; $counter++) {
2505  $newValue = $value . $counter;
2506  if (class_exists(\Doctrine\DBAL\ForwardCompatibility\Result::class) && $statement instanceof \Doctrine\DBAL\ForwardCompatibility\Result) {
2507  $statement = $statement->getIterator();
2508  }
2509  $statement->bindValue(1, $newValue);
2510  $statement->execute();
2511  if (!$statement->fetchColumn()) {
2512  break;
2513  }
2514  }
2515  }
2516 
2517  if ($originalValue !== $newValue) {
2518  $this->‪log($table, $id, SystemLogDatabaseAction::CHECK, 0, SystemLogErrorClassification::WARNING, 'The value of the field "%s" has been changed from "%s" to "%s" as it is required to be unique.', 1, [$field, $originalValue, $newValue], $newPid);
2519  }
2520 
2521  return $newValue;
2522  }
2523 
2534  protected function ‪getUniqueCountStatement(
2535  string $value,
2536  string $table,
2537  string $field,
2538  int $uid,
2539  int $pid
2540  ) {
2541  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
2542  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
2543  $queryBuilder
2544  ->count('uid')
2545  ->from($table)
2546  ->where(
2547  $queryBuilder->expr()->eq($field, $queryBuilder->createPositionalParameter($value)),
2548  $queryBuilder->expr()->neq('uid', $queryBuilder->createPositionalParameter($uid, \PDO::PARAM_INT))
2549  );
2550  // ignore translations of current record if field is configured with l10n_mode = "exclude"
2551  if ((‪$GLOBALS['TCA'][$table]['columns'][$field]['l10n_mode'] ?? '') === 'exclude'
2552  && (‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? '') !== ''
2553  && (‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? '') !== '') {
2554  $queryBuilder
2555  ->andWhere(
2556  $queryBuilder->expr()->orX(
2557  // records without l10n_parent must be taken into account (in any language)
2558  $queryBuilder->expr()->eq(
2559  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
2560  $queryBuilder->createPositionalParameter(0, \PDO::PARAM_INT)
2561  ),
2562  // translations of other records must be taken into account
2563  $queryBuilder->expr()->neq(
2564  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
2565  $queryBuilder->createPositionalParameter($uid, \PDO::PARAM_INT)
2566  )
2567  )
2568  );
2569  }
2570  if ($pid !== 0) {
2571  $queryBuilder->andWhere(
2572  $queryBuilder->expr()->eq('pid', $queryBuilder->createPositionalParameter($pid, \PDO::PARAM_INT))
2573  );
2574  } else {
2575  // pid>=0 for versioning
2576  $queryBuilder->andWhere(
2577  $queryBuilder->expr()->gte('pid', $queryBuilder->createPositionalParameter(0, \PDO::PARAM_INT))
2578  );
2579  }
2580  return $queryBuilder;
2581  }
2582 
2595  public function ‪getRecordsWithSameValue($tableName, $uid, $fieldName, $value, $pageId = 0)
2596  {
2597  $result = [];
2598  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
2599  return $result;
2600  }
2601 
2602  $uid = (int)$uid;
2603  $pageId = (int)$pageId;
2604 
2605  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
2606  $queryBuilder->getRestrictions()
2607  ->removeAll()
2608  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2609  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->BE_USER->workspace));
2610 
2611  $queryBuilder->select('*')
2612  ->from($tableName)
2613  ->where(
2614  $queryBuilder->expr()->eq(
2615  $fieldName,
2616  $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR)
2617  ),
2618  $queryBuilder->expr()->neq(
2619  'uid',
2620  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
2621  )
2622  );
2623 
2624  if ($pageId) {
2625  $queryBuilder->andWhere(
2626  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT))
2627  );
2628  }
2629 
2630  $result = $queryBuilder->execute()->fetchAll();
2631 
2632  return $result;
2633  }
2634 
2642  public function ‪checkValue_text_Eval($value, $evalArray, $is_in)
2643  {
2644  $res = [];
2645  $set = true;
2646  foreach ($evalArray as $func) {
2647  switch ($func) {
2648  case 'trim':
2649  $value = trim($value);
2650  break;
2651  case 'required':
2652  if (!$value) {
2653  $set = false;
2654  }
2655  break;
2656  default:
2657  if (isset(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
2658  if (class_exists($func)) {
2659  $evalObj = GeneralUtility::makeInstance($func);
2660  if (method_exists($evalObj, 'evaluateFieldValue')) {
2661  $value = $evalObj->evaluateFieldValue($value, $is_in, $set);
2662  }
2663  }
2664  }
2665  }
2666  }
2667  if ($set) {
2668  $res['value'] = $value;
2669  }
2670  return $res;
2671  }
2672 
2684  public function ‪checkValue_input_Eval($value, $evalArray, $is_in, string $table = '', $id = ''): array
2685  {
2686  $res = [];
2687  $set = true;
2688  foreach ($evalArray as $func) {
2689  switch ($func) {
2690  case 'int':
2691  case 'year':
2692  $value = (int)$value;
2693  break;
2694  case 'time':
2695  case 'timesec':
2696  // If $value is a pure integer we have the number of seconds, we can store that directly
2697  if ($value !== '' && !‪MathUtility::canBeInterpretedAsInteger($value)) {
2698  // $value is an ISO 8601 date
2699  $value = (new \DateTime($value))->getTimestamp();
2700  }
2701  break;
2702  case 'date':
2703  case 'datetime':
2704  // If $value is a pure integer we have the number of seconds, we can store that directly
2705  if ($value !== null && $value !== '' && !‪MathUtility::canBeInterpretedAsInteger($value)) {
2706  // The value we receive from JS is an ISO 8601 date, which is always in UTC. (the JS code works like that, on purpose!)
2707  // For instance "1999-11-11T11:11:11Z"
2708  // Since the user actually specifies the time in the server's local time, we need to mangle this
2709  // to reflect the server TZ. So we make this 1999-11-11T11:11:11+0200 (assuming Europe/Vienna here)
2710  // In the database we store the date in UTC (1999-11-11T09:11:11Z), hence we take the timestamp of this converted value.
2711  // For achieving this we work with timestamps only (which are UTC) and simply adjust it for the
2712  // TZ difference.
2713  try {
2714  // Make the date from JS a timestamp
2715  $value = (new \DateTime($value))->getTimestamp();
2716  } catch (\Exception $e) {
2717  // set the default timezone value to achieve the value of 0 as a result
2718  $value = (int)date('Z', 0);
2719  }
2720 
2721  // @todo this hacky part is problematic when it comes to times around DST switch! Add test to prove that this is broken.
2722  $value -= date('Z', $value);
2723  }
2724  break;
2725  case 'double2':
2726  $value = preg_replace('/[^0-9,\\.-]/', '', $value);
2727  $negative = $value[0] === '-';
2728  $value = strtr($value, [',' => '.', '-' => '']);
2729  if (strpos($value, '.') === false) {
2730  $value .= '.0';
2731  }
2732  $valueArray = explode('.', $value);
2733  $dec = array_pop($valueArray);
2734  $value = implode('', $valueArray) . '.' . $dec;
2735  if ($negative) {
2736  $value *= -1;
2737  }
2738  $value = number_format($value, 2, '.', '');
2739  break;
2740  case 'md5':
2741  if (strlen($value) !== 32) {
2742  $set = false;
2743  }
2744  break;
2745  case 'trim':
2746  $value = trim($value);
2747  break;
2748  case 'upper':
2749  $value = mb_strtoupper($value, 'utf-8');
2750  break;
2751  case 'lower':
2752  $value = mb_strtolower($value, 'utf-8');
2753  break;
2754  case 'required':
2755  if (!isset($value) || $value === '') {
2756  $set = false;
2757  }
2758  break;
2759  case 'is_in':
2760  $c = mb_strlen($value);
2761  if ($c) {
2762  $newVal = '';
2763  for ($a = 0; $a < $c; $a++) {
2764  $char = mb_substr($value, $a, 1);
2765  if (mb_strpos($is_in, $char) !== false) {
2766  $newVal .= $char;
2767  }
2768  }
2769  $value = $newVal;
2770  }
2771  break;
2772  case 'nospace':
2773  $value = str_replace(' ', '', $value);
2774  break;
2775  case 'alpha':
2776  $value = preg_replace('/[^a-zA-Z]/', '', $value);
2777  break;
2778  case 'num':
2779  $value = preg_replace('/[^0-9]/', '', $value);
2780  break;
2781  case 'alphanum':
2782  $value = preg_replace('/[^a-zA-Z0-9]/', '', $value);
2783  break;
2784  case 'alphanum_x':
2785  $value = preg_replace('/[^a-zA-Z0-9_-]/', '', $value);
2786  break;
2787  case 'domainname':
2788  if (!preg_match('/^[a-z0-9.\\-]*$/i', $value)) {
2789  $value = (string)‪HttpUtility::idn_to_ascii($value);
2790  }
2791  break;
2792  case 'email':
2793  if ((string)$value !== '') {
2794  $this->‪checkValue_input_ValidateEmail($value, $set, $table, $id);
2795  }
2796  break;
2797  case 'saltedPassword':
2798  // An incoming value is either the salted password if the user did not change existing password
2799  // when submitting the form, or a plaintext new password that needs to be turned into a salted password now.
2800  // The strategy is to see if a salt instance can be created from the incoming value. If so,
2801  // no new password was submitted and we keep the value. If no salting instance can be created,
2802  // incoming value must be a new plain text value that needs to be hashed.
2803  $hashFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
2804  $mode = $table === 'fe_users' ? 'FE' : 'BE';
2805  try {
2806  $hashFactory->get($value, $mode);
2807  } catch (‪InvalidPasswordHashException $e) {
2808  // We got no salted password instance, incoming value must be a new plaintext password
2809  // Get an instance of the current configured salted password strategy and hash the value
2810  $newHashInstance = $hashFactory->getDefaultHashInstance($mode);
2811  $value = $newHashInstance->getHashedPassword($value);
2812  }
2813  break;
2814  default:
2815  if (isset(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
2816  if (class_exists($func)) {
2817  $evalObj = GeneralUtility::makeInstance($func);
2818  if (method_exists($evalObj, 'evaluateFieldValue')) {
2819  $value = $evalObj->evaluateFieldValue($value, $is_in, $set);
2820  }
2821  }
2822  }
2823  }
2824  }
2825  if ($set) {
2826  $res['value'] = $value;
2827  }
2828  return $res;
2829  }
2830 
2841  protected function ‪checkValue_input_ValidateEmail($value, &$set, string $table, $id)
2842  {
2843  if (GeneralUtility::validEmail($value)) {
2844  return;
2845  }
2846 
2847  $set = false;
2848  $this->‪log(
2849  $table,
2850  $id,
2851  SystemLogDatabaseAction::UPDATE,
2852  0,
2853  SystemLogErrorClassification::SECURITY_NOTICE,
2854  '"%s" is not a valid e-mail address.',
2855  -1,
2856  [$value, $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.invalidEmail')]
2857  );
2858  }
2859 
2873  public function ‪checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, $type, $currentTable, $currentField)
2874  {
2875  if ($type === 'group') {
2876  $tables = $tcaFieldConf['allowed'];
2877  } elseif (!empty($tcaFieldConf['special']) && $tcaFieldConf['special'] === 'languages') {
2878  $tables = 'sys_language';
2879  } else {
2880  $tables = $tcaFieldConf['foreign_table'];
2881  }
2882  $prep = $type === 'group' ? $tcaFieldConf['prepend_tname'] : '';
2883  $newRelations = implode(',', $valueArray);
2885  $dbAnalysis = $this->‪createRelationHandlerInstance();
2886  $dbAnalysis->registerNonTableValues = !empty($tcaFieldConf['allowNonIdValues']);
2887  $dbAnalysis->start($newRelations, $tables, '', 0, $currentTable, $tcaFieldConf);
2888  if ($tcaFieldConf['MM']) {
2889  // convert submitted items to use version ids instead of live ids
2890  // (only required for MM relations in a workspace context)
2891  $dbAnalysis->convertItemArray();
2892  if ($status === 'update') {
2894  $oldRelations_dbAnalysis = $this->‪createRelationHandlerInstance();
2895  $oldRelations_dbAnalysis->registerNonTableValues = !empty($tcaFieldConf['allowNonIdValues']);
2896  // Db analysis with $id will initialize with the existing relations
2897  $oldRelations_dbAnalysis->start('', $tables, $tcaFieldConf['MM'], $id, $currentTable, $tcaFieldConf);
2898  $oldRelations = implode(',', $oldRelations_dbAnalysis->getValueArray());
2899  $dbAnalysis->writeMM($tcaFieldConf['MM'], $id, $prep);
2900  if ($oldRelations != $newRelations) {
2901  $this->mmHistoryRecords[$currentTable . ':' . $id]['oldRecord'][$currentField] = $oldRelations;
2902  $this->mmHistoryRecords[$currentTable . ':' . $id]['newRecord'][$currentField] = $newRelations;
2903  } else {
2904  $this->mmHistoryRecords[$currentTable . ':' . $id]['oldRecord'][$currentField] = '';
2905  $this->mmHistoryRecords[$currentTable . ':' . $id]['newRecord'][$currentField] = '';
2906  }
2907  } else {
2908  $this->dbAnalysisStore[] = [$dbAnalysis, $tcaFieldConf['MM'], $id, $prep, $currentTable];
2909  }
2910  $valueArray = $dbAnalysis->countItems();
2911  } else {
2912  $valueArray = $dbAnalysis->getValueArray($prep);
2913  }
2914  // 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.
2915  return $valueArray;
2916  }
2917 
2926  {
2927  $valueArray = ‪GeneralUtility::trimExplode(',', $value, true);
2928  foreach ($valueArray as &$newVal) {
2929  $temp = explode('|', $newVal, 2);
2930  $newVal = str_replace(['|', ','], '', rawurldecode($temp[0]));
2931  }
2932  unset($newVal);
2933  return $valueArray;
2934  }
2935 
2952  public function ‪checkValue_flex_procInData($dataPart, $dataPart_current, $uploadedFiles, $dataStructure, $pParams, $callBackFunc = '', array $workspaceOptions = [])
2953  {
2954  if (is_array($dataPart)) {
2955  foreach ($dataPart as $sKey => $sheetDef) {
2956  if (isset($dataStructure['sheets'][$sKey]) && is_array($dataStructure['sheets'][$sKey]) && is_array($sheetDef)) {
2957  foreach ($sheetDef as $lKey => $lData) {
2959  $dataPart[$sKey][$lKey],
2960  $dataPart_current[$sKey][$lKey],
2961  $uploadedFiles[$sKey][$lKey],
2962  $dataStructure['sheets'][$sKey]['ROOT']['el'],
2963  $pParams,
2964  $callBackFunc,
2965  $sKey . '/' . $lKey . '/',
2966  $workspaceOptions
2967  );
2968  }
2969  }
2970  }
2971  }
2972  return $dataPart;
2973  }
2974 
2990  public function ‪checkValue_flex_procInData_travDS(&$dataValues, $dataValues_current, $uploadedFiles, $DSelements, $pParams, $callBackFunc, $structurePath, array $workspaceOptions = [])
2991  {
2992  if (!is_array($DSelements)) {
2993  return;
2994  }
2995 
2996  // For each DS element:
2997  foreach ($DSelements as $key => $dsConf) {
2998  // Array/Section:
2999  if ($DSelements[$key]['type'] === 'array') {
3000  if (!is_array($dataValues[$key]['el'])) {
3001  continue;
3002  }
3003 
3004  if ($DSelements[$key]['section']) {
3005  foreach ($dataValues[$key]['el'] as $ik => $el) {
3006  if (!is_array($el)) {
3007  continue;
3008  }
3009 
3010  if (!is_array($dataValues_current[$key]['el'])) {
3011  $dataValues_current[$key]['el'] = [];
3012  }
3013  $theKey = key($el);
3014  if (!is_array($dataValues[$key]['el'][$ik][$theKey]['el'])) {
3015  continue;
3016  }
3017 
3018  $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);
3019  }
3020  } else {
3021  if (!isset($dataValues[$key]['el'])) {
3022  $dataValues[$key]['el'] = [];
3023  }
3024  $this->‪checkValue_flex_procInData_travDS($dataValues[$key]['el'], $dataValues_current[$key]['el'], $uploadedFiles[$key]['el'], $DSelements[$key]['el'], $pParams, $callBackFunc, $structurePath . $key . '/el/', $workspaceOptions);
3025  }
3026  } else {
3027  // When having no specific sheets, it's "TCEforms.config", when having a sheet, it's just "config"
3028  $fieldConfiguration = $dsConf['TCEforms']['config'] ?? $dsConf['config'] ?? null;
3029  // init with value from config for passthrough fields
3030  if (!empty($fieldConfiguration['type']) && $fieldConfiguration['type'] === 'passthrough') {
3031  if (!empty($dataValues_current[$key]['vDEF'])) {
3032  // If there is existing value, keep it
3033  $dataValues[$key]['vDEF'] = $dataValues_current[$key]['vDEF'];
3034  } elseif (
3035  !empty($fieldConfiguration['default'])
3036  && isset($pParams[1])
3038  ) {
3039  // If is new record and a default is specified for field, use it.
3040  $dataValues[$key]['vDEF'] = $fieldConfiguration['default'];
3041  }
3042  }
3043  if (!is_array($fieldConfiguration) || !is_array($dataValues[$key])) {
3044  continue;
3045  }
3046 
3047  foreach ($dataValues[$key] as $vKey => $data) {
3048  if ($callBackFunc) {
3049  if (is_object($this->callBackObj)) {
3050  $res = $this->callBackObj->{$callBackFunc}($pParams, $fieldConfiguration, $dataValues[$key][$vKey], $dataValues_current[$key][$vKey], $uploadedFiles[$key][$vKey], $structurePath . $key . '/' . $vKey . '/', $workspaceOptions);
3051  } else {
3052  $res = $this->{$callBackFunc}($pParams, $fieldConfiguration, $dataValues[$key][$vKey], $dataValues_current[$key][$vKey], $uploadedFiles[$key][$vKey], $structurePath . $key . '/' . $vKey . '/', $workspaceOptions);
3053  }
3054  } else {
3055  // Default
3056  [$CVtable, $CVid, $CVcurValue, $CVstatus, $CVrealPid, $CVrecFID, $CVtscPID] = $pParams;
3057 
3058  $additionalData = [
3059  'flexFormId' => $CVrecFID,
3060  'flexFormPath' => trim(rtrim($structurePath, '/') . '/' . $key . '/' . $vKey, '/'),
3061  ];
3062 
3063  $res = $this->‪checkValue_SW([], $dataValues[$key][$vKey], $fieldConfiguration, $CVtable, $CVid, $dataValues_current[$key][$vKey], $CVstatus, $CVrealPid, $CVrecFID, '', $uploadedFiles[$key][$vKey], $CVtscPID, $additionalData);
3064  }
3065  // Adding the value:
3066  if (isset($res['value'])) {
3067  $dataValues[$key][$vKey] = $res['value'];
3068  }
3069  // 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.
3070  // 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).
3071  if (mb_substr($vKey, -9) !== '.vDEFbase') {
3072  if (‪$GLOBALS['TYPO3_CONF_VARS']['BE']['flexFormXMLincludeDiffBase'] && $vKey !== 'vDEF' && ((string)$dataValues[$key][$vKey] !== (string)$dataValues_current[$key][$vKey] || !isset($dataValues_current[$key][$vKey . '.vDEFbase']))) {
3073  // 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:
3074  if (isset($dataValues[$key]['vDEF'])) {
3075  $diffValue = $dataValues[$key]['vDEF'];
3076  } else {
3077  // If not found (for translators with no access to the default language) we use the one from the current-value data set:
3078  $diffValue = $dataValues_current[$key]['vDEF'];
3079  }
3080  // 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.
3081  $dataValues[$key][$vKey . '.vDEFbase'] = $diffValue;
3082  }
3083  }
3084  }
3085  }
3086  }
3087  }
3088 
3101  protected function ‪checkValue_inline_processDBdata($valueArray, $tcaFieldConf, $id, $status, $table, $field, array $additionalData = null)
3102  {
3103  $foreignTable = $tcaFieldConf['foreign_table'];
3104  $valueArray = $this->‪applyFiltersToValues($tcaFieldConf, $valueArray);
3105  // Fetch the related child records using \TYPO3\CMS\Core\Database\RelationHandler
3107  $dbAnalysis = $this->‪createRelationHandlerInstance();
3108  $dbAnalysis->start(implode(',', $valueArray), $foreignTable, '', 0, $table, $tcaFieldConf);
3109  // IRRE with a pointer field (database normalization):
3110  if ($tcaFieldConf['foreign_field']) {
3111  // if the record was imported, sorting was also imported, so skip this
3112  $skipSorting = (bool)$this->callFromImpExp;
3113  // update record in intermediate table (sorting & pointer uid to parent record)
3114  $dbAnalysis->writeForeignField($tcaFieldConf, $id, 0, $skipSorting);
3115  $newValue = $dbAnalysis->countItems(false);
3116  } elseif ($this->‪getInlineFieldType($tcaFieldConf) === 'mm') {
3117  // In order to fully support all the MM stuff, directly call checkValue_group_select_processDBdata instead of repeating the needed code here
3118  $valueArray = $this->‪checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, 'select', $table, $field);
3119  $newValue = $valueArray[0];
3120  } else {
3121  $valueArray = $dbAnalysis->getValueArray();
3122  // Checking that the number of items is correct:
3123  $valueArray = $this->‪checkValue_checkMax($tcaFieldConf, $valueArray);
3124  $newValue = $this->‪castReferenceValue(implode(',', $valueArray), $tcaFieldConf);
3125  }
3126  return $newValue;
3127  }
3128 
3129  /*********************************************
3130  *
3131  * PROCESSING COMMANDS
3132  *
3133  ********************************************/
3140  public function ‪process_cmdmap()
3141  {
3142  // Editing frozen:
3143  if ($this->BE_USER->workspace !== 0 && $this->BE_USER->workspaceRec['freeze']) {
3144  $this->‪newlog('All editing in this workspace has been frozen!', SystemLogErrorClassification::USER_ERROR);
3145  return false;
3146  }
3147  // Hook initialization:
3148  $hookObjectsArr = [];
3149  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'] ?? [] as $className) {
3150  $hookObj = GeneralUtility::makeInstance($className);
3151  if (method_exists($hookObj, 'processCmdmap_beforeStart')) {
3152  $hookObj->processCmdmap_beforeStart($this);
3153  }
3154  $hookObjectsArr[] = $hookObj;
3155  }
3156  $pasteDatamap = [];
3157  // Traverse command map:
3158  foreach ($this->cmdmap as $table => $_) {
3159  // Check if the table may be modified!
3160  $modifyAccessList = $this->‪checkModifyAccessList($table);
3161  if (!$modifyAccessList) {
3162  $this->‪log($table, 0, SystemLogDatabaseAction::UPDATE, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to modify table \'%s\' without permission', 1, [$table]);
3163  }
3164  // Check basic permissions and circumstances:
3165  if (!isset(‪$GLOBALS['TCA'][$table]) || $this->‪tableReadOnly($table) || !is_array($this->cmdmap[$table]) || !$modifyAccessList) {
3166  continue;
3167  }
3168 
3169  // Traverse the command map:
3170  foreach ($this->cmdmap[$table] as $id => $incomingCmdArray) {
3171  if (!is_array($incomingCmdArray)) {
3172  continue;
3173  }
3174 
3175  if ($table === 'pages') {
3176  // for commands on pages do a pagetree-refresh
3177  $this->pagetreeNeedsRefresh = true;
3178  }
3179 
3180  foreach ($incomingCmdArray as $command => $value) {
3181  $pasteUpdate = false;
3182  if (is_array($value) && isset($value['action']) && $value['action'] === 'paste') {
3183  // Extended paste command: $command is set to "move" or "copy"
3184  // $value['update'] holds field/value pairs which should be updated after copy/move operation
3185  // $value['target'] holds original $value (target of move/copy)
3186  $pasteUpdate = $value['update'];
3187  $value = $value['target'];
3188  }
3189  foreach ($hookObjectsArr as $hookObj) {
3190  if (method_exists($hookObj, 'processCmdmap_preProcess')) {
3191  $hookObj->processCmdmap_preProcess($command, $table, $id, $value, $this, $pasteUpdate);
3192  }
3193  }
3194  // Init copyMapping array:
3195  // Must clear this array before call from here to those functions:
3196  // Contains mapping information between new and old id numbers.
3197  $this->copyMappingArray = [];
3198  // process the command
3199  $commandIsProcessed = false;
3200  foreach ($hookObjectsArr as $hookObj) {
3201  if (method_exists($hookObj, 'processCmdmap')) {
3202  $hookObj->processCmdmap($command, $table, $id, $value, $commandIsProcessed, $this, $pasteUpdate);
3203  }
3204  }
3205  // Only execute default commands if a hook hasn't been processed the command already
3206  if (!$commandIsProcessed) {
3207  $procId = $id;
3208  $backupUseTransOrigPointerField = ‪$this->useTransOrigPointerField;
3209  // Branch, based on command
3210  switch ($command) {
3211  case 'move':
3212  $this->‪moveRecord($table, $id, $value);
3213  break;
3214  case 'copy':
3215  $target = $value['target'] ?? $value;
3216  $ignoreLocalization = (bool)($value['ignoreLocalization'] ?? false);
3217  if ($table === 'pages') {
3218  $this->‪copyPages($id, $target);
3219  } else {
3220  $this->‪copyRecord($table, $id, $target, true, [], '', 0, $ignoreLocalization);
3221  }
3222  $procId = $this->copyMappingArray[$table][$id];
3223  break;
3224  case 'localize':
3225  $this->useTransOrigPointerField = true;
3226  $this->‪localize($table, $id, $value);
3227  break;
3228  case 'copyToLanguage':
3229  $this->useTransOrigPointerField = false;
3230  $this->‪localize($table, $id, $value);
3231  break;
3232  case 'inlineLocalizeSynchronize':
3233  $this->‪inlineLocalizeSynchronize($table, $id, $value);
3234  break;
3235  case 'delete':
3236  $this->‪deleteAction($table, $id);
3237  break;
3238  case 'undelete':
3239  $this->‪undeleteRecord($table, $id);
3240  break;
3241  }
3242  $this->useTransOrigPointerField = $backupUseTransOrigPointerField;
3243  if (is_array($pasteUpdate)) {
3244  $pasteDatamap[$table][$procId] = $pasteUpdate;
3245  }
3246  }
3247  foreach ($hookObjectsArr as $hookObj) {
3248  if (method_exists($hookObj, 'processCmdmap_postProcess')) {
3249  $hookObj->processCmdmap_postProcess($command, $table, $id, $value, $this, $pasteUpdate, $pasteDatamap);
3250  }
3251  }
3252  // Merging the copy-array info together for remapping purposes.
3253  ‪ArrayUtility::mergeRecursiveWithOverrule($this->copyMappingArray_merged, $this->copyMappingArray);
3254  }
3255  }
3256  }
3258  $copyTCE = $this->‪getLocalTCE();
3259  $copyTCE->start($pasteDatamap, [], $this->BE_USER);
3260  $copyTCE->process_datamap();
3261  $this->errorLog = array_merge($this->errorLog, $copyTCE->errorLog);
3262  unset($copyTCE);
3263 
3264  // Finally, before exit, check if there are ID references to remap.
3265  // This might be the case if versioning or copying has taken place!
3266  $this->‪remapListedDBRecords();
3267  $this->‪processRemapStack();
3268  foreach ($hookObjectsArr as $hookObj) {
3269  if (method_exists($hookObj, 'processCmdmap_afterFinish')) {
3270  $hookObj->processCmdmap_afterFinish($this);
3271  }
3272  }
3273  if ($this->‪isOuterMostInstance()) {
3274  $this->referenceIndexUpdater->update();
3275  $this->‪processClearCacheQueue();
3276  $this->‪resetNestedElementCalls();
3277  }
3278  }
3279 
3280  /*********************************************
3281  *
3282  * Cmd: Copying
3283  *
3284  ********************************************/
3299  public function ‪copyRecord($table, $uid, $destPid, $first = false, ‪$overrideValues = [], $excludeFields = '', $language = 0, $ignoreLocalization = false)
3300  {
3301  $uid = ($origUid = (int)$uid);
3302  // Only copy if the table is defined in $GLOBALS['TCA'], a uid is given and the record wasn't copied before:
3303  if (empty(‪$GLOBALS['TCA'][$table]) || $uid === 0) {
3304  return null;
3305  }
3306  if ($this->‪isRecordCopied($table, $uid)) {
3307  return null;
3308  }
3309 
3310  // Fetch record with permission check
3311  $row = $this->‪recordInfoWithPermissionCheck($table, $uid, ‪Permission::PAGE_SHOW);
3312 
3313  // This checks if the record can be selected which is all that a copy action requires.
3314  if ($row === false) {
3315  $this->‪log($table, $uid, SystemLogDatabaseAction::INSERT, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to copy record "%s:%s" which does not exist or you do not have permission to read', -1, [$table, $uid]);
3316  return null;
3317  }
3318 
3319  // 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...
3320  $tscPID = ‪BackendUtility::getTSconfig_pidValue($table, $uid, $destPid);
3321 
3322  // Check if table is allowed on destination page
3323  if (!$this->‪isTableAllowedForThisPage($tscPID, $table)) {
3324  $this->‪log($table, $uid, SystemLogDatabaseAction::INSERT, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to insert record "%s:%s" on a page (%s) that can\'t store record type.', -1, [$table, $uid, $tscPID]);
3325  return null;
3326  }
3327 
3328  $fullLanguageCheckNeeded = $table !== 'pages';
3329  // Used to check language and general editing rights
3330  if (!$ignoreLocalization && ($language <= 0 || !$this->BE_USER->checkLanguageAccess($language)) && !$this->BE_USER->recordEditAccessInternals($table, $uid, false, false, $fullLanguageCheckNeeded)) {
3331  $this->‪log($table, $uid, SystemLogDatabaseAction::INSERT, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to copy record "%s:%s" without having permissions to do so. [' . $this->BE_USER->errorMsg . '].', -1, [$table, $uid]);
3332  return null;
3333  }
3334 
3335  $data = [];
3336  $nonFields = array_unique(‪GeneralUtility::trimExplode(',', 'uid,perms_userid,perms_groupid,perms_user,perms_group,perms_everybody,t3ver_oid,t3ver_wsid,t3ver_state,t3ver_count,t3ver_stage,t3ver_tstamp,' . $excludeFields, true));
3337  ‪BackendUtility::workspaceOL($table, $row, $this->BE_USER->workspace);
3339 
3340  // Initializing:
3341  $theNewID = ‪StringUtility::getUniqueId('NEW');
3342  $enableField = isset(‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']) ? ‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'] : '';
3343  $headerField = ‪$GLOBALS['TCA'][$table]['ctrl']['label'];
3344  // Getting "copy-after" fields if applicable:
3345  $copyAfterFields = $destPid < 0 ? $this->‪fixCopyAfterDuplFields($table, $uid, abs($destPid), 0) : [];
3346  // Page TSconfig related:
3347  $TSConfig = ‪BackendUtility::getPagesTSconfig($tscPID)['TCEMAIN.'] ?? [];
3348  $tE = $this->‪getTableEntries($table, $TSConfig);
3349  // Traverse ALL fields of the selected record:
3350  foreach ($row as $field => $value) {
3351  if (!in_array($field, $nonFields, true)) {
3352  // Get TCA configuration for the field:
3353  $conf = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'];
3354  // Preparation/Processing of the value:
3355  // "pid" is hardcoded of course:
3356  // isset() won't work here, since values can be NULL in each of the arrays
3357  // except setDefaultOnCopyArray, since we exploded that from a string
3358  if ($field === 'pid') {
3359  $value = $destPid;
3360  } elseif (array_key_exists($field, ‪$overrideValues)) {
3361  // Override value...
3362  $value = ‪$overrideValues[$field];
3363  } elseif (array_key_exists($field, $copyAfterFields)) {
3364  // Copy-after value if available:
3365  $value = $copyAfterFields[$field];
3366  } else {
3367  // Hide at copy may override:
3368  if ($first && $field == $enableField && ‪$GLOBALS['TCA'][$table]['ctrl']['hideAtCopy'] && !$this->neverHideAtCopy && !$tE['disableHideAtCopy']) {
3369  $value = 1;
3370  }
3371  // Prepend label on copy:
3372  if ($first && $field == $headerField && ‪$GLOBALS['TCA'][$table]['ctrl']['prependAtCopy'] && !$tE['disablePrependAtCopy']) {
3373  $value = $this->‪getCopyHeader($table, $this->‪resolvePid($table, $destPid), $field, $this->‪clearPrefixFromValue($table, $value), 0);
3374  }
3375  // Processing based on the TCA config field type (files, references, flexforms...)
3376  $value = $this->‪copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $tscPID, $language);
3377  }
3378  // Add value to array.
3379  $data[$table][$theNewID][$field] = $value;
3380  }
3381  }
3382  // Overriding values:
3383  if (‪$GLOBALS['TCA'][$table]['ctrl']['editlock']) {
3384  $data[$table][$theNewID][‪$GLOBALS['TCA'][$table]['ctrl']['editlock']] = 0;
3385  }
3386  // Setting original UID:
3387  if (‪$GLOBALS['TCA'][$table]['ctrl']['origUid']) {
3388  $data[$table][$theNewID][‪$GLOBALS['TCA'][$table]['ctrl']['origUid']] = $uid;
3389  }
3390  // Do the copy by simply submitting the array through DataHandler:
3392  $copyTCE = $this->‪getLocalTCE();
3393  $copyTCE->start($data, [], $this->BE_USER);
3394  $copyTCE->process_datamap();
3395  // Getting the new UID:
3396  $theNewSQLID = $copyTCE->substNEWwithIDs[$theNewID];
3397  if ($theNewSQLID) {
3398  $this->copyMappingArray[$table][$origUid] = $theNewSQLID;
3399  // Keep automatically versionized record information:
3400  if (isset($copyTCE->autoVersionIdMap[$table][$theNewSQLID])) {
3401  $this->autoVersionIdMap[$table][$theNewSQLID] = $copyTCE->autoVersionIdMap[$table][$theNewSQLID];
3402  }
3403  }
3404  $this->errorLog = array_merge($this->errorLog, $copyTCE->errorLog);
3405  unset($copyTCE);
3406  if (!$ignoreLocalization && $language == 0) {
3407  //repointing the new translation records to the parent record we just created
3408  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $theNewSQLID;
3409  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'])) {
3410  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['translationSource']] = 0;
3411  }
3412  $this->‪copyL10nOverlayRecords($table, $uid, $destPid, $first, ‪$overrideValues, $excludeFields);
3413  }
3414 
3415  return $theNewSQLID;
3416  }
3417 
3426  public function ‪copyPages($uid, $destPid)
3427  {
3428  // Initialize:
3429  $uid = (int)$uid;
3430  $destPid = (int)$destPid;
3432  $copyTablesAlongWithPage = $this->‪getAllowedTablesToCopyWhenCopyingAPage();
3433  // Begin to copy pages if we're allowed to:
3434  if ($this->admin || in_array('pages', $copyTablesAlongWithPage, true)) {
3435  // Copy this page we're on. And set first-flag (this will trigger that the record is hidden if that is configured)
3436  // This method also copies the localizations of a page
3437  $theNewRootID = $this->‪copySpecificPage($uid, $destPid, $copyTablesAlongWithPage, true);
3438  // If we're going to copy recursively
3439  if ($theNewRootID && $this->copyTree) {
3440  // Get ALL subpages to copy (read-permissions are respected!):
3441  $CPtable = $this->‪int_pageTreeInfo([], $uid, (int)$this->copyTree, $theNewRootID);
3442  // Now copying the subpages:
3443  foreach ($CPtable as $thePageUid => $thePagePid) {
3444  $newPid = $this->copyMappingArray['pages'][$thePagePid];
3445  if (isset($newPid)) {
3446  $this->‪copySpecificPage($thePageUid, $newPid, $copyTablesAlongWithPage);
3447  } else {
3448  $this->‪log('pages', $uid, SystemLogDatabaseAction::CHECK, 0, SystemLogErrorClassification::USER_ERROR, 'Something went wrong during copying branch');
3449  break;
3450  }
3451  }
3452  }
3453  } else {
3454  $this->‪log('pages', $uid, SystemLogDatabaseAction::CHECK, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to copy page without permission to this table');
3455  }
3456  }
3457 
3467  protected function ‪getAllowedTablesToCopyWhenCopyingAPage(): array
3468  {
3469  // Finding list of tables to copy.
3470  // These are the tables, the user may modify
3471  $copyTablesArray = $this->admin ? $this->‪compileAdminTables() : explode(',', $this->BE_USER->groupData['tables_modify']);
3472  // If not all tables are allowed then make a list of allowed tables.
3473  // That is the tables that figure in both allowed tables AND the copyTable-list
3474  if (strpos($this->copyWhichTables, '*') === false) {
3475  $definedTablesToCopy = ‪GeneralUtility::trimExplode(',', $this->copyWhichTables, true);
3476  // Pages are always allowed
3477  $definedTablesToCopy[] = 'pages';
3478  $definedTablesToCopy = array_flip($definedTablesToCopy);
3479  foreach ($copyTablesArray as $k => $table) {
3480  if (!$table || !isset($definedTablesToCopy[$table])) {
3481  unset($copyTablesArray[$k]);
3482  }
3483  }
3484  }
3485  $copyTablesArray = array_unique($copyTablesArray);
3486  return $copyTablesArray;
3487  }
3498  public function ‪copySpecificPage($uid, $destPid, $copyTablesArray, $first = false)
3499  {
3500  // Copy the page itself:
3501  $theNewRootID = $this->‪copyRecord('pages', $uid, $destPid, $first);
3502  $currentWorkspaceId = (int)$this->BE_USER->workspace;
3503  // If a new page was created upon the copy operation we will proceed with all the tables ON that page:
3504  ‪if ($theNewRootID) {
3505  foreach ($copyTablesArray as $table) {
3506  // All records under the page is copied.
3507  if ($table && is_array(‪$GLOBALS['TCA'][$table]) && $table !== 'pages') {
3508  ‪$fields = ['uid'];
3509  $languageField = null;
3510  $transOrigPointerField = null;
3511  $translationSourceField = null;
3513  $languageField = ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'];
3514  $transOrigPointerField = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
3515  ‪$fields[] = $languageField;
3516  ‪$fields[] = $transOrigPointerField;
3517  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'])) {
3518  $translationSourceField = ‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'];
3519  ‪$fields[] = $translationSourceField;
3520  }
3521  }
3522  $isTableWorkspaceEnabled = ‪BackendUtility::isTableWorkspaceEnabled($table);
3523  if ($isTableWorkspaceEnabled) {
3524  ‪$fields[] = 't3ver_oid';
3525  ‪$fields[] = 't3ver_state';
3526  ‪$fields[] = 't3ver_wsid';
3527  ‪$fields[] = 't3ver_move_id';
3528  }
3529  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
3530  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
3531  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $currentWorkspaceId));
3532  $queryBuilder
3533  ->select(...‪$fields)
3534  ->from($table)
3535  ->where(
3536  $queryBuilder->expr()->eq(
3537  'pid',
3538  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
3539  )
3540  );
3541  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['sortby'])) {
3542  $queryBuilder->orderBy(‪$GLOBALS['TCA'][$table]['ctrl']['sortby'], 'DESC');
3543  }
3544  $queryBuilder->addOrderBy('uid');
3545  try {
3546  $result = $queryBuilder->execute();
3547  $rows = [];
3548  $movedLiveIds = [];
3549  $movedLiveRecords = [];
3550  while ($row = $result->fetch()) {
3551  if ($isTableWorkspaceEnabled && (int)$row['t3ver_state'] === ‪VersionState::MOVE_PLACEHOLDER) {
3552  $movedLiveIds[(int)$row['t3ver_move_id']] = (int)$row['uid'];
3553  }
3554  $rows[(int)$row['uid']] = $row;
3555  }
3556  // Resolve placeholders of workspace versions
3557  if (!empty($rows) && $currentWorkspaceId > 0 && $isTableWorkspaceEnabled) {
3558  // If a record was moved within the page, the PlainDataResolver needs the move placeholder
3559  // but not the original live version, otherwise the move placeholder is not considered at all
3560  // For this reason, we find the live ids, where there was also a move placeholder in the SQL
3561  // query above in $movedLiveIds and now we removed them before handing them over to PlainDataResolver.
3562  // see changeContentSortingAndCopyDraftPage test
3563  foreach ($movedLiveIds as $liveId => $movePlaceHolderId) {
3564  if (isset($rows[$liveId])) {
3565  $movedLiveRecords[$movePlaceHolderId] = $rows[$liveId];
3566  unset($rows[$liveId]);
3567  }
3568  }
3569  $rows = array_reverse(
3571  $table,
3572  implode(',', ‪$fields),
3573  ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'],
3574  array_keys($rows)
3575  ),
3576  true
3577  );
3578  foreach ($movedLiveRecords as $movePlaceHolderId => $liveRecord) {
3579  $rows[$movePlaceHolderId] = $liveRecord;
3580  }
3581  }
3582  if (is_array($rows)) {
3583  $languageSourceMap = [];
3584  ‪$overrideValues = $translationSourceField ? [$translationSourceField => 0] : [];
3585  $doRemap = false;
3586  foreach ($rows as $row) {
3587  // Skip localized records that will be processed in
3588  // copyL10nOverlayRecords() on copying the default language record
3589  $transOrigPointer = $row[$transOrigPointerField];
3590  if ($row[$languageField] > 0 && $transOrigPointer > 0 && (isset($rows[$transOrigPointer]) || isset($movedLiveIds[$transOrigPointer]))) {
3591  continue;
3592  }
3593  // Copying each of the underlying records...
3594  $newUid = $this->‪copyRecord($table, $row['uid'], $theNewRootID, false, ‪$overrideValues);
3595  if ($translationSourceField) {
3596  $languageSourceMap[$row['uid']] = $newUid;
3597  if ($row[$languageField] > 0) {
3598  $doRemap = true;
3599  }
3600  }
3601  }
3602  if ($doRemap) {
3603  //remap is needed for records in non-default language records in the "free mode"
3604  $this->‪copy_remapTranslationSourceField($table, $rows, $languageSourceMap);
3605  }
3606  }
3607  } catch (DBALException $e) {
3608  $databaseErrorMessage = $e->getPrevious()->getMessage();
3609  $this->‪log($table, $uid, SystemLogDatabaseAction::CHECK, 0, SystemLogErrorClassification::USER_ERROR, 'An SQL error occurred: ' . $databaseErrorMessage);
3610  }
3611  }
3612  }
3613  $this->‪processRemapStack();
3614  return $theNewRootID;
3615  }
3616  return null;
3617  }
3618 
3635  public function ‪copyRecord_raw($table, $uid, $pid, $overrideArray = [], array $workspaceOptions = [])
3636  {
3637  $uid = (int)$uid;
3638  // Stop any actions if the record is marked to be deleted:
3639  // (this can occur if IRRE elements are versionized and child elements are removed)
3640  if ($this->‪isElementToBeDeleted($table, $uid)) {
3641  return null;
3642  }
3643  // Only copy if the table is defined in TCA, a uid is given and the record wasn't copied before:
3644  if (!‪$GLOBALS['TCA'][$table] || !$uid || $this->‪isRecordCopied($table, $uid)) {
3645  return null;
3646  }
3647 
3648  // Fetch record with permission check
3650 
3651  // This checks if the record can be selected which is all that a copy action requires.
3652  if ($row === false) {
3653  $this->‪log(
3654  $table,
3655  $uid,
3656  SystemLogDatabaseAction::DELETE,
3657  0,
3658  SystemLogErrorClassification::USER_ERROR,
3659  'Attempt to rawcopy/versionize record which either does not exist or you don\'t have permission to read'
3660  );
3661  return null;
3662  }
3663 
3664  // Set up fields which should not be processed. They are still written - just passed through no-questions-asked!
3665  $nonFields = ['uid', 'pid', 't3ver_oid', 't3ver_wsid', 't3ver_state', 't3ver_count', 't3ver_stage', 't3ver_tstamp', 'perms_userid', 'perms_groupid', 'perms_user', 'perms_group', 'perms_everybody'];
3666 
3667  // Merge in override array.
3668  $row = array_merge($row, $overrideArray);
3669  // Traverse ALL fields of the selected record:
3670  foreach ($row as $field => $value) {
3671  if (!in_array($field, $nonFields, true)) {
3672  // Get TCA configuration for the field:
3673  $conf = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'];
3674  if (is_array($conf)) {
3675  // Processing based on the TCA config field type (files, references, flexforms...)
3676  $value = $this->‪copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $pid, 0, $workspaceOptions);
3677  }
3678  // Add value to array.
3679  $row[$field] = $value;
3680  }
3681  }
3682  $row['pid'] = $pid;
3683  // Setting original UID:
3684  if (‪$GLOBALS['TCA'][$table]['ctrl']['origUid']) {
3685  $row[‪$GLOBALS['TCA'][$table]['ctrl']['origUid']] = $uid;
3686  }
3687  // Do the copy by internal function
3688  $theNewSQLID = $this->‪insertNewCopyVersion($table, $row, $pid);
3689 
3690  // When a record is copied in workspace (eg. to create a delete placeholder record for a live record), records
3691  // pointing to that record need a reference index update. This is for instance the case in FAL, if a sys_file_reference
3692  // for a eg. tt_content record is marked as deleted. The tt_content record then needs a reference index update.
3693  // This scenario seems to currently only show up if in workspaces, so the refindex update is restricted to this for now.
3694  if (!empty($workspaceOptions)) {
3695  $this->referenceIndexUpdater->registerUpdateForReferencesToItem($table, (int)$row['uid'], (int)$this->BE_USER->workspace);
3696  }
3697 
3698  if ($theNewSQLID) {
3699  $this->‪dbAnalysisStoreExec();
3700  $this->dbAnalysisStore = [];
3701  return $this->copyMappingArray[$table][$uid] = $theNewSQLID;
3702  }
3703  return null;
3704  }
3705 
3716  public function ‪insertNewCopyVersion($table, $fieldArray, $realPid)
3717  {
3718  $id = ‪StringUtility::getUniqueId('NEW');
3719  // $fieldArray is set as current record.
3720  // 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...
3721  $this->checkValue_currentRecord = $fieldArray;
3722  // Makes sure that transformations aren't processed on the copy.
3723  $backupDontProcessTransformations = ‪$this->dontProcessTransformations;
3724  $this->dontProcessTransformations = true;
3725  // Traverse record and input-process each value:
3726  foreach ($fieldArray as $field => $fieldValue) {
3727  if (isset(‪$GLOBALS['TCA'][$table]['columns'][$field])) {
3728  // Evaluating the value.
3729  $res = $this->‪checkValue($table, $field, $fieldValue, $id, 'new', $realPid, 0, $fieldArray);
3730  if (isset($res['value'])) {
3731  $fieldArray[$field] = $res['value'];
3732  }
3733  }
3734  }
3735  // System fields being set:
3736  if (‪$GLOBALS['TCA'][$table]['ctrl']['crdate']) {
3737  $fieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['crdate']] = ‪$GLOBALS['EXEC_TIME'];
3738  }
3739  if (‪$GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
3740  $fieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = ‪$this->userid;
3741  }
3742  if (‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
3743  $fieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = ‪$GLOBALS['EXEC_TIME'];
3744  }
3745  // Finally, insert record:
3746  $this->‪insertDB($table, $id, $fieldArray, true);
3747  // Resets dontProcessTransformations to the previous state.
3748  $this->dontProcessTransformations = $backupDontProcessTransformations;
3749  // Return new id:
3750  return $this->substNEWwithIDs[$id];
3751  }
3752 
3769  public function ‪copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $realDestPid, $language = 0, array $workspaceOptions = [])
3770  {
3771  $inlineSubType = $this->‪getInlineFieldType($conf);
3772  // Get the localization mode for the current (parent) record (keep|select):
3773  // Register if there are references to take care of or MM is used on an inline field (no change to value):
3774  if ($this->‪isReferenceField($conf) || $inlineSubType === 'mm') {
3775  $value = $this->‪copyRecord_processManyToMany($table, $uid, $field, $value, $conf, $language);
3776  } elseif ($inlineSubType !== false) {
3777  $value = $this->‪copyRecord_processInline($table, $uid, $field, $value, $row, $conf, $realDestPid, $language, $workspaceOptions);
3778  }
3779  // 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())
3780  if ($conf['type'] === 'flex') {
3781  // Get current value array:
3782  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
3783  $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
3784  ['config' => $conf],
3785  $table,
3786  $field,
3787  $row
3788  );
3789  $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
3790  $currentValueArray = ‪GeneralUtility::xml2array($value);
3791  // Traversing the XML structure, processing files:
3792  if (is_array($currentValueArray)) {
3793  $currentValueArray['data'] = $this->‪checkValue_flex_procInData($currentValueArray['data'], [], [], $dataStructureArray, [$table, $uid, $field, $realDestPid], 'copyRecord_flexFormCallBack', $workspaceOptions);
3794  // Setting value as an array! -> which means the input will be processed according to the 'flex' type when the new copy is created.
3795  $value = $currentValueArray;
3796  }
3797  }
3798  return $value;
3799  }
3800 
3812  protected function ‪copyRecord_processManyToMany($table, $uid, $field, $value, $conf, $language)
3813  {
3814  $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
3815  $prependName = $conf['type'] === 'group' ? $conf['prepend_tname'] : '';
3816  $mmTable = isset($conf['MM']) && $conf['MM'] ? $conf['MM'] : '';
3817  $localizeForeignTable = isset($conf['foreign_table']) && ‪BackendUtility::isTableLocalizable($conf['foreign_table']);
3818  // Localize referenced records of select fields:
3819  $localizingNonManyToManyFieldReferences = empty($mmTable) && $localizeForeignTable && isset($conf['localizeReferencesAtParentLocalization']) && $conf['localizeReferencesAtParentLocalization'];
3821  $dbAnalysis = $this->‪createRelationHandlerInstance();
3822  $dbAnalysis->start($value, $allowedTables, $mmTable, $uid, $table, $conf);
3823  $purgeItems = false;
3824  if ($language > 0 && $localizingNonManyToManyFieldReferences) {
3825  foreach ($dbAnalysis->itemArray as $index => $item) {
3826  // Since select fields can reference many records, check whether there's already a localization:
3827  $recordLocalization = ‪BackendUtility::getRecordLocalization($item['table'], $item['id'], $language);
3828  if ($recordLocalization) {
3829  $dbAnalysis->itemArray[$index]['id'] = $recordLocalization[0]['uid'];
3830  } elseif ($this->‪isNestedElementCallRegistered($item['table'], $item['id'], 'localize-' . (string)$language) === false) {
3831  $dbAnalysis->itemArray[$index]['id'] = $this->‪localize($item['table'], $item['id'], $language);
3832  }
3833  }
3834  $purgeItems = true;
3835  }
3836 
3837  if ($purgeItems || $mmTable) {
3838  $dbAnalysis->purgeItemArray();
3839  $value = implode(',', $dbAnalysis->getValueArray($prependName));
3840  }
3841  // Setting the value in this array will notify the remapListedDBRecords() function that this field MAY need references to be corrected
3842  if ($value) {
3843  $this->registerDBList[$table][$uid][$field] = $value;
3844  }
3845 
3846  return $value;
3847  }
3848 
3863  protected function ‪copyRecord_processInline(
3864  $table,
3865  $uid,
3866  $field,
3867  $value,
3868  $row,
3869  $conf,
3870  $realDestPid,
3871  $language,
3872  array $workspaceOptions
3873  ) {
3874  // Fetch the related child records using \TYPO3\CMS\Core\Database\RelationHandler
3876  $dbAnalysis = $this->‪createRelationHandlerInstance();
3877  $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
3878  // Walk through the items, copy them and remember the new id:
3879  foreach ($dbAnalysis->itemArray as $k => $v) {
3880  $newId = null;
3881  // If language is set and differs from original record, this isn't a copy action but a localization of our parent/ancestor:
3882  if ($language > 0 && ‪BackendUtility::isTableLocalizable($table) && $language != $row[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]) {
3883  // Children should be localized when the parent gets localized the first time, just do it:
3884  $newId = $this->‪localize($v['table'], $v['id'], $language);
3885  } else {
3886  if (!‪MathUtility::canBeInterpretedAsInteger($realDestPid)) {
3887  $newId = $this->‪copyRecord($v['table'], $v['id'], -$v['id']);
3888  // If the destination page id is a NEW string, keep it on the same page
3889  } elseif ($this->BE_USER->workspace > 0 && ‪BackendUtility::isTableWorkspaceEnabled($v['table'])) {
3890  // A filled $workspaceOptions indicated that this call
3891  // has it's origin in previous versionizeRecord() processing
3892  if (!empty($workspaceOptions)) {
3893  // Versions use live default id, thus the "new"
3894  // id is the original live default child record
3895  $newId = $v['id'];
3896  $this->‪versionizeRecord(
3897  $v['table'],
3898  $v['id'],
3899  $workspaceOptions['label'] ?? 'Auto-created for WS #' . $this->BE_USER->workspace,
3900  $workspaceOptions['delete'] ?? false
3901  );
3902  // Otherwise just use plain copyRecord() to create placeholders etc.
3903  } else {
3904  // If a record has been copied already during this request,
3905  // prevent superfluous duplication and use the existing copy
3906  if (isset($this->copyMappingArray[$v['table']][$v['id']])) {
3907  $newId = $this->copyMappingArray[$v['table']][$v['id']];
3908  } else {
3909  $newId = $this->‪copyRecord($v['table'], $v['id'], $realDestPid);
3910  }
3911  }
3912  } elseif ($this->BE_USER->workspace > 0 && !‪BackendUtility::isTableWorkspaceEnabled($v['table'])) {
3913  // We are in workspace context creating a new parent version and have a child table
3914  // that is not workspace aware. We don't do anything with this child.
3915  continue;
3916  } else {
3917  // If a record has been copied already during this request,
3918  // prevent superfluous duplication and use the existing copy
3919  if (isset($this->copyMappingArray[$v['table']][$v['id']])) {
3920  $newId = $this->copyMappingArray[$v['table']][$v['id']];
3921  } else {
3922  $newId = $this->‪copyRecord_raw($v['table'], $v['id'], $realDestPid, [], $workspaceOptions);
3923  }
3924  }
3925  }
3926  // If the current field is set on a page record, update the pid of related child records:
3927  if ($table === 'pages') {
3928  $this->registerDBPids[$v['table']][$v['id']] = $uid;
3929  } elseif (isset($this->registerDBPids[$table][$uid])) {
3930  $this->registerDBPids[$v['table']][$v['id']] = $this->registerDBPids[$table][$uid];
3931  }
3932  $dbAnalysis->itemArray[$k]['id'] = $newId;
3933  }
3934  // Store the new values, we will set up the uids for the subtype later on (exception keep localization from original record):
3935  $value = implode(',', $dbAnalysis->getValueArray());
3936  $this->registerDBList[$table][$uid][$field] = $value;
3937 
3938  return $value;
3939  }
3940 
3956  public function ‪copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $_1, $_2, $_3, $workspaceOptions)
3957  {
3958  // Extract parameters:
3959  [$table, $uid, $field, $realDestPid] = $pParams;
3960  // If references are set for this field, set flag so they can be corrected later (in ->remapListedDBRecords())
3961  if (($this->‪isReferenceField($dsConf) || $this->‪getInlineFieldType($dsConf) !== false) && (string)$dataValue !== '') {
3962  $dataValue = $this->‪copyRecord_procBasedOnFieldType($table, $uid, $field, $dataValue, [], $dsConf, $realDestPid, 0, $workspaceOptions);
3963  $this->registerDBList[$table][$uid][$field] = 'FlexForm_reference';
3964  }
3965  // Return
3966  return ['value' => $dataValue];
3967  }
3968 
3980  public function ‪copyL10nOverlayRecords($table, $uid, $destPid, $first = false, ‪$overrideValues = [], $excludeFields = '')
3981  {
3982  // There's no need to perform this for tables that are not localizable
3984  return;
3985  }
3986 
3987  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
3988  $queryBuilder->getRestrictions()
3989  ->removeAll()
3990  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
3991  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->BE_USER->workspace));
3992 
3993  $queryBuilder->select('*')
3994  ->from($table)
3995  ->where(
3996  $queryBuilder->expr()->eq(
3997  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
3998  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT, ':pointer')
3999  )
4000  );
4001 
4002  // Never copy the actual move placeholders around, as the newly copied records are
4003  // Always created as new record / new placeholder pairs
4005  $queryBuilder->andWhere(
4006  $queryBuilder->expr()->notIn(
4007  't3ver_state',
4009  )
4010  );
4011  }
4012 
4013  // If $destPid is < 0, get the pid of the record with uid equal to abs($destPid)
4014  $tscPID = ‪BackendUtility::getTSconfig_pidValue($table, $uid, $destPid);
4015  // Get the localized records to be copied
4016  $l10nRecords = $queryBuilder->execute()->fetchAll();
4017  if (is_array($l10nRecords)) {
4018  $localizedDestPids = [];
4019  // If $destPid < 0, then it is the uid of the original language record we are inserting after
4020  if ($destPid < 0) {
4021  // Get the localized records of the record we are inserting after
4022  $queryBuilder->setParameter('pointer', abs($destPid), \PDO::PARAM_INT);
4023  $destL10nRecords = $queryBuilder->execute()->fetchAll();
4024  // Index the localized record uids by language
4025  if (is_array($destL10nRecords)) {
4026  foreach ($destL10nRecords as $record) {
4027  $localizedDestPids[$record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]] = -$record['uid'];
4028  }
4029  }
4030  }
4031  $languageSourceMap = [
4032  $uid => ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']]
4033  ];
4034  // Copy the localized records after the corresponding localizations of the destination record
4035  foreach ($l10nRecords as $record) {
4036  $localizedDestPid = (int)$localizedDestPids[$record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]];
4037  if ($localizedDestPid < 0) {
4038  $newUid = $this->‪copyRecord($table, $record['uid'], $localizedDestPid, $first, ‪$overrideValues, $excludeFields, $record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]);
4039  } else {
4040  $newUid = $this->‪copyRecord($table, $record['uid'], $destPid < 0 ? $tscPID : $destPid, $first, ‪$overrideValues, $excludeFields, $record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]);
4041  }
4042  $languageSourceMap[$record['uid']] = $newUid;
4043  }
4044  $this->‪copy_remapTranslationSourceField($table, $l10nRecords, $languageSourceMap);
4045  }
4046  }
4047 
4055  protected function ‪copy_remapTranslationSourceField($table, $l10nRecords, $languageSourceMap)
4056  {
4057  if (empty(‪$GLOBALS['TCA'][$table]['ctrl']['translationSource']) || empty(‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
4058  return;
4059  }
4060  $translationSourceFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'];
4061  $translationParentFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
4062 
4063  //We can avoid running these update queries by sorting the $l10nRecords by languageSource dependency (in copyL10nOverlayRecords)
4064  //and first copy records depending on default record (and map the field).
4065  foreach ($l10nRecords as $record) {
4066  $oldSourceUid = $record[$translationSourceFieldName];
4067  if ($oldSourceUid <= 0 && $record[$translationParentFieldName] > 0) {
4068  //BC fix - in connected mode 'translationSource' field should not be 0
4069  $oldSourceUid = $record[$translationParentFieldName];
4070  }
4071  if ($oldSourceUid > 0) {
4072  if (empty($languageSourceMap[$oldSourceUid])) {
4073  // we don't have mapping information available e.g when copyRecord returned null
4074  continue;
4075  }
4076  $newFieldValue = $languageSourceMap[$oldSourceUid];
4077  $updateFields = [
4078  $translationSourceFieldName => $newFieldValue
4079  ];
4080  GeneralUtility::makeInstance(ConnectionPool::class)
4081  ->getConnectionForTable($table)
4082  ->update($table, $updateFields, ['uid' => (int)$languageSourceMap[$record['uid']]]);
4083  if ($this->BE_USER->workspace > 0) {
4084  GeneralUtility::makeInstance(ConnectionPool::class)
4085  ->getConnectionForTable($table)
4086  ->update($table, $updateFields, ['t3ver_oid' => (int)$languageSourceMap[$record['uid']], 't3ver_wsid' => $this->BE_USER->workspace]);
4087  }
4088  }
4089  }
4090  }
4091 
4092  /*********************************************
4093  *
4094  * Cmd: Moving, Localizing
4095  *
4096  ********************************************/
4105  public function ‪moveRecord($table, $uid, $destPid)
4106  {
4107  if (!‪$GLOBALS['TCA'][$table]) {
4108  return;
4109  }
4110 
4111  // In case the record to be moved turns out to be an offline version,
4112  // we have to find the live version and work on that one.
4113  if ($lookForLiveVersion = ‪BackendUtility::getLiveVersionOfRecord($table, $uid, 'uid')) {
4114  $uid = $lookForLiveVersion['uid'];
4115  }
4116  // Initialize:
4117  $destPid = (int)$destPid;
4118  // Get this before we change the pid (for logging)
4119  $propArr = $this->‪getRecordProperties($table, $uid);
4120  $moveRec = $this->‪getRecordProperties($table, $uid, true);
4121  // This is the actual pid of the moving to destination
4122  $resolvedPid = $this->‪resolvePid($table, $destPid);
4123  // 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.
4124  // If the record is a page, then there are two options: If the page is moved within itself,
4125  // (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.
4126  if ($table !== 'pages' || $resolvedPid == $moveRec['pid']) {
4127  // Edit rights for the record...
4128  $mayMoveAccess = $this->‪checkRecordUpdateAccess($table, $uid);
4129  } else {
4130  $mayMoveAccess = $this->‪doesRecordExist($table, $uid, ‪Permission::PAGE_DELETE);
4131  }
4132  // Finding out, if the record may be moved TO another place. Here we check insert-rights (non-pages = edit, pages = new),
4133  // unless the pages are moved on the same pid, then edit-rights are checked
4134  if ($table !== 'pages' || $resolvedPid != $moveRec['pid']) {
4135  // Insert rights for the record...
4136  $mayInsertAccess = $this->‪checkRecordInsertAccess($table, $resolvedPid, SystemLogDatabaseAction::MOVE);
4137  } else {
4138  $mayInsertAccess = $this->‪checkRecordUpdateAccess($table, $uid);
4139  }
4140  // Checking if there is anything else disallowing moving the record by checking if editing is allowed
4141  $fullLanguageCheckNeeded = $table !== 'pages';
4142  $mayEditAccess = $this->BE_USER->recordEditAccessInternals($table, $uid, false, false, $fullLanguageCheckNeeded);
4143  // If moving is allowed, begin the processing:
4144  if (!$mayEditAccess) {
4145  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to move record "%s" (%s) without having permissions to do so. [' . $this->BE_USER->errorMsg . ']', 14, [$propArr['header'], $table . ':' . $uid], $propArr['event_pid']);
4146  return;
4147  }
4148 
4149  if (!$mayMoveAccess) {
4150  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to move record \'%s\' (%s) without having permissions to do so.', 14, [$propArr['header'], $table . ':' . $uid], $propArr['event_pid']);
4151  return;
4152  }
4153 
4154  if (!$mayInsertAccess) {
4155  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to move record \'%s\' (%s) without having permissions to insert.', 14, [$propArr['header'], $table . ':' . $uid], $propArr['event_pid']);
4156  return;
4157  }
4158 
4159  $recordWasMoved = false;
4160  // Move the record via a hook, used e.g. for versioning
4161  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'] ?? [] as $className) {
4162  $hookObj = GeneralUtility::makeInstance($className);
4163  if (method_exists($hookObj, 'moveRecord')) {
4164  $hookObj->moveRecord($table, $uid, $destPid, $propArr, $moveRec, $resolvedPid, $recordWasMoved, $this);
4165  }
4166  }
4167  // Move the record if a hook hasn't moved it yet
4168  if (!$recordWasMoved) {
4169  $this->‪moveRecord_raw($table, $uid, $destPid);
4170  }
4171  }
4172 
4183  public function ‪moveRecord_raw($table, $uid, $destPid)
4184  {
4185  $sortColumn = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
4186  $origDestPid = $destPid;
4187  // This is the actual pid of the moving to destination
4188  $resolvedPid = $this->‪resolvePid($table, $destPid);
4189  // Checking if the pid is negative, but no sorting row is defined. In that case, find the correct pid.
4190  // Basically this check make the error message 4-13 meaning less... But you can always remove this check if you
4191  // prefer the error instead of a no-good action (which is to move the record to its own page...)
4192  if (($destPid < 0 && !$sortColumn) || $destPid >= 0) {
4193  $destPid = $resolvedPid;
4194  }
4195  // Get this before we change the pid (for logging)
4196  $propArr = $this->‪getRecordProperties($table, $uid);
4197  $moveRec = $this->‪getRecordProperties($table, $uid, true);
4198  // Prepare user defined objects (if any) for hooks which extend this function:
4199  $hookObjectsArr = [];
4200  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'] ?? [] as $className) {
4201  $hookObjectsArr[] = GeneralUtility::makeInstance($className);
4202  }
4203  // Timestamp field:
4204  $updateFields = [];
4205  if (‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
4206  $updateFields[‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = ‪$GLOBALS['EXEC_TIME'];
4207  }
4208 
4209  // Check if this is a translation of a page, if so then it just needs to be kept "sorting" in sync
4210  // Usually called from moveL10nOverlayRecords()
4211  if ($table === 'pages') {
4212  $defaultLanguagePageUid = $this->‪getDefaultLanguagePageId((int)$uid);
4213  // In workspaces, the default language page may have been moved to a different pid than the
4214  // default language page record of live workspace. In this case, localized pages need to be
4215  // moved to the pid of the workspace move record.
4216  $defaultLanguagePageWorkspaceOverlay = ‪BackendUtility::getWorkspaceVersionOfRecord((int)$this->BE_USER->workspace, 'pages', $defaultLanguagePageUid, 'uid');
4217  if (is_array($defaultLanguagePageWorkspaceOverlay)) {
4218  $defaultLanguagePageUid = (int)$defaultLanguagePageWorkspaceOverlay['uid'];
4219  }
4220  if ($defaultLanguagePageUid !== (int)$uid) {
4221  // If the default language page has been moved, localized pages need to be moved to
4222  // that pid and sorting, too.
4223  $originalTranslationRecord = $this->‪recordInfo($table, $defaultLanguagePageUid, 'pid,' . $sortColumn);
4224  $updateFields[$sortColumn] = $originalTranslationRecord[$sortColumn];
4225  $destPid = $originalTranslationRecord['pid'];
4226  }
4227  }
4228 
4229  // Insert as first element on page (where uid = $destPid)
4230  if ($destPid >= 0) {
4231  if ($table !== 'pages' || $this->‪destNotInsideSelf($destPid, $uid)) {
4232  // Clear cache before moving
4233  [$parentUid] = ‪BackendUtility::getTSCpid($table, $uid, '');
4234  $this->‪registerRecordIdForPageCacheClearing($table, $uid, $parentUid);
4235  // Setting PID
4236  $updateFields['pid'] = $destPid;
4237  // Table is sorted by 'sortby'
4238  if ($sortColumn && !isset($updateFields[$sortColumn])) {
4239  $sortNumber = $this->‪getSortNumber($table, $uid, $destPid);
4240  $updateFields[$sortColumn] = $sortNumber;
4241  }
4242  // Check for child records that have also to be moved
4243  $this->‪moveRecord_procFields($table, $uid, $destPid);
4244  // Create query for update:
4245  GeneralUtility::makeInstance(ConnectionPool::class)
4246  ->getConnectionForTable($table)
4247  ->update($table, $updateFields, ['uid' => (int)$uid]);
4248  // Check for the localizations of that element
4249  $this->‪moveL10nOverlayRecords($table, $uid, $destPid, $destPid);
4250  // Call post processing hooks:
4251  foreach ($hookObjectsArr as $hookObj) {
4252  if (method_exists($hookObj, 'moveRecord_firstElementPostProcess')) {
4253  $hookObj->moveRecord_firstElementPostProcess($table, $uid, $destPid, $moveRec, $updateFields, $this);
4254  }
4255  }
4256 
4257  $this->‪getRecordHistoryStore()->‪moveRecord($table, $uid, ['oldPageId' => $propArr['pid'], 'newPageId' => $destPid, 'oldData' => $propArr, 'newData' => $updateFields], $this->correlationId);
4258  if ($this->enableLogging) {
4259  // Logging...
4260  $oldpagePropArr = $this->‪getRecordProperties('pages', $propArr['pid']);
4261  if ($destPid != $propArr['pid']) {
4262  // Logged to old page
4263  $newPropArr = $this->‪getRecordProperties($table, $uid);
4264  $newpagePropArr = $this->‪getRecordProperties('pages', $destPid);
4265  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, $destPid, SystemLogErrorClassification::MESSAGE, 'Moved record \'%s\' (%s) to page \'%s\' (%s)', 2, [$propArr['header'], $table . ':' . $uid, $newpagePropArr['header'], $newPropArr['pid']], $propArr['pid']);
4266  // Logged to new page
4267  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, $destPid, SystemLogErrorClassification::MESSAGE, 'Moved record \'%s\' (%s) from page \'%s\' (%s)', 3, [$propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']], $destPid);
4268  } else {
4269  // Logged to new page
4270  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, $destPid, SystemLogErrorClassification::MESSAGE, 'Moved record \'%s\' (%s) on page \'%s\' (%s)', 4, [$propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']], $destPid);
4271  }
4272  }
4273  // Clear cache after moving
4274  $this->‪registerRecordIdForPageCacheClearing($table, $uid);
4275  $this->‪fixUniqueInPid($table, $uid);
4276  $this->‪fixUniqueInSite($table, (int)$uid);
4277  if ($table === 'pages') {
4278  $this->‪fixUniqueInSiteForSubpages((int)$uid);
4279  }
4280  } elseif ($this->enableLogging) {
4281  $destPropArr = $this->‪getRecordProperties('pages', $destPid);
4282  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, 0, SystemLogErrorClassification::USER_ERROR, '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']);
4283  }
4284  } elseif ($sortColumn) {
4285  // Put after another record
4286  // Table is being sorted
4287  // Save the position to which the original record is requested to be moved
4288  $originalRecordDestinationPid = $destPid;
4289  $sortInfo = $this->‪getSortNumber($table, $uid, $destPid);
4290  // Setting the destPid to the new pid of the record.
4291  $destPid = $sortInfo['pid'];
4292  // If not an array, there was an error (which is already logged)
4293  if (is_array($sortInfo)) {
4294  if ($table !== 'pages' || $this->‪destNotInsideSelf($destPid, $uid)) {
4295  // clear cache before moving
4296  $this->‪registerRecordIdForPageCacheClearing($table, $uid);
4297  // We now update the pid and sortnumber (if not set for page translations)
4298  $updateFields['pid'] = $destPid;
4299  if (!isset($updateFields[$sortColumn])) {
4300  $updateFields[$sortColumn] = $sortInfo['sortNumber'];
4301  }
4302  // Check for child records that have also to be moved
4303  $this->‪moveRecord_procFields($table, $uid, $destPid);
4304  // Create query for update:
4305  GeneralUtility::makeInstance(ConnectionPool::class)
4306  ->getConnectionForTable($table)
4307  ->update($table, $updateFields, ['uid' => (int)$uid]);
4308  // Check for the localizations of that element
4309  $this->‪moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid);
4310  // Call post processing hooks:
4311  foreach ($hookObjectsArr as $hookObj) {
4312  if (method_exists($hookObj, 'moveRecord_afterAnotherElementPostProcess')) {
4313  $hookObj->moveRecord_afterAnotherElementPostProcess($table, $uid, $destPid, $origDestPid, $moveRec, $updateFields, $this);
4314  }
4315  }
4316  $this->‪getRecordHistoryStore()->‪moveRecord($table, $uid, ['oldPageId' => $propArr['pid'], 'newPageId' => $destPid, 'oldData' => $propArr, 'newData' => $updateFields], $this->correlationId);
4317  if ($this->enableLogging) {
4318  // Logging...
4319  $oldpagePropArr = $this->‪getRecordProperties('pages', $propArr['pid']);
4320  if ($destPid != $propArr['pid']) {
4321  // Logged to old page
4322  $newPropArr = $this->‪getRecordProperties($table, $uid);
4323  $newpagePropArr = $this->‪getRecordProperties('pages', $destPid);
4324  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, 0, SystemLogErrorClassification::MESSAGE, 'Moved record \'%s\' (%s) to page \'%s\' (%s)', 2, [$propArr['header'], $table . ':' . $uid, $newpagePropArr['header'], $newPropArr['pid']], $propArr['pid']);
4325  // Logged to old page
4326  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, 0, SystemLogErrorClassification::MESSAGE, 'Moved record \'%s\' (%s) from page \'%s\' (%s)', 3, [$propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']], $destPid);
4327  } else {
4328  // Logged to old page
4329  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, 0, SystemLogErrorClassification::MESSAGE, 'Moved record \'%s\' (%s) on page \'%s\' (%s)', 4, [$propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']], $destPid);
4330  }
4331  }
4332  // Clear cache after moving
4333  $this->‪registerRecordIdForPageCacheClearing($table, $uid);
4334  $this->‪fixUniqueInPid($table, $uid);
4335  $this->‪fixUniqueInSite($table, (int)$uid);
4336  if ($table === 'pages') {
4337  $this->‪fixUniqueInSiteForSubpages((int)$uid);
4338  }
4339  } elseif ($this->enableLogging) {
4340  $destPropArr = $this->‪getRecordProperties('pages', $destPid);
4341  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, 0, SystemLogErrorClassification::USER_ERROR, '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']);
4342  }
4343  } else {
4344  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, 0, SystemLogErrorClassification::USER_ERROR, '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']);
4345  }
4346  }
4347  }
4348 
4358  public function ‪moveRecord_procFields($table, $uid, $destPid)
4359  {
4360  $row = ‪BackendUtility::getRecordWSOL($table, $uid);
4361  if (is_array($row) && (int)$destPid !== (int)$row['pid']) {
4362  $conf = ‪$GLOBALS['TCA'][$table]['columns'];
4363  foreach ($row as $field => $value) {
4364  $this->‪moveRecord_procBasedOnFieldType($table, $uid, $destPid, $field, $value, $conf[$field]['config']);
4365  }
4366  }
4367  }
4368 
4380  public function ‪moveRecord_procBasedOnFieldType($table, $uid, $destPid, $field, $value, $conf)
4381  {
4382  $dbAnalysis = null;
4383  if ($conf['type'] === 'inline') {
4384  $foreign_table = $conf['foreign_table'];
4385  $moveChildrenWithParent = !isset($conf['behaviour']['disableMovingChildrenWithParent']) || !$conf['behaviour']['disableMovingChildrenWithParent'];
4386  if ($foreign_table && $moveChildrenWithParent) {
4387  $inlineType = $this->‪getInlineFieldType($conf);
4388  if ($inlineType === 'list' || $inlineType === 'field') {
4389  if ($table === 'pages') {
4390  // If the inline elements are related to a page record,
4391  // make sure they reside at that page and not at its parent
4392  $destPid = $uid;
4393  }
4394  $dbAnalysis = $this->‪createRelationHandlerInstance();
4395  $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
4396  }
4397  }
4398  }
4399  // Move the records
4400  if (isset($dbAnalysis)) {
4401  // Moving records to a positive destination will insert each
4402  // record at the beginning, thus the order is reversed here:
4403  foreach (array_reverse($dbAnalysis->itemArray) as $v) {
4404  $this->‪moveRecord($v['table'], $v['id'], $destPid);
4405  }
4406  }
4407  }
4408 
4418  public function ‪moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid)
4419  {
4420  // There's no need to perform this for non-localizable tables
4422  return;
4423  }
4424 
4425  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
4426  $queryBuilder->getRestrictions()
4427  ->removeAll()
4428  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
4429  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->BE_USER->workspace));
4430 
4431  $l10nRecords = $queryBuilder->select('*')
4432  ->from($table)
4433  ->where(
4434  $queryBuilder->expr()->eq(
4435  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
4436  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT, ':pointer')
4437  )
4438  )
4439  ->execute()
4440  ->fetchAll();
4441 
4442  if (is_array($l10nRecords)) {
4443  $localizedDestPids = [];
4444  // If $$originalRecordDestinationPid < 0, then it is the uid of the original language record we are inserting after
4445  if ($originalRecordDestinationPid < 0) {
4446  // Get the localized records of the record we are inserting after
4447  $queryBuilder->setParameter('pointer', abs($originalRecordDestinationPid), \PDO::PARAM_INT);
4448  $destL10nRecords = $queryBuilder->execute()->fetchAll();
4449  // Index the localized record uids by language
4450  if (is_array($destL10nRecords)) {
4451  foreach ($destL10nRecords as $record) {
4452  $localizedDestPids[$record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]] = -$record['uid'];
4453  }
4454  }
4455  }
4456  // Move the localized records after the corresponding localizations of the destination record
4457  foreach ($l10nRecords as $record) {
4458  $localizedDestPid = (int)$localizedDestPids[$record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]];
4459  if ($localizedDestPid < 0) {
4460  $this->‪moveRecord($table, $record['uid'], $localizedDestPid);
4461  } else {
4462  $this->‪moveRecord($table, $record['uid'], $destPid);
4463  }
4464  }
4465  }
4466  }
4467 
4477  public function ‪localize($table, $uid, $language)
4478  {
4479  $newId = false;
4480  $uid = (int)$uid;
4481  if (!‪$GLOBALS['TCA'][$table] || !$uid || $this->‪isNestedElementCallRegistered($table, $uid, 'localize-' . (string)$language) !== false) {
4482  return false;
4483  }
4484 
4485  $this->‪registerNestedElementCall($table, $uid, 'localize-' . (string)$language);
4486  if (!‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] || !‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
4487  $this->‪newlog('Localization failed; "languageField" and "transOrigPointerField" must be defined for the table ' . $table, SystemLogErrorClassification::USER_ERROR);
4488  return false;
4489  }
4490  $langRec = ‪BackendUtility::getRecord('sys_language', (int)$language, 'uid,title');
4491  if (!$langRec) {
4492  $this->‪newlog('Sys language UID "' . $language . '" not found valid!', SystemLogErrorClassification::USER_ERROR);
4493  return false;
4494  }
4495 
4496  if (!$this->‪doesRecordExist($table, $uid, ‪Permission::PAGE_SHOW)) {
4497  $this->‪newlog('Attempt to localize record ' . $table . ':' . $uid . ' without permission.', SystemLogErrorClassification::USER_ERROR);
4498  return false;
4499  }
4500 
4501  // Getting workspace overlay if possible - this will localize versions in workspace if any
4502  $row = ‪BackendUtility::getRecordWSOL($table, $uid);
4503  if (!is_array($row)) {
4504  $this->‪newlog('Attempt to localize record ' . $table . ':' . $uid . ' that did not exist!', SystemLogErrorClassification::USER_ERROR);
4505  return false;
4506  }
4507 
4508  // Make sure that records which are translated from another language than the default language have a correct
4509  // localization source set themselves, before translating them to another language.
4510  if ((int)$row[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] !== 0
4511  && $row[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0) {
4512  $localizationParentRecord = ‪BackendUtility::getRecord(
4513  $table,
4514  $row[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']]
4515  );
4516  if ((int)$localizationParentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] !== 0) {
4517  $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)!', SystemLogErrorClassification::USER_ERROR);
4518  return false;
4519  }
4520  }
4521 
4522  // Default language records must never have a localization parent as they are the origin of any translation.
4523  if ((int)$row[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] !== 0
4524  && (int)$row[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] === 0) {
4525  $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)!', SystemLogErrorClassification::USER_ERROR);
4526  return false;
4527  }
4528 
4529  $recordLocalizations = ‪BackendUtility::getRecordLocalization($table, $uid, $language, 'AND pid=' . (int)$row['pid']);
4530 
4531  if (!empty($recordLocalizations)) {
4532  $this->‪newlog(sprintf(
4533  'Localization failed: there already are localizations (%s) for language %d of the "%s" record %d!',
4534  implode(', ', array_column($recordLocalizations, 'uid')),
4535  $language,
4536  $table,
4537  $uid
4538  ), 1);
4539  return false;
4540  }
4541 
4542  // Initialize:
4543  ‪$overrideValues = [];
4544  // Set override values:
4545  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $langRec['uid'];
4546  // If the translated record is a default language record, set it's uid as localization parent of the new record.
4547  // If translating from any other language, no override is needed; we just can copy the localization parent of
4548  // the original record (which is pointing to the correspondent default language record) to the new record.
4549  // In copy / free mode the TransOrigPointer field is always set to 0, as no connection to the localization parent is wanted in that case.
4550  // For pages, there is no "copy/free mode".
4551  if (($this->useTransOrigPointerField || $table === 'pages') && (int)$row[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] === 0) {
4552  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $uid;
4553  } elseif (!$this->useTransOrigPointerField) {
4554  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = 0;
4555  }
4556  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['translationSource'])) {
4557  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['translationSource']] = $uid;
4558  }
4559  // Copy the type (if defined in both tables) from the original record so that translation has same type as original record
4560  if (isset(‪$GLOBALS['TCA'][$table]['ctrl']['type'])) {
4561  ‪$overrideValues[‪$GLOBALS['TCA'][$table]['ctrl']['type']] = $row[‪$GLOBALS['TCA'][$table]['ctrl']['type']];
4562  }
4563  // Set exclude Fields:
4564  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $fN => $fCfg) {
4565  $translateToMsg = '';
4566  // Check if we are just prefixing:
4567  if ($fCfg['l10n_mode'] === 'prefixLangTitle') {
4568  if (($fCfg['config']['type'] === 'text' || $fCfg['config']['type'] === 'input') && (string)$row[$fN] !== '') {
4569  [$tscPID] = ‪BackendUtility::getTSCpid($table, $uid, '');
4570  $TSConfig = ‪BackendUtility::getPagesTSconfig($tscPID)['TCEMAIN.'] ?? [];
4571  $tE = $this->‪getTableEntries($table, $TSConfig);
4572  if (!empty($TSConfig['translateToMessage']) && !$tE['disablePrependAtCopy']) {
4573  $translateToMsg = $this->‪getLanguageService()->‪sL($TSConfig['translateToMessage']);
4574  $translateToMsg = @sprintf($translateToMsg, $langRec['title']);
4575  }
4576 
4577  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processTranslateToClass'] ?? [] as $className) {
4578  $hookObj = GeneralUtility::makeInstance($className);
4579  if (method_exists($hookObj, 'processTranslateTo_copyAction')) {
4580  $hookObj->processTranslateTo_copyAction($row[$fN], $langRec, $this, $fN);
4581  }
4582  }
4583  if (!empty($translateToMsg)) {
4584  ‪$overrideValues[$fN] = '[' . $translateToMsg . '] ' . $row[$fN];
4585  } else {
4586  ‪$overrideValues[$fN] = $row[$fN];
4587  }
4588  }
4589  }
4590  }
4591 
4592  if ($table !== 'pages') {
4593  // Get the uid of record after which this localized record should be inserted
4594  $previousUid = $this->‪getPreviousLocalizedRecordUid($table, $uid, $row['pid'], $language);
4595  // Execute the copy:
4596  $newId = $this->‪copyRecord($table, $uid, -$previousUid, true, ‪$overrideValues, '', $language);
4597  $autoVersionNewId = $this->‪getAutoVersionId($table, $newId);
4598  if ($autoVersionNewId !== null) {
4599  $this->‪triggerRemapAction($table, $newId, [$this, 'placeholderShadowing'], [$table, $autoVersionNewId], true);
4600  }
4601  } else {
4602  // Create new page which needs to contain the same pid as the original page
4603  ‪$overrideValues['pid'] = $row['pid'];
4604  // Take over the hidden state of the original language state, this is done due to legacy reasons where-as
4605  // pages_language_overlay was set to "hidden -> default=0" but pages hidden -> default 1"
4606  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])) {
4607  $hiddenFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
4608  ‪$overrideValues[$hiddenFieldName] = $row[$hiddenFieldName] ?? ‪$GLOBALS['TCA'][$table]['columns'][$hiddenFieldName]['config']['default'];
4609  }
4610  $temporaryId = ‪StringUtility::getUniqueId('NEW');
4611  $copyTCE = $this->‪getLocalTCE();
4612  $copyTCE->start([$table => [$temporaryId => ‪$overrideValues]], [], $this->BE_USER);
4613  $copyTCE->process_datamap();
4614  // Getting the new UID as if it had been copied:
4615  $theNewSQLID = $copyTCE->substNEWwithIDs[$temporaryId];
4616  if ($theNewSQLID) {
4617  $this->copyMappingArray[$table][$uid] = $theNewSQLID;
4618  $newId = $theNewSQLID;
4619  }
4620  }
4621 
4622  return $newId;
4623  }
4624 
4641  protected function ‪inlineLocalizeSynchronize($table, $id, $command)
4642  {
4643  $parentRecord = ‪BackendUtility::getRecordWSOL($table, $id);
4644 
4645  // Backward-compatibility handling
4646  if (!is_array($command)) {
4647  // <field>, (localize | synchronize | <uid>):
4648  $parts = ‪GeneralUtility::trimExplode(',', $command);
4649  $command = [
4650  'field' => $parts[0],
4651  // The previous process expected $id to point to the localized record already
4652  'language' => (int)$parentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']]
4653  ];
4655  $command['action'] = $parts[1];
4656  } else {
4657  $command['ids'] = [$parts[1]];
4658  }
4659  }
4660 
4661  // In case the parent record is the default language record, fetch the localization
4662  if (empty($parentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']])) {
4663  // Fetch the live record
4664  // @todo: this needs to be revisited, as getRecordLocalization() does a BackendWorkspaceRestriction
4665  // based on $GLOBALS[BE_USER], which could differ from the $this->BE_USER->workspace value
4666  $parentRecordLocalization = ‪BackendUtility::getRecordLocalization($table, $id, $command['language'], 'AND t3ver_oid=0');
4667  if (empty($parentRecordLocalization)) {
4668  if ($this->enableLogging) {
4669  $this->‪log($table, $id, SystemLogGenericAction::UNDEFINED, 0, SystemLogErrorClassification::MESSAGE, 'Localization for parent record ' . $table . ':' . $id . '" cannot be fetched', -1, [], $this->‪eventPid($table, $id, $parentRecord['pid']));
4670  }
4671  return;
4672  }
4673  $parentRecord = $parentRecordLocalization[0];
4674  $id = $parentRecord['uid'];
4675  // Process overlay for current selected workspace
4676  ‪BackendUtility::workspaceOL($table, $parentRecord);
4677  }
4678 
4679  $field = $command['field'];
4680  $language = $command['language'];
4681  $action = $command['action'];
4682  $ids = $command['ids'] ?? [];
4683 
4684  if (!$field || !($action === 'localize' || $action === 'synchronize') && empty($ids) || !isset(‪$GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
4685  return;
4686  }
4687 
4688  $config = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'];
4689  $foreignTable = $config['foreign_table'];
4690 
4691  $transOrigPointer = (int)$parentRecord[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
4692  $childTransOrigPointerField = ‪$GLOBALS['TCA'][$foreignTable]['ctrl']['transOrigPointerField'];
4693 
4694  if (!$parentRecord || !is_array($parentRecord) || $language <= 0 || !$transOrigPointer) {
4695  return;
4696  }
4697 
4698  $inlineSubType = $this->‪getInlineFieldType($config);
4699  if ($inlineSubType === false) {
4700  return;
4701  }
4702 
4703  $transOrigRecord = ‪BackendUtility::getRecordWSOL($table, $transOrigPointer);
4704 
4705  $removeArray = [];
4706  $mmTable = $inlineSubType === 'mm' && isset($config['MM']) && $config['MM'] ? $config['MM'] : '';
4707  // Fetch children from original language parent:
4709  $dbAnalysisOriginal = $this->‪createRelationHandlerInstance();
4710  $dbAnalysisOriginal->start($transOrigRecord[$field], $foreignTable, $mmTable, $transOrigRecord['uid'], $table, $config);
4711  $elementsOriginal = [];
4712  foreach ($dbAnalysisOriginal->itemArray as $item) {
4713  $elementsOriginal[$item['id']] = $item;
4714  }
4715  unset($dbAnalysisOriginal);
4716  // Fetch children from current localized parent:
4718  $dbAnalysisCurrent = $this->‪createRelationHandlerInstance();
4719  $dbAnalysisCurrent->start($parentRecord[$field], $foreignTable, $mmTable, $id, $table, $config);
4720  // Perform synchronization: Possibly removal of already localized records:
4721  if ($action === 'synchronize') {
4722  foreach ($dbAnalysisCurrent->itemArray as $index => $item) {
4723  $childRecord = ‪BackendUtility::getRecordWSOL($item['table'], $item['id']);
4724  if (isset($childRecord[$childTransOrigPointerField]) && $childRecord[$childTransOrigPointerField] > 0) {
4725  $childTransOrigPointer = $childRecord[$childTransOrigPointerField];
4726  // If synchronization is requested, child record was translated once, but original record does not exist anymore, remove it:
4727  if (!isset($elementsOriginal[$childTransOrigPointer])) {
4728  unset($dbAnalysisCurrent->itemArray[$index]);
4729  $removeArray[$item['table']][$item['id']]['delete'] = 1;
4730  }
4731  }
4732  }
4733  }
4734  // Perform synchronization/localization: Possibly add unlocalized records for original language:
4735  if ($action === 'localize' || $action === 'synchronize') {
4736  foreach ($elementsOriginal as $originalId => $item) {
4737  if ($this->‪isRecordLocalized((string)$item['table'], (int)$item['id'], (int)$language)) {
4738  continue;
4739  }
4740  $item['id'] = $this->‪localize($item['table'], $item['id'], $language);
4741  $item['id'] = $this->‪overlayAutoVersionId($item['table'], $item['id']);
4742  $dbAnalysisCurrent->itemArray[] = $item;
4743  }
4744  } elseif (!empty($ids)) {
4745  foreach ($ids as $childId) {
4746  if (!‪MathUtility::canBeInterpretedAsInteger($childId) || !isset($elementsOriginal[$childId])) {
4747  continue;
4748  }
4749  $item = $elementsOriginal[$childId];
4750  if ($this->‪isRecordLocalized((string)$item['table'], (int)$item['id'], (int)$language)) {
4751  continue;
4752  }
4753  $item['id'] = $this->‪localize($item['table'], $item['id'], $language);
4754  $item['id'] = $this->‪overlayAutoVersionId($item['table'], $item['id']);
4755  $dbAnalysisCurrent->itemArray[] = $item;
4756  }
4757  }
4758  // Store the new values, we will set up the uids for the subtype later on (exception keep localization from original record):
4759  $value = implode(',', $dbAnalysisCurrent->getValueArray());
4760  $this->registerDBList[$table][$id][$field] = $value;
4761  // Remove child records (if synchronization requested it):
4762  if (is_array($removeArray) && !empty($removeArray)) {
4764  $tce = GeneralUtility::makeInstance(__CLASS__, $this->referenceIndexUpdater);
4765  $tce->enableLogging = ‪$this->enableLogging;
4766  $tce->start([], $removeArray, $this->BE_USER);
4767  $tce->process_cmdmap();
4768  unset($tce);
4769  }
4770  $updateFields = [];
4771  // Handle, reorder and store relations:
4772  if ($inlineSubType === 'list') {
4773  $updateFields = [$field => $value];
4774  } elseif ($inlineSubType === 'field') {
4775  $dbAnalysisCurrent->writeForeignField($config, $id);
4776  $updateFields = [$field => $dbAnalysisCurrent->countItems(false)];
4777  } elseif ($inlineSubType === 'mm') {
4778  $dbAnalysisCurrent->writeMM($config['MM'], $id);
4779  $updateFields = [$field => $dbAnalysisCurrent->countItems(false)];
4780  }
4781  // Update field referencing to child records of localized parent record:
4782  if (!empty($updateFields)) {
4783  $this->‪updateDB($table, $id, $updateFields);
4784  }
4785  }
4786 
4795  protected function ‪isRecordLocalized(string $table, int $uid, int $language): bool
4796  {
4797  $row = ‪BackendUtility::getRecordWSOL($table, $uid);
4798  $localizations = ‪BackendUtility::getRecordLocalization($table, $uid, $language, 'pid=' . (int)$row['pid']);
4799  return !empty($localizations);
4800  }
4801 
4802  /*********************************************
4803  *
4804  * Cmd: Deleting
4805  *
4806  ********************************************/
4814  public function ‪deleteAction($table, $id)
4815  {
4816  $recordToDelete = ‪BackendUtility::getRecord($table, $id);
4817  // Record asked to be deleted was found:
4818  if (is_array($recordToDelete)) {
4819  $recordWasDeleted = false;
4820  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'] ?? [] as $className) {
4821  $hookObj = GeneralUtility::makeInstance($className);
4822  if (method_exists($hookObj, 'processCmdmap_deleteAction')) {
4823  $hookObj->processCmdmap_deleteAction($table, $id, $recordToDelete, $recordWasDeleted, $this);
4824  }
4825  }
4826  // Delete the record if a hook hasn't deleted it yet
4827  if (!$recordWasDeleted) {
4828  $this->‪deleteEl($table, $id);
4829  }
4830  }
4831  }
4832 
4843  public function ‪deleteEl($table, $uid, $noRecordCheck = false, $forceHardDelete = false, bool $deleteRecordsOnPage = true)
4844  {
4845  if ($table === 'pages') {
4846  $this->‪deletePages($uid, $noRecordCheck, $forceHardDelete, $deleteRecordsOnPage);
4847  } else {
4848  $this->‪deleteVersionsForRecord($table, $uid, $forceHardDelete);
4849  $this->‪deleteRecord($table, $uid, $noRecordCheck, $forceHardDelete);
4850  }
4851  }
4852 
4861  public function ‪deleteVersionsForRecord($table, $uid, $forceHardDelete)
4862  {
4863  $versions = ‪BackendUtility::selectVersionsOfRecord($table, $uid, 'uid,pid,t3ver_wsid,t3ver_state', $this->BE_USER->workspace ?: null);
4864  if (is_array($versions)) {
4865  foreach ($versions as $verRec) {
4866  if (!$verRec['_CURRENT_VERSION']) {
4867  $currentUserWorkspace = null;
4868  if ((int)$verRec['t3ver_wsid'] !== (int)$this->BE_USER->workspace) {
4869  // If deleting records from 'foreign' / 'other' workspaces, the be user must be put into
4870  // this workspace temporarily so stuff like refindex updating is registered for this workspace
4871  // when deleting records in there.
4872  $currentUserWorkspace = $this->BE_USER->workspace;
4873  $this->BE_USER->workspace = (int)$verRec['t3ver_wsid'];
4874  }
4875  if ($table === 'pages') {
4876  $this->‪deletePages($verRec['uid'], true, $forceHardDelete);
4877  } else {
4878  $this->‪deleteRecord($table, $verRec['uid'], true, $forceHardDelete);
4879  }
4880  if ($currentUserWorkspace !== null) {
4881  // Switch back workspace
4882  $this->BE_USER->workspace = $currentUserWorkspace;
4883  }
4884  // Delete move-placeholder
4885  $versionState = ‪VersionState::cast($verRec['t3ver_state']);
4886  if ($versionState->equals(‪VersionState::MOVE_POINTER)) {
4887  $versionMovePlaceholder = ‪BackendUtility::getMovePlaceholder($table, $uid, 'uid', $verRec['t3ver_wsid']);
4888  if (!empty($versionMovePlaceholder)) {
4889  $this->‪deleteEl($table, $versionMovePlaceholder['uid'], true, $forceHardDelete);
4890  }
4891  }
4892  }
4893  }
4894  }
4895  }
4896 
4904  public function ‪undeleteRecord($table, $uid)
4905  {
4906  if ($this->‪isRecordUndeletable($table, $uid)) {
4907  $this->‪deleteRecord($table, $uid, true, false, true);
4908  }
4909  }
4910 
4924  public function ‪deleteRecord($table, $uid, $noRecordCheck = false, $forceHardDelete = false, $undeleteRecord = false)
4925  {
4926  $currentUserWorkspace = (int)$this->BE_USER->workspace;
4927  $uid = (int)$uid;
4928  if (!‪$GLOBALS['TCA'][$table] || !$uid) {
4929  $this->‪log($table, $uid, SystemLogDatabaseAction::DELETE, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to delete record without delete-permissions. [' . $this->BE_USER->errorMsg . ']');
4930  return;
4931  }
4932  // Skip processing already deleted records
4933  if (!$forceHardDelete && !$undeleteRecord && $this->‪hasDeletedRecord($table, $uid)) {
4934  return;
4935  }
4936 
4937  // Checking if there is anything else disallowing deleting the record by checking if editing is allowed
4938  $deletedRecord = $forceHardDelete || $undeleteRecord;
4939  $fullLanguageAccessCheck = true;
4940  if ($table === 'pages') {
4941  // If this is a page translation, the full language access check should not be done
4942  $defaultLanguagePageId = $this->‪getDefaultLanguagePageId($uid);
4943  if ($defaultLanguagePageId !== $uid) {
4944  $fullLanguageAccessCheck = false;
4945  }
4946  }
4947  $hasEditAccess = $this->BE_USER->recordEditAccessInternals($table, $uid, false, $deletedRecord, $fullLanguageAccessCheck);
4948  if (!$hasEditAccess) {
4949  $this->‪log($table, $uid, SystemLogDatabaseAction::DELETE, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to delete record without delete-permissions');
4950  return;
4951  }
4952  if ($table === 'pages') {
4953  $perms = ‪Permission::PAGE_DELETE;
4954  } elseif ($table === 'sys_file_reference' && array_key_exists('pages', $this->datamap)) {
4955  // @todo: find a more generic way to handle content relations of a page (without needing content editing access to that page)
4956  $perms = ‪Permission::PAGE_EDIT;
4957  } else {
4958  $perms = ‪Permission::CONTENT_EDIT;
4959  }
4960  if (!$noRecordCheck && !$this->‪doesRecordExist($table, $uid, $perms)) {
4961  return;
4962  }
4963 
4964  // Clear cache before deleting the record, else the correct page cannot be identified by clear_cache
4965  [$parentUid] = ‪BackendUtility::getTSCpid($table, $uid, '');
4966  $this->‪registerRecordIdForPageCacheClearing($table, $uid, $parentUid);
4967  $deleteField = ‪$GLOBALS['TCA'][$table]['ctrl']['delete'];
4968  $databaseErrorMessage = '';
4969  if ($deleteField && !$forceHardDelete) {
4970  $updateFields = [
4971  $deleteField => $undeleteRecord ? 0 : 1
4972  ];
4973  if (‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
4974  $updateFields[‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = ‪$GLOBALS['EXEC_TIME'];
4975  }
4976  // before (un-)deleting this record, check for child records or references
4977  $this->‪deleteRecord_procFields($table, $uid, $undeleteRecord);
4978  try {
4979  // Delete all l10n records as well, impossible during undelete because it might bring too many records back to life
4980  if (!$undeleteRecord) {
4981  $this->deletedRecords[$table][] = (int)$uid;
4982  $this->‪deleteL10nOverlayRecords($table, $uid);
4983  }
4984  GeneralUtility::makeInstance(ConnectionPool::class)
4985  ->getConnectionForTable($table)
4986  ->update($table, $updateFields, ['uid' => (int)$uid]);
4987  } catch (DBALException $e) {
4988  $databaseErrorMessage = $e->getPrevious()->getMessage();
4989  }
4990  } else {
4991  // Delete the hard way...:
4992  try {
4993  $this->‪hardDeleteSingleRecord($table, (int)$uid);
4994  $this->deletedRecords[$table][] = (int)$uid;
4995  $this->‪deleteL10nOverlayRecords($table, $uid);
4996  } catch (DBALException $e) {
4997  $databaseErrorMessage = $e->getPrevious()->getMessage();
4998  }
4999  }
5000  if ($this->enableLogging) {
5001  $state = $undeleteRecord ? SystemLogDatabaseAction::INSERT : SystemLogDatabaseAction::DELETE;
5002  if ($databaseErrorMessage === '') {
5003  if ($forceHardDelete) {
5004  $message = 'Record \'%s\' (%s) was deleted unrecoverable from page \'%s\' (%s)';
5005  } else {
5006  $message = $state === 1 ? 'Record \'%s\' (%s) was restored on page \'%s\' (%s)' : 'Record \'%s\' (%s) was deleted from page \'%s\' (%s)';
5007  }
5008  $propArr = $this->‪getRecordProperties($table, $uid);
5009  $pagePropArr = $this->‪getRecordProperties('pages', $propArr['pid']);
5010 
5011  $this->‪log($table, $uid, $state, 0, SystemLogErrorClassification::MESSAGE, $message, 0, [
5012  $propArr['header'],
5013  $table . ':' . $uid,
5014  $pagePropArr['header'],
5015  $propArr['pid']
5016  ], $propArr['event_pid']);
5017  } else {
5018  $this->‪log($table, $uid, $state, 0, SystemLogErrorClassification::TODAYS_SPECIAL, $databaseErrorMessage);
5019  }
5020  }
5021 
5022  // Add history entry
5023  if ($undeleteRecord) {
5024  $this->‪getRecordHistoryStore()->‪undeleteRecord($table, $uid, $this->correlationId);
5025  } else {
5026  $this->‪getRecordHistoryStore()->‪deleteRecord($table, $uid, $this->correlationId);
5027  }
5028 
5029  // Update reference index with table/uid on left side (recuid)
5030  $this->‪updateRefIndex($table, $uid);
5031  // Update reference index with table/uid on right side (ref_uid). Important if children of a relation are deleted / undeleted.
5032  $this->referenceIndexUpdater->registerUpdateForReferencesToItem($table, $uid, $currentUserWorkspace);
5033  }
5034 
5044  public function ‪deletePages($uid, $force = false, $forceHardDelete = false, bool $deleteRecordsOnPage = true)
5045  {
5046  $uid = (int)$uid;
5047  if ($uid === 0) {
5048  if ($this->enableLogging) {
5049  $this->‪log('pages', $uid, SystemLogGenericAction::UNDEFINED, 0, SystemLogErrorClassification::SYSTEM_ERROR, 'Deleting all pages starting from the root-page is disabled.', -1, [], 0);
5050  }
5051  return;
5052  }
5053  // Getting list of pages to delete:
5054  if ($force) {
5055  // Returns the branch WITHOUT permission checks (0 secures that), so it cannot return -1
5056  $pageIdsInBranch = $this->‪doesBranchExist('', $uid, 0, true);
5057  $res = ‪GeneralUtility::intExplode(',', $pageIdsInBranch . $uid, true);
5058  } else {
5059  $res = $this->‪canDeletePage($uid);
5060  }
5061  // Perform deletion if not error:
5062  if (is_array($res)) {
5063  foreach ($res as $deleteId) {
5064  $this->‪deleteSpecificPage($deleteId, $forceHardDelete, $deleteRecordsOnPage);
5065  }
5066  } else {
5067  $this->‪log(
5068  'pages',
5069  $uid,
5070  SystemLogGenericAction::UNDEFINED,
5071  0,
5072  SystemLogErrorClassification::SYSTEM_ERROR,
5073  $res,
5074  -1,
5075  [$res]
5076  );
5077  }
5078  }
5079 
5089  public function ‪deleteSpecificPage($uid, $forceHardDelete = false, bool $deleteRecordsOnPage = true)
5090  {
5091  $uid = (int)$uid;
5092  if (!$uid) {
5093  // Early void return on invalid uid
5094  return;
5095  }
5096  $forceHardDelete = (bool)$forceHardDelete;
5097 
5098  // Delete either a default language page or a translated page
5099  $pageIdInDefaultLanguage = $this->‪getDefaultLanguagePageId($uid);
5100  $isPageTranslation = false;
5101  $pageLanguageId = 0;
5102  if ($pageIdInDefaultLanguage !== $uid) {
5103  // For translated pages, translated records in other tables (eg. tt_content) for the
5104  // to-delete translated page have their pid field set to the uid of the default language record,
5105  // NOT the uid of the translated page record.
5106  // If a translated page is deleted, only translations of records in other tables of this language
5107  // should be deleted. The code checks if the to-delete page is a translated page and
5108  // adapts the query for other tables to use the uid of the default language page as pid together
5109  // with the language id of the translated page.
5110  $isPageTranslation = true;
5111  $pageLanguageId = $this->‪pageInfo($uid, ‪$GLOBALS['TCA']['pages']['ctrl']['languageField']);
5112  }
5113 
5114  if ($deleteRecordsOnPage) {
5115  $tableNames = $this->‪compileAdminTables();
5116  foreach ($tableNames as $table) {
5117  if ($table === 'pages' || ($isPageTranslation && !‪BackendUtility::isTableLocalizable($table))) {
5118  // Skip pages table. And skip table if not translatable, but a translated page is deleted
5119  continue;
5120  }
5121 
5122  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
5123  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
5124  $queryBuilder
5125  ->select('uid')
5126  ->from($table);
5127 
5128  if ($isPageTranslation) {
5129  // Only delete records in the specified language
5130  $queryBuilder->where(
5131  $queryBuilder->expr()->eq(
5132  'pid',
5133  $queryBuilder->createNamedParameter($pageIdInDefaultLanguage, \PDO::PARAM_INT)
5134  ),
5135  $queryBuilder->expr()->eq(
5136  ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'],
5137  $queryBuilder->createNamedParameter($pageLanguageId, \PDO::PARAM_INT)
5138  )
5139  );
5140  } else {
5141  // Delete all records on this page
5142  $queryBuilder->where(
5143  $queryBuilder->expr()->eq(
5144  'pid',
5145  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
5146  )
5147  );
5148  }
5149 
5150  $currentUserWorkspace = (int)$this->BE_USER->workspace;
5151  ‪if ($currentUserWorkspace !== 0 && ‪BackendUtility::isTableWorkspaceEnabled($table)) {
5152  // If we are in a workspace, make sure only records of this workspace are deleted.
5153  $queryBuilder->andWhere(
5154  $queryBuilder->expr()->eq(
5155  't3ver_wsid',
5156  $queryBuilder->createNamedParameter($currentUserWorkspace, \PDO::PARAM_INT)
5157  )
5158  );
5159  }
5160 
5161  $statement = $queryBuilder->execute();
5162 
5163  while ($row = $statement->fetch()) {
5164  // Handle a detail related to workspace placeholder records, delete any
5165  // further workspace overlays for the record in question, then delete the record.
5166  $this->‪copyMovedRecordToNewLocation($table, $row['uid']);
5167  $this->‪deleteVersionsForRecord($table, $row['uid'], $forceHardDelete);
5168  $this->‪deleteRecord($table, $row['uid'], true, $forceHardDelete);
5169  }
5170  }
5171  }
5172 
5173  // Handle a detail related to workspace placeholder records, delete any
5174  // further workspace overlays for the page in question, then delete the page.
5175  $this->‪copyMovedRecordToNewLocation('pages', $uid);
5176  $this->‪deleteVersionsForRecord('pages', $uid, $forceHardDelete);
5177  $this->‪deleteRecord('pages', $uid, true, $forceHardDelete);
5178  }
5179 
5192  protected function ‪copyMovedRecordToNewLocation($table, $uid)
5193  {
5194  if ($this->BE_USER->workspace > 0) {
5195  $originalRecord = ‪BackendUtility::getRecord($table, $uid);
5196  $movePlaceholder = ‪BackendUtility::getMovePlaceholder($table, $uid);
5197  // Check whether target page to copied to is different to current page
5198  // Cloning on the same page is superfluous and does not help at all
5199  if (!empty($originalRecord) && !empty($movePlaceholder) && (int)$originalRecord['pid'] !== (int)$movePlaceholder['pid']) {
5200  // If move placeholder exists, copy to new location
5201  // This will create a New placeholder on the new location
5202  // and a version for this new placeholder
5203  $command = [
5204  $table => [
5205  $uid => [
5206  'copy' => '-' . $movePlaceholder['uid']
5207  ]
5208  ]
5209  ];
5211  $dataHandler = GeneralUtility::makeInstance(__CLASS__, $this->referenceIndexUpdater);
5212  $dataHandler->enableLogging = ‪$this->enableLogging;
5213  $dataHandler->neverHideAtCopy = true;
5214  $dataHandler->start([], $command, $this->BE_USER);
5215  $dataHandler->process_cmdmap();
5216  unset($dataHandler);
5217 
5218  // Delete move placeholder
5219  $this->‪deleteRecord($table, $movePlaceholder['uid'], true, true);
5220  }
5221  }
5222  }
5223 
5231  public function ‪canDeletePage($uid)
5232  {
5233  $uid = (int)$uid;
5234  $isTranslatedPage = null;
5235 
5236  // If we may at all delete this page
5237  // If this is a page translation, do the check against the perms_* of the default page
5238  // Because it is currently only deleting the translation
5239  $defaultLanguagePageId = $this->‪getDefaultLanguagePageId($uid);
5240  if ($defaultLanguagePageId !== $uid) {
5241  if ($this->‪doesRecordExist('pages', (int)$defaultLanguagePageId, ‪Permission::PAGE_DELETE)) {
5242  $isTranslatedPage = true;
5243  } else {
5244  return 'Attempt to delete page without permissions';
5245  }
5246  } elseif (!$this->‪doesRecordExist('pages', $uid, ‪Permission::PAGE_DELETE)) {
5247  return 'Attempt to delete page without permissions';
5248  }
5249 
5250  $pageIdsInBranch = $this->‪doesBranchExist('', $uid, ‪Permission::PAGE_DELETE, true);
5251 
5252  if ($this->deleteTree) {
5253  if ($pageIdsInBranch === -1) {
5254  return 'Attempt to delete pages in branch without permissions';
5255  }
5256 
5257  $pagesInBranch = ‪GeneralUtility::intExplode(',', $pageIdsInBranch . $uid, true);
5258  } else {
5259  if ($pageIdsInBranch === -1) {
5260  return 'Attempt to delete page without permissions';
5261  }
5262  if ($pageIdsInBranch !== '') {
5263  return 'Attempt to delete page which has subpages';
5264  }
5265 
5266  $pagesInBranch = [$uid];
5267  }
5268 
5269  if ($disallowedTables = $this->‪checkForRecordsFromDisallowedTables($pagesInBranch)) {
5270  return 'Attempt to delete records from disallowed tables (' . implode(', ', $disallowedTables) . ')';
5271  }
5272 
5273  foreach ($pagesInBranch as $pageInBranch) {
5274  if (!$this->BE_USER->recordEditAccessInternals('pages', $pageInBranch, false, false, $isTranslatedPage ? false : true)) {
5275  return 'Attempt to delete page which has prohibited localizations.';
5276  }
5277  }
5278  return $pagesInBranch;
5279  }
5280 
5289  public function ‪cannotDeleteRecord($table, $id)
5290  {
5291  if ($table === 'pages') {
5292  $res = $this->‪canDeletePage($id);
5293  return is_array($res) ? false : $res;
5294  }
5295  if ($table === 'sys_file_reference' && array_key_exists('pages', $this->datamap)) {
5296  // @todo: find a more generic way to handle content relations of a page (without needing content editing access to that page)
5297  $perms = ‪Permission::PAGE_EDIT;
5298  } else {
5300  }
5301  return $this->‪doesRecordExist($table, $id, $perms) ? false : 'No permission to delete record';
5302  }
5303 
5312  public function ‪isRecordUndeletable($table, $uid)
5313  {
5314  $result = false;
5315  $record = ‪BackendUtility::getRecord($table, $uid, 'pid', '', false);
5316  if ($record['pid']) {
5317  $page = ‪BackendUtility::getRecord('pages', $record['pid'], 'deleted, title, uid', '', false);
5318  // The page containing the record is not deleted, thus the record can be undeleted:
5319  if (!$page['deleted']) {
5320  $result = true;
5321  } else {
5322  $this->‪log($table, $uid, SystemLogDatabaseAction::DELETE, '', SystemLogErrorClassification::USER_ERROR, 'Record cannot be undeleted since the page containing it is deleted! Undelete page "' . $page['title'] . ' (UID: ' . $page['uid'] . ')" first');
5323  }
5324  } else {
5325  // The page containing the record is on rootlevel, so there is no parent record to check, and the record can be undeleted:
5326  $result = true;
5327  }
5328  return $result;
5329  }
5330 
5341  public function ‪deleteRecord_procFields($table, $uid, $undeleteRecord = false)
5342  {
5343  $conf = ‪$GLOBALS['TCA'][$table]['columns'];
5344  $row = ‪BackendUtility::getRecord($table, $uid, '*', '', false);
5345  if (empty($row)) {
5346  return;
5347  }
5348  foreach ($row as $field => $value) {
5349  $this->‪deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf[$field]['config'], $undeleteRecord);
5350  }
5351  }
5352 
5366  public function ‪deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf, $undeleteRecord = false)
5367  {
5368  if ($conf['type'] === 'inline') {
5369  $foreign_table = $conf['foreign_table'];
5370  if ($foreign_table) {
5371  $inlineType = $this->‪getInlineFieldType($conf);
5372  if ($inlineType === 'list' || $inlineType === 'field') {
5374  $dbAnalysis = $this->‪createRelationHandlerInstance();
5375  $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
5376  $dbAnalysis->undeleteRecord = true;
5377 
5378  $enableCascadingDelete = true;
5379  // non type save comparison is intended!
5380  if (isset($conf['behaviour']['enableCascadingDelete']) && $conf['behaviour']['enableCascadingDelete'] == false) {
5381  $enableCascadingDelete = false;
5382  }
5383 
5384  // Walk through the items and remove them
5385  foreach ($dbAnalysis->itemArray as $v) {
5386  if (!$undeleteRecord) {
5387  if ($enableCascadingDelete) {
5388  $this->‪deleteAction($v['table'], $v['id']);
5389  }
5390  } else {
5391  $this->‪undeleteRecord($v['table'], $v['id']);
5392  }
5393  }
5394  }
5395  }
5396  } elseif ($this->‪isReferenceField($conf)) {
5397  $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
5398  $dbAnalysis = $this->‪createRelationHandlerInstance();
5399  $dbAnalysis->start($value, $allowedTables, $conf['MM'], $uid, $table, $conf);
5400  foreach ($dbAnalysis->itemArray as $v) {
5401  $this->‪updateRefIndex($v['table'], $v['id']);
5402  }
5403  }
5404  }
5405 
5413  public function ‪deleteL10nOverlayRecords($table, $uid)
5414  {
5415  // Check whether table can be localized
5417  return;
5418  }
5419 
5420  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
5421  $queryBuilder->getRestrictions()
5422  ->removeAll()
5423  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
5424  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->BE_USER->workspace));
5425 
5426  $queryBuilder->select('*')
5427  ->from($table)
5428  ->where(
5429  $queryBuilder->expr()->eq(
5430  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
5431  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
5432  )
5433  );
5434 
5435  $result = $queryBuilder->execute();
5436  while ($record = $result->fetch()) {
5437  // Ignore workspace delete placeholders. Those records have been marked for
5438  // deletion before - deleting them again in a workspace would revert that state.
5439  if ((int)$this->BE_USER->workspace > 0 && ‪BackendUtility::isTableWorkspaceEnabled($table)) {
5440  ‪BackendUtility::workspaceOL($table, $record, $this->BE_USER->workspace);
5441  if (‪VersionState::cast($record['t3ver_state'])->equals(‪VersionState::DELETE_PLACEHOLDER)) {
5442  continue;
5443  }
5444  }
5445  $this->‪deleteAction($table, (int)$record['t3ver_oid'] > 0 ? (int)$record['t3ver_oid'] : (int)$record['uid']);
5446  }
5447  }
5448 
5449  /*********************************************
5450  *
5451  * Cmd: Workspace discard & flush
5452  *
5453  ********************************************/
5454 
5467  public function ‪discard(string $table, ?int $uid, array $record = null): void
5468  {
5469  if ($uid === null && $record === null) {
5470  throw new \RuntimeException('Either record $uid or $record row must be given', 1600373491);
5471  }
5472 
5473  // Fetch record we are dealing with if not given
5474  if ($record === null) {
5475  $record = ‪BackendUtility::getRecord($table, $uid);
5476  }
5477  if (!is_array($record)) {
5478  return;
5479  }
5480  $uid = (int)$record['uid'];
5481 
5482  // Call hook and return if hook took care of the element
5483  $recordWasDiscarded = false;
5484  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'] ?? [] as $className) {
5485  $hookObj = GeneralUtility::makeInstance($className);
5486  if (method_exists($hookObj, 'processCmdmap_discardAction')) {
5487  $hookObj->processCmdmap_discardAction($table, $uid, $record, $recordWasDiscarded);
5488  }
5489  }
5490 
5491  $userWorkspace = (int)$this->BE_USER->workspace;
5492  ‪if ($recordWasDiscarded
5493  || $userWorkspace === 0
5495  || $this->‪hasDeletedRecord($table, $uid)
5496  ) {
5497  return;
5498  }
5499 
5500  // Gather versioned, live and placeholder record if there are any
5501  $liveRecord = null;
5502  $versionRecord = null;
5503  $placeholderRecord = null;
5504  if ((int)$record['t3ver_wsid'] === 0) {
5505  $liveRecord = $record;
5506  $record = ‪BackendUtility::getWorkspaceVersionOfRecord($userWorkspace, $table, $uid);
5507  }
5508  if (!is_array($record)) {
5509  return;
5510  }
5511  $recordState = ‪VersionState::cast($record['t3ver_state']);
5512  if ($recordState->equals(‪VersionState::NEW_PLACEHOLDER)) {
5513  $placeholderRecord = $record;
5514  $versionRecord = ‪BackendUtility::getWorkspaceVersionOfRecord($userWorkspace, $table, $uid);
5515  if (!is_array($versionRecord)) {
5516  return;
5517  }
5518  } elseif ($recordState->equals(‪VersionState::NEW_PLACEHOLDER_VERSION)) {
5519  $versionRecord = $record;
5520  $placeholderRecord = ‪BackendUtility::getLiveVersionOfRecord($table, $uid);
5521  if (!is_array($placeholderRecord)) {
5522  return;
5523  }
5524  } elseif ($recordState->equals(‪VersionState::MOVE_POINTER)) {
5525  $versionRecord = $record;
5526  $liveRecord = $liveRecord ?: ‪BackendUtility::getLiveVersionOfRecord($table, $uid);
5527  if (!is_array($liveRecord)) {
5528  return;
5529  }
5530  $placeholderRecord = ‪BackendUtility::getMovePlaceholder($table, $liveRecord['uid']);
5531  if (!is_array($placeholderRecord)) {
5532  return;
5533  }
5534  } else {
5535  $versionRecord = $record;
5536  $liveRecord = $liveRecord ?: ‪BackendUtility::getLiveVersionOfRecord($table, $uid);
5537  if (!is_array($liveRecord)) {
5538  return;
5539  }
5540  }
5541  // Do not use $record, $recordState and $uid below anymore, rely on $versionRecord, $liveRecord and $placeholderRecord
5542 
5543  // User access checks
5544  if ($userWorkspace !== (int)$versionRecord['t3ver_wsid']) {
5545  $this->‪newlog('Attempt to discard workspace record ' . $table . ':' . $versionRecord['uid'] . ' failed: Different workspace', SystemLogErrorClassification::USER_ERROR);
5546  return;
5547  }
5548  if ($errorCode = $this->BE_USER->workspaceCannotEditOfflineVersion($table, $versionRecord['uid'])) {
5549  $this->‪newlog('Attempt to discard workspace record ' . $table . ':' . $versionRecord['uid'] . ' failed: ' . $errorCode, SystemLogErrorClassification::USER_ERROR);
5550  return;
5551  }
5552  if (!$this->‪checkRecordUpdateAccess($table, $versionRecord['uid'])) {
5553  $this->‪newlog('Attempt to discard workspace record ' . $table . ':' . $versionRecord['uid'] . ' failed: User has no edit access', SystemLogErrorClassification::USER_ERROR);
5554  return;
5555  }
5556  $fullLanguageAccessCheck = !($table === 'pages' && (int)$versionRecord[‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']] !== 0);
5557  if (!$this->BE_USER->recordEditAccessInternals($table, $versionRecord, false, true, $fullLanguageAccessCheck)) {
5558  $this->‪newlog('Attempt to discard workspace record ' . $table . ':' . $versionRecord['uid'] . ' failed: User has no delete access', SystemLogErrorClassification::USER_ERROR);
5559  return;
5560  }
5561 
5562  // Perform discard operations
5563  $versionState = ‪VersionState::cast($versionRecord['t3ver_state']);
5564  if ($table === 'pages' && $versionState->equals(‪VersionState::NEW_PLACEHOLDER_VERSION)) {
5565  // When discarding a new page page, there can be new sub pages and new records.
5566  // Those need to be discarded, otherwise they'd end up as records without parent page.
5567  $this->‪discardSubPagesAndRecordsOnPage($versionRecord);
5568  }
5569 
5570  $this->‪discardLocalizationOverlayRecords($table, $versionRecord);
5571  $this->‪discardRecordRelations($table, $versionRecord);
5572  $this->‪hardDeleteSingleRecord($table, (int)$versionRecord['uid']);
5573  $this->deletedRecords[$table][] = (int)$versionRecord['uid'];
5574  $this->‪registerReferenceIndexRowsForDrop($table, (int)$versionRecord['uid'], $userWorkspace);
5575  $this->‪getRecordHistoryStore()->‪deleteRecord($table, (int)$versionRecord['uid'], $this->correlationId);
5576  $this->‪log(
5577  $table,
5578  (int)$versionRecord['uid'],
5579  SystemLogDatabaseAction::DELETE,
5580  0,
5581  SystemLogErrorClassification::MESSAGE,
5582  'Record ' . $table . ':' . $versionRecord['uid'] . ' was deleted unrecoverable from page ' . $versionRecord['pid'],
5583  0,
5584  [],
5585  (int)$versionRecord['pid']
5586  );
5587 
5588  if ($versionState->equals(‪VersionState::MOVE_POINTER)
5589  || $versionState->equals(‪VersionState::NEW_PLACEHOLDER_VERSION)
5590  ) {
5591  // Drop placeholder records if any
5592  $this->‪hardDeleteSingleRecord($table, (int)$placeholderRecord['uid']);
5593  $this->deletedRecords[$table][] = (int)$placeholderRecord['uid'];
5594  }
5595  }
5596 
5603  protected function ‪discardSubPagesAndRecordsOnPage(array $page): void
5604  {
5605  $isLocalizedPage = false;
5606  $sysLanguageId = (int)$page[‪$GLOBALS['TCA']['pages']['ctrl']['languageField']];
5607  if ($sysLanguageId > 0) {
5608  // New or moved localized page.
5609  // Discard records on this page localization, but no sub pages.
5610  // Records of a translated page have the pid set to the default language page uid. Found in l10n_parent.
5611  // @todo: Discard other page translations that inherit from this?! (l10n_source field)
5612  $isLocalizedPage = true;
5613  $pid = (int)$page[‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']];
5614  } else {
5615  // New or moved default language page.
5616  // Discard any sub pages and all other records of this page, including any page localizations.
5617  // The t3ver_state=-1 record is incoming here. Records on this page have their pid field set to the uid
5618  // of the t3ver_state=1 record, which is in the t3ver_oid field of the incoming record.
5619  $pid = (int)$page['t3ver_oid'];
5620  }
5621  $tables = $this->‪compileAdminTables();
5622  foreach ($tables as $table) {
5623  if (($isLocalizedPage && $table === 'pages')
5624  || ($isLocalizedPage && !‪BackendUtility::isTableLocalizable($table))
5626  ) {
5627  continue;
5628  }
5629  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
5630  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
5631  $queryBuilder->select('*')
5632  ->from($table)
5633  ->where(
5634  $queryBuilder->expr()->eq(
5635  'pid',
5636  $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)
5637  ),
5638  $queryBuilder->expr()->eq(
5639  't3ver_wsid',
5640  $queryBuilder->createNamedParameter((int)$this->BE_USER->workspace, \PDO::PARAM_INT)
5641  )
5642  );
5643  if ($isLocalizedPage) {
5644  // Add sys_language_uid = x restriction if discarding a localized page
5645  $queryBuilder->andWhere(
5646  $queryBuilder->expr()->eq(
5647  ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'],
5648  $queryBuilder->createNamedParameter($sysLanguageId, \PDO::PARAM_INT)
5649  )
5650  );
5651  }
5652  $statement = $queryBuilder->execute();
5653  while ($row = $statement->fetch()) {
5654  $this->‪discard($table, null, $row);
5655  }
5656  }
5657  }
5658 
5665  protected function ‪discardRecordRelations(string $table, array $record): void
5666  {
5667  foreach ($record as $field => $value) {
5668  $fieldConfig = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'] ?? null;
5669  if (!isset($fieldConfig['type'])) {
5670  continue;
5671  }
5672  if ($fieldConfig['type'] === 'inline') {
5673  $foreignTable = $fieldConfig['foreign_table'] ?? null;
5674  if (!$foreignTable
5675  || (isset($fieldConfig['behaviour']['enableCascadingDelete'])
5676  && (bool)$fieldConfig['behaviour']['enableCascadingDelete'] === false)
5677  ) {
5678  continue;
5679  }
5680  $inlineType = $this->‪getInlineFieldType($fieldConfig);
5681  if ($inlineType === 'list' || $inlineType === 'field') {
5682  $dbAnalysis = $this->‪createRelationHandlerInstance();
5683  $dbAnalysis->start($value, $fieldConfig['foreign_table'], '', (int)$record['uid'], $table, $fieldConfig);
5684  $dbAnalysis->undeleteRecord = true;
5685  foreach ($dbAnalysis->itemArray as $relationRecord) {
5686  $this->‪discard($relationRecord['table'], (int)$relationRecord['id']);
5687  }
5688  }
5689  } elseif ($this->‪isReferenceField($fieldConfig) && !empty($fieldConfig['MM'])) {
5690  $this->‪discardMmRelations($table, $fieldConfig, $record);
5691  }
5692  // @todo not inline and not mm - probably not handled correctly and has no proper test coverage yet
5693  }
5694  }
5695 
5704  protected function ‪discardMmRelations(string $table, array $fieldConfig, array $record): void
5705  {
5706  $recordUid = (int)$record['uid'];
5707  $mmTableName = $fieldConfig['MM'];
5708  // left - non foreign - uid_local vs. right - foreign - uid_foreign decision
5709  $relationUidFieldName = isset($fieldConfig['MM_opposite_field']) ? 'uid_foreign' : 'uid_local';
5710  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($mmTableName);
5711  $queryBuilder->delete($mmTableName)->where(
5712  // uid_local = given uid OR uid_foreign = given uid
5713  $queryBuilder->expr()->eq($relationUidFieldName, $queryBuilder->createNamedParameter($recordUid, \PDO::PARAM_INT))
5714  );
5715  if (!empty($fieldConfig['MM_table_where']) && is_string($fieldConfig['MM_table_where'])) {
5716  $queryBuilder->andWhere(
5717  ‪QueryHelper::stripLogicalOperatorPrefix(str_replace('###THIS_UID###', (string)$recordUid, $fieldConfig['MM_table_where']))
5718  );
5719  }
5720  $mmMatchFields = $fieldConfig['MM_match_fields'] ?? [];
5721  foreach ($mmMatchFields as $fieldName => $fieldValue) {
5722  $queryBuilder->andWhere(
5723  $queryBuilder->expr()->eq($fieldName, $queryBuilder->createNamedParameter($fieldValue, \PDO::PARAM_STR))
5724  );
5725  }
5726  $queryBuilder->execute();
5727  }
5728 
5735  protected function ‪discardLocalizationOverlayRecords(string $table, array $record): void
5736  {
5738  return;
5739  }
5740  $uid = (int)$record['uid'];
5741  $versionState = ‪VersionState::cast($record['t3ver_state']);
5742  if ($versionState->equals(‪VersionState::NEW_PLACEHOLDER_VERSION)) {
5743  // The t3ver_state=-1 record is incoming here. Localization overlays of this record have their uid field set
5744  // to the uid of the t3ver_state=1 record, which is in the t3ver_oid field of the incoming record.
5745  $uid = (int)$record['t3ver_oid'];
5746  }
5747  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
5748  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
5749  $statement = $queryBuilder->select('*')
5750  ->from($table)
5751  ->where(
5752  $queryBuilder->expr()->eq(
5753  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
5754  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
5755  ),
5756  $queryBuilder->expr()->eq(
5757  't3ver_wsid',
5758  $queryBuilder->createNamedParameter((int)$this->BE_USER->workspace, \PDO::PARAM_INT)
5759  )
5760  )
5761  ->execute();
5762  while ($record = $statement->fetch()) {
5763  $this->‪discard($table, null, $record);
5764  }
5765  }
5766 
5767  /*********************************************
5768  *
5769  * Cmd: Versioning
5770  *
5771  ********************************************/
5784  public function ‪versionizeRecord($table, $id, $label, $delete = false)
5785  {
5786  $id = (int)$id;
5787  // Stop any actions if the record is marked to be deleted:
5788  // (this can occur if IRRE elements are versionized and child elements are removed)
5789  if ($this->‪isElementToBeDeleted($table, $id)) {
5790  return null;
5791  }
5792  if (!‪BackendUtility::isTableWorkspaceEnabled($table) || $id <= 0) {
5793  $this->‪newlog('Versioning is not supported for this table "' . $table . '" / ' . $id, SystemLogErrorClassification::USER_ERROR);
5794  return null;
5795  }
5796 
5797  // Fetch record with permission check
5798  $row = $this->‪recordInfoWithPermissionCheck($table, $id, ‪Permission::PAGE_SHOW);
5799 
5800  // This checks if the record can be selected which is all that a copy action requires.
5801  if ($row === false) {
5802  $this->‪newlog(
5803  'The record does not exist or you don\'t have correct permissions to make a new version (copy) of this record "' . $table . ':' . $id . '"',
5804  SystemLogErrorClassification::USER_ERROR
5805  );
5806  return null;
5807  }
5808 
5809  // Record must be online record, otherwise we would create a version of a version
5810  if (($row['t3ver_oid'] ?? 0) > 0) {
5811  $this->‪newlog('Record "' . $table . ':' . $id . '" you wanted to versionize was already a version in archive (record has an online ID)!', SystemLogErrorClassification::USER_ERROR);
5812  return null;
5813  }
5814 
5815  // Record must not be placeholder for moving.
5816  if (‪VersionState::cast($row['t3ver_state'])->equals(‪VersionState::MOVE_PLACEHOLDER)) {
5817  $this->‪newlog('Record cannot be versioned because it is a placeholder for a moving operation', SystemLogErrorClassification::USER_ERROR);
5818  return null;
5819  }
5820 
5821  if ($delete && $this->‪cannotDeleteRecord($table, $id)) {
5822  $this->‪newlog('Record cannot be deleted: ' . $this->‪cannotDeleteRecord($table, $id), SystemLogErrorClassification::USER_ERROR);
5823  return null;
5824  }
5825 
5826  // Set up the values to override when making a raw-copy:
5827  $overrideArray = [
5828  't3ver_oid' => $id,
5829  't3ver_wsid' => $this->BE_USER->workspace,
5830  't3ver_state' => (string)($delete ? new VersionState(‪VersionState::DELETE_PLACEHOLDER) : new VersionState(‪VersionState::DEFAULT_STATE)),
5831  't3ver_count' => 0,
5832  't3ver_stage' => 0,
5833  't3ver_tstamp' => 0
5834  ];
5835  if (‪$GLOBALS['TCA'][$table]['ctrl']['editlock']) {
5836  $overrideArray[‪$GLOBALS['TCA'][$table]['ctrl']['editlock']] = 0;
5837  }
5838  // Checking if the record already has a version in the current workspace of the backend user
5839  $versionRecord = ['uid' => null];
5840  if ($this->BE_USER->workspace !== 0) {
5841  // Look for version already in workspace:
5842  $versionRecord = ‪BackendUtility::getWorkspaceVersionOfRecord($this->BE_USER->workspace, $table, $id, 'uid');
5843  }
5844  // Create new version of the record and return the new uid
5845  if (empty($versionRecord['uid'])) {
5846  // Create raw-copy and return result:
5847  // The information of the label to be used for the workspace record
5848  // as well as the information whether the record shall be removed
5849  // must be forwarded (creating remove placeholders on a workspace are
5850  // done by copying the record and override several fields).
5851  $workspaceOptions = [
5852  'delete' => $delete,
5853  'label' => $label,
5854  ];
5855  return $this->‪copyRecord_raw($table, $id, (int)$row['pid'], $overrideArray, $workspaceOptions);
5856  }
5857  // Reuse the existing record and return its uid
5858  // (prior to TYPO3 CMS 6.2, an error was thrown here, which
5859  // did not make much sense since the information is available)
5860  return $versionRecord['uid'];
5861  }
5862 
5872  public function ‪version_remapMMForVersionSwap($table, $id, $swapWith)
5873  {
5874  // Actually, selecting the records fully is only need if flexforms are found inside... This could be optimized ...
5875  $currentRec = ‪BackendUtility::getRecord($table, $id);
5876  $swapRec = ‪BackendUtility::getRecord($table, $swapWith);
5877  $this->version_remapMMForVersionSwap_reg = [];
5878  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
5879  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $fConf) {
5880  $conf = $fConf['config'];
5881  if ($this->‪isReferenceField($conf)) {
5882  $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
5883  $prependName = $conf['type'] === 'group' ? $conf['prepend_tname'] : '';
5884  if ($conf['MM']) {
5886  $dbAnalysis = $this->‪createRelationHandlerInstance();
5887  $dbAnalysis->start('', $allowedTables, $conf['MM'], $id, $table, $conf);
5888  if (!empty($dbAnalysis->getValueArray($prependName))) {
5889  $this->version_remapMMForVersionSwap_reg[$id][$field] = [$dbAnalysis, $conf['MM'], $prependName];
5890  }
5892  $dbAnalysis = $this->‪createRelationHandlerInstance();
5893  $dbAnalysis->start('', $allowedTables, $conf['MM'], $swapWith, $table, $conf);
5894  if (!empty($dbAnalysis->getValueArray($prependName))) {
5895  $this->version_remapMMForVersionSwap_reg[$swapWith][$field] = [$dbAnalysis, $conf['MM'], $prependName];
5896  }
5897  }
5898  } elseif ($conf['type'] === 'flex') {
5899  // Current record
5900  $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
5901  $fConf,
5902  $table,
5903  $field,
5904  $currentRec
5905  );
5906  $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
5907  $currentValueArray = ‪GeneralUtility::xml2array($currentRec[$field]);
5908  if (is_array($currentValueArray)) {
5909  $this->‪checkValue_flex_procInData($currentValueArray['data'], [], [], $dataStructureArray, [$table, $id, $field], 'version_remapMMForVersionSwap_flexFormCallBack');
5910  }
5911  // Swap record
5912  $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
5913  $fConf,
5914  $table,
5915  $field,
5916  $swapRec
5917  );
5918  $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
5919  $currentValueArray = ‪GeneralUtility::xml2array($swapRec[$field]);
5920  if (is_array($currentValueArray)) {
5921  $this->‪checkValue_flex_procInData($currentValueArray['data'], [], [], $dataStructureArray, [$table, $swapWith, $field], 'version_remapMMForVersionSwap_flexFormCallBack');
5922  }
5923  }
5924  }
5925  // Execute:
5926  $this->‪version_remapMMForVersionSwap_execSwap($table, $id, $swapWith);
5927  }
5928 
5942  public function ‪version_remapMMForVersionSwap_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)
5943  {
5944  // Extract parameters:
5945  [$table, $uid, $field] = $pParams;
5946  if ($this->‪isReferenceField($dsConf)) {
5947  $allowedTables = $dsConf['type'] === 'group' ? $dsConf['allowed'] : $dsConf['foreign_table'];
5948  $prependName = $dsConf['type'] === 'group' ? $dsConf['prepend_tname'] : '';
5949  if ($dsConf['MM']) {
5951  $dbAnalysis = $this->‪createRelationHandlerInstance();
5952  $dbAnalysis->start('', $allowedTables, $dsConf['MM'], $uid, $table, $dsConf);
5953  $this->version_remapMMForVersionSwap_reg[$uid][$field . '/' . $path] = [$dbAnalysis, $dsConf['MM'], $prependName];
5954  }
5955  }
5956  }
5957 
5968  public function ‪version_remapMMForVersionSwap_execSwap($table, $id, $swapWith)
5969  {
5970  if (is_array($this->version_remapMMForVersionSwap_reg[$id])) {
5971  foreach ($this->version_remapMMForVersionSwap_reg[$id] as $field => $str) {
5972  $str[0]->remapMM($str[1], $id, -$id, $str[2]);
5973  }
5974  }
5975  if (is_array($this->version_remapMMForVersionSwap_reg[$swapWith])) {
5976  foreach ($this->version_remapMMForVersionSwap_reg[$swapWith] as $field => $str) {
5977  $str[0]->remapMM($str[1], $swapWith, $id, $str[2]);
5978  }
5979  }
5980  if (is_array($this->version_remapMMForVersionSwap_reg[$id])) {
5981  foreach ($this->version_remapMMForVersionSwap_reg[$id] as $field => $str) {
5982  $str[0]->remapMM($str[1], -$id, $swapWith, $str[2]);
5983  }
5984  }
5985  }
5986 
5987  /*********************************************
5988  *
5989  * Cmd: Helper functions
5990  *
5991  ********************************************/
5992 
5998  protected function ‪getLocalTCE()
5999  {
6000  $copyTCE = GeneralUtility::makeInstance(DataHandler::class, $this->referenceIndexUpdater);
6001  $copyTCE->copyTree = ‪$this->copyTree;
6002  $copyTCE->enableLogging = ‪$this->enableLogging;
6003  // Transformations should NOT be carried out during copy
6004  $copyTCE->dontProcessTransformations = true;
6005  // make sure the isImporting flag is transferred, so all hooks know if
6006  // the current process is an import process
6007  $copyTCE->isImporting = ‪$this->isImporting;
6008  $copyTCE->bypassAccessCheckForRecords = ‪$this->bypassAccessCheckForRecords;
6009  $copyTCE->bypassWorkspaceRestrictions = ‪$this->bypassWorkspaceRestrictions;
6010  return $copyTCE;
6011  }
6012 
6017  public function ‪remapListedDBRecords()
6018  {
6019  if (!empty($this->registerDBList)) {
6020  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
6021  foreach ($this->registerDBList as $table => $records) {
6022  foreach ($records as $uid => ‪$fields) {
6023  $newData = [];
6024  $theUidToUpdate = $this->copyMappingArray_merged[$table][$uid] ?? null;
6025  $theUidToUpdate_saveTo = ‪BackendUtility::wsMapId($table, $theUidToUpdate);
6026  foreach (‪$fields as $fieldName => $value) {
6027  $conf = ‪$GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
6028  switch ($conf['type']) {
6029  case 'group':
6030  case 'select':
6031  $vArray = $this->‪remapListedDBRecords_procDBRefs($conf, $value, $theUidToUpdate, $table);
6032  if (is_array($vArray)) {
6033  $newData[$fieldName] = implode(',', $vArray);
6034  }
6035  break;
6036  case 'flex':
6037  if ($value === 'FlexForm_reference') {
6038  // This will fetch the new row for the element
6039  $origRecordRow = $this->‪recordInfo($table, $theUidToUpdate, '*');
6040  if (is_array($origRecordRow)) {
6041  ‪BackendUtility::workspaceOL($table, $origRecordRow);
6042  // Get current data structure and value array:
6043  $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
6044  ['config' => $conf],
6045  $table,
6046  $fieldName,
6047  $origRecordRow
6048  );
6049  $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
6050  $currentValueArray = ‪GeneralUtility::xml2array($origRecordRow[$fieldName]);
6051  // Do recursive processing of the XML data:
6052  $currentValueArray['data'] = $this->‪checkValue_flex_procInData($currentValueArray['data'], [], [], $dataStructureArray, [$table, $theUidToUpdate, $fieldName], 'remapListedDBRecords_flexFormCallBack');
6053  // The return value should be compiled back into XML, ready to insert directly in the field (as we call updateDB() directly later):
6054  if (is_array($currentValueArray['data'])) {
6055  $newData[$fieldName] = $this->‪checkValue_flexArray2Xml($currentValueArray, true);
6056  }
6057  }
6058  }
6059  break;
6060  case 'inline':
6061  $this->‪remapListedDBRecords_procInline($conf, $value, $uid, $table);
6062  break;
6063  default:
6064  $this->logger->debug('Field type should not appear here: ' . $conf['type']);
6065  }
6066  }
6067  // If any fields were changed, those fields are updated!
6068  if (!empty($newData)) {
6069  $this->‪updateDB($table, $theUidToUpdate_saveTo, $newData);
6070  }
6071  }
6072  }
6073  }
6074  }
6075 
6089  public function ‪remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
6090  {
6091  // Extract parameters:
6092  [$table, $uid, $field] = $pParams;
6093  // If references are set for this field, set flag so they can be corrected later:
6094  if ($this->‪isReferenceField($dsConf) && (string)$dataValue !== '') {
6095  $vArray = $this->‪remapListedDBRecords_procDBRefs($dsConf, $dataValue, $uid, $table);
6096  if (is_array($vArray)) {
6097  $dataValue = implode(',', $vArray);
6098  }
6099  }
6100  // Return
6101  return ['value' => $dataValue];
6102  }
6103 
6115  public function ‪remapListedDBRecords_procDBRefs($conf, $value, $MM_localUid, $table)
6116  {
6117  // Initialize variables
6118  // Will be set TRUE if an upgrade should be done...
6119  $set = false;
6120  // Allowed tables for references.
6121  $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
6122  // Table name to prepend the UID
6123  $prependName = $conf['type'] === 'group' ? $conf['prepend_tname'] : '';
6124  // Which tables that should possibly not be remapped
6125  $dontRemapTables = ‪GeneralUtility::trimExplode(',', $conf['dontRemapTablesOnCopy'], true);
6126  // Convert value to list of references:
6127  $dbAnalysis = $this->‪createRelationHandlerInstance();
6128  $dbAnalysis->registerNonTableValues = $conf['type'] === 'select' && $conf['allowNonIdValues'];
6129  $dbAnalysis->start($value, $allowedTables, $conf['MM'], $MM_localUid, $table, $conf);
6130  // Traverse those references and map IDs:
6131  foreach ($dbAnalysis->itemArray as $k => $v) {
6132  $mapID = $this->copyMappingArray_merged[$v['table']][$v['id']];
6133  if ($mapID && !in_array($v['table'], $dontRemapTables, true)) {
6134  $dbAnalysis->itemArray[$k]['id'] = $mapID;
6135  $set = true;
6136  }
6137  }
6138  if (!empty($conf['MM'])) {
6139  // Purge invalid items (live/version)
6140  $dbAnalysis->purgeItemArray();
6141  if ($dbAnalysis->isPurged()) {
6142  $set = true;
6143  }
6144 
6145  // If record has been versioned/copied in this process, handle invalid relations of the live record
6146  $liveId = ‪BackendUtility::getLiveVersionIdOfRecord($table, $MM_localUid);
6147  $originalId = 0;
6148  if (!empty($this->copyMappingArray_merged[$table])) {
6149  $originalId = array_search($MM_localUid, $this->copyMappingArray_merged[$table]);
6150  }
6151  if (!empty($liveId) && !empty($originalId) && (int)$liveId === (int)$originalId) {
6152  $liveRelations = $this->‪createRelationHandlerInstance();
6153  $liveRelations->setWorkspaceId(0);
6154  $liveRelations->start('', $allowedTables, $conf['MM'], $liveId, $table, $conf);
6155  // Purge invalid relations in the live workspace ("0")
6156  $liveRelations->purgeItemArray(0);
6157  if ($liveRelations->isPurged()) {
6158  $liveRelations->writeMM($conf['MM'], $liveId, $prependName);
6159  }
6160  }
6161  }
6162  // If a change has been done, set the new value(s)
6163  if ($set) {
6164  if ($conf['MM']) {
6165  $dbAnalysis->writeMM($conf['MM'], $MM_localUid, $prependName);
6166  } else {
6167  return $dbAnalysis->getValueArray($prependName);
6168  }
6169  }
6170  return null;
6171  }
6172 
6182  public function ‪remapListedDBRecords_procInline($conf, $value, $uid, $table)
6183  {
6184  $theUidToUpdate = $this->copyMappingArray_merged[$table][$uid] ?? null;
6185  if ($conf['foreign_table']) {
6186  $inlineType = $this->‪getInlineFieldType($conf);
6187  if ($inlineType === 'mm') {
6188  $this->‪remapListedDBRecords_procDBRefs($conf, $value, $theUidToUpdate, $table);
6189  } elseif ($inlineType !== false) {
6191  $dbAnalysis = $this->‪createRelationHandlerInstance();
6192  $dbAnalysis->start($value, $conf['foreign_table'], '', 0, $table, $conf);
6193 
6194  $updatePidForRecords = [];
6195  // Update values for specific versioned records
6196  foreach ($dbAnalysis->itemArray as &$item) {
6197  $updatePidForRecords[$item['table']][] = $item['id'];
6198  $versionedId = $this->‪getAutoVersionId($item['table'], $item['id']);
6199  if ($versionedId !== null) {
6200  $updatePidForRecords[$item['table']][] = $versionedId;
6201  $item['id'] = $versionedId;
6202  }
6203  }
6204 
6205  // Update child records if using pointer fields ('foreign_field'):
6206  if ($inlineType === 'field') {
6207  $dbAnalysis->writeForeignField($conf, $uid, $theUidToUpdate);
6208  }
6209  $thePidToUpdate = null;
6210  // If the current field is set on a page record, update the pid of related child records:
6211  if ($table === 'pages') {
6212  $thePidToUpdate = $theUidToUpdate;
6213  } elseif (isset($this->registerDBPids[$table][$uid])) {
6214  $thePidToUpdate = $this->registerDBPids[$table][$uid];
6215  $thePidToUpdate = $this->copyMappingArray_merged['pages'][$thePidToUpdate];
6216  }
6217 
6218  // Update child records if change to pid is required
6219  if ($thePidToUpdate && !empty($updatePidForRecords)) {
6220  // Ensure that only the default language page is used as PID
6221  $thePidToUpdate = $this->‪getDefaultLanguagePageId($thePidToUpdate);
6222  // @todo: this can probably go away
6223  // ensure, only live page ids are used as 'pid' values
6224  $liveId = ‪BackendUtility::getLiveVersionIdOfRecord('pages', $theUidToUpdate);
6225  if ($liveId !== null) {
6226  $thePidToUpdate = $liveId;
6227  }
6228  $updateValues = ['pid' => $thePidToUpdate];
6229  foreach ($updatePidForRecords as $tableName => $uids) {
6230  if (empty($tableName) || empty($uids)) {
6231  continue;
6232  }
6233  $conn = GeneralUtility::makeInstance(ConnectionPool::class)
6234  ->getConnectionForTable($tableName);
6235  foreach ($uids as $updateUid) {
6236  $conn->update($tableName, $updateValues, ['uid' => $updateUid]);
6237  }
6238  }
6239  }
6240  }
6241  }
6242  }
6243 
6249  public function ‪processRemapStack()
6250  {
6251  // Processes the remap stack:
6252  if (is_array($this->remapStack)) {
6253  $remapFlexForms = [];
6254  $hookPayload = [];
6255 
6256  $newValue = null;
6257  foreach ($this->remapStack as $remapAction) {
6258  // If no position index for the arguments was set, skip this remap action:
6259  if (!is_array($remapAction['pos'])) {
6260  continue;
6261  }
6262  // Load values from the argument array in remapAction:
6263  $field = $remapAction['field'];
6264  $id = $remapAction['args'][$remapAction['pos']['id']];
6265  $rawId = $id;
6266  $table = $remapAction['args'][$remapAction['pos']['table']];
6267  $valueArray = $remapAction['args'][$remapAction['pos']['valueArray']];
6268  $tcaFieldConf = $remapAction['args'][$remapAction['pos']['tcaFieldConf']];
6269  $additionalData = $remapAction['additionalData'];
6270  // The record is new and has one or more new ids (in case of versioning/workspaces):
6271  if (strpos($id, 'NEW') !== false) {
6272  // Replace NEW...-ID with real uid:
6273  $id = $this->substNEWwithIDs[$id];
6274  // If the new parent record is on a non-live workspace or versionized, it has another new id:
6275  if (isset($this->autoVersionIdMap[$table][$id])) {
6276  $id = $this->autoVersionIdMap[$table][$id];
6277  }
6278  $remapAction['args'][$remapAction['pos']['id']] = $id;
6279  }
6280  // Replace relations to NEW...-IDs in field value (uids of child records):
6281  if (is_array($valueArray)) {
6282  foreach ($valueArray as $key => $value) {
6283  if (strpos($value, 'NEW') !== false) {
6284  if (strpos($value, '_') === false) {
6285  $affectedTable = $tcaFieldConf['foreign_table'];
6286  $prependTable = false;
6287  } else {
6288  $parts = explode('_', $value);
6289  $value = array_pop($parts);
6290  $affectedTable = implode('_', $parts);
6291  $prependTable = true;
6292  }
6293  $value = $this->substNEWwithIDs[$value];
6294  // The record is new, but was also auto-versionized and has another new id:
6295  if (isset($this->autoVersionIdMap[$affectedTable][$value])) {
6296  $value = $this->autoVersionIdMap[$affectedTable][$value];
6297  }
6298  if ($prependTable) {
6299  $value = $affectedTable . '_' . $value;
6300  }
6301  // Set a hint that this was a new child record:
6302  $this->newRelatedIDs[$affectedTable][] = $value;
6303  $valueArray[$key] = $value;
6304  }
6305  }
6306  $remapAction['args'][$remapAction['pos']['valueArray']] = $valueArray;
6307  }
6308  // Process the arguments with the defined function:
6309  if (!empty($remapAction['func'])) {
6310  $newValue = call_user_func_array([$this, $remapAction['func']], $remapAction['args']);
6311  }
6312  // If array is returned, check for maxitems condition, if string is returned this was already done:
6313  if (is_array($newValue)) {
6314  $newValue = implode(',', $this->‪checkValue_checkMax($tcaFieldConf, $newValue));
6315  // The reference casting is only required if
6316  // checkValue_group_select_processDBdata() returns an array
6317  $newValue = $this->‪castReferenceValue($newValue, $tcaFieldConf);
6318  }
6319  // Update in database (list of children (csv) or number of relations (foreign_field)):
6320  if (!empty($field)) {
6321  $fieldArray = [$field => $newValue];
6322  if (‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
6323  $fieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = ‪$GLOBALS['EXEC_TIME'];
6324  }
6325  $this->‪updateDB($table, $id, $fieldArray);
6326  } elseif (!empty($additionalData['flexFormId']) && !empty($additionalData['flexFormPath'])) {
6327  // Collect data to update FlexForms
6328  $flexFormId = $additionalData['flexFormId'];
6329  $flexFormPath = $additionalData['flexFormPath'];
6330 
6331  if (!isset($remapFlexForms[$flexFormId])) {
6332  $remapFlexForms[$flexFormId] = [];
6333  }
6334 
6335  $remapFlexForms[$flexFormId][$flexFormPath] = $newValue;
6336  }
6337 
6338  // Collect elements that shall trigger processDatamap_afterDatabaseOperations
6339  if (isset($this->remapStackRecords[$table][$rawId]['processDatamap_afterDatabaseOperations'])) {
6340  $hookArgs = $this->remapStackRecords[$table][$rawId]['processDatamap_afterDatabaseOperations'];
6341  if (!isset($hookPayload[$table][$rawId])) {
6342  $hookPayload[$table][$rawId] = [
6343  'status' => $hookArgs['status'],
6344  'fieldArray' => $hookArgs['fieldArray'],
6345  'hookObjects' => $hookArgs['hookObjectsArr'],
6346  ];
6347  }
6348  $hookPayload[$table][$rawId]['fieldArray'][$field] = $newValue;
6349  }
6350  }
6351 
6352  if ($remapFlexForms) {
6353  foreach ($remapFlexForms as $flexFormId => $modifications) {
6354  $this->‪updateFlexFormData($flexFormId, $modifications);
6355  }
6356  }
6357 
6358  foreach ($hookPayload as $tableName => $rawIdPayload) {
6359  foreach ($rawIdPayload as $rawId => $payload) {
6360  foreach ($payload['hookObjects'] as $hookObject) {
6361  if (!method_exists($hookObject, 'processDatamap_afterDatabaseOperations')) {
6362  continue;
6363  }
6364  $hookObject->processDatamap_afterDatabaseOperations(
6365  $payload['status'],
6366  $tableName,
6367  $rawId,
6368  $payload['fieldArray'],
6369  $this
6370  );
6371  }
6372  }
6373  }
6374  }
6375  // Processes the remap stack actions:
6376  if ($this->remapStackActions) {
6377  foreach ($this->remapStackActions as $action) {
6378  if (isset($action['callback'], $action['arguments'])) {
6379  call_user_func_array($action['callback'], $action['arguments']);
6380  }
6381  }
6382  }
6383  // Reset:
6384  $this->remapStack = [];
6385  $this->remapStackRecords = [];
6386  $this->remapStackActions = [];
6387  }
6388 
6395  protected function ‪updateFlexFormData($flexFormId, array $modifications)
6396  {
6397  [$table, $uid, $field] = explode(':', $flexFormId, 3);
6398 
6399  if (!‪MathUtility::canBeInterpretedAsInteger($uid) && !empty($this->substNEWwithIDs[$uid])) {
6400  $uid = $this->substNEWwithIDs[$uid];
6401  }
6402 
6403  $record = $this->‪recordInfo($table, $uid, '*');
6404 
6405  if (!$table || !$uid || !$field || !is_array($record)) {
6406  return;
6407  }
6408 
6409  ‪BackendUtility::workspaceOL($table, $record);
6410 
6411  // Get current data structure and value array:
6412  $valueStructure = ‪GeneralUtility::xml2array($record[$field]);
6413 
6414  // Do recursive processing of the XML data:
6415  foreach ($modifications as $path => $value) {
6416  $valueStructure['data'] = ‪ArrayUtility::setValueByPath(
6417  $valueStructure['data'],
6418  $path,
6419  $value
6420  );
6421  }
6422 
6423  if (is_array($valueStructure['data'])) {
6424  // The return value should be compiled back into XML
6425  $values = [
6426  $field => $this->‪checkValue_flexArray2Xml($valueStructure, true),
6427  ];
6428 
6429  $this->‪updateDB($table, $uid, $values);
6430  }
6431  }
6432 
6447  protected function ‪triggerRemapAction($table, $id, array $callback, array $arguments, $forceRemapStackActions = false)
6448  {
6449  // Check whether the affected record is marked to be remapped:
6450  if (!$forceRemapStackActions && !isset($this->remapStackRecords[$table][$id]) && !isset($this->remapStackChildIds[$id])) {
6451  call_user_func_array($callback, $arguments);
6452  } else {
6453  $this->‪addRemapAction($table, $id, $callback, $arguments);
6454  }
6455  }
6456 
6466  public function ‪addRemapAction($table, $id, array $callback, array $arguments)
6467  {
6468  $this->remapStackActions[] = [
6469  'affects' => [
6470  'table' => $table,
6471  'id' => $id
6472  ],
6473  'callback' => $callback,
6474  'arguments' => $arguments
6475  ];
6476  }
6477 
6490  public function ‪getVersionizedIncomingFieldArray($table, $id, &$incomingFieldArray, &‪$registerDBList)
6491  {
6492  if (is_array(‪$registerDBList[$table][$id])) {
6493  foreach ($incomingFieldArray as $field => $value) {
6494  $fieldConf = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config'];
6495  if (‪$registerDBList[$table][$id][$field] && ($foreignTable = $fieldConf['foreign_table'])) {
6496  $newValueArray = [];
6497  $origValueArray = is_array($value) ? $value : explode(',', $value);
6498  // Update the uids of the copied records, but also take care about new records:
6499  foreach ($origValueArray as $childId) {
6500  $newValueArray[] = $this->autoVersionIdMap[$foreignTable][$childId] ?: $childId;
6501  }
6502  // Set the changed value to the $incomingFieldArray
6503  $incomingFieldArray[$field] = implode(',', $newValueArray);
6504  }
6505  }
6506  // Clean up the $registerDBList array:
6507  unset(‪$registerDBList[$table][$id]);
6508  if (empty(‪$registerDBList[$table])) {
6509  unset(‪$registerDBList[$table]);
6510  }
6511  }
6512  }
6513 
6520  protected function ‪hardDeleteSingleRecord(string $table, int $uid): void
6521  {
6522  GeneralUtility::makeInstance(ConnectionPool::class)
6523  ->getConnectionForTable($table)
6524  ->delete($table, ['uid' => $uid], [\PDO::PARAM_INT]);
6525  }
6526 
6527  /*****************************
6528  *
6529  * Access control / Checking functions
6530  *
6531  *****************************/
6539  public function ‪checkModifyAccessList($table)
6540  {
6541  $res = $this->admin || (!$this->‪tableAdminOnly($table) && isset($this->BE_USER->groupData['tables_modify']) && GeneralUtility::inList($this->BE_USER->groupData['tables_modify'], $table));
6542  // Hook 'checkModifyAccessList': Post-processing of the state of access
6543  foreach ($this->‪getCheckModifyAccessListHookObjects() as $hookObject) {
6545  $hookObject->checkModifyAccessList($res, $table, $this);
6546  }
6547  return $res;
6548  }
6549 
6558  public function ‪isRecordInWebMount($table, $id)
6559  {
6560  if (!isset($this->isRecordInWebMount_Cache[$table . ':' . $id])) {
6561  $recP = $this->‪getRecordProperties($table, $id);
6562  $this->isRecordInWebMount_Cache[$table . ':' . $id] = $this->‪isInWebMount($recP['event_pid']);
6563  }
6564  return $this->isRecordInWebMount_Cache[$table . ':' . $id];
6565  }
6566 
6574  public function ‪isInWebMount($pid)
6575  {
6576  if (!isset($this->isInWebMount_Cache[$pid])) {
6577  $this->isInWebMount_Cache[$pid] = $this->BE_USER->isInWebMount($pid);
6578  }
6579  return $this->isInWebMount_Cache[$pid];
6580  }
6581 
6592  public function ‪checkRecordUpdateAccess($table, $id, $data = false, $hookObjectsArr = null)
6593  {
6594  $res = null;
6595  if (is_array($hookObjectsArr)) {
6596  foreach ($hookObjectsArr as $hookObj) {
6597  if (method_exists($hookObj, 'checkRecordUpdateAccess')) {
6598  $res = $hookObj->checkRecordUpdateAccess($table, $id, $data, $res, $this);
6599  }
6600  }
6601  if (isset($res)) {
6602  return (bool)$res;
6603  }
6604  }
6605  $res = false;
6606 
6607  if (‪$GLOBALS['TCA'][$table] && (int)$id > 0) {
6608  $cacheId = 'checkRecordUpdateAccess_' . $table . '_' . $id;
6609 
6610  // If information is cached, return it
6611  $cachedValue = $this->runtimeCache->get($cacheId);
6612  if (!empty($cachedValue)) {
6613  return $cachedValue;
6614  }
6615 
6616  if ($table === 'pages' || ($table === 'sys_file_reference' && array_key_exists('pages', $this->datamap))) {
6617  // @todo: find a more generic way to handle content relations of a page (without needing content editing access to that page)
6618  $perms = ‪Permission::PAGE_EDIT;
6619  } else {
6620  $perms = ‪Permission::CONTENT_EDIT;
6621  }
6622  if ($this->‪doesRecordExist($table, $id, $perms)) {
6623  $res = 1;
6624  }
6625  // Cache the result
6626  $this->runtimeCache->set($cacheId, $res);
6627  }
6628  return $res;
6629  }
6630 
6641  public function ‪checkRecordInsertAccess($insertTable, $pid, $action = SystemLogDatabaseAction::INSERT)
6642  {
6643  $pid = (int)$pid;
6644  if ($pid < 0) {
6645  return false;
6646  }
6647  // If information is cached, return it
6648  if (isset($this->recInsertAccessCache[$insertTable][$pid])) {
6649  return $this->recInsertAccessCache[$insertTable][$pid];
6650  }
6651 
6652  $res = false;
6653  if ($insertTable === 'pages') {
6654  $perms = ‪Permission::PAGE_NEW;
6655  } elseif (($insertTable === 'sys_file_reference') && array_key_exists('pages', $this->datamap)) {
6656  // @todo: find a more generic way to handle content relations of a page (without needing content editing access to that page)
6657  $perms = ‪Permission::PAGE_EDIT;
6658  } else {
6660  }
6661  $pageExists = (bool)$this->‪doesRecordExist('pages', $pid, $perms);
6662  // 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
6663  if ($pageExists || $pid === 0 && ($this->admin || ‪BackendUtility::isRootLevelRestrictionIgnored($insertTable))) {
6664  // Check permissions
6665  if ($this->‪isTableAllowedForThisPage($pid, $insertTable)) {
6666  $res = true;
6667  // Cache the result
6668  $this->recInsertAccessCache[$insertTable][$pid] = $res;
6669  } elseif ($this->enableLogging) {
6670  $propArr = $this->‪getRecordProperties('pages', $pid);
6671  $this->‪log($insertTable, $pid, $action, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to insert record on page \'%s\' (%s) where this table, %s, is not allowed', 11, [$propArr['header'], $pid, $insertTable], $propArr['event_pid']);
6672  }
6673  } elseif ($this->enableLogging) {
6674  $propArr = $this->‪getRecordProperties('pages', $pid);
6675  $this->‪log($insertTable, $pid, $action, 0, SystemLogErrorClassification::USER_ERROR, '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']);
6676  }
6677  return $res;
6678  }
6679 
6688  public function ‪isTableAllowedForThisPage($page_uid, $checkTable)
6689  {
6690  $page_uid = (int)$page_uid;
6691  $rootLevelSetting = (int)‪$GLOBALS['TCA'][$checkTable]['ctrl']['rootLevel'];
6692  // 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.
6693  if ($checkTable !== 'pages' && $rootLevelSetting !== -1 && ($rootLevelSetting xor !$page_uid)) {
6694  return false;
6695  }
6696  $allowed = false;
6697  // Check root-level
6698  if (!$page_uid) {
6699  if ($this->admin || ‪BackendUtility::isRootLevelRestrictionIgnored($checkTable)) {
6700  $allowed = true;
6701  }
6702  } else {
6703  // Check non-root-level
6704  $doktype = $this->‪pageInfo($page_uid, 'doktype');
6705  $allowedTableList = ‪$GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'] ?? ‪$GLOBALS['PAGES_TYPES']['default']['allowedTables'];
6706  $allowedArray = ‪GeneralUtility::trimExplode(',', $allowedTableList, true);
6707  // If all tables or the table is listed as an allowed type, return TRUE
6708  if (strpos($allowedTableList, '*') !== false || in_array($checkTable, $allowedArray, true)) {
6709  $allowed = true;
6710  }
6711  }
6712  return $allowed;
6713  }
6714 
6726  public function ‪doesRecordExist($table, $id, $perms)
6727  {
6729  trigger_error('Support for handing in permissions as string into "doesRecordExist" will be not be supported with TYPO3 v11 anymore. Use the Permission BitSet class instead.', E_USER_DEPRECATED);
6730  }
6731  return $this->‪recordInfoWithPermissionCheck($table, $id, $perms, 'uid, pid') !== false;
6732  }
6733 
6744  protected function ‪doesRecordExist_pageLookUp($id, $perms, $columns = ['uid'])
6745  {
6746  $cacheId = md5('doesRecordExist_pageLookUp_' . $id . '_' . $perms . '_' . implode(
6747  '_',
6748  $columns
6749  ) . '_' . (string)$this->admin);
6750 
6751  // If result is cached, return it
6752  $cachedResult = $this->runtimeCache->get($cacheId);
6753  if (!empty($cachedResult)) {
6754  return $cachedResult;
6755  }
6756 
6757  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
6758  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
6759  $queryBuilder
6760  ->select(...$columns)
6761  ->from('pages')
6762  ->where($queryBuilder->expr()->eq(
6763  'uid',
6764  $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
6765  ));
6766  if ($perms && !$this->admin) {
6767  $queryBuilder->andWhere($this->BE_USER->getPagePermsClause($perms));
6768  }
6769  if (!$this->admin && ‪$GLOBALS['TCA']['pages']['ctrl']['editlock'] &&
6771  ) {
6772  $queryBuilder->andWhere($queryBuilder->expr()->eq(
6773  ‪$GLOBALS['TCA']['pages']['ctrl']['editlock'],
6774  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
6775  ));
6776  }
6777 
6778  $row = $queryBuilder->execute()->fetch();
6779  $this->runtimeCache->set($cacheId, $row);
6780 
6781  return $row;
6782  }
6783 
6797  public function ‪doesBranchExist($inList, $pid, $perms, $recurse)
6798  {
6799  $pid = (int)$pid;
6800  $perms = (int)$perms;
6801  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
6802  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
6803  $result = $queryBuilder
6804  ->select('uid', 'perms_userid', 'perms_groupid', 'perms_user', 'perms_group', 'perms_everybody')
6805  ->from('pages')
6806  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))
6807  ->orderBy('sorting')
6808  ->execute();
6809  while ($row = $result->fetch()) {
6810  // IF admin, then it's OK
6811  if ($this->admin || $this->BE_USER->doesUserHaveAccess($row, $perms)) {
6812  $inList .= $row['uid'] . ',';
6813  if ($recurse) {
6814  // Follow the subpages recursively...
6815  $inList = $this->‪doesBranchExist($inList, $row['uid'], $perms, $recurse);
6816  if ($inList === -1) {
6817  return -1;
6818  }
6819  }
6820  } else {
6821  // No permissions
6822  return -1;
6823  }
6824  }
6825  return $inList;
6826  }
6827 
6835  public function ‪tableReadOnly($table)
6836  {
6837  // Returns TRUE if table is readonly
6838  return (bool)‪$GLOBALS['TCA'][$table]['ctrl']['readOnly'];
6839  }
6840 
6848  public function ‪tableAdminOnly($table)
6849  {
6850  // Returns TRUE if table is admin-only
6851  return !empty(‪$GLOBALS['TCA'][$table]['ctrl']['adminOnly']);
6852  }
6853 
6863  public function ‪destNotInsideSelf($destinationId, $id)
6864  {
6865  $loopCheck = 100;
6866  $destinationId = (int)$destinationId;
6867  $id = (int)$id;
6868  if ($destinationId === $id) {
6869  return false;
6870  }
6871  while ($destinationId !== 0 && $loopCheck > 0) {
6872  $loopCheck--;
6873  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
6874  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
6875  $result = $queryBuilder
6876  ->select('pid', 'uid', 't3ver_oid', 't3ver_wsid')
6877  ->from('pages')
6878  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($destinationId, \PDO::PARAM_INT)))
6879  ->execute();
6880  if ($row = $result->fetch()) {
6881  ‪BackendUtility::fixVersioningPid('pages', $row);
6882  if ($row['pid'] == $id) {
6883  return false;
6884  }
6885  $destinationId = (int)$row['pid'];
6886  } else {
6887  return false;
6888  }
6889  }
6890  return true;
6891  }
6892 
6900  public function ‪getExcludeListArray()
6901  {
6902  $list = [];
6903  if (isset($this->BE_USER->groupData['non_exclude_fields'])) {
6904  $nonExcludeFieldsArray = array_flip(‪GeneralUtility::trimExplode(',', $this->BE_USER->groupData['non_exclude_fields']));
6905  foreach (‪$GLOBALS['TCA'] as $table => $tableConfiguration) {
6906  if (isset($tableConfiguration['columns'])) {
6907  foreach ($tableConfiguration['columns'] as $field => $config) {
6908  $isExcludeField = ($config['exclude'] ?? false);
6909  $isOnlyVisibleForAdmins = (‪$GLOBALS['TCA'][$table]['columns'][$field]['displayCond'] ?? '') === 'HIDE_FOR_NON_ADMINS';
6910  $editorHasPermissionForThisField = isset($nonExcludeFieldsArray[$table . ':' . $field]);
6911  if ($isOnlyVisibleForAdmins || ($isExcludeField && !$editorHasPermissionForThisField)) {
6912  $list[] = $table . '-' . $field;
6913  }
6914  }
6915  }
6916  }
6917  }
6918 
6919  return $list;
6920  }
6921 
6930  public function ‪doesPageHaveUnallowedTables($page_uid, $doktype)
6931  {
6932  $page_uid = (int)$page_uid;
6933  if (!$page_uid) {
6934  // Not a number. Probably a new page
6935  return false;
6936  }
6937  $allowedTableList = ‪$GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'] ?? ‪$GLOBALS['PAGES_TYPES']['default']['allowedTables'];
6938  // If all tables are allowed, return early
6939  if (strpos($allowedTableList, '*') !== false) {
6940  return false;
6941  }
6942  $allowedArray = ‪GeneralUtility::trimExplode(',', $allowedTableList, true);
6943  $tableList = [];
6944  $allTableNames = $this->‪compileAdminTables();
6945  foreach ($allTableNames as $table) {
6946  // If the table is not in the allowed list, check if there are records...
6947  if (in_array($table, $allowedArray, true)) {
6948  continue;
6949  }
6950  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
6951  $queryBuilder->getRestrictions()->removeAll();
6952  $count = $queryBuilder
6953  ->count('uid')
6954  ->from($table)
6955  ->where($queryBuilder->expr()->eq(
6956  'pid',
6957  $queryBuilder->createNamedParameter($page_uid, \PDO::PARAM_INT)
6958  ))
6959  ->execute()
6960  ->fetchColumn(0);
6961  if ($count) {
6962  $tableList[] = $table;
6963  }
6964  }
6965  return implode(',', $tableList);
6966  }
6967 
6968  /*****************************
6969  *
6970  * Information lookup
6971  *
6972  *****************************/
6982  public function ‪pageInfo($id, $field)
6983  {
6984  if (!isset($this->pageCache[$id])) {
6985  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
6986  $queryBuilder->getRestrictions()->removeAll();
6987  $row = $queryBuilder
6988  ->select('*')
6989  ->from('pages')
6990  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
6991  ->execute()
6992  ->fetch();
6993  if ($row) {
6994  $this->pageCache[$id] = $row;
6995  }
6996  }
6997  return $this->pageCache[$id][$field];
6998  }
6999 
7010  public function ‪recordInfo($table, $id, $fieldList)
7011  {
7012  // Skip, if searching for NEW records or there's no TCA table definition
7013  if ((int)$id === 0 || !isset(‪$GLOBALS['TCA'][$table])) {
7014  return null;
7015  }
7016  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7017  $queryBuilder->getRestrictions()->removeAll();
7018  $result = $queryBuilder
7019  ->select(...‪GeneralUtility::trimExplode(',', $fieldList))
7020  ->from($table)
7021  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
7022  ->execute()
7023  ->fetch();
7024  return $result ?: null;
7025  }
7026 
7037  protected function ‪recordInfoWithPermissionCheck(string $table, int $id, $perms, string $fieldList = '*')
7038  {
7039  if ($this->bypassAccessCheckForRecords) {
7040  $columns = ‪GeneralUtility::trimExplode(',', $fieldList, true);
7041 
7042  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7043  $queryBuilder->getRestrictions()->removeAll();
7044 
7045  $record = $queryBuilder->select(...$columns)
7046  ->from($table)
7047  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
7048  ->execute()
7049  ->fetch();
7050 
7051  return $record ?: false;
7052  }
7053  // Processing the incoming $perms (from possible string to integer that can be AND'ed)
7055  trigger_error('Support for handing in permissions as string into "recordInfoWithPermissionCheck" will be not be supported with TYPO3 v11 anymore. Use the Permission BitSet class instead.', E_USER_DEPRECATED);
7056  if ($table !== 'pages') {
7057  switch ($perms) {
7058  case 'edit':
7059  case 'delete':
7060  case 'new':
7061  // This holds it all in case the record is not page!!
7062  if ($table === 'sys_file_reference' && array_key_exists('pages', $this->datamap)) {
7063  $perms = 'edit';
7064  } else {
7065  $perms = 'editcontent';
7066  }
7067  break;
7068  }
7069  }
7070  $perms = (int)(‪Permission::getMap()[$perms] ?? 0);
7071  } else {
7072  $perms = (int)$perms;
7073  }
7074  if (!$perms) {
7075  throw new \RuntimeException('Internal ERROR: no permissions to check for non-admin user', 1270853920);
7076  }
7077  // For all tables: Check if record exists:
7078  $isWebMountRestrictionIgnored = ‪BackendUtility::isWebMountRestrictionIgnored($table);
7079  if (is_array(‪$GLOBALS['TCA'][$table]) && $id > 0 && ($this->admin || $isWebMountRestrictionIgnored || $this->‪isRecordInWebMount($table, $id))) {
7080  $columns = ‪GeneralUtility::trimExplode(',', $fieldList, true);
7081  if ($table !== 'pages') {
7082  // Find record without checking page
7083  // @todo: This should probably check for editlock
7084  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7085  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
7086  ‪$output = $queryBuilder
7087  ->select(...$columns)
7088  ->from($table)
7089  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
7090  ->execute()
7091  ->fetch();
7093  // If record found, check page as well:
7094  if (is_array(‪$output)) {
7095  // Looking up the page for record:
7096  $pageRec = $this->‪doesRecordExist_pageLookUp(‪$output['pid'], $perms);
7097  // 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):
7098  $isRootLevelRestrictionIgnored = ‪BackendUtility::isRootLevelRestrictionIgnored($table);
7099  if (is_array($pageRec) || !‪$output['pid'] && ($this->admin || $isRootLevelRestrictionIgnored)) {
7100  return ‪$output;
7101  }
7102  }
7103  return false;
7104  }
7105  return $this->‪doesRecordExist_pageLookUp($id, $perms, $columns);
7106  }
7107  return false;
7108  }
7109 
7122  public function ‪getRecordProperties($table, $id, $noWSOL = false)
7123  {
7124  $row = $table === 'pages' && !$id ? ['title' => '[root-level]', 'uid' => 0, 'pid' => 0] : $this->‪recordInfo($table, $id, '*');
7125  if (!$noWSOL) {
7126  ‪BackendUtility::workspaceOL($table, $row);
7127  }
7128  return $this->‪getRecordPropertiesFromRow($table, $row);
7129  }
7130 
7139  public function ‪getRecordPropertiesFromRow($table, $row)
7140  {
7141  if (‪$GLOBALS['TCA'][$table]) {
7143  $liveUid = ($row['t3ver_oid'] ?? null) ? $row['t3ver_oid'] : $row['uid'];
7144  return [
7145  'header' => ‪BackendUtility::getRecordTitle($table, $row),
7146  'pid' => $row['pid'],
7147  'event_pid' => $this->‪eventPid($table, (int)$liveUid, $row['pid']),
7148  't3ver_state' => ‪BackendUtility::isTableWorkspaceEnabled($table) ? $row['t3ver_state'] : '',
7149  '_ORIG_pid' => $row['_ORIG_pid']
7150  ];
7151  }
7152  return null;
7153  }
7154 
7162  public function ‪eventPid($table, $uid, $pid)
7163  {
7164  return $table === 'pages' ? $uid : $pid;
7165  }
7166 
7167  /*********************************************
7168  *
7169  * Storing data to Database Layer
7170  *
7171  ********************************************/
7181  public function ‪updateDB($table, $id, $fieldArray)
7182  {
7183  if (is_array($fieldArray) && is_array(‪$GLOBALS['TCA'][$table]) && (int)$id) {
7184  // Do NOT update the UID field, ever!
7185  unset($fieldArray['uid']);
7186  if (!empty($fieldArray)) {
7187  $fieldArray = $this->‪insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray);
7189  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
7190 
7191  $types = [];
7192  $platform = $connection->getDatabasePlatform();
7193  if ($platform instanceof SQLServerPlatform) {
7194  // mssql needs to set proper PARAM_LOB and others to update fields
7195  $tableDetails = $connection->getSchemaManager()->listTableDetails($table);
7196  foreach ($fieldArray as $columnName => $columnValue) {
7197  $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
7198  }
7199  }
7200 
7201  // Execute the UPDATE query:
7202  $updateErrorMessage = '';
7203  try {
7204  $connection->‪update($table, $fieldArray, ['uid' => (int)$id], $types);
7205  } catch (DBALException $e) {
7206  $updateErrorMessage = $e->getPrevious()->getMessage();
7207  }
7208  // If succeeds, do...:
7209  if ($updateErrorMessage === '') {
7210  // Update reference index:
7211  $this->‪updateRefIndex($table, $id);
7212  // Set History data
7213  $historyEntryId = 0;
7214  if (isset($this->historyRecords[$table . ':' . $id])) {
7215  $historyEntryId = $this->‪getRecordHistoryStore()->‪modifyRecord($table, $id, $this->historyRecords[$table . ':' . $id], $this->correlationId);
7216  }
7217  if ($this->enableLogging) {
7218  if ($this->checkStoredRecords) {
7219  $newRow = $this->‪checkStoredRecord($table, $id, $fieldArray, SystemLogDatabaseAction::UPDATE);
7220  } else {
7221  $newRow = $fieldArray;
7222  $newRow['uid'] = $id;
7223  }
7224  // Set log entry:
7225  $propArr = $this->‪getRecordPropertiesFromRow($table, $newRow);
7226  $isOfflineVersion = (bool)($newRow['t3ver_oid'] ?? 0);
7227  $this->‪log($table, $id, SystemLogDatabaseAction::UPDATE, $propArr['pid'], SystemLogErrorClassification::MESSAGE, 'Record \'%s\' (%s) was updated.' . ($isOfflineVersion ? ' (Offline version).' : ' (Online).'), 10, [$propArr['header'], $table . ':' . $id, 'history' => $historyEntryId], $propArr['event_pid']);
7228  }
7229  // Clear cache for relevant pages:
7230  $this->‪registerRecordIdForPageCacheClearing($table, $id);
7231  // Unset the pageCache for the id if table was page.
7232  if ($table === 'pages') {
7233  unset($this->pageCache[$id]);
7234  }
7235  } else {
7236  $this->‪log($table, $id, SystemLogDatabaseAction::UPDATE, 0, SystemLogErrorClassification::SYSTEM_ERROR, 'SQL error: \'%s\' (%s)', 12, [$updateErrorMessage, $table . ':' . $id]);
7237  }
7238  }
7239  }
7240  }
7241 
7255  public function ‪insertDB($table, $id, $fieldArray, $newVersion = false, $suggestedUid = 0, $dontSetNewIdIndex = false)
7256  {
7257  if (is_array($fieldArray) && is_array(‪$GLOBALS['TCA'][$table]) && isset($fieldArray['pid'])) {
7258  // Do NOT insert the UID field, ever!
7259  unset($fieldArray['uid']);
7260  if (!empty($fieldArray)) {
7261  // Check for "suggestedUid".
7262  // This feature is used by the import functionality to force a new record to have a certain UID value.
7263  // This is only recommended for use when the destination server is a passive mirror of another server.
7264  // As a security measure this feature is available only for Admin Users (for now)
7265  $suggestedUid = (int)$suggestedUid;
7266  if ($this->BE_USER->isAdmin() && $suggestedUid && $this->suggestedInsertUids[$table . ':' . $suggestedUid]) {
7267  // When the value of ->suggestedInsertUids[...] is "DELETE" it will try to remove the previous record
7268  if ($this->suggestedInsertUids[$table . ':' . $suggestedUid] === 'DELETE') {
7269  $this->‪hardDeleteSingleRecord($table, (int)$suggestedUid);
7270  }
7271  $fieldArray['uid'] = $suggestedUid;
7272  }
7273  $fieldArray = $this->‪insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray);
7274  $typeArray = [];
7275  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'])
7276  && array_key_exists(‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'], $fieldArray)
7277  ) {
7278  $typeArray[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']] = ‪Connection::PARAM_LOB;
7279  }
7280  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
7281  $insertErrorMessage = '';
7282  try {
7283  // Execute the INSERT query:
7284  $connection->‪insert(
7285  $table,
7286  $fieldArray,
7287  $typeArray
7288  );
7289  } catch (DBALException $e) {
7290  $insertErrorMessage = $e->getPrevious()->getMessage();
7291  }
7292  // If succees, do...:
7293  if ($insertErrorMessage === '') {
7294  // Set mapping for NEW... -> real uid:
7295  // the NEW_id now holds the 'NEW....' -id
7296  $NEW_id = $id;
7297  $id = $this->‪postProcessDatabaseInsert($connection, $table, $suggestedUid);
7298 
7299  if (!$dontSetNewIdIndex) {
7300  $this->substNEWwithIDs[$NEW_id] = $id;
7301  $this->substNEWwithIDs_table[$NEW_id] = $table;
7302  }
7303  $newRow = [];
7304  if ($this->enableLogging) {
7305  // Checking the record is properly saved if configured
7306  if ($this->checkStoredRecords) {
7307  $newRow = $this->‪checkStoredRecord($table, $id, $fieldArray, SystemLogDatabaseAction::INSERT);
7308  } else {
7309  $newRow = $fieldArray;
7310  $newRow['uid'] = $id;
7311  }
7312  }
7313  // Update reference index:
7314  $this->‪updateRefIndex($table, $id);
7315 
7316  // Store in history
7317  $this->‪getRecordHistoryStore()->‪addRecord($table, $id, $newRow, $this->correlationId);
7318 
7319  if ($newVersion) {
7320  if ($this->enableLogging) {
7321  $propArr = $this->‪getRecordPropertiesFromRow($table, $newRow);
7322  $this->‪log($table, $id, SystemLogDatabaseAction::INSERT, 0, SystemLogErrorClassification::MESSAGE, '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);
7323  }
7324  } else {
7325  if ($this->enableLogging) {
7326  $propArr = $this->‪getRecordPropertiesFromRow($table, $newRow);
7327  $page_propArr = $this->‪getRecordProperties('pages', $propArr['pid']);
7328  $this->‪log($table, $id, SystemLogDatabaseAction::INSERT, 0, SystemLogErrorClassification::MESSAGE, 'Record \'%s\' (%s) was inserted on page \'%s\' (%s)', 10, [$propArr['header'], $table . ':' . $id, $page_propArr['header'], $newRow['pid']], $newRow['pid'], $NEW_id);
7329  }
7330  // Clear cache for relevant pages:
7331  $this->‪registerRecordIdForPageCacheClearing($table, $id);
7332  }
7333  return $id;
7334  }
7335  if ($this->enableLogging) {
7336  $this->‪log($table, $id, SystemLogDatabaseAction::INSERT, 0, SystemLogErrorClassification::SYSTEM_ERROR, 'SQL error: \'%s\' (%s)', 12, [$insertErrorMessage, $table . ':' . $id]);
7337  }
7338  }
7339  }
7340  return null;
7341  }
7342 
7355  public function ‪checkStoredRecord($table, $id, $fieldArray, $action)
7356  {
7357  $id = (int)$id;
7358  if (is_array(‪$GLOBALS['TCA'][$table]) && $id) {
7359  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7360  $queryBuilder->getRestrictions()->removeAll();
7361 
7362  $row = $queryBuilder
7363  ->select('*')
7364  ->from($table)
7365  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
7366  ->execute()
7367  ->fetch();
7368 
7369  if (!empty($row)) {
7370  // Traverse array of values that was inserted into the database and compare with the actually stored value:
7371  ‪$errors = [];
7372  foreach ($fieldArray as $key => $value) {
7373  if (!$this->checkStoredRecords_loose || $value || $row[$key]) {
7374  if (is_float($row[$key])) {
7375  // if the database returns the value as double, compare it as double
7376  if ((double)$value !== (double)$row[$key]) {
7377  ‪$errors[] = $key;
7378  }
7379  } else {
7380  $dbType = ‪$GLOBALS['TCA'][$table]['columns'][$key]['config']['dbType'] ?? false;
7381  if ($dbType === 'datetime' || $dbType === 'time') {
7382  $row[$key] = $this->‪normalizeTimeFormat($table, $row[$key], $dbType);
7383  }
7384  if ((string)$value !== (string)$row[$key]) {
7385  // The is_numeric check catches cases where we want to store a float/double value
7386  // and database returns the field as a string with the least required amount of
7387  // significant digits, i.e. "0.00" being saved and "0" being read back.
7388  if (is_numeric($value) && is_numeric($row[$key])) {
7389  if ((double)$value === (double)$row[$key]) {
7390  continue;
7391  }
7392  }
7393  ‪$errors[] = $key;
7394  }
7395  }
7396  }
7397  }
7398  // Set log message if there were fields with unmatching values:
7399  if (!empty(‪$errors)) {
7400  $message = sprintf(
7401  '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.',
7402  $id,
7403  $table,
7404  implode(', ', ‪$errors)
7405  );
7406  $this->‪log($table, $id, $action, 0, SystemLogErrorClassification::USER_ERROR, $message);
7407  }
7408  // Return selected rows:
7409  return $row;
7410  }
7411  }
7412  return null;
7413  }
7414 
7425  public function ‪setHistory($table, $id, $logId)
7426  {
7427  if (isset($this->historyRecords[$table . ':' . $id])) {
7429  $table,
7430  $id,
7431  $this->historyRecords[$table . ':' . $id],
7432  $this->correlationId
7433  );
7434  }
7435  }
7436 
7440  protected function ‪getRecordHistoryStore(): RecordHistoryStore
7441  {
7442  return GeneralUtility::makeInstance(
7443  RecordHistoryStore::class,
7445  $this->BE_USER->user['uid'],
7446  $this->BE_USER->user['ses_backuserid'] ?? null,
7447  ‪$GLOBALS['EXEC_TIME'],
7448  $this->BE_USER->workspace
7449  );
7450  }
7451 
7461  public function ‪updateRefIndex($table, $uid, int $workspace = null): void
7462  {
7463  if ($workspace === null) {
7464  $workspace = (int)$this->BE_USER->workspace;
7465  }
7466  $this->referenceIndexUpdater->registerForUpdate((string)$table, (int)$uid, $workspace);
7467  }
7468 
7479  public function ‪registerReferenceIndexRowsForDrop(string $table, int $uid, int $workspace): void
7480  {
7481  $this->referenceIndexUpdater->registerForDrop($table, $uid, $workspace);
7482  }
7483 
7484  /*********************************************
7485  *
7486  * Misc functions
7487  *
7488  ********************************************/
7520  public function ‪getSortNumber($table, $uid, $pid)
7521  {
7522  $sortColumn = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
7523  if (!$sortColumn) {
7524  return null;
7525  }
7526 
7527  $considerWorkspaces = ‪BackendUtility::isTableWorkspaceEnabled($table);
7528  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
7529  $queryBuilder = $connectionPool->getQueryBuilderForTable($table);
7530  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
7531 
7532  $queryBuilder
7533  ->select($sortColumn, 'pid', 'uid')
7534  ->from($table);
7535 
7536  // find and return the sorting value for the first record on that pid
7537  if ($pid >= 0) {
7538  // Fetches the first record (lowest sorting) under this pid
7539  $queryBuilder
7540  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)));
7541 
7542  if ($considerWorkspaces) {
7543  $queryBuilder->andWhere(
7544  $queryBuilder->expr()->eq('t3ver_oid', 0)
7545  );
7546  }
7547  $row = $queryBuilder
7548  ->orderBy($sortColumn, 'ASC')
7549  ->addOrderBy('uid', 'ASC')
7550  ->setMaxResults(1)
7551  ->execute()
7552  ->fetch();
7553 
7554  if (!empty($row)) {
7555  // The top record was the record itself, so we return its current sorting value
7556  if ($row['uid'] == $uid) {
7557  return $row[$sortColumn];
7558  }
7559  // If the record sorting value < 1 we must resort all the records under this pid
7560  if ($row[$sortColumn] < 1) {
7561  $this->‪increaseSortingOfFollowingRecords($table, (int)$pid);
7562  // Lowest sorting value after full resorting is $sortIntervals
7563  return ‪$this->sortIntervals;
7564  }
7565  // Sorting number between current top element and zero
7566  return floor($row[$sortColumn] / 2);
7567  }
7568  // No records, so we choose the default value as sorting-number
7569  return ‪$this->sortIntervals;
7570  }
7571 
7572  // Find and return first possible sorting value AFTER record with given uid ($pid)
7573  // Fetches the record which is supposed to be the prev record
7574  $row = $queryBuilder
7575  ->where($queryBuilder->expr()->eq(
7576  'uid',
7577  $queryBuilder->createNamedParameter(abs($pid), \PDO::PARAM_INT)
7578  ))
7579  ->execute()
7580  ->fetch();
7581 
7582  // There is a previous record
7583  if (!empty($row)) {
7584  // Look, if the record UID happens to be an offline record. If so, find its live version.
7585  // Offline uids will be used when a page is versionized as "branch" so this is when we must correct
7586  // - otherwise a pid of "-1" and a wrong sort-row number is returned which we don't want.
7587  if ($lookForLiveVersion = ‪BackendUtility::getLiveVersionOfRecord($table, $row['uid'], $sortColumn . ',pid,uid')) {
7588  $row = $lookForLiveVersion;
7589  }
7590  // Fetch move placeholder, since it might point to a new page in the current workspace.
7591  // Only do that if we're not inserting a record after itself, otherwise we'd update the
7592  // move placeholder that we're currently trying to move around with data of itself.
7593  $movePlaceholder = ‪BackendUtility::getMovePlaceholder($table, $row['uid'], 'uid,pid,' . $sortColumn);
7594  if ($movePlaceholder && ((int)$movePlaceholder['uid'] !== (int)$uid)) {
7595  $row = $movePlaceholder;
7596  }
7597  // If the record should be inserted after itself, keep the current sorting information:
7598  if ((int)$row['uid'] === (int)$uid) {
7599  $sortNumber = $row[$sortColumn];
7600  } else {
7601  $queryBuilder = $connectionPool->getQueryBuilderForTable($table);
7602  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
7603 
7604  $queryBuilder
7605  ->select($sortColumn, 'pid', 'uid')
7606  ->from($table)
7607  ->where(
7608  $queryBuilder->expr()->eq(
7609  'pid',
7610  $queryBuilder->createNamedParameter($row['pid'], \PDO::PARAM_INT)
7611  ),
7612  $queryBuilder->expr()->gte(
7613  $sortColumn,
7614  $queryBuilder->createNamedParameter($row[$sortColumn], \PDO::PARAM_INT)
7615  )
7616  )
7617  ->orderBy($sortColumn, 'ASC')
7618  ->addOrderBy('uid', 'DESC')
7619  ->setMaxResults(2);
7620 
7621  if ($considerWorkspaces) {
7622  $queryBuilder->andWhere(
7623  $queryBuilder->expr()->eq('t3ver_oid', 0)
7624  );
7625  }
7626 
7627  $subResults = $queryBuilder
7628  ->execute()
7629  ->fetchAll();
7630  // Fetches the next record in order to calculate the in-between sortNumber
7631  // There was a record afterwards
7632  if (count($subResults) === 2) {
7633  // There was a record afterwards, fetch that
7634  $subrow = array_pop($subResults);
7635  // The sortNumber is found in between these values
7636  $sortNumber = $row[$sortColumn] + floor(($subrow[$sortColumn] - $row[$sortColumn]) / 2);
7637  // The sortNumber happened NOT to be between the two surrounding numbers, so we'll have to resort the list
7638  if ($sortNumber <= $row[$sortColumn] || $sortNumber >= $subrow[$sortColumn]) {
7639  $this->‪increaseSortingOfFollowingRecords($table, (int)$row['pid'], (int)$row[$sortColumn]);
7640  $sortNumber = $row[$sortColumn] + ‪$this->sortIntervals;
7641  }
7642  } else {
7643  // If after the last record in the list, we just add the sortInterval to the last sortvalue
7644  $sortNumber = $row[$sortColumn] + ‪$this->sortIntervals;
7645  }
7646  }
7647  return ['pid' => $row['pid'], 'sortNumber' => $sortNumber];
7648  }
7649  if ($this->enableLogging) {
7650  $propArr = $this->‪getRecordProperties($table, $uid);
7651  // OK, don't insert $propArr['event_pid'] here...
7652  $this->‪log($table, $uid, SystemLogDatabaseAction::MOVE, 0, SystemLogErrorClassification::USER_ERROR, 'Attempt to move record \'%s\' (%s) to after a non-existing record (uid=%s)', 1, [$propArr['header'], $table . ':' . $uid, abs($pid)], $propArr['pid']);
7653  }
7654  // There MUST be a previous record or else this cannot work
7655  return false;
7656  }
7657 
7668  protected function ‪increaseSortingOfFollowingRecords(string $table, int $pid, int $sortingValue = null): void
7669  {
7670  $sortBy = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
7671  if ($sortBy) {
7672  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7673 
7674  $queryBuilder
7675  ->update($table)
7676  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))
7677  ->set($sortBy, $queryBuilder->quoteIdentifier($sortBy) . ' + ' . $this->sortIntervals . ' + ' . $this->sortIntervals, false);
7678  if ($sortingValue !== null) {
7679  $queryBuilder->andWhere($queryBuilder->expr()->gt($sortBy, $sortingValue));
7680  }
7682  $queryBuilder
7683  ->andWhere(
7684  $queryBuilder->expr()->eq('t3ver_oid', 0)
7685  );
7686  }
7687 
7688  $deleteColumn = ‪$GLOBALS['TCA'][$table]['ctrl']['delete'] ?? '';
7689  if ($deleteColumn) {
7690  $queryBuilder->andWhere($queryBuilder->expr()->eq($deleteColumn, 0));
7691  }
7692 
7693  $queryBuilder->execute();
7694  }
7695  }
7696 
7714  protected function ‪getPreviousLocalizedRecordUid($table, $uid, $pid, $language)
7715  {
7716  $previousLocalizedRecordUid = $uid;
7717  $sortColumn = ‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
7718  if ($sortColumn) {
7719  $select = [$sortColumn, 'pid', 'uid'];
7720  // For content elements, we also need the colPos
7721  if ($table === 'tt_content') {
7722  $select[] = 'colPos';
7723  }
7724  // Get the sort value of the default language record
7725  $row = ‪BackendUtility::getRecord($table, $uid, implode(',', $select));
7726  if (is_array($row)) {
7727  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
7728  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
7729 
7730  $queryBuilder
7731  ->select(...$select)
7732  ->from($table)
7733  ->where(
7734  $queryBuilder->expr()->eq(
7735  'pid',
7736  $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)
7737  ),
7738  $queryBuilder->expr()->eq(
7739  ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'],
7740  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
7741  ),
7742  $queryBuilder->expr()->lt(
7743  $sortColumn,
7744  $queryBuilder->createNamedParameter($row[$sortColumn], \PDO::PARAM_INT)
7745  )
7746  )
7747  ->orderBy($sortColumn, 'DESC')
7748  ->addOrderBy('uid', 'DESC')
7749  ->setMaxResults(1);
7750  if ($table === 'tt_content') {
7751  $queryBuilder
7752  ->andWhere(
7753  $queryBuilder->expr()->eq(
7754  'colPos',
7755  $queryBuilder->createNamedParameter($row['colPos'], \PDO::PARAM_INT)
7756  )
7757  );
7758  }
7759  // If there is an element, find its localized record in specified localization language on this page
7760  if ($previousRow = $queryBuilder->execute()->fetch()) {
7761  $previousLocalizedRecord = ‪BackendUtility::getRecordLocalization($table, $previousRow['uid'], $language, 'pid=' . (int)$pid);
7762  if (is_array($previousLocalizedRecord[0])) {
7763  $previousLocalizedRecordUid = $previousLocalizedRecord[0]['uid'];
7764  }
7765  }
7766  }
7767  }
7768  return $previousLocalizedRecordUid;
7769  }
7770 
7780  public function ‪setTSconfigPermissions($fieldArray, $TSConfig_p)
7781  {
7782  trigger_error('DataHandler->setTSconfigPermissions will be removed in TYPO3 v11.0. Use the PagePermissionAssembler API instead.', E_USER_DEPRECATED);
7783  if ((string)$TSConfig_p['userid'] !== '') {
7784  $fieldArray['perms_userid'] = (int)$TSConfig_p['userid'];
7785  }
7786  if ((string)$TSConfig_p['groupid'] !== '') {
7787  $fieldArray['perms_groupid'] = (int)$TSConfig_p['groupid'];
7788  }
7789  if ((string)$TSConfig_p['user'] !== '') {
7790  $fieldArray['perms_user'] = ‪MathUtility::canBeInterpretedAsInteger($TSConfig_p['user']) ? $TSConfig_p['user'] : $this->‪assemblePermissions($TSConfig_p['user']);
7791  }
7792  if ((string)$TSConfig_p['group'] !== '') {
7793  $fieldArray['perms_group'] = ‪MathUtility::canBeInterpretedAsInteger($TSConfig_p['group']) ? $TSConfig_p['group'] : $this->‪assemblePermissions($TSConfig_p['group']);
7794  }
7795  if ((string)$TSConfig_p['everybody'] !== '') {
7796  $fieldArray['perms_everybody'] = ‪MathUtility::canBeInterpretedAsInteger($TSConfig_p['everybody']) ? $TSConfig_p['everybody'] : $this->‪assemblePermissions($TSConfig_p['everybody']);
7797  }
7798  return $fieldArray;
7799  }
7800 
7809  public function ‪newFieldArray($table)
7810  {
7811  $fieldArray = [];
7812  if (is_array(‪$GLOBALS['TCA'][$table]['columns'])) {
7813  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $content) {
7814  if (isset($this->defaultValues[$table][$field])) {
7815  $fieldArray[$field] = $this->defaultValues[$table][$field];
7816  } elseif (isset($content['config']['default'])) {
7817  $fieldArray[$field] = $content['config']['default'];
7818  }
7819  }
7820  }
7821  return $fieldArray;
7822  }
7823 
7831  public function ‪addDefaultPermittedLanguageIfNotSet($table, &$incomingFieldArray)
7832  {
7833  // Checking languages:
7834  if (‪$GLOBALS['TCA'][$table]['ctrl']['languageField']) {
7835  if (!isset($incomingFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']])) {
7836  // Language field must be found in input row - otherwise it does not make sense.
7837  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
7838  ->getQueryBuilderForTable('sys_language');
7839  $queryBuilder->getRestrictions()
7840  ->removeAll()
7841  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
7842  $queryBuilder
7843  ->select('uid')
7844  ->from('sys_language')
7845  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)));
7846  $rows = array_merge([['uid' => 0]], $queryBuilder->execute()->fetchAll(), [['uid' => -1]]);
7847  foreach ($rows as $r) {
7848  if ($this->BE_USER->checkLanguageAccess($r['uid'])) {
7849  $incomingFieldArray[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $r['uid'];
7850  break;
7851  }
7852  }
7853  }
7854  }
7855  }
7856 
7865  public function ‪overrideFieldArray($table, $data)
7866  {
7867  if (is_array($this->overrideValues[$table])) {
7868  $data = array_merge($data, $this->overrideValues[$table]);
7869  }
7870  return $data;
7871  }
7872 
7883  public function ‪compareFieldArrayWithCurrentAndUnset($table, $id, $fieldArray)
7884  {
7885  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
7886  $queryBuilder = $connection->‪createQueryBuilder();
7887  $queryBuilder->‪getRestrictions()->‪removeAll();
7888  $currentRecord = $queryBuilder->select('*')
7889  ->from($table)
7890  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
7891  ->execute()
7892  ->fetch();
7893  // If the current record exists (which it should...), begin comparison:
7894  if (is_array($currentRecord)) {
7895  $tableDetails = $connection->getSchemaManager()->listTableDetails($table);
7896  $columnRecordTypes = [];
7897  foreach ($currentRecord as $columnName => $_) {
7898  $columnRecordTypes[$columnName] = '';
7899  $type = $tableDetails->getColumn($columnName)->getType();
7900  if ($type instanceof IntegerType) {
7901  $columnRecordTypes[$columnName] = 'int';
7902  }
7903  }
7904  // Unset the fields which are similar:
7905  foreach ($fieldArray as $col => $val) {
7906  $fieldConfiguration = ‪$GLOBALS['TCA'][$table]['columns'][$col]['config'];
7907  $isNullField = (!empty($fieldConfiguration['eval']) && GeneralUtility::inList($fieldConfiguration['eval'], 'null'));
7908 
7909  // Unset fields if stored and submitted values are equal - except the current field holds MM relations.
7910  // In general this avoids to store superfluous data which also will be visualized in the editing history.
7911  if (!$fieldConfiguration['MM'] && $this->‪isSubmittedValueEqualToStoredValue($val, $currentRecord[$col], $columnRecordTypes[$col], $isNullField)) {
7912  unset($fieldArray[$col]);
7913  } else {
7914  if (!isset($this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$col])) {
7915  $this->historyRecords[$table . ':' . $id]['oldRecord'][$col] = $currentRecord[$col];
7916  } elseif ($this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$col] != $this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$col]) {
7917  $this->historyRecords[$table . ':' . $id]['oldRecord'][$col] = $this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$col];
7918  }
7919  if (!isset($this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$col])) {
7920  $this->historyRecords[$table . ':' . $id]['newRecord'][$col] = $fieldArray[$col];
7921  } elseif ($this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$col] != $this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$col]) {
7922  $this->historyRecords[$table . ':' . $id]['newRecord'][$col] = $this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$col];
7923  }
7924  }
7925  }
7926  } else {
7927  // If the current record does not exist this is an error anyways and we just return an empty array here.
7928  $fieldArray = [];
7929  }
7930  return $fieldArray;
7931  }
7932 
7945  protected function ‪isSubmittedValueEqualToStoredValue($submittedValue, $storedValue, $storedType, $allowNull = false)
7946  {
7947  // No NULL values are allowed, this is the regular behaviour.
7948  // Thus, check whether strings are the same or whether integer values are empty ("0" or "").
7949  if (!$allowNull) {
7950  $result = (string)$submittedValue === (string)$storedValue || $storedType === 'int' && (int)$storedValue === (int)$submittedValue;
7951  // Null values are allowed, but currently there's a real (not NULL) value.
7952  // Thus, ensure no NULL value was submitted and fallback to the regular behaviour.
7953  } elseif ($storedValue !== null) {
7954  $result = (
7955  $submittedValue !== null
7956  && $this->‪isSubmittedValueEqualToStoredValue($submittedValue, $storedValue, $storedType, false)
7957  );
7958  // Null values are allowed, and currently there's a NULL value.
7959  // Thus, check whether a NULL value was submitted.
7960  } else {
7961  $result = ($submittedValue === null);
7962  }
7964  return $result;
7965  }
7966 
7976  public function ‪assemblePermissions($string)
7977  {
7978  trigger_error('DataHandler->assemblePermissions will be removed in TYPO3 v11.0. Use the PagePermissionAssembler API instead.', E_USER_DEPRECATED);
7979  $keyArr = ‪GeneralUtility::trimExplode(',', $string, true);
7980  $value = 0;
7981  $permissionMap = ‪Permission::getMap();
7982  foreach ($keyArr as $key) {
7983  if ($key && isset($permissionMap[$key])) {
7984  $value |= $permissionMap[$key];
7985  }
7986  }
7987  return $value;
7988  }
7989 
7997  public function ‪convNumEntityToByteValue($input)
7998  {
7999  $token = md5(microtime());
8000  $parts = explode($token, preg_replace('/(&#([0-9]+);)/', $token . '\\2' . $token, $input));
8001  foreach ($parts as $k => $v) {
8002  if ($k % 2) {
8003  $v = (int)$v;
8004  // Just to make sure that control bytes are not converted.
8005  if ($v > 32) {
8006  $parts[$k] = chr($v);
8007  }
8008  }
8009  }
8010  return implode('', $parts);
8011  }
8012 
8018  public function ‪disableDeleteClause()
8019  {
8020  $this->‪disableDeleteClause = true;
8021  }
8022 
8030  public function ‪deleteClause($table)
8031  {
8032  // Returns the proper delete-clause if any for a table from TCA
8033  if (!$this->‪disableDeleteClause && $GLOBALS['TCA'][$table]['ctrl']['delete']) {
8034  return ' AND ' . $table . '.' . ‪$GLOBALS['TCA'][$table]['ctrl']['delete'] . '=0';
8035  }
8036  return '';
8037  }
8038 
8045  {
8046  if (!$this->‪disableDeleteClause) {
8047  $restrictions->‪add(GeneralUtility::makeInstance(DeletedRestriction::class));
8048  }
8049  }
8050 
8059  protected function ‪getOriginalParentOfRecord($table, $uid)
8060  {
8061  if (isset(self::$recordPidsForDeletedRecords[$table][$uid])) {
8062  return self::$recordPidsForDeletedRecords[$table][$uid];
8063  }
8064  [$parentUid] = ‪BackendUtility::getTSCpid($table, $uid, '');
8065  return [$parentUid];
8066  }
8067 
8076  public function ‪getTableEntries($table, $TSconfig)
8077  {
8078  $tA = is_array($TSconfig['table.'][$table . '.']) ? $TSconfig['table.'][$table . '.'] : [];
8079  $dA = is_array($TSconfig['default.']) ? $TSconfig['default.'] : [];
8081  return $dA;
8082  }
8083 
8092  public function ‪getPID($table, $uid)
8093  {
8094  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
8095  $queryBuilder->getRestrictions()
8096  ->removeAll();
8097  $queryBuilder->select('pid')
8098  ->from($table)
8099  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)));
8100  if ($row = $queryBuilder->execute()->fetch()) {
8101  return $row['pid'];
8102  }
8103  return false;
8104  }
8105 
8111  public function ‪dbAnalysisStoreExec()
8112  {
8113  foreach ($this->dbAnalysisStore as $action) {
8114  $id = ‪BackendUtility::wsMapId($action[4], ‪MathUtility::canBeInterpretedAsInteger($action[2]) ? $action[2] : $this->substNEWwithIDs[$action[2]]);
8115  if ($id) {
8116  $action[0]->writeMM($action[1], $id, $action[3]);
8117  }
8118  }
8119  }
8120 
8132  public function ‪int_pageTreeInfo($CPtable, $pid, $counter, $rootID)
8133  {
8134  if ($counter) {
8135  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
8136  $restrictions = $queryBuilder->getRestrictions()->removeAll();
8137  $this->‪addDeleteRestriction($restrictions);
8138  $queryBuilder
8139  ->select('uid')
8140  ->from('pages')
8141  ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))
8142  ->orderBy('sorting', 'DESC');
8143  if (!$this->admin) {
8144  $queryBuilder->andWhere($this->BE_USER->getPagePermsClause(‪Permission::PAGE_SHOW));
8145  }
8146  if ((int)$this->BE_USER->workspace === 0) {
8147  $queryBuilder->andWhere(
8148  $queryBuilder->expr()->eq('t3ver_wsid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
8149  );
8150  } else {
8151  $queryBuilder->andWhere($queryBuilder->expr()->in(
8152  't3ver_wsid',
8153  $queryBuilder->createNamedParameter([0, $this->BE_USER->workspace], Connection::PARAM_INT_ARRAY)
8154  ));
8155  }
8156  $result = $queryBuilder->execute();
8157 
8158  $pages = [];
8159  while ($row = $result->fetch()) {
8160  $pages[$row['uid']] = $row;
8161  }
8162 
8163  // Resolve placeholders of workspace versions
8164  if (!empty($pages) && (int)$this->BE_USER->workspace !== 0) {
8165  $pages = array_reverse(
8167  'pages',
8168  'uid',
8169  'sorting',
8170  array_keys($pages)
8171  ),
8172  true
8173  );
8174  }
8175 
8176  foreach ($pages as $page) {
8177  if ($page['uid'] != $rootID) {
8178  $CPtable[$page['uid']] = $pid;
8179  // If the uid is NOT the rootID of the copyaction and if we are supposed to walk further down
8180  if ($counter - 1) {
8181  $CPtable = $this->‪int_pageTreeInfo($CPtable, $page['uid'], $counter - 1, $rootID);
8182  }
8183  }
8184  }
8185  }
8186  return $CPtable;
8187  }
8188 
8195  public function ‪compileAdminTables()
8196  {
8197  return array_keys(‪$GLOBALS['TCA']);
8198  }
8199 
8207  public function ‪fixUniqueInPid($table, $uid)
8208  {
8209  if (empty(‪$GLOBALS['TCA'][$table])) {
8210  return;
8211  }
8212 
8213  $curData = $this->‪recordInfo($table, $uid, '*');
8214  $newData = [];
8215  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $conf) {
8216  if ($conf['config']['type'] === 'input' && (string)$curData[$field] !== '') {
8217  $evalCodesArray = ‪GeneralUtility::trimExplode(',', $conf['config']['eval'], true);
8218  if (in_array('uniqueInPid', $evalCodesArray, true)) {
8219  $newV = $this->‪getUnique($table, $field, $curData[$field], $uid, $curData['pid']);
8220  if ((string)$newV !== (string)$curData[$field]) {
8221  $newData[$field] = $newV;
8222  }
8223  }
8224  }
8225  }
8226  // IF there are changed fields, then update the database
8227  if (!empty($newData)) {
8228  $this->‪updateDB($table, $uid, $newData);
8229  }
8230  }
8231 
8239  protected function ‪fixUniqueInSite(string $table, int $uid): bool
8240  {
8241  $curData = $this->‪recordInfo($table, $uid, '*');
8242  $workspaceId = $this->BE_USER->workspace;
8243  $newData = [];
8244  foreach (‪$GLOBALS['TCA'][$table]['columns'] as $field => $conf) {
8245  if ($conf['config']['type'] === 'slug' && (string)$curData[$field] !== '') {
8246  $evalCodesArray = ‪GeneralUtility::trimExplode(',', $conf['config']['eval'], true);
8247  if (in_array('uniqueInSite', $evalCodesArray, true)) {
8248  $helper = GeneralUtility::makeInstance(SlugHelper::class, $table, $field, $conf['config'], $workspaceId);
8249  $state = ‪RecordStateFactory::forName($table)->fromArray($curData);
8250  $newValue = $helper->buildSlugForUniqueInSite($curData[$field], $state);
8251  if ((string)$newValue !== (string)$curData[$field]) {
8252  $newData[$field] = $newValue;
8253  }
8254  }
8255  }
8256  }
8257  // IF there are changed fields, then update the database
8258  if (!empty($newData)) {
8259  $this->‪updateDB($table, $uid, $newData);
8260  return true;
8261  }
8262  return false;
8263  }
8264 
8269  protected function ‪fixUniqueInSiteForSubpages(int $pageId)
8270  {
8271  // Get ALL subpages to update - read-permissions are respected
8272  $subPages = $this->‪int_pageTreeInfo([], $pageId, 99, $pageId);
8273  // Now fix uniqueInSite for subpages
8274  foreach ($subPages as $thePageUid => $thePagePid) {
8275  $recordWasModified = $this->‪fixUniqueInSite('pages', $thePageUid);
8276  if ($recordWasModified) {
8277  // @todo: Add logging and history - but how? we don't know the data that was in the system before
8278  }
8279  }
8280  }
8281 
8294  public function ‪fixCopyAfterDuplFields($table, $uid, $prevUid, $update, $newData = [])
8295  {
8296  if (‪$GLOBALS['TCA'][$table] && ‪$GLOBALS['TCA'][$table]['ctrl']['copyAfterDuplFields']) {
8297  $prevData = $this->‪recordInfo($table, $prevUid, '*');
8298  $theFields = ‪GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][$table]['ctrl']['copyAfterDuplFields'], true);
8299  foreach ($theFields as $field) {
8300  if (‪$GLOBALS['TCA'][$table]['columns'][$field] && ($update || !isset($newData[$field]))) {
8301  $newData[$field] = $prevData[$field];
8302  }
8303  }
8304  if ($update && !empty($newData)) {
8305  $this->‪updateDB($table, $uid, $newData);
8306  }
8307  }
8308  return $newData;
8309  }
8310 
8323  protected function ‪castReferenceValue($value, array $configuration)
8324  {
8325  if ((string)$value !== '') {
8326  return $value;
8327  }
8328 
8329  if (!empty($configuration['MM']) || !empty($configuration['foreign_field'])) {
8330  return 0;
8331  }
8332 
8333  if (array_key_exists('default', $configuration)) {
8334  return $configuration['default'];
8335  }
8336 
8337  return $value;
8338  }
8339 
8347  public function ‪isReferenceField($conf)
8348  {
8349  return $conf['type'] === 'group' && $conf['internal_type'] === 'db' || $conf['type'] === 'select' && $conf['foreign_table'];
8350  }
8351 
8360  public function ‪getInlineFieldType($conf)
8361  {
8362  if ($conf['type'] !== 'inline' || !$conf['foreign_table']) {
8363  return false;
8364  }
8365  if ($conf['foreign_field']) {
8366  // The reference to the parent is stored in a pointer field in the child record
8367  return 'field';
8368  }
8369  if ($conf['MM']) {
8370  // Regular MM intermediate table is used to store data
8371  return 'mm';
8372  }
8373  // An item list (separated by comma) is stored (like select type is doing)
8374  return 'list';
8375  }
8376 
8389  public function ‪getCopyHeader($table, $pid, $field, $value, $count, $prevTitle = '')
8390  {
8391  // Set title value to check for:
8392  $checkTitle = $value;
8393  if ($count > 0) {
8394  $checkTitle = $value . rtrim(' ' . sprintf($this->‪prependLabel($table), $count));
8395  }
8396  // Do check:
8397  if ($prevTitle != $checkTitle || $count < 100) {
8398  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
8399  $this->‪addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
8400  $rowCount = $queryBuilder
8401  ->count('uid')
8402  ->from($table)
8403  ->where(
8404  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)),
8405  $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($checkTitle, \PDO::PARAM_STR))
8406  )
8407  ->execute()
8408  ->fetchColumn(0);
8409  if ($rowCount) {
8410  return $this->‪getCopyHeader($table, $pid, $field, $value, $count + 1, $checkTitle);
8411  }
8412  }
8413  // Default is to just return the current input title if no other was returned before:
8414  return $checkTitle;
8415  }
8416 
8425  public function ‪prependLabel($table)
8426  {
8427  return $this->‪getLanguageService()->‪sL($GLOBALS['TCA'][$table]['ctrl']['prependAtCopy']);
8428  }
8429 
8438  public function ‪resolvePid($table, $pid)
8439  {
8440  $pid = (int)$pid;
8441  if ($pid < 0) {
8442  $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
8443  $query->getRestrictions()
8444  ->removeAll();
8445  $row = $query
8446  ->select('pid')
8447  ->from($table)
8448  ->where($query->expr()->eq('uid', $query->createNamedParameter(abs($pid), \PDO::PARAM_INT)))
8449  ->execute()
8450  ->fetch();
8451  $pid = (int)$row['pid'];
8452  }
8453  return $pid;
8454  }
8455 
8464  public function ‪clearPrefixFromValue($table, $value)
8465  {
8466  $regex = '/\s' . sprintf(preg_quote($this->‪prependLabel($table)), '[0-9]*') . '$/';
8467  return @preg_replace($regex, '', $value);
8468  }
8469 
8477  protected function ‪checkForRecordsFromDisallowedTables(array $pageIds): ?array
8478  {
8479  if ($this->admin) {
8480  return null;
8481  }
8482 
8483  $disallowedTables = [];
8484  if (!empty($pageIds)) {
8485  $tableNames = $this->‪compileAdminTables();
8486  foreach ($tableNames as $table) {
8487  $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
8488  $query->getRestrictions()
8489  ->removeAll()
8490  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8491  $count = $query->count('uid')
8492  ->from($table)
8493  ->where($query->expr()->in(
8494  'pid',
8495  $query->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
8496  ))
8497  ->execute()
8498  ->fetchColumn(0);
8499  if ($count && ($this->‪tableReadOnly($table) || !$this->‪checkModifyAccessList($table))) {
8500  $disallowedTables[] = $table;
8501  }
8502  }
8503  }
8504  return !empty($disallowedTables) ? $disallowedTables : null;
8505  }
8506 
8515  public function ‪isRecordCopied($table, $uid)
8516  {
8517  // If the record was copied:
8518  if (isset($this->copyMappingArray[$table][$uid])) {
8519  return true;
8520  }
8521  if (isset($this->copyMappingArray[$table]) && in_array($uid, array_values($this->copyMappingArray[$table]))) {
8522  return true;
8523  }
8524  return false;
8525  }
8526 
8527  /******************************
8528  *
8529  * Clearing cache
8530  *
8531  ******************************/
8532 
8543  public function ‪registerRecordIdForPageCacheClearing($table, $uid, $pid = null)
8544  {
8545  if (!is_array(static::$recordsToClearCacheFor[$table])) {
8546  static::$recordsToClearCacheFor[$table] = [];
8547  }
8548  static::$recordsToClearCacheFor[$table][] = (int)$uid;
8549  if ($pid !== null) {
8550  if (!is_array(static::$recordPidsForDeletedRecords[$table])) {
8551  static::$recordPidsForDeletedRecords[$table] = [];
8552  }
8553  static::$recordPidsForDeletedRecords[$table][$uid][] = (int)$pid;
8554  }
8555  }
8556 
8560  protected function ‪processClearCacheQueue()
8561  {
8562  $tagsToClear = [];
8563  $clearCacheCommands = [];
8564 
8565  foreach (static::$recordsToClearCacheFor as $table => $uids) {
8566  foreach (array_unique($uids) as $uid) {
8567  if (!isset(‪$GLOBALS['TCA'][$table]) || $uid <= 0) {
8568  return;
8569  }
8570  // For move commands we may get more then 1 parent.
8571  $pageUids = $this->‪getOriginalParentOfRecord($table, $uid);
8572  foreach ($pageUids as $originalParent) {
8573  [$tagsToClearFromPrepare, $clearCacheCommandsFromPrepare]
8574  = $this->‪prepareCacheFlush($table, $uid, $originalParent);
8575  $tagsToClear = array_merge($tagsToClear, $tagsToClearFromPrepare);
8576  $clearCacheCommands = array_merge($clearCacheCommands, $clearCacheCommandsFromPrepare);
8577  }
8578  }
8579  }
8580 
8582  $cacheManager = $this->‪getCacheManager();
8583  $cacheManager->flushCachesInGroupByTags('pages', array_keys($tagsToClear));
8584 
8585  // Filter duplicate cache commands from cacheQueue
8586  $clearCacheCommands = array_unique($clearCacheCommands);
8587  // Execute collected clear cache commands from page TSConfig
8588  foreach ($clearCacheCommands as $command) {
8589  $this->‪clear_cacheCmd($command);
8590  }
8591 
8592  // Reset the cache clearing array
8593  static::$recordsToClearCacheFor = [];
8594 
8595  // Reset the original pid array
8596  static::$recordPidsForDeletedRecords = [];
8597  }
8598 
8608  protected function ‪prepareCacheFlush($table, $uid, $pid)
8609  {
8610  $tagsToClear = [];
8611  $clearCacheCommands = [];
8612  $pageUid = 0;
8613  $clearCacheEnabled = true;
8614  // Get Page TSconfig relevant:
8615  $TSConfig = ‪BackendUtility::getPagesTSconfig($pid)['TCEMAIN.'] ?? [];
8616 
8617  if (!empty($TSConfig['clearCache_disable'])) {
8618  $clearCacheEnabled = false;
8619  }
8620 
8621  if ($clearCacheEnabled && $this->BE_USER->workspace !== 0 && ‪BackendUtility::isTableWorkspaceEnabled($table)) {
8622  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
8623  $queryBuilder = $connectionPool->getQueryBuilderForTable($table);
8624  $queryBuilder->getRestrictions()
8625  ->removeAll()
8626  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8627  $count = $queryBuilder
8628  ->count('uid')
8629  ->from($table)
8630  ->where(
8631  $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
8632  $queryBuilder->expr()->eq('t3ver_oid', 0)
8633  )
8634  ->execute()
8635  ->fetchColumn();
8636  if ($count === 0) {
8637  $clearCacheEnabled = false;
8638  }
8639  }
8640 
8641  if ($clearCacheEnabled) {
8642  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
8643  // If table is "pages":
8644  $pageIdsThatNeedCacheFlush = [];
8645  if ($table === 'pages') {
8646  // Find out if the record is a get the original page
8647  $pageUid = $this->‪getDefaultLanguagePageId($uid);
8648 
8649  // Builds list of pages on the SAME level as this page (siblings)
8650  $queryBuilder = $connectionPool->getQueryBuilderForTable('pages');
8651  $queryBuilder->getRestrictions()
8652  ->removeAll()
8653  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8654  $siblings = $queryBuilder
8655  ->select('A.pid AS pid', 'B.uid AS uid')
8656  ->from('pages', 'A')
8657  ->from('pages', 'B')
8658  ->where(
8659  $queryBuilder->expr()->eq('A.uid', $queryBuilder->createNamedParameter($pageUid, \PDO::PARAM_INT)),
8660  $queryBuilder->expr()->eq('B.pid', $queryBuilder->quoteIdentifier('A.pid')),
8661  $queryBuilder->expr()->gte('A.pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
8662  )
8663  ->execute();
8664 
8665  $parentPageId = 0;
8666  while ($row_tmp = $siblings->fetch()) {
8667  $pageIdsThatNeedCacheFlush[] = (int)$row_tmp['uid'];
8668  $parentPageId = (int)$row_tmp['pid'];
8669  // Add children as well:
8670  if ($TSConfig['clearCache_pageSiblingChildren']) {
8671  $siblingChildrenQuery = $connectionPool->getQueryBuilderForTable('pages');
8672  $siblingChildrenQuery->getRestrictions()
8673  ->removeAll()
8674  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8675  $siblingChildren = $siblingChildrenQuery
8676  ->select('uid')
8677  ->from('pages')
8678  ->where($siblingChildrenQuery->expr()->eq(
8679  'pid',
8680  $siblingChildrenQuery->createNamedParameter($row_tmp['uid'], \PDO::PARAM_INT)
8681  ))
8682  ->execute();
8683  while ($row_tmp2 = $siblingChildren->fetch()) {
8684  $pageIdsThatNeedCacheFlush[] = (int)$row_tmp2['uid'];
8685  }
8686  }
8687  }
8688  // Finally, add the parent page as well when clearing a specific page
8689  if ($parentPageId > 0) {
8690  $pageIdsThatNeedCacheFlush[] = $parentPageId;
8691  }
8692  // Add grand-parent as well if configured
8693  if ($TSConfig['clearCache_pageGrandParent']) {
8694  $parentQuery = $connectionPool->getQueryBuilderForTable('pages');
8695  $parentQuery->getRestrictions()
8696  ->removeAll()
8697  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8698  $row_tmp = $parentQuery
8699  ->select('pid')
8700  ->from('pages')
8701  ->where($parentQuery->expr()->eq(
8702  'uid',
8703  $parentQuery->createNamedParameter($parentPageId, \PDO::PARAM_INT)
8704  ))
8705  ->execute()
8706  ->fetch();
8707  if (!empty($row_tmp)) {
8708  $pageIdsThatNeedCacheFlush[] = (int)$row_tmp['pid'];
8709  }
8710  }
8711  } else {
8712  // For other tables than "pages", delete cache for the records "parent page".
8713  $pageIdsThatNeedCacheFlush[] = $pageUid = (int)$this->‪getPID($table, $uid);
8714  // Add the parent page as well
8715  if ($TSConfig['clearCache_pageGrandParent']) {
8716  $parentQuery = $connectionPool->getQueryBuilderForTable('pages');
8717  $parentQuery->getRestrictions()
8718  ->removeAll()
8719  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
8720  $parentPageRecord = $parentQuery
8721  ->select('pid')
8722  ->from('pages')
8723  ->where($parentQuery->expr()->eq(
8724  'uid',
8725  $parentQuery->createNamedParameter($pageUid, \PDO::PARAM_INT)
8726  ))
8727  ->execute()
8728  ->fetch();
8729  if (!empty($parentPageRecord)) {
8730  $pageIdsThatNeedCacheFlush[] = (int)$parentPageRecord['pid'];
8731  }
8732  }
8733  }
8734  // Call pre-processing function for clearing of cache for page ids:
8735  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval'] ?? [] as $funcName) {
8736  $_params = ['pageIdArray' => &$pageIdsThatNeedCacheFlush, 'table' => $table, 'uid' => $uid, 'functionID' => 'clear_cache()'];
8737  // Returns the array of ids to clear, FALSE if nothing should be cleared! Never an empty array!
8738  GeneralUtility::callUserFunction($funcName, $_params, $this);
8739  }
8740  // Delete cache for selected pages:
8741  foreach ($pageIdsThatNeedCacheFlush as $pageId) {
8742  $tagsToClear['pageId_' . $pageId] = true;
8743  }
8744  // Queue delete cache for current table and record
8745  $tagsToClear[$table] = true;
8746  $tagsToClear[$table . '_' . $uid] = true;
8747  }
8748  // Clear cache for pages entered in TSconfig:
8749  if (!empty($TSConfig['clearCacheCmd'])) {
8750  $commands = ‪GeneralUtility::trimExplode(',', $TSConfig['clearCacheCmd'], true);
8751  $clearCacheCommands = array_unique($commands);
8752  }
8753  // Call post processing function for clear-cache:
8754  $_params = ['table' => $table, 'uid' => $uid, 'uid_page' => $pageUid, 'TSConfig' => $TSConfig, 'tags' => $tagsToClear, 'clearCacheEnabled' => $clearCacheEnabled];
8755  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'] ?? [] as $_funcRef) {
8756  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
8757  }
8758  return [
8759  $tagsToClear,
8760  $clearCacheCommands
8761  ];
8762  }
8763 
8802  public function ‪clear_cacheCmd($cacheCmd)
8803  {
8804  if (is_object($this->BE_USER)) {
8805  $this->BE_USER->writeLog(SystemLogType::CACHE, SystemLogCacheAction::CLEAR, SystemLogErrorClassification::MESSAGE, 0, 'User %s has cleared the cache (cacheCmd=%s)', [$this->BE_USER->user['username'], $cacheCmd]);
8806  }
8807  $userTsConfig = $this->BE_USER->getTSConfig();
8808  switch (strtolower($cacheCmd)) {
8809  case 'pages':
8810  if ($this->admin || ($userTsConfig['options.']['clearCache.']['pages'] ?? false)) {
8811  $this->‪getCacheManager()->‪flushCachesInGroup('pages');
8812  }
8813  break;
8814  case 'all':
8815  // allow to clear all caches if the TS config option is enabled or the option is not explicitly
8816  // disabled for admins (which could clear all caches by default). The latter option is useful
8817  // for big production sites where it should be possible to restrict the cache clearing for some admins.
8818  if (($userTsConfig['options.']['clearCache.']['all'] ?? false)
8819  || ($this->admin && (bool)($userTsConfig['options.']['clearCache.']['all'] ?? true))
8820  ) {
8821  $this->‪getCacheManager()->‪flushCaches();
8822  GeneralUtility::makeInstance(ConnectionPool::class)
8823  ->getConnectionForTable('cache_treelist')
8824  ->truncate('cache_treelist');
8825 
8826  // Delete Opcode Cache
8827  GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
8828  }
8829  break;
8830  }
8831 
8832  $tagsToFlush = [];
8833  // Clear cache for a page ID!
8835  $list_cache = [$cacheCmd];
8836  // Call pre-processing function for clearing of cache for page ids:
8837  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval'] ?? [] as $funcName) {
8838  $_params = ['pageIdArray' => &$list_cache, 'cacheCmd' => $cacheCmd, 'functionID' => 'clear_cacheCmd()'];
8839  // Returns the array of ids to clear, FALSE if nothing should be cleared! Never an empty array!
8840  GeneralUtility::callUserFunction($funcName, $_params, $this);
8841  }
8842  // Delete cache for selected pages:
8843  if (is_array($list_cache)) {
8844  foreach ($list_cache as $pageId) {
8845  $tagsToFlush[] = 'pageId_' . (int)$pageId;
8846  }
8847  }
8848  }
8849  // flush cache by tag
8850  if (GeneralUtility::isFirstPartOfStr(strtolower($cacheCmd), 'cachetag:')) {
8851  $cacheTag = substr($cacheCmd, 9);
8852  $tagsToFlush[] = $cacheTag;
8853  }
8854  // process caching framework operations
8855  if (!empty($tagsToFlush)) {
8856  $this->‪getCacheManager()->‪flushCachesInGroupByTags('pages', $tagsToFlush);
8857  }
8858 
8859  // Call post processing function for clear-cache:
8860  $_params = ['cacheCmd' => strtolower($cacheCmd), 'tags' => $tagsToFlush];
8861  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'] ?? [] as $_funcRef) {
8862  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
8863  }
8864  }
8865 
8866  /*****************************
8867  *
8868  * Logging
8869  *
8870  *****************************/
8889  public function ‪log($table, $recuid, $action, $recpid, $error, $details, $details_nr = -1, $data = [], $event_pid = -1, $NEWid = '')
8890  {
8891  if (!$this->enableLogging) {
8892  return 0;
8893  }
8894  // Type value for DataHandler
8895  if (!$this->storeLogMessages) {
8896  $details = '';
8897  }
8898  if ($error > 0) {
8899  $detailMessage = $details;
8900  if (is_array($data)) {
8901  $detailMessage = vsprintf($details, $data);
8902  }
8903  $this->errorLog[] = '[' . SystemLogType::DB . '.' . $action . '.' . $details_nr . ']: ' . $detailMessage;
8904  }
8905  return $this->BE_USER->writelog(SystemLogType::DB, $action, $error, $details_nr, $details, $data, $table, $recuid, $recpid, $event_pid, $NEWid);
8906  }
8907 
8917  public function ‪newlog($message, $error = SystemLogErrorClassification::MESSAGE)
8918  {
8919  return $this->‪log('', 0, SystemLogGenericAction::UNDEFINED, 0, $error, $message, -1);
8920  }
8921 
8926  public function ‪printLogErrorMessages()
8927  {
8928  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
8929  $queryBuilder->getRestrictions()->removeAll();
8930  $result = $queryBuilder
8931  ->select('*')
8932  ->from('sys_log')
8933  ->where(
8934  $queryBuilder->expr()->eq('type', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
8935  $queryBuilder->expr()->lt('action', $queryBuilder->createNamedParameter(256, \PDO::PARAM_INT)),
8936  $queryBuilder->expr()->eq(
8937  'userid',
8938  $queryBuilder->createNamedParameter($this->BE_USER->user['uid'], \PDO::PARAM_INT)
8939  ),
8940  $queryBuilder->expr()->eq(
8941  'tstamp',
8942  $queryBuilder->createNamedParameter(‪$GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
8943  ),
8944  $queryBuilder->expr()->neq('error', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
8945  )
8946  ->execute();
8947 
8948  while ($row = $result->fetch()) {
8949  $log_data = unserialize($row['log_data']);
8950  $msg = $row['error'] . ': ' . sprintf($row['details'], $log_data[0], $log_data[1], $log_data[2], $log_data[3], $log_data[4]);
8952  $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, '', $row['error'] === 4 ? ‪FlashMessage::WARNING : ‪FlashMessage::ERROR, true);
8954  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
8955  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
8956  $defaultFlashMessageQueue->enqueue($flashMessage);
8957  }
8958  }
8959 
8960  /*****************************
8961  *
8962  * Internal (do not use outside Core!)
8963  *
8964  *****************************/
8973  protected function ‪getDefaultLanguagePageId(int $pageId): int
8974  {
8975  $localizationParentFieldName = ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'];
8976  $row = $this->‪recordInfo('pages', $pageId, $localizationParentFieldName);
8977  $localizationParent = (int)$row[$localizationParentFieldName];
8978  if ($localizationParent > 0) {
8979  return $localizationParent;
8980  }
8981  return $pageId;
8982  }
8983 
8994  public function ‪insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray)
8995  {
8996  $result = $fieldArray;
8997  foreach ($fieldArray as $field => $value) {
8999  && ‪$GLOBALS['TCA'][$table]['columns'][$field]['config']['type'] === 'inline'
9000  && ‪$GLOBALS['TCA'][$table]['columns'][$field]['config']['foreign_field']) {
9001  $result[$field] = count(‪GeneralUtility::trimExplode(',', $value, true));
9002  }
9003  }
9004  return $result;
9005  }
9006 
9016  public function ‪hasDeletedRecord($tableName, $uid)
9017  {
9018  return
9019  !empty($this->deletedRecords[$tableName])
9020  && in_array($uid, $this->deletedRecords[$tableName])
9021  ;
9022  }
9023 
9032  public function ‪getAutoVersionId($table, $id): ?int
9033  {
9034  $result = null;
9035  if (isset($this->autoVersionIdMap[$table][$id])) {
9036  $result = (int)trim($this->autoVersionIdMap[$table][$id]);
9037  }
9038  return $result;
9039  }
9040 
9048  protected function ‪overlayAutoVersionId($table, $id)
9049  {
9050  $autoVersionId = $this->‪getAutoVersionId($table, $id);
9051  if ($autoVersionId !== null) {
9052  $id = $autoVersionId;
9053  }
9054  return $id;
9055  }
9056 
9062  protected function ‪addNewValuesToRemapStackChildIds(array $idValues)
9063  {
9064  foreach ($idValues as $idValue) {
9065  if (strpos($idValue, 'NEW') === 0) {
9066  $this->remapStackChildIds[$idValue] = true;
9067  }
9068  }
9069  }
9070 
9081  protected function ‪resolveVersionedRecords($tableName, $fieldNames, $sortingField, array $liveIds)
9082  {
9083  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
9084  ->getConnectionForTable($tableName);
9085  $sortingStatement = !empty($sortingField)
9086  ? [$connection->‪quoteIdentifier($sortingField)]
9087  : null;
9089  $resolver = GeneralUtility::makeInstance(
9090  PlainDataResolver::class,
9091  $tableName,
9092  $liveIds,
9093  $sortingStatement
9094  );
9095 
9096  $resolver->setWorkspaceId($this->BE_USER->workspace);
9097  $resolver->setKeepDeletePlaceholder(false);
9098  $resolver->setKeepMovePlaceholder(false);
9099  $resolver->setKeepLiveIds(true);
9100  $recordIds = $resolver->get();
9101 
9102  $records = [];
9103  foreach ($recordIds as $recordId) {
9104  $records[$recordId] = ‪BackendUtility::getRecord($tableName, $recordId, $fieldNames);
9105  }
9106 
9107  return $records;
9108  }
9109 
9117  protected function ‪getOuterMostInstance()
9118  {
9119  if (!isset($this->outerMostInstance)) {
9120  $stack = array_reverse(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS));
9121  foreach ($stack as $stackItem) {
9122  if (isset($stackItem['object']) && $stackItem['object'] instanceof self) {
9123  $this->outerMostInstance = $stackItem['object'];
9124  break;
9125  }
9126  }
9127  }
9129  }
9130 
9138  public function ‪isOuterMostInstance()
9139  {
9140  return $this->‪getOuterMostInstance() === $this;
9141  }
9142 
9148  protected function ‪getRuntimeCache()
9149  {
9150  return $this->‪getCacheManager()->‪getCache('runtime');
9151  }
9152 
9161  protected function ‪isNestedElementCallRegistered($table, $id, $identifier)
9162  {
9163  $nestedElementCalls = (array)$this->runtimeCache->get($this->cachePrefixNestedElementCalls);
9164  return isset($nestedElementCalls[$identifier][$table][$id]);
9165  }
9166 
9175  protected function ‪registerNestedElementCall($table, $id, $identifier)
9176  {
9177  $nestedElementCalls = (array)$this->runtimeCache->get($this->cachePrefixNestedElementCalls);
9178  $nestedElementCalls[$identifier][$table][$id] = true;
9179  $this->runtimeCache->set($this->cachePrefixNestedElementCalls, $nestedElementCalls);
9180  }
9181 
9185  protected function ‪resetNestedElementCalls()
9186  {
9187  $this->runtimeCache->remove($this->cachePrefixNestedElementCalls);
9188  }
9189 
9201  protected function ‪isElementToBeDeleted($table, $id)
9202  {
9203  $elementsToBeDeleted = (array)$this->runtimeCache->get('core-datahandler-elementsToBeDeleted');
9204  return isset($elementsToBeDeleted[$table][$id]);
9205  }
9206 
9212  protected function ‪registerElementsToBeDeleted()
9213  {
9214  $elementsToBeDeleted = (array)$this->runtimeCache->get('core-datahandler-elementsToBeDeleted');
9215  $this->runtimeCache->set('core-datahandler-elementsToBeDeleted', array_merge($elementsToBeDeleted, $this->‪getCommandMapElements('delete')));
9216  }
9217 
9223  protected function ‪resetElementsToBeDeleted()
9224  {
9225  $this->runtimeCache->remove('core-datahandler-elementsToBeDeleted');
9226  }
9227 
9235  protected function ‪unsetElementsToBeDeleted(array $elements)
9236  {
9237  $elements = ‪ArrayUtility::arrayDiffKeyRecursive($elements, $this->‪getCommandMapElements('delete'));
9238  foreach ($elements as $key => $value) {
9239  if (empty($value)) {
9240  unset($elements[$key]);
9241  }
9242  }
9243  return $elements;
9244  }
9245 
9252  protected function ‪getCommandMapElements($needle)
9253  {
9254  $elements = [];
9255  foreach ($this->cmdmap as $tableName => $idArray) {
9256  foreach ($idArray as $id => $commandArray) {
9257  foreach ($commandArray as $command => $value) {
9258  if ($value && $command == $needle) {
9259  $elements[$tableName][$id] = true;
9260  }
9261  }
9262  }
9263  }
9264  return $elements;
9265  }
9266 
9271  protected function ‪controlActiveElements()
9272  {
9273  if (!empty($this->control['active'])) {
9274  $this->‪setNullValues(
9275  $this->control['active'],
9276  $this->datamap
9277  );
9278  }
9279  }
9280 
9289  protected function ‪setNullValues(array $active, array &$haystack)
9290  {
9291  foreach ($active as $key => $value) {
9292  // Nested data is processes recursively
9293  if (is_array($value)) {
9294  $this->‪setNullValues(
9295  $value,
9296  $haystack[$key]
9297  );
9298  } elseif ($value == 0) {
9299  // Field has not been activated in the user interface,
9300  // thus a NULL value shall be stored in the database
9301  $haystack[$key] = null;
9302  }
9303  }
9304  }
9305 
9309  public function ‪setCorrelationId(CorrelationId ‪$correlationId): void
9310  {
9311  $this->correlationId = ‪$correlationId;
9312  }
9313 
9317  public function ‪getCorrelationId(): ?CorrelationId
9318  {
9319  return ‪$this->correlationId;
9320  }
9321 
9331  protected function ‪postProcessDatabaseInsert(Connection $connection, string $tableName, int $suggestedUid): int
9332  {
9333  if ($suggestedUid !== 0 && $connection->getDatabasePlatform() instanceof PostgreSqlPlatform) {
9334  $this->‪postProcessPostgresqlInsert($connection, $tableName);
9335  // The last inserted id on postgresql is actually the last value generated by the sequence.
9336  // On a forced UID insert this might not be the actual value or the sequence might not even
9337  // have generated a value yet.
9338  // Return the actual ID we forced on insert as a surrogate.
9339  return $suggestedUid;
9340  }
9341  if ($connection->getDatabasePlatform() instanceof SQLServerPlatform) {
9342  return $this->‪postProcessSqlServerInsert($connection, $tableName);
9343  }
9344  $id = $connection->lastInsertId($tableName);
9345  return (int)$id;
9346  }
9347 
9362  protected function ‪postProcessSqlServerInsert(Connection $connection, string $tableName): int
9363  {
9364  $id = $connection->lastInsertId($tableName);
9365  if (!((int)$id > 0)) {
9366  $table = $connection->quoteIdentifier($tableName);
9367  $result = $connection->executeQuery('SELECT IDENT_CURRENT(\'' . $table . '\') AS id')->fetch();
9368  if (isset($result['id']) && $result['id'] > 0) {
9369  $id = $result['id'];
9370  }
9371  }
9372  return (int)$id;
9373  }
9374 
9383  protected function postProcessPostgresqlInsert(Connection $connection, string $tableName)
9384  {
9385  $queryBuilder = $connection->createQueryBuilder();
9386  $queryBuilder->getRestrictions()->removeAll();
9387  $row = $queryBuilder->select('PGT.schemaname', 'S.relname', 'C.attname', 'T.relname AS tablename')
9388  ->from('pg_class', 'S')
9389  ->from('pg_depend', 'D')
9390  ->from('pg_class', 'T')
9391  ->from('pg_attribute', 'C')
9392  ->from('pg_tables', 'PGT')
9393  ->where(
9394  $queryBuilder->expr()->eq('S.relkind', $queryBuilder->quote('S')),
9395  $queryBuilder->expr()->eq('S.oid', $queryBuilder->quoteIdentifier('D.objid')),
9396  $queryBuilder->expr()->eq('D.refobjid', $queryBuilder->quoteIdentifier('T.oid')),
9397  $queryBuilder->expr()->eq('D.refobjid', $queryBuilder->quoteIdentifier('C.attrelid')),
9398  $queryBuilder->expr()->eq('D.refobjsubid', $queryBuilder->quoteIdentifier('C.attnum')),
9399  $queryBuilder->expr()->eq('T.relname', $queryBuilder->quoteIdentifier('PGT.tablename')),
9400  $queryBuilder->expr()->eq('PGT.tablename', $queryBuilder->quote($tableName))
9401  )
9402  ->setMaxResults(1)
9403  ->execute()
9404  ->fetch();
9405 
9406  if ($row !== false) {
9407  $connection->exec(
9408  sprintf(
9409  'SELECT SETVAL(%s, COALESCE(MAX(%s), 0)+1, FALSE) FROM %s',
9410  $connection->quote($row['schemaname'] . '.' . $row['relname']),
9411  $connection->quoteIdentifier($row['attname']),
9412  $connection->quoteIdentifier($row['schemaname'] . '.' . $row['tablename'])
9413  )
9414  );
9415  }
9416  }
9417 
9424  protected function getFieldEvalCacheIdentifier($additionalIdentifier)
9425  {
9426  return 'core-datahandler-eval-' . md5($additionalIdentifier);
9427  }
9428 
9432  protected function createRelationHandlerInstance()
9433  {
9434  $isWorkspacesLoaded = ExtensionManagementUtility::isLoaded('workspaces');
9435  $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
9436  $relationHandler->setWorkspaceId($this->BE_USER->workspace);
9437  $relationHandler->setUseLiveReferenceIds($isWorkspacesLoaded);
9438  $relationHandler->setUseLiveParentIds($isWorkspacesLoaded);
9439  $relationHandler->setReferenceIndexUpdater($this->referenceIndexUpdater);
9440  return $relationHandler;
9441  }
9442 
9448  protected function getCacheManager()
9449  {
9450  return GeneralUtility::makeInstance(CacheManager::class);
9451  }
9452 
9458  protected function getResourceFactory()
9459  {
9460  return GeneralUtility::makeInstance(ResourceFactory::class);
9461  }
9462 
9466  protected function getLanguageService()
9467  {
9468  return $GLOBALS['LANG'];
9469  }
9470 
9475  public function getHistoryRecords(): array
9476  {
9477  return $this->historyRecords;
9478  }
9479 }
‪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:3702
‪TYPO3\CMS\Core\SysLog\Action\Cache
Definition: Cache.php:24
‪TYPO3\CMS\Core\DataHandling\DataHandler\updateRefIndex
‪updateRefIndex($table, $uid, int $workspace=null)
Definition: DataHandler.php:7394
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:84
‪TYPO3\CMS\Core\DataHandling\DataHandler\tableAdminOnly
‪bool tableAdminOnly($table)
Definition: DataHandler.php:6781
‪TYPO3\CMS\Core\DataHandling\DataHandler\version_remapMMForVersionSwap_flexFormCallBack
‪version_remapMMForVersionSwap_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)
Definition: DataHandler.php:5875
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_group_select_explodeSelectGroupValue
‪array checkValue_group_select_explodeSelectGroupValue($value)
Definition: DataHandler.php:2858
‪TYPO3\CMS\Core\DataHandling\DataHandler\resolveFieldConfigurationAndRespectColumnsOverrides
‪array resolveFieldConfigurationAndRespectColumnsOverrides(string $table, string $field)
Definition: DataHandler.php:1600
‪TYPO3\CMS\Core\DataHandling\DataHandler\fixUniqueInSiteForSubpages
‪fixUniqueInSiteForSubpages(int $pageId)
Definition: DataHandler.php:8202
‪TYPO3\CMS\Core\DataHandling\DataHandler\copy_remapTranslationSourceField
‪copy_remapTranslationSourceField($table, $l10nRecords, $languageSourceMap)
Definition: DataHandler.php:3988
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\modifyRecord
‪string modifyRecord(string $table, int $uid, array $payload, CorrelationId $correlationId=null)
Definition: RecordHistoryStore.php:110
‪TYPO3\CMS\Core\DataHandling\DataHandler\$recordsToClearCacheFor
‪static array $recordsToClearCacheFor
Definition: DataHandler.php:561
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordsWithSameValue
‪array getRecordsWithSameValue($tableName, $uid, $fieldName, $value, $pageId=0)
Definition: DataHandler.php:2528
‪TYPO3\CMS\Core\DataHandling\DataHandler\remapListedDBRecords_flexFormCallBack
‪array remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
Definition: DataHandler.php:6022
‪TYPO3\CMS\Core\Utility\GeneralUtility\xml2array
‪static mixed xml2array($string, $NSprefix='', $reportDocTag=false)
Definition: GeneralUtility.php:1531
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkRecordInsertAccess
‪bool checkRecordInsertAccess($insertTable, $pid, $action=SystemLogDatabaseAction::INSERT)
Definition: DataHandler.php:6574
‪TYPO3\CMS\Core\DataHandling\DataHandler\setHistory
‪setHistory($table, $id, $logId)
Definition: DataHandler.php:7358
‪TYPO3\CMS\Core\DataHandling\DataHandler\getPlaceholderTitleForTableLabel
‪string getPlaceholderTitleForTableLabel($table, $placeholderContent=null)
Definition: DataHandler.php:1318
‪TYPO3\CMS\Core\DataHandling\DataHandler\getDefaultLanguagePageId
‪int getDefaultLanguagePageId(int $pageId)
Definition: DataHandler.php:8906
‪TYPO3\CMS\Core\Utility\HttpUtility\idn_to_ascii
‪static string bool idn_to_ascii(string $domain)
Definition: HttpUtility.php:195
‪TYPO3\CMS\Core\Database\Query\QueryHelper\getDateTimeFormats
‪static array getDateTimeFormats()
Definition: QueryHelper.php:177
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord
‪moveRecord($table, $uid, $destPid)
Definition: DataHandler.php:4038
‪TYPO3\CMS\Core\DataHandling\DataHandler\newlog
‪int newlog($message, $error=SystemLogErrorClassification::MESSAGE)
Definition: DataHandler.php:8850
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord
‪int null copyRecord($table, $uid, $destPid, $first=false, $overrideValues=[], $excludeFields='', $language=0, $ignoreLocalization=false)
Definition: DataHandler.php:3232
‪TYPO3\CMS\Core\DataHandling\DataHandler\$reverseOrder
‪bool $reverseOrder
Definition: DataHandler.php:108
‪TYPO3\CMS\Core\DataHandling\DataHandler\$substNEWwithIDs
‪array $substNEWwithIDs
Definition: DataHandler.php:262
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidParentRowException
Definition: InvalidParentRowException.php:23
‪TYPO3\CMS\Core\DataHandling\DataHandler\$isInWebMount_Cache
‪array $isInWebMount_Cache
Definition: DataHandler.php:440
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory
Definition: PasswordHashFactory.php:27
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\USER_BACKEND
‪const USER_BACKEND
Definition: RecordHistoryStore.php:38
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerNestedElementCall
‪registerNestedElementCall($table, $id, $identifier)
Definition: DataHandler.php:9108
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyL10nOverlayRecords
‪copyL10nOverlayRecords($table, $uid, $destPid, $first=false, $overrideValues=[], $excludeFields='')
Definition: DataHandler.php:3913
‪TYPO3\CMS\Core\DataHandling\DataHandler\clearPrefixFromValue
‪string clearPrefixFromValue($table, $value)
Definition: DataHandler.php:8397
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_text_Eval
‪array checkValue_text_Eval($value, $evalArray, $is_in)
Definition: DataHandler.php:2575
‪TYPO3\CMS\Core\DataHandling\DataHandler\$isRecordInWebMount_Cache
‪array $isRecordInWebMount_Cache
Definition: DataHandler.php:434
‪TYPO3\CMS\Core\DataHandling\DataHandler\increaseSortingOfFollowingRecords
‪increaseSortingOfFollowingRecords(string $table, int $pid, int $sortingValue=null)
Definition: DataHandler.php:7601
‪TYPO3\CMS\Core\DataHandling\DataHandler\fixUniqueInPid
‪fixUniqueInPid($table, $uid)
Definition: DataHandler.php:8140
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Core\DataHandling\DataHandler\discardLocalizationOverlayRecords
‪discardLocalizationOverlayRecords(string $table, array $record)
Definition: DataHandler.php:5668
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForCheck
‪array checkValueForCheck($res, $value, $tcaFieldConf, $table, $id, $realPid, $field)
Definition: DataHandler.php:1930
‪TYPO3\CMS\Core\Cache\CacheManager\getCache
‪FrontendInterface getCache($identifier)
Definition: CacheManager.php:141
‪TYPO3\CMS\Core\DataHandling\DataHandler\$remapStack
‪array $remapStack
Definition: DataHandler.php:487
‪TYPO3\CMS\Core\DataHandling\DataHandler\$pageCache
‪array $pageCache
Definition: DataHandler.php:446
‪TYPO3\CMS\Core\DataHandling\DataHandler\$storeLogMessages
‪bool $storeLogMessages
Definition: DataHandler.php:95
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordLocalization
‪static mixed getRecordLocalization($table, $uid, $language, $andWhereClause='')
Definition: BackendUtility.php:285
‪TYPO3\CMS\Core\DataHandling\DataHandler\getUnique
‪string getUnique($table, $field, $value, $id, $newPid=0)
Definition: DataHandler.php:2416
‪TYPO3\CMS\Core\DataHandling\DataHandler\doesRecordExist_pageLookUp
‪bool array doesRecordExist_pageLookUp($id, $perms, $columns=['uid'])
Definition: DataHandler.php:6677
‪TYPO3\CMS\Core\Versioning\VersionState\NEW_PLACEHOLDER
‪const NEW_PLACEHOLDER
Definition: VersionState.php:47
‪TYPO3\CMS\Core\DataHandling\DataHandler\canDeletePage
‪int[] string canDeletePage($uid)
Definition: DataHandler.php:5164
‪TYPO3\CMS\Core\DataHandling\DataHandler\getInlineFieldType
‪string bool getInlineFieldType($conf)
Definition: DataHandler.php:8293
‪TYPO3\CMS\Core\DataHandling\DataHandler\overrideFieldArray
‪array overrideFieldArray($table, $data)
Definition: DataHandler.php:7798
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerRecordIdForPageCacheClearing
‪registerRecordIdForPageCacheClearing($table, $uid, $pid=null)
Definition: DataHandler.php:8476
‪TYPO3\CMS\Core\DataHandling\DataHandler\$cmdmap
‪array $cmdmap
Definition: DataHandler.php:382
‪TYPO3\CMS\Core\DataHandling\DataHandler\printLogErrorMessages
‪printLogErrorMessages()
Definition: DataHandler.php:8859
‪TYPO3\CMS\Core\DataHandling\DataHandler\getOriginalParentOfRecord
‪int[] getOriginalParentOfRecord($table, $uid)
Definition: DataHandler.php:7992
‪TYPO3\CMS\Core\DataHandling\DataHandler\isElementToBeDeleted
‪bool isElementToBeDeleted($table, $id)
Definition: DataHandler.php:9134
‪TYPO3\CMS\Core\DataHandling\DataHandler\isInWebMount
‪bool isInWebMount($pid)
Definition: DataHandler.php:6507
‪TYPO3\CMS\Core\SysLog\Action
Definition: Cache.php:18
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_NEW
‪const PAGE_NEW
Definition: Permission.php:48
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteEl
‪deleteEl($table, $uid, $noRecordCheck=false, $forceHardDelete=false, bool $deleteRecordsOnPage=true)
Definition: DataHandler.php:4776
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\instance
‪static DataMapProcessor instance(array $dataMap, BackendUserAuthentication $backendUser, ReferenceIndexUpdater $referenceIndexUpdater=null)
Definition: DataMapProcessor.php:89
‪TYPO3\CMS\Core\DataHandling\DataHandler\disableDeleteClause
‪disableDeleteClause()
Definition: DataHandler.php:7951
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkForRecordsFromDisallowedTables
‪string[] null checkForRecordsFromDisallowedTables(array $pageIds)
Definition: DataHandler.php:8410
‪TYPO3\CMS\Core\Database\Connection\lastInsertId
‪string lastInsertId($tableName=null, string $fieldName='uid')
Definition: Connection.php:452
‪TYPO3\CMS\Core\Exception
Definition: Exception.php:22
‪TYPO3\CMS\Core\DataHandling\DataHandler\_ACTION_FLEX_FORMdata
‪_ACTION_FLEX_FORMdata(&$valueArray, $actionCMDs)
Definition: DataHandler.php:2286
‪TYPO3\CMS\Core\DataHandling\DataHandler\hook_processDatamap_afterDatabaseOperations
‪hook_processDatamap_afterDatabaseOperations(&$hookObjectsArr, &$status, &$table, &$id, &$fieldArray)
Definition: DataHandler.php:782
‪TYPO3\CMS\Core\DataHandling\DataHandler\isSubmittedValueEqualToStoredValue
‪bool isSubmittedValueEqualToStoredValue($submittedValue, $storedValue, $storedType, $allowNull=false)
Definition: DataHandler.php:7878
‪TYPO3\CMS\Core\DataHandling\DataHandler\compareFieldArrayWithCurrentAndUnset
‪array compareFieldArrayWithCurrentAndUnset($table, $id, $fieldArray)
Definition: DataHandler.php:7816
‪TYPO3\CMS\Core\DataHandling\DataHandler\assemblePermissions
‪int assemblePermissions($string)
Definition: DataHandler.php:7909
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForGroupSelect
‪array checkValueForGroupSelect($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $recFID, $uploadedFiles, $field)
Definition: DataHandler.php:2051
‪TYPO3\CMS\Core\Cache\CacheManager\flushCachesInGroupByTags
‪flushCachesInGroupByTags($groupIdentifier, array $tags)
Definition: CacheManager.php:235
‪TYPO3\CMS\Core\DataHandling\DataHandler\getFieldEvalCacheIdentifier
‪string getFieldEvalCacheIdentifier($additionalIdentifier)
Definition: DataHandler.php:9357
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForFlex
‪array checkValueForFlex($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $tscPID, $uploadedFiles, $field)
Definition: DataHandler.php:2167
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyPages
‪copyPages($uid, $destPid)
Definition: DataHandler.php:3359
‪TYPO3\CMS\Core\DataHandling\DataHandler\getCopyHeader
‪string getCopyHeader($table, $pid, $field, $value, $count, $prevTitle='')
Definition: DataHandler.php:8322
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:35
‪TYPO3\CMS\Core\DataHandling\DataHandler\getTableEntries
‪array getTableEntries($table, $TSconfig)
Definition: DataHandler.php:8009
‪TYPO3\CMS\Core\DataHandling\DataHandler\$errorLog
‪array $errorLog
Definition: DataHandler.php:296
‪TYPO3\CMS\Core\DataHandling\DataHandler\remapListedDBRecords_procInline
‪remapListedDBRecords_procInline($conf, $value, $uid, $table)
Definition: DataHandler.php:6115
‪TYPO3\CMS\Core\DataHandling\DataHandler\destNotInsideSelf
‪bool destNotInsideSelf($destinationId, $id)
Definition: DataHandler.php:6796
‪TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException
Definition: InvalidPasswordHashException.php:26
‪TYPO3\CMS\Core\DataHandling\DataHandler\getOuterMostInstance
‪DataHandler getOuterMostInstance()
Definition: DataHandler.php:9050
‪TYPO3\CMS\Core\DataHandling\DataHandler\getExcludeListArray
‪array getExcludeListArray()
Definition: DataHandler.php:6833
‪TYPO3\CMS\Core\DataHandling\DataHandler\$callBackObj
‪object $callBackObj
Definition: DataHandler.php:239
‪TYPO3\CMS\Core\DataHandling\DataHandler\$BE_USER
‪BackendUserAuthentication $BE_USER
Definition: DataHandler.php:321
‪TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface\removeAll
‪QueryRestrictionContainerInterface removeAll()
‪TYPO3\CMS\Core\DataHandling
Definition: DataHandler.php:16
‪if
‪if(PHP_SAPI !=='cli')
Definition: splitAcceptanceTests.php:33
‪TYPO3\CMS\Core\DataHandling\DataHandler\discard
‪discard(string $table, ?int $uid, array $record=null)
Definition: DataHandler.php:5400
‪TYPO3\CMS\Core\DataHandling\DataHandler\resetNestedElementCalls
‪resetNestedElementCalls()
Definition: DataHandler.php:9118
‪TYPO3\CMS\Core\DataHandling\DataHandler\resolveSortingAndPidForNewRecord
‪array resolveSortingAndPidForNewRecord(string $table, int $pid, array $fieldArray)
Definition: DataHandler.php:1225
‪TYPO3\CMS\Core\DataHandling\DataHandler\isRecordCopied
‪bool isRecordCopied($table, $uid)
Definition: DataHandler.php:8448
‪TYPO3\CMS\Core\DataHandling\DataHandler\dbAnalysisStoreExec
‪dbAnalysisStoreExec()
Definition: DataHandler.php:8044
‪TYPO3\CMS\Core\Database\Connection\insert
‪int insert($tableName, array $data, array $types=[])
Definition: Connection.php:211
‪TYPO3\CMS\Core\DataHandling\DataHandler\$excludedTablesAndFields
‪array $excludedTablesAndFields
Definition: DataHandler.php:363
‪TYPO3\CMS\Core\DataHandling\DataHandler\getPreviousLocalizedRecordUid
‪int getPreviousLocalizedRecordUid($table, $uid, $pid, $language)
Definition: DataHandler.php:7647
‪TYPO3\CMS\Core\DataHandling\DataHandler\$enableLogging
‪bool $enableLogging
Definition: DataHandler.php:101
‪TYPO3\CMS\Backend\Utility\BackendUtility\getItemLabel
‪static string getItemLabel($table, $col)
Definition: BackendUtility.php:1521
‪TYPO3\CMS\Backend\Utility\BackendUtility\purgeComputedPropertiesFromRecord
‪static array< string, mixed > purgeComputedPropertiesFromRecord(array $record)
Definition: BackendUtility.php:172
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForRadio
‪array checkValueForRadio($res, $value, $tcaFieldConf, $table, $id, $pid, $field)
Definition: DataHandler.php:2002
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForInline
‪array bool checkValueForInline($res, $value, $tcaFieldConf, $table, $id, $status, $field, array $additionalData=null)
Definition: DataHandler.php:2349
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_checkMax
‪array checkValue_checkMax($tcaFieldConf, $valueArray)
Definition: DataHandler.php:2388
‪TYPO3\CMS\Core\DataHandling\DataHandler\getLanguageService
‪LanguageService getLanguageService()
Definition: DataHandler.php:9399
‪TYPO3\CMS\Core\SysLog\Action\Database
Definition: Database.php:24
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\getRestrictions
‪QueryRestrictionContainerInterface getRestrictions()
Definition: QueryBuilder.php:104
‪TYPO3\CMS\Core\DataHandling\DataHandler\getSortNumber
‪int array bool null getSortNumber($table, $uid, $pid)
Definition: DataHandler.php:7453
‪TYPO3\CMS\Core\DataHandling\DataHandler\addNewValuesToRemapStackChildIds
‪addNewValuesToRemapStackChildIds(array $idValues)
Definition: DataHandler.php:8995
‪TYPO3\CMS\Backend\Utility\BackendUtility\getLiveVersionIdOfRecord
‪static int null getLiveVersionIdOfRecord($table, $uid)
Definition: BackendUtility.php:3765
‪TYPO3\CMS\Core\DataHandling\DataHandler\$substNEWwithIDs_table
‪array $substNEWwithIDs_table
Definition: DataHandler.php:269
‪TYPO3\CMS\Core\DataHandling\DataHandler\tableReadOnly
‪bool tableReadOnly($table)
Definition: DataHandler.php:6768
‪TYPO3\CMS\Core\DataHandling\DataHandler\isRecordUndeletable
‪bool isRecordUndeletable($table, $uid)
Definition: DataHandler.php:5245
‪TYPO3\CMS\Core\Utility\ArrayUtility\mergeRecursiveWithOverrule
‪static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
Definition: ArrayUtility.php:654
‪TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface
Definition: QueryRestrictionContainerInterface.php:25
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteRecord
‪deleteRecord($table, $uid, $noRecordCheck=false, $forceHardDelete=false, $undeleteRecord=false)
Definition: DataHandler.php:4857
‪TYPO3\CMS\Core\Database\Connection\quoteIdentifier
‪string quoteIdentifier($identifier)
Definition: Connection.php:133
‪TYPO3\CMS\Core\Versioning\VersionState\DELETE_PLACEHOLDER
‪const DELETE_PLACEHOLDER
Definition: VersionState.php:55
‪TYPO3\CMS\Core\DataHandling\DataHandler\postProcessSqlServerInsert
‪int postProcessSqlServerInsert(Connection $connection, string $tableName)
Definition: DataHandler.php:9295
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:194
‪TYPO3\CMS\Core\DataHandling\DataHandler\compileAdminTables
‪array compileAdminTables()
Definition: DataHandler.php:8128
‪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:1631
‪TYPO3\CMS\Core\DataHandling\DataHandler\postProcessPostgresqlInsert
‪postProcessPostgresqlInsert(Connection $connection, string $tableName)
Definition: DataHandler.php:9316
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_POINTER
‪const MOVE_POINTER
Definition: VersionState.php:73
‪TYPO3\CMS\Backend\Utility\BackendUtility\isRootLevelRestrictionIgnored
‪static bool isRootLevelRestrictionIgnored($table)
Definition: BackendUtility.php:4063
‪TYPO3\CMS\Core\DataHandling\DataHandler\$remapStackActions
‪array $remapStackActions
Definition: DataHandler.php:507
‪TYPO3\CMS\Core\DataHandling\DataHandler\$runtimeCache
‪TYPO3 CMS Core Cache Frontend FrontendInterface $runtimeCache
Definition: DataHandler.php:574
‪TYPO3\CMS\Core\DataHandling\DataHandler\$pagePermissionAssembler
‪PagePermissionAssembler $pagePermissionAssembler
Definition: DataHandler.php:357
‪TYPO3\CMS\Core\DataHandling\DataHandler\$correlationId
‪CorrelationId null $correlationId
Definition: DataHandler.php:246
‪$fields
‪$fields
Definition: pages.php:5
‪TYPO3\CMS\Core\DataHandling\DataHandler\undeleteRecord
‪undeleteRecord($table, $uid)
Definition: DataHandler.php:4837
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteAction
‪deleteAction($table, $id)
Definition: DataHandler.php:4747
‪TYPO3\CMS\Core\Database\Connection\update
‪int update($tableName, array $data, array $identifier, array $types=[])
Definition: Connection.php:302
‪TYPO3\CMS\Core\DataHandling\DataHandler\recordInfo
‪array null recordInfo($table, $id, $fieldList)
Definition: DataHandler.php:6943
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkModifyAccessList
‪bool checkModifyAccessList($table)
Definition: DataHandler.php:6472
‪TYPO3\CMS\Core\DataHandling\DataHandler\resolvePid
‪int resolvePid($table, $pid)
Definition: DataHandler.php:8371
‪TYPO3\CMS\Core\DataHandling\DataHandler\$recInsertAccessCache
‪array $recInsertAccessCache
Definition: DataHandler.php:428
‪TYPO3\CMS\Core\DataHandling\DataHandler\isOuterMostInstance
‪bool isOuterMostInstance()
Definition: DataHandler.php:9071
‪TYPO3\CMS\Core\DataHandling\DataHandler\$remapStackChildIds
‪array $remapStackChildIds
Definition: DataHandler.php:501
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:24
‪TYPO3\CMS\Core\DataHandling\DataHandler\getCheckModifyAccessListHookObjects
‪array getCheckModifyAccessListHookObjects()
Definition: DataHandler.php:807
‪TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory
Definition: RecordStateFactory.php:26
‪TYPO3\CMS\Backend\Utility\BackendUtility\isWebMountRestrictionIgnored
‪static bool isWebMountRestrictionIgnored($table)
Definition: BackendUtility.php:4050
‪TYPO3\CMS\Core\DataHandling\DataHandler\overlayAutoVersionId
‪int overlayAutoVersionId($table, $id)
Definition: DataHandler.php:8981
‪TYPO3\CMS\Core\Database\Query\QueryBuilder
Definition: QueryBuilder.php:52
‪TYPO3\CMS\Core\DataHandling\DataHandler\$bypassWorkspaceRestrictions
‪bool $bypassWorkspaceRestrictions
Definition: DataHandler.php:170
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:43
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableWorkspaceEnabled
‪static bool isTableWorkspaceEnabled($table)
Definition: BackendUtility.php:4021
‪TYPO3\CMS\Core\DataHandling\DataHandler\setControl
‪setControl(array $control)
Definition: DataHandler.php:605
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore
Definition: RecordHistoryStore.php:31
‪TYPO3\CMS\Core\DataHandling\DataHandler\getLocalTCE
‪DataHandler getLocalTCE()
Definition: DataHandler.php:5931
‪TYPO3\CMS\Core\DataHandling\DataHandler\discardSubPagesAndRecordsOnPage
‪discardSubPagesAndRecordsOnPage(array $page)
Definition: DataHandler.php:5536
‪TYPO3\CMS\Core\DataHandling\DataHandler\$checkStoredRecords
‪bool $checkStoredRecords
Definition: DataHandler.php:123
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidParentRowRootException
Definition: InvalidParentRowRootException.php:22
‪TYPO3\CMS\Core\DataHandling\DataHandler\hasDeletedRecord
‪bool hasDeletedRecord($tableName, $uid)
Definition: DataHandler.php:8949
‪TYPO3\CMS\Core\DataHandling\DataHandler\triggerRemapAction
‪triggerRemapAction($table, $id, array $callback, array $arguments, $forceRemapStackActions=false)
Definition: DataHandler.php:6380
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor
Definition: DataMapProcessor.php:52
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\addRecord
‪string addRecord(string $table, int $uid, array $payload, CorrelationId $correlationId=null)
Definition: RecordHistoryStore.php:85
‪TYPO3\CMS\Core\DataHandling\DataHandler\updateDB
‪updateDB($table, $id, $fieldArray)
Definition: DataHandler.php:7114
‪TYPO3\CMS\Core\Type\Enumeration\cast
‪static static cast($value)
Definition: Enumeration.php:186
‪TYPO3\CMS\Core\DataHandling\DataHandler\getAutoVersionId
‪int null getAutoVersionId($table, $id)
Definition: DataHandler.php:8965
‪TYPO3\CMS\Core\DataHandling\DataHandler\doesBranchExist
‪string int doesBranchExist($inList, $pid, $perms, $recurse)
Definition: DataHandler.php:6730
‪TYPO3\CMS\Core\Messaging\AbstractMessage\WARNING
‪const WARNING
Definition: AbstractMessage.php:30
‪TYPO3\CMS\Core\DataHandling\DataHandler\prepareCacheFlush
‪array prepareCacheFlush($table, $uid, $pid)
Definition: DataHandler.php:8541
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveL10nOverlayRecords
‪moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid)
Definition: DataHandler.php:4351
‪TYPO3\CMS\Core\DataHandling\DataHandler\setDefaultsFromUserTS
‪setDefaultsFromUserTS($userTS)
Definition: DataHandler.php:704
‪TYPO3\CMS\Core\DataHandling\DataHandler\remapListedDBRecords
‪remapListedDBRecords()
Definition: DataHandler.php:5950
‪TYPO3\CMS\Backend\Utility\BackendUtility\fixVersioningPid
‪static fixVersioningPid($table, &$rr, $ignoreWorkspaceMatch=false)
Definition: BackendUtility.php:3511
‪TYPO3\CMS\Core\DataHandling\DataHandler\discardRecordRelations
‪discardRecordRelations(string $table, array $record)
Definition: DataHandler.php:5598
‪TYPO3\CMS\Core\DataHandling\DataHandler\$pMap
‪array $pMap
Definition: DataHandler.php:402
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord_raw
‪int copyRecord_raw($table, $uid, $pid, $overrideArray=[], array $workspaceOptions=[])
Definition: DataHandler.php:3568
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue
‪array checkValue($table, $field, $value, $id, $status, $realPid, $tscPID, $incomingFieldArray=[])
Definition: DataHandler.php:1502
‪TYPO3\CMS\Core\DataHandling\DataHandler\getVersionizedIncomingFieldArray
‪getVersionizedIncomingFieldArray($table, $id, &$incomingFieldArray, &$registerDBList)
Definition: DataHandler.php:6423
‪TYPO3\CMS\Core\DataHandling\DataHandler\$cachePrefixNestedElementCalls
‪string $cachePrefixNestedElementCalls
Definition: DataHandler.php:580
‪TYPO3\CMS\Core\DataHandling\DataHandler\localize
‪int bool localize($table, $uid, $language)
Definition: DataHandler.php:4410
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord_procFields
‪moveRecord_procFields($table, $uid, $destPid)
Definition: DataHandler.php:4291
‪TYPO3\CMS\Core\DataHandling\DataHandler\$datamap
‪array $datamap
Definition: DataHandler.php:376
‪TYPO3\CMS\Core\DataHandling\DataHandler\recordInfoWithPermissionCheck
‪array bool recordInfoWithPermissionCheck(string $table, int $id, $perms, string $fieldList=' *')
Definition: DataHandler.php:6970
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForText
‪array checkValueForText($value, $tcaFieldConf, $table, $id, $realPid, $field)
Definition: DataHandler.php:1742
‪TYPO3\CMS\Core\DataHandling\DataHandler\deletePages
‪deletePages($uid, $force=false, $forceHardDelete=false, bool $deleteRecordsOnPage=true)
Definition: DataHandler.php:4977
‪TYPO3\CMS\Core\DataHandling\DataHandler\$registerDBPids
‪array $registerDBPids
Definition: DataHandler.php:468
‪TYPO3\CMS\Core\DataHandling\DataHandler\$outerMostInstance
‪TYPO3 CMS Core DataHandling DataHandler $outerMostInstance
Definition: DataHandler.php:555
‪TYPO3\CMS\Core\DataHandling\DataHandler\isTableAllowedForThisPage
‪bool isTableAllowedForThisPage($page_uid, $checkTable)
Definition: DataHandler.php:6621
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidIdentifierException
Definition: InvalidIdentifierException.php:22
‪TYPO3\CMS\Core\DataHandling\DataHandler\isReferenceField
‪bool isReferenceField($conf)
Definition: DataHandler.php:8280
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:32
‪TYPO3\CMS\Core\DataHandling\DataHandler\$userid
‪int $userid
Definition: DataHandler.php:328
‪TYPO3\CMS\Core\DataHandling\DataHandler\$remapStackRecords
‪array $remapStackRecords
Definition: DataHandler.php:495
‪TYPO3\CMS\Core\DataHandling\DataHandler\$username
‪string $username
Definition: DataHandler.php:335
‪TYPO3\CMS\Core\DataHandling\DataHandler\$copyMappingArray
‪array $copyMappingArray
Definition: DataHandler.php:480
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteRecord_procBasedOnFieldType
‪deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf, $undeleteRecord=false)
Definition: DataHandler.php:5299
‪TYPO3\CMS\Core\DataHandling\DataHandler\$newRelatedIDs
‪array $newRelatedIDs
Definition: DataHandler.php:276
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\process
‪array process()
Definition: DataMapProcessor.php:127
‪TYPO3\CMS\Core\DataHandling\DataHandler\$suggestedInsertUids
‪array $suggestedInsertUids
Definition: DataHandler.php:231
‪TYPO3\CMS\Core\DataHandling\DataHandler\$checkValue_currentRecord
‪array $checkValue_currentRecord
Definition: DataHandler.php:534
‪TYPO3\CMS\Core\DataHandling\Model\CorrelationId
Definition: CorrelationId.php:29
‪TYPO3\CMS\Core\Cache\CacheManager\flushCaches
‪flushCaches()
Definition: CacheManager.php:176
‪TYPO3\CMS\Core\Utility\ArrayUtility\arrayDiffKeyRecursive
‪static array arrayDiffKeyRecursive(array $array1, array $array2)
Definition: ArrayUtility.php:768
‪TYPO3\CMS\Core\Database\Query\QueryHelper\getDateTimeTypes
‪static array getDateTimeTypes()
Definition: QueryHelper.php:202
‪TYPO3\CMS\Core\DataHandling\DataHandler\$disableDeleteClause
‪bool $disableDeleteClause
Definition: DataHandler.php:540
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForInput
‪array checkValueForInput($value, $tcaFieldConf, $table, $id, $realPid, $field)
Definition: DataHandler.php:1786
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools
Definition: FlexFormTools.php:38
‪TYPO3\CMS\Core\Resource\ResourceFactory
Definition: ResourceFactory.php:41
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkStoredRecord
‪array null checkStoredRecord($table, $id, $fieldArray, $action)
Definition: DataHandler.php:7288
‪TYPO3\CMS\Core\DataHandling\DataHandler\$defaultValues
‪array $defaultValues
Definition: DataHandler.php:203
‪TYPO3\CMS\Backend\Utility\BackendUtility\getLiveVersionOfRecord
‪static array null getLiveVersionOfRecord($table, $uid, $fields=' *')
Definition: BackendUtility.php:3748
‪TYPO3\CMS\Core\DataHandling\DataHandler\$pagetreeNeedsRefresh
‪bool $pagetreeNeedsRefresh
Definition: DataHandler.php:309
‪TYPO3\CMS\Core\DataHandling\DataHandler\$overrideValues
‪array $overrideValues
Definition: DataHandler.php:212
‪TYPO3\CMS\Core\DataHandling\DataHandler\clear_cacheCmd
‪clear_cacheCmd($cacheCmd)
Definition: DataHandler.php:8735
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\undeleteRecord
‪string undeleteRecord(string $table, int $uid, CorrelationId $correlationId=null)
Definition: RecordHistoryStore.php:157
‪TYPO3\CMS\Core\DataHandling\DataHandler\$sortIntervals
‪int $sortIntervals
Definition: DataHandler.php:421
‪TYPO3\CMS\Backend\Utility\BackendUtility\getPagesTSconfig
‪static array getPagesTSconfig($id)
Definition: BackendUtility.php:698
‪TYPO3\CMS\Core\DataHandling\DataHandler\versionizeRecord
‪int null versionizeRecord($table, $id, $label, $delete=false)
Definition: DataHandler.php:5717
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordTitle
‪static string getRecordTitle($table, $row, $prep=false, $forceResult=true)
Definition: BackendUtility.php:1541
‪TYPO3\CMS\Core\Html\RteHtmlParser
Definition: RteHtmlParser.php:38
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord_raw
‪moveRecord_raw($table, $uid, $destPid)
Definition: DataHandler.php:4116
‪TYPO3\CMS\Core\DataHandling\DataHandler\$dbAnalysisStore
‪array $dbAnalysisStore
Definition: DataHandler.php:454
‪TYPO3\CMS\Core\DataHandling\DataHandler\eventPid
‪int eventPid($table, $uid, $pid)
Definition: DataHandler.php:7095
‪TYPO3\CMS\Core\DataHandling\DataHandler\insertUpdateDB_preprocessBasedOnFieldType
‪array insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray)
Definition: DataHandler.php:8927
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\deleteRecord
‪string deleteRecord(string $table, int $uid, CorrelationId $correlationId=null)
Definition: RecordHistoryStore.php:134
‪TYPO3\CMS\Backend\Utility\BackendUtility\getMovePlaceholder
‪static array bool getMovePlaceholder($table, $uid, $fields=' *', $workspace=null)
Definition: BackendUtility.php:3851
‪TYPO3\CMS\Core\SysLog\Error
Definition: Error.php:24
‪TYPO3\CMS\Core\DataHandling\DataHandler\normalizeTimeFormat
‪string normalizeTimeFormat(string $table, string $value, string $dbType)
Definition: DataHandler.php:1199
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_input_Eval
‪array checkValue_input_Eval($value, $evalArray, $is_in, string $table='', $id='')
Definition: DataHandler.php:2617
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:35
‪TYPO3\CMS\Core\Cache\CacheManager\flushCachesInGroup
‪flushCachesInGroup($groupIdentifier)
Definition: CacheManager.php:190
‪TYPO3\CMS\Core\DataHandling\DataHandler\getPID
‪int false getPID($table, $uid)
Definition: DataHandler.php:8025
‪TYPO3\CMS\Core\DataHandling\DataHandler\$defaultPermissions
‪array $defaultPermissions
Definition: DataHandler.php:349
‪TYPO3\CMS\Core\Type\Bitmask\Permission\getMap
‪static array getMap()
Definition: Permission.php:67
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteL10nOverlayRecords
‪deleteL10nOverlayRecords($table, $uid)
Definition: DataHandler.php:5346
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkRecordUpdateAccess
‪bool checkRecordUpdateAccess($table, $id, $data=false, $hookObjectsArr=null)
Definition: DataHandler.php:6525
‪TYPO3\CMS\Core\DataHandling\DataHandler\int_pageTreeInfo
‪array int_pageTreeInfo($CPtable, $pid, $counter, $rootID)
Definition: DataHandler.php:8065
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:33
‪TYPO3\CMS\Core\DataHandling\DataHandler\fixCopyAfterDuplFields
‪array fixCopyAfterDuplFields($table, $uid, $prevUid, $update, $newData=[])
Definition: DataHandler.php:8227
‪TYPO3\CMS\Core\DataHandling\DataHandler\$isImporting
‪bool $isImporting
Definition: DataHandler.php:148
‪TYPO3\CMS\Core\DataHandling\DataHandler\doesRecordExist
‪bool doesRecordExist($table, $id, $perms)
Definition: DataHandler.php:6659
‪TYPO3\CMS\Core\DataHandling\DataHandler\createRelationHandlerInstance
‪RelationHandler createRelationHandlerInstance()
Definition: DataHandler.php:9365
‪TYPO3\CMS\Core\Service\OpcodeCacheService
Definition: OpcodeCacheService.php:25
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForSlug
‪array checkValueForSlug(string $value, array $tcaFieldConf, string $table, $id, int $realPid, string $field, array $incomingFieldArray=[])
Definition: DataHandler.php:1885
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteVersionsForRecord
‪deleteVersionsForRecord($table, $uid, $forceHardDelete)
Definition: DataHandler.php:4794
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_group_select_processDBdata
‪array checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, $type, $currentTable, $currentField)
Definition: DataHandler.php:2806
‪TYPO3\CMS\Core\DataHandling\DataHandler\getCacheManager
‪CacheManager getCacheManager()
Definition: DataHandler.php:9381
‪TYPO3\CMS\Core\DataHandling\DataHandler\$copyWhichTables
‪string $copyWhichTables
Definition: DataHandler.php:185
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyMovedRecordToNewLocation
‪copyMovedRecordToNewLocation($table, $uid)
Definition: DataHandler.php:5125
‪TYPO3\CMS\Core\DataHandling\DataHandler\copySpecificPage
‪int null copySpecificPage($uid, $destPid, $copyTablesArray, $first=false)
Definition: DataHandler.php:3431
‪TYPO3\CMS\Core\DataHandling\DataHandler\$checkSimilar
‪bool $checkSimilar
Definition: DataHandler.php:116
‪TYPO3\CMS\Core\DataHandling\DataHandler\$historyRecords
‪array $historyRecords
Definition: DataHandler.php:394
‪TYPO3\CMS\Core\DataHandling\DataHandler\process_cmdmap
‪void bool process_cmdmap()
Definition: DataHandler.php:3073
‪TYPO3\CMS\Core\DataHandling\DataHandler\getUniqueCountStatement
‪QueryBuilder getUniqueCountStatement(string $value, string $table, string $field, int $uid, int $pid)
Definition: DataHandler.php:2467
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:75
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_flex_procInData
‪array checkValue_flex_procInData($dataPart, $dataPart_current, $uploadedFiles, $dataStructure, $pParams, $callBackFunc='', array $workspaceOptions=[])
Definition: DataHandler.php:2885
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerElementsToBeDeleted
‪registerElementsToBeDeleted()
Definition: DataHandler.php:9145
‪TYPO3\CMS\Backend\Utility\BackendUtility\getWorkspaceVersionOfRecord
‪static array bool getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields=' *')
Definition: BackendUtility.php:3705
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordWSOL
‪static array getRecordWSOL( $table, $uid, $fields=' *', $where='', $useDeleteClause=true, $unsetMovePointers=false)
Definition: BackendUtility.php:139
‪TYPO3\CMS\Core\DataHandling\DataHandler\getCorrelationId
‪CorrelationId null getCorrelationId()
Definition: DataHandler.php:9250
‪TYPO3\CMS\Core\DataHandling\DataHandler\processClearCacheQueue
‪processClearCacheQueue()
Definition: DataHandler.php:8493
‪TYPO3\CMS\Core\DataHandling\DataHandler\getAllowedTablesToCopyWhenCopyingAPage
‪array getAllowedTablesToCopyWhenCopyingAPage()
Definition: DataHandler.php:3400
‪TYPO3\CMS\Core\DataHandling\DataHandler\process_uploads
‪process_uploads()
Definition: DataHandler.php:758
‪TYPO3\CMS\Core\DataHandling\DataHandler\setCorrelationId
‪setCorrelationId(CorrelationId $correlationId)
Definition: DataHandler.php:9242
‪TYPO3\CMS\Core\DataHandling\DataHandler\convNumEntityToByteValue
‪string convNumEntityToByteValue($input)
Definition: DataHandler.php:7930
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:95
‪$errors
‪$errors
Definition: annotationChecker.php:121
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:24
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static string[] trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:1059
‪$output
‪$output
Definition: annotationChecker.php:119
‪TYPO3\CMS\Core\Utility\ArrayUtility\setValueByPath
‪static array setValueByPath(array $array, $path, $value, $delimiter='/')
Definition: ArrayUtility.php:272
‪TYPO3\CMS\Core\DataHandling\DataHandler\version_remapMMForVersionSwap
‪version_remapMMForVersionSwap($table, $id, $swapWith)
Definition: DataHandler.php:5805
‪TYPO3\CMS\Core\DataHandling\DataHandler\setMirror
‪setMirror($mirror)
Definition: DataHandler.php:674
‪TYPO3\CMS\Core\DataHandling\DataHandler\unsetElementsToBeDeleted
‪array unsetElementsToBeDeleted(array $elements)
Definition: DataHandler.php:9168
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidPointerFieldValueException
Definition: InvalidPointerFieldValueException.php:22
‪TYPO3\CMS\Core\DataHandling\DataHandler\$version_remapMMForVersionSwap_reg
‪array $version_remapMMForVersionSwap_reg
Definition: DataHandler.php:548
‪TYPO3\CMS\Core\DataHandling\DataHandler\$bypassAccessCheckForRecords
‪bool $bypassAccessCheckForRecords
Definition: DataHandler.php:177
‪TYPO3\CMS\Core\DataHandling\DataHandler\controlActiveElements
‪controlActiveElements()
Definition: DataHandler.php:9204
‪TYPO3\CMS\Core\DataHandling\DataHandler\moveRecord_procBasedOnFieldType
‪moveRecord_procBasedOnFieldType($table, $uid, $destPid, $field, $value, $conf)
Definition: DataHandler.php:4313
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:36
‪TYPO3\CMS\Core\Versioning\VersionState\NEW_PLACEHOLDER_VERSION
‪const NEW_PLACEHOLDER_VERSION
Definition: VersionState.php:33
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:22
‪TYPO3\CMS\Core\DataHandling\DataHandler\processRemapStack
‪processRemapStack()
Definition: DataHandler.php:6182
‪TYPO3\CMS\Core\DataHandling\DataHandler\prependLabel
‪string prependLabel($table)
Definition: DataHandler.php:8358
‪TYPO3\CMS\Core\DataHandling\DataHandler\$copyTree
‪int $copyTree
Definition: DataHandler.php:193
‪TYPO3\CMS\Core\DataHandling\DataHandler\insertNewCopyVersion
‪int insertNewCopyVersion($table, $fieldArray, $realPid)
Definition: DataHandler.php:3649
‪TYPO3\CMS\Core\DataHandling\DataHandler\addRemapAction
‪addRemapAction($table, $id, array $callback, array $arguments)
Definition: DataHandler.php:6399
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_inline
‪checkValue_inline($res, $value, $tcaFieldConf, $PP, $field, array $additionalData=null)
Definition: DataHandler.php:2328
‪TYPO3\CMS\Core\DataHandling\DataHandler\$admin
‪bool $admin
Definition: DataHandler.php:342
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord_processInline
‪string copyRecord_processInline( $table, $uid, $field, $value, $row, $conf, $realDestPid, $language, array $workspaceOptions)
Definition: DataHandler.php:3796
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static string getUniqueId($prefix='')
Definition: StringUtility.php:92
‪TYPO3\CMS\Core\DataHandling\DataHandler\postProcessDatabaseInsert
‪int postProcessDatabaseInsert(Connection $connection, string $tableName, int $suggestedUid)
Definition: DataHandler.php:9264
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:24
‪TYPO3\CMS\Core\DataHandling\DataHandler\version_remapMMForVersionSwap_execSwap
‪version_remapMMForVersionSwap_execSwap($table, $id, $swapWith)
Definition: DataHandler.php:5901
‪TYPO3\CMS\Core\DataHandling\DataHandler\start
‪start($data, $cmd, $altUserObject=null)
Definition: DataHandler.php:619
‪TYPO3\CMS\Backend\Utility\BackendUtility\getTSCpid
‪static array getTSCpid($table, $uid, $pid)
Definition: BackendUtility.php:3208
‪TYPO3\CMS\Core\DataHandling\DataHandler\applyFiltersToValues
‪array mixed applyFiltersToValues(array $tcaFieldConfiguration, array $values)
Definition: DataHandler.php:2130
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableLocalizable
‪static bool isTableLocalizable($table)
Definition: BackendUtility.php:578
‪TYPO3\CMS\Core\DataHandling\DataHandler\getCommandMapElements
‪array getCommandMapElements($needle)
Definition: DataHandler.php:9185
‪TYPO3\CMS\Core\Type\Bitmask\Permission\CONTENT_EDIT
‪const CONTENT_EDIT
Definition: Permission.php:53
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordHistoryStore
‪RecordHistoryStore getRecordHistoryStore()
Definition: DataHandler.php:7373
‪TYPO3\CMS\Core\Database\Query\QueryHelper\stripLogicalOperatorPrefix
‪static string stripLogicalOperatorPrefix(string $constraint)
Definition: QueryHelper.php:165
‪TYPO3\CMS\Backend\Utility\BackendUtility\getTSconfig_pidValue
‪static int getTSconfig_pidValue($table, $uid, $pid)
Definition: BackendUtility.php:3137
‪TYPO3\CMS\Core\Versioning\VersionState\DEFAULT_STATE
‪const DEFAULT_STATE
Definition: VersionState.php:39
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:24
‪TYPO3\CMS\Core\DataHandling\PagePermissionAssembler
Definition: PagePermissionAssembler.php:34
‪TYPO3\CMS\Core\DataHandling\DataHandler\remapListedDBRecords_procDBRefs
‪array null remapListedDBRecords_procDBRefs($conf, $value, $MM_localUid, $table)
Definition: DataHandler.php:6048
‪TYPO3\CMS\Core\DataHandling\DataHandler\$autoVersionIdMap
‪array $autoVersionIdMap
Definition: DataHandler.php:256
‪TYPO3\CMS\Core\DataHandling\DataHandler\fillInFieldArray
‪array fillInFieldArray($table, $id, $fieldArray, $incomingFieldArray, $realPid, $status, $tscPID)
Definition: DataHandler.php:1348
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_flexArray2Xml
‪string checkValue_flexArray2Xml($array, $addPrologue=false)
Definition: DataHandler.php:2272
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteClause
‪string deleteClause($table)
Definition: DataHandler.php:7963
‪$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:3586
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Core\DataHandling\DataHandler\addDefaultPermittedLanguageIfNotSet
‪addDefaultPermittedLanguageIfNotSet($table, &$incomingFieldArray)
Definition: DataHandler.php:7764
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_EDIT
‪const PAGE_EDIT
Definition: Permission.php:38
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_DELETE
‪const PAGE_DELETE
Definition: Permission.php:43
‪TYPO3\CMS\Core\DataHandling\DataHandler\isRecordInWebMount
‪bool isRecordInWebMount($table, $id)
Definition: DataHandler.php:6491
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord_processManyToMany
‪mixed copyRecord_processManyToMany($table, $uid, $field, $value, $conf, $language)
Definition: DataHandler.php:3745
‪TYPO3\CMS\Backend\Utility\BackendUtility\selectVersionsOfRecord
‪static array null selectVersionsOfRecord( $table, $uid, $fields=' *', $workspace=0, $includeDeletedRecords=false, $row=null)
Definition: BackendUtility.php:3416
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static int[] intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:988
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidParentRowLoopException
Definition: InvalidParentRowLoopException.php:22
‪TYPO3\CMS\Core\DataHandling\DataHandler\placeholderShadowing
‪placeholderShadowing($table, $id)
Definition: DataHandler.php:1261
‪TYPO3\CMS\Core\DataHandling\DataHandler\$checkModifyAccessListHookObjects
‪array $checkModifyAccessListHookObjects
Definition: DataHandler.php:544
‪TYPO3\CMS\Core\DataHandling\DataHandler\$useTransOrigPointerField
‪bool $useTransOrigPointerField
Definition: DataHandler.php:162
‪TYPO3\CMS\Core\DataHandling\DataHandler\applyDefaultsForFieldArray
‪array applyDefaultsForFieldArray(string $table, int $pageId, array $prepopulatedFieldArray)
Definition: DataHandler.php:736
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteSpecificPage
‪deleteSpecificPage($uid, $forceHardDelete=false, bool $deleteRecordsOnPage=true)
Definition: DataHandler.php:5022
‪TYPO3\CMS\Core\DataHandling\DataHandler\setNullValues
‪setNullValues(array $active, array &$haystack)
Definition: DataHandler.php:9222
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Core\DataHandling\DataHandler\$dontProcessTransformations
‪bool $dontProcessTransformations
Definition: DataHandler.php:154
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValueForInternalReferences
‪array checkValueForInternalReferences(array $res, $value, $tcaFieldConf, $table, $id, $field)
Definition: DataHandler.php:1698
‪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:2923
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_input_ValidateEmail
‪checkValue_input_ValidateEmail($value, &$set, string $table, $id)
Definition: DataHandler.php:2774
‪TYPO3\CMS\Core\DataHandling\DataHandler\deleteRecord_procFields
‪deleteRecord_procFields($table, $uid, $undeleteRecord=false)
Definition: DataHandler.php:5274
‪TYPO3\CMS\Core\Database\Connection\createQueryBuilder
‪TYPO3 CMS Core Database Query QueryBuilder createQueryBuilder()
Definition: Connection.php:117
‪TYPO3\CMS\Core\Utility\HttpUtility
Definition: HttpUtility.php:24
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordProperties
‪array getRecordProperties($table, $id, $noWSOL=false)
Definition: DataHandler.php:7055
‪TYPO3\CMS\Core\DataHandling\DataHandler\resolveVersionedRecords
‪array resolveVersionedRecords($tableName, $fieldNames, $sortingField, array $liveIds)
Definition: DataHandler.php:9014
‪TYPO3\CMS\Core\DataHandling\DataHandler\addDeleteRestriction
‪addDeleteRestriction(QueryRestrictionContainerInterface $restrictions)
Definition: DataHandler.php:7977
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\DataHandling\DataHandlerCheckModifyAccessListHookInterface
Definition: DataHandlerCheckModifyAccessListHookInterface.php:22
‪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:8256
‪TYPO3\CMS\Core\DataHandling\DataHandler\process_datamap
‪bool void process_datamap()
Definition: DataHandler.php:833
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Core\DataHandling\DataHandler\__construct
‪__construct(ReferenceIndexUpdater $referenceIndexUpdater=null)
Definition: DataHandler.php:587
‪TYPO3\CMS\Core\DataHandling\DataHandler\$deleteTree
‪bool $deleteTree
Definition: DataHandler.php:136
‪TYPO3\CMS\Core\DataHandling\DataHandler\discardMmRelations
‪discardMmRelations(string $table, array $fieldConfig, array $record)
Definition: DataHandler.php:5637
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:22
‪TYPO3\CMS\Core\DataHandling\DataHandler\doesPageHaveUnallowedTables
‪bool array doesPageHaveUnallowedTables($page_uid, $doktype)
Definition: DataHandler.php:6863
‪TYPO3\CMS\Core\DataHandling\DataHandler\$callFromImpExp
‪bool $callFromImpExp
Definition: DataHandler.php:525
‪TYPO3\CMS\Core\DataHandling\DataHandler\pageInfo
‪string pageInfo($id, $field)
Definition: DataHandler.php:6915
‪TYPO3\CMS\Core\DataHandling\DataHandler\copyRecord_flexFormCallBack
‪array copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $_1, $_2, $_3, $workspaceOptions)
Definition: DataHandler.php:3889
‪TYPO3\CMS\Core\DataHandling\DataHandler\registerReferenceIndexRowsForDrop
‪registerReferenceIndexRowsForDrop(string $table, int $uid, int $workspace)
Definition: DataHandler.php:7412
‪TYPO3\CMS\Core\DataHandling\DataHandler\setTSconfigPermissions
‪array setTSconfigPermissions($fieldArray, $TSConfig_p)
Definition: DataHandler.php:7713
‪TYPO3\CMS\Core\Configuration\Richtext
Definition: Richtext.php:33
‪TYPO3\CMS\Core\DataHandling\DataHandler\$neverHideAtCopy
‪bool $neverHideAtCopy
Definition: DataHandler.php:142
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\moveRecord
‪string moveRecord(string $table, int $uid, array $payload, CorrelationId $correlationId=null)
Definition: RecordHistoryStore.php:181
‪TYPO3\CMS\Core\DataHandling\ReferenceIndexUpdater
Definition: ReferenceIndexUpdater.php:34
‪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:8822
‪TYPO3\CMS\Core\DataHandling\DataHandler\isNestedElementCallRegistered
‪bool isNestedElementCallRegistered($table, $id, $identifier)
Definition: DataHandler.php:9094
‪TYPO3\CMS\Core\DataHandling\DataHandler\hardDeleteSingleRecord
‪hardDeleteSingleRecord(string $table, int $uid)
Definition: DataHandler.php:6453
‪TYPO3\CMS\Core\DataHandling\DataHandler\$referenceIndexUpdater
‪ReferenceIndexUpdater $referenceIndexUpdater
Definition: DataHandler.php:517
‪TYPO3\CMS\Core\DataHandling\DataHandler\updateFlexFormData
‪updateFlexFormData($flexFormId, array $modifications)
Definition: DataHandler.php:6328
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:27
‪TYPO3\CMS\Backend\Utility\BackendUtility\wsMapId
‪static int wsMapId($table, $uid)
Definition: BackendUtility.php:3827
‪TYPO3\CMS\Core\DataHandling\DataHandler\$pagetreeRefreshFieldsFromPages
‪array $pagetreeRefreshFieldsFromPages
Definition: DataHandler.php:302
‪TYPO3\CMS\Core\DataHandling\DataHandler\fixUniqueInSite
‪bool fixUniqueInSite(string $table, int $uid)
Definition: DataHandler.php:8172
‪TYPO3\CMS\Core\DataHandling\DataHandler\resetElementsToBeDeleted
‪resetElementsToBeDeleted()
Definition: DataHandler.php:9156
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRecordPropertiesFromRow
‪array null getRecordPropertiesFromRow($table, $row)
Definition: DataHandler.php:7072
‪TYPO3\CMS\Core\Messaging\AbstractMessage\ERROR
‪const ERROR
Definition: AbstractMessage.php:31
‪TYPO3\CMS\Core\DataHandling\DataHandler\cannotDeleteRecord
‪string cannotDeleteRecord($table, $id)
Definition: DataHandler.php:5222
‪TYPO3\CMS\Core\DataHandling\DataHandler\checkValue_inline_processDBdata
‪string checkValue_inline_processDBdata($valueArray, $tcaFieldConf, $id, $status, $table, $field, array $additionalData=null)
Definition: DataHandler.php:3034
‪TYPO3\CMS\Core\DataHandling\DataHandler\insertDB
‪int null insertDB($table, $id, $fieldArray, $newVersion=false, $suggestedUid=0, $dontSetNewIdIndex=false)
Definition: DataHandler.php:7188
‪TYPO3\CMS\Core\DataHandling\DataHandler\isRecordLocalized
‪bool isRecordLocalized(string $table, int $uid, int $language)
Definition: DataHandler.php:4728
‪TYPO3\CMS\Core\DataHandling\DataHandler\$mmHistoryRecords
‪array $mmHistoryRecords
Definition: DataHandler.php:388
‪TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory\forName
‪static static forName(string $name)
Definition: RecordStateFactory.php:35
‪TYPO3\CMS\Core\SysLog\Type
Definition: Type.php:24
‪TYPO3\CMS\Core\DataHandling\DataHandler\getRuntimeCache
‪FrontendInterface getRuntimeCache()
Definition: DataHandler.php:9081
‪TYPO3\CMS\Core\DataHandling\DataHandler\$control
‪array $control
Definition: DataHandler.php:370
‪TYPO3\CMS\Core\DataHandling\DataHandler\$registerDBList
‪array $registerDBList
Definition: DataHandler.php:461
‪TYPO3\CMS\Core\DataHandling\DataHandler\$recordPidsForDeletedRecords
‪static array $recordPidsForDeletedRecords
Definition: DataHandler.php:568
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_PLACEHOLDER
‪const MOVE_PLACEHOLDER
Definition: VersionState.php:72
‪TYPO3\CMS\Core\DataHandling\DataHandler\$copyMappingArray_merged
‪array $copyMappingArray_merged
Definition: DataHandler.php:283
‪TYPO3\CMS\Core\Database\Connection\PARAM_LOB
‪const PARAM_LOB
Definition: Connection.php:57
‪TYPO3\CMS\Core\DataHandling\Model\CorrelationId\forScope
‪static static forScope(string $scope)
Definition: CorrelationId.php:57
‪TYPO3\CMS\Core\DataHandling\DataHandler\$data_disableFields
‪array $data_disableFields
Definition: DataHandler.php:221
‪TYPO3\CMS\Core\DataHandling\DataHandler\newFieldArray
‪array newFieldArray($table)
Definition: DataHandler.php:7742
‪TYPO3\CMS\Core\DataHandling\DataHandler\inlineLocalizeSynchronize
‪inlineLocalizeSynchronize($table, $id, $command)
Definition: DataHandler.php:4574
‪TYPO3\CMS\Core\DataHandling\DataHandler\$deletedRecords
‪array $deletedRecords
Definition: DataHandler.php:289
‪TYPO3\CMS\Core\DataHandling\DataHandler\$checkStoredRecords_loose
‪bool $checkStoredRecords_loose
Definition: DataHandler.php:129
‪TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction
Definition: WorkspaceRestriction.php:39
‪TYPO3\CMS\Backend\Utility\BackendUtility\getTCAtypeValue
‪static string getTCAtypeValue($table, $row)
Definition: BackendUtility.php:636