TYPO3 CMS  TYPO3_6-2
DataHandler.php
Go to the documentation of this file.
1 <?php
3 
22 
38 class DataHandler {
39 
40  // *********************
41  // Public variables you can configure before using the class:
42  // *********************
49  public $storeLogMessages = TRUE;
50 
56  public $enableLogging = TRUE;
57 
64  public $reverseOrder = FALSE;
65 
72  public $checkSimilar = TRUE;
73 
80  public $stripslashes_values = TRUE;
81 
88  public $checkStoredRecords = TRUE;
89 
96 
103  public $deleteTree = FALSE;
104 
110  public $neverHideAtCopy = FALSE;
111 
117  public $isImporting = FALSE;
118 
125 
132 
140  public $updateModeL10NdiffData = TRUE;
141 
149 
157 
164  public $bypassFileHandling = FALSE;
165 
173 
180  public $copyWhichTables = '*';
181 
189  public $copyTree = 0;
190 
199  public $defaultValues = array();
200 
208  public $overrideValues = array();
209 
217  public $alternativeFileName = array();
218 
224  public $alternativeFilePath = array();
225 
233  public $data_disableFields = array();
234 
244  public $suggestedInsertUids = array();
245 
252  public $callBackObj;
253 
254  // *********************
255  // Internal variables (mapping arrays) which can be used (read-only) from outside
256  // *********************
263  public $autoVersionIdMap = array();
264 
271  public $substNEWwithIDs = array();
272 
279  public $substNEWwithIDs_table = array();
280 
287  public $newRelatedIDs = array();
288 
295  public $copyMappingArray_merged = array();
296 
303  public $copiedFileMap = array();
304 
311  public $RTEmagic_copyIndex = array();
312 
319  public $errorLog = array();
320 
321 
322  // *********************
323  // Internal Variables, do not touch.
324  // *********************
325 
326  // Variables set in init() function:
327 
334  public $BE_USER;
335 
342  public $userid;
343 
350  public $username;
351 
358  public $admin;
359 
366  public $defaultPermissions = array(
367  'user' => 'show,edit,delete,new,editcontent',
368  'group' => 'show,edit,new,editcontent',
369  'everybody' => ''
370  );
371 
379 
386  protected $control = array();
387 
394  public $datamap = array();
395 
402  public $cmdmap = array();
403 
404  // Internal static:
411  public $pMap = array(
412  'show' => 1,
413  // 1st bit
414  'edit' => 2,
415  // 2nd bit
416  'delete' => 4,
417  // 3rd bit
418  'new' => 8,
419  // 4th bit
420  'editcontent' => 16
421  );
422 
429  public $sortIntervals = 256;
430 
431  // Internal caching arrays
438  public $recUpdateAccessCache = array();
439 
446  public $recInsertAccessCache = array();
447 
454  public $isRecordInWebMount_Cache = array();
455 
462  public $isInWebMount_Cache = array();
463 
470  public $cachedTSconfig = array();
471 
478  public $pageCache = array();
479 
486  public $checkWorkspaceCache = array();
487 
488  // Other arrays:
495  public $dbAnalysisStore = array();
496 
503  public $removeFilesStore = array();
504 
511  public $uploadedFileArray = array();
512 
519  public $registerDBList = array();
520 
527  public $registerDBPids = array();
528 
539  public $copyMappingArray = array();
540 
547  public $remapStack = array();
548 
556  public $remapStackRecords = array();
557 
563  protected $remapStackChildIds = array();
564 
570  protected $remapStackActions = array();
571 
577  protected $remapStackRefIndex = array();
578 
585  public $updateRefIndexStack = array();
586 
594  public $callFromImpExp = FALSE;
595 
602  public $newIndexMap = array();
603 
604  // Various
612  public $fileFunc;
613 
620  public $checkValue_currentRecord = array();
621 
628  public $autoVersioningUpdate = FALSE;
629 
635  protected $disableDeleteClause = FALSE;
636 
641 
646 
653  protected $outerMostInstance = NULL;
654 
660  static protected $recordsToClearCacheFor = array();
661 
668  protected static $recordPidsForDeletedRecords = array();
669 
676 
682  protected $runtimeCache = NULL;
683 
689  protected $cachePrefixNestedElementCalls = 'core-datahandler-nestedElementCalls-';
690 
694  public function __construct() {
695  $this->databaseConnection = $GLOBALS['TYPO3_DB'];
696  $this->runtimeCache = $this->getMemoryCache();
697  }
698 
702  public function setControl(array $control) {
703  $this->control = $control;
704  }
705 
716  public function start($data, $cmd, $altUserObject = '') {
717  // Initializing BE_USER
718  $this->BE_USER = is_object($altUserObject) ? $altUserObject : $GLOBALS['BE_USER'];
719  $this->userid = $this->BE_USER->user['uid'];
720  $this->username = $this->BE_USER->user['username'];
721  $this->admin = $this->BE_USER->user['admin'];
722  if ($this->BE_USER->uc['recursiveDelete']) {
723  $this->deleteTree = 1;
724  }
725  if ($GLOBALS['TYPO3_CONF_VARS']['BE']['explicitConfirmationOfTranslation'] && $this->updateModeL10NdiffData === TRUE) {
726  $this->updateModeL10NdiffData = FALSE;
727  }
728  // Initializing default permissions for pages
729  $defaultPermissions = $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPermissions'];
730  if (isset($defaultPermissions['user'])) {
731  $this->defaultPermissions['user'] = $defaultPermissions['user'];
732  }
733  if (isset($defaultPermissions['group'])) {
734  $this->defaultPermissions['group'] = $defaultPermissions['group'];
735  }
736  if (isset($defaultPermissions['everybody'])) {
737  $this->defaultPermissions['everybody'] = $defaultPermissions['everybody'];
738  }
739  // generates the excludelist, based on TCA/exclude-flag and non_exclude_fields for the user:
740  $this->exclude_array = $this->admin ? array() : $this->getExcludeListArray();
741  // Setting the data and cmd arrays
742  if (is_array($data)) {
743  reset($data);
744  $this->datamap = $data;
745  }
746  if (is_array($cmd)) {
747  reset($cmd);
748  $this->cmdmap = $cmd;
749  }
750  }
751 
760  public function setMirror($mirror) {
761  if (is_array($mirror)) {
762  foreach ($mirror as $table => $uid_array) {
763  if (isset($this->datamap[$table])) {
764  foreach ($uid_array as $id => $uidList) {
765  if (isset($this->datamap[$table][$id])) {
766  $theIdsInArray = GeneralUtility::trimExplode(',', $uidList, TRUE);
767  foreach ($theIdsInArray as $copyToUid) {
768  $this->datamap[$table][$copyToUid] = $this->datamap[$table][$id];
769  }
770  }
771  }
772  }
773  }
774  }
775  }
776 
784  public function setDefaultsFromUserTS($userTS) {
785  if (is_array($userTS)) {
786  foreach ($userTS as $k => $v) {
787  $k = substr($k, 0, -1);
788  if ($k && is_array($v) && isset($GLOBALS['TCA'][$k])) {
789  if (is_array($this->defaultValues[$k])) {
790  $this->defaultValues[$k] = array_merge($this->defaultValues[$k], $v);
791  } else {
792  $this->defaultValues[$k] = $v;
793  }
794  }
795  }
796  }
797  }
798 
807  public function process_uploads($postFiles) {
808  if (is_array($postFiles)) {
809  // Editing frozen:
810  if ($this->BE_USER->workspace !== 0 && $this->BE_USER->workspaceRec['freeze']) {
811  $this->newlog('All editing in this workspace has been frozen!', 1);
812  return FALSE;
813  }
814  $subA = reset($postFiles);
815  if (is_array($subA)) {
816  if (is_array($subA['name']) && is_array($subA['type']) && is_array($subA['tmp_name']) && is_array($subA['size'])) {
817  // Initialize the uploadedFilesArray:
818  $this->uploadedFileArray = array();
819  // For each entry:
820  foreach ($subA as $key => $values) {
821  $this->process_uploads_traverseArray($this->uploadedFileArray, $values, $key);
822  }
823  } else {
824  $this->uploadedFileArray = $subA;
825  }
826  }
827  }
828  }
829 
841  public function process_uploads_traverseArray(&$outputArr, $inputArr, $keyToSet) {
842  if (is_array($inputArr)) {
843  foreach ($inputArr as $key => $value) {
844  $this->process_uploads_traverseArray($outputArr[$key], $inputArr[$key], $keyToSet);
845  }
846  } else {
847  $outputArr[$keyToSet] = $inputArr;
848  }
849  }
850 
851  /*********************************************
852  *
853  * HOOKS
854  *
855  *********************************************/
871  public function hook_processDatamap_afterDatabaseOperations(&$hookObjectsArr, &$status, &$table, &$id, &$fieldArray) {
872  // Process hook directly:
873  if (!isset($this->remapStackRecords[$table][$id])) {
874  foreach ($hookObjectsArr as $hookObj) {
875  if (method_exists($hookObj, 'processDatamap_afterDatabaseOperations')) {
876  $hookObj->processDatamap_afterDatabaseOperations($status, $table, $id, $fieldArray, $this);
877  }
878  }
879  } else {
880  $this->remapStackRecords[$table][$id]['processDatamap_afterDatabaseOperations'] = array(
881  'status' => $status,
882  'fieldArray' => $fieldArray,
883  'hookObjectsArr' => $hookObjectsArr
884  );
885  }
886  }
887 
894  protected function getCheckModifyAccessListHookObjects() {
895  if (!isset($this->checkModifyAccessListHookObjects)) {
896  $this->checkModifyAccessListHookObjects = array();
897  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['checkModifyAccessList'])) {
898  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['checkModifyAccessList'] as $classData) {
899  $hookObject = GeneralUtility::getUserObj($classData);
900  if (!$hookObject instanceof \TYPO3\CMS\Core\DataHandling\DataHandlerCheckModifyAccessListHookInterface) {
901  throw new \UnexpectedValueException('$hookObject must implement interface \\TYPO3\\CMS\\Core\\DataHandling\\DataHandlerCheckModifyAccessListHookInterface', 1251892472);
902  }
903  $this->checkModifyAccessListHookObjects[] = $hookObject;
904  }
905  }
906  }
908  }
909 
910  /*********************************************
911  *
912  * PROCESSING DATA
913  *
914  *********************************************/
921  public function process_datamap() {
922  $this->controlActiveElements();
923 
924  // Keep versionized(!) relations here locally:
925  $registerDBList = array();
927  $this->datamap = $this->unsetElementsToBeDeleted($this->datamap);
928  // Editing frozen:
929  if ($this->BE_USER->workspace !== 0 && $this->BE_USER->workspaceRec['freeze']) {
930  $this->newlog('All editing in this workspace has been frozen!', 1);
931  return FALSE;
932  }
933  // First prepare user defined objects (if any) for hooks which extend this function:
934  $hookObjectsArr = array();
935  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'])) {
936  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'] as $classRef) {
937  $hookObject = GeneralUtility::getUserObj($classRef);
938  if (method_exists($hookObject, 'processDatamap_beforeStart')) {
939  $hookObject->processDatamap_beforeStart($this);
940  }
941  $hookObjectsArr[] = $hookObject;
942  }
943  }
944  // 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.
945  $orderOfTables = array();
946  // Set pages first.
947  if (isset($this->datamap['pages'])) {
948  $orderOfTables[] = 'pages';
949  }
950  $orderOfTables = array_unique(array_merge($orderOfTables, array_keys($this->datamap)));
951  // Process the tables...
952  foreach ($orderOfTables as $table) {
953  // Check if
954  // - table is set in $GLOBALS['TCA'],
955  // - table is NOT readOnly
956  // - the table is set with content in the data-array (if not, there's nothing to process...)
957  // - permissions for tableaccess OK
958  $modifyAccessList = $this->checkModifyAccessList($table);
959  if (!$modifyAccessList) {
960  $id = 0;
961  $this->log($table, $id, 2, 0, 1, 'Attempt to modify table \'%s\' without permission', 1, array($table));
962  }
963  if (isset($GLOBALS['TCA'][$table]) && !$this->tableReadOnly($table) && is_array($this->datamap[$table]) && $modifyAccessList) {
964  if ($this->reverseOrder) {
965  $this->datamap[$table] = array_reverse($this->datamap[$table], 1);
966  }
967  // For each record from the table, do:
968  // $id is the record uid, may be a string if new records...
969  // $incomingFieldArray is the array of fields
970  foreach ($this->datamap[$table] as $id => $incomingFieldArray) {
971  if (is_array($incomingFieldArray)) {
972  // Handle native date/time fields
973  $dateTimeFormats = $GLOBALS['TYPO3_DB']->getDateTimeFormats($table);
974  foreach ($GLOBALS['TCA'][$table]['columns'] as $column => $config) {
975  if (isset($incomingFieldArray[$column])) {
976  if (isset($config['config']['dbType']) && GeneralUtility::inList('date,datetime', $config['config']['dbType'])) {
977  $emptyValue = $dateTimeFormats[$config['config']['dbType']]['empty'];
978  $format = $dateTimeFormats[$config['config']['dbType']]['format'];
979  $incomingFieldArray[$column] = $incomingFieldArray[$column] && $incomingFieldArray[$column] !== $emptyValue ? gmdate($format, $incomingFieldArray[$column]) : $emptyValue;
980  }
981  }
982  }
983  // Hook: processDatamap_preProcessFieldArray
984  foreach ($hookObjectsArr as $hookObj) {
985  if (method_exists($hookObj, 'processDatamap_preProcessFieldArray')) {
986  $hookObj->processDatamap_preProcessFieldArray($incomingFieldArray, $table, $id, $this);
987  }
988  }
989  // ******************************
990  // Checking access to the record
991  // ******************************
992  $createNewVersion = FALSE;
993  $recordAccess = FALSE;
994  $old_pid_value = '';
995  $this->autoVersioningUpdate = FALSE;
996  // Is it a new record? (Then Id is a string)
997  if (!\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($id)) {
998  // Get a fieldArray with default values
999  $fieldArray = $this->newFieldArray($table);
1000  // A pid must be set for new records.
1001  if (isset($incomingFieldArray['pid'])) {
1002  // $value = the pid
1003  $pid_value = $incomingFieldArray['pid'];
1004  // Checking and finding numerical pid, it may be a string-reference to another value
1005  $OK = 1;
1006  // If a NEW... id
1007  if (strstr($pid_value, 'NEW')) {
1008  if ($pid_value[0] === '-') {
1009  $negFlag = -1;
1010  $pid_value = substr($pid_value, 1);
1011  } else {
1012  $negFlag = 1;
1013  }
1014  // Trying to find the correct numerical value as it should be mapped by earlier processing of another new record.
1015  if (isset($this->substNEWwithIDs[$pid_value])) {
1016  if ($negFlag === 1) {
1017  $old_pid_value = $this->substNEWwithIDs[$pid_value];
1018  }
1019  $pid_value = (int)($negFlag * $this->substNEWwithIDs[$pid_value]);
1020  } else {
1021  $OK = 0;
1022  }
1023  }
1024  $pid_value = (int)$pid_value;
1025  // The $pid_value is now the numerical pid at this point
1026  if ($OK) {
1027  $sortRow = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
1028  // Points to a page on which to insert the element, possibly in the top of the page
1029  if ($pid_value >= 0) {
1030  // If this table is sorted we better find the top sorting number
1031  if ($sortRow) {
1032  $fieldArray[$sortRow] = $this->getSortNumber($table, 0, $pid_value);
1033  }
1034  // The numerical pid is inserted in the data array
1035  $fieldArray['pid'] = $pid_value;
1036  } else {
1037  // points to another record before ifself
1038  // If this table is sorted we better find the top sorting number
1039  if ($sortRow) {
1040  // Because $pid_value is < 0, getSortNumber returns an array
1041  $tempArray = $this->getSortNumber($table, 0, $pid_value);
1042  $fieldArray['pid'] = $tempArray['pid'];
1043  $fieldArray[$sortRow] = $tempArray['sortNumber'];
1044  } else {
1045  // Here we fetch the PID of the record that we point to...
1046  $tempdata = $this->recordInfo($table, abs($pid_value), 'pid');
1047  $fieldArray['pid'] = $tempdata['pid'];
1048  }
1049  }
1050  }
1051  }
1052  $theRealPid = $fieldArray['pid'];
1053  // Now, check if we may insert records on this pid.
1054  if ($theRealPid >= 0) {
1055  // Checks if records can be inserted on this $pid.
1056  $recordAccess = $this->checkRecordInsertAccess($table, $theRealPid);
1057  if ($recordAccess) {
1058  $this->addDefaultPermittedLanguageIfNotSet($table, $incomingFieldArray);
1059  $recordAccess = $this->BE_USER->recordEditAccessInternals($table, $incomingFieldArray, TRUE);
1060  if (!$recordAccess) {
1061  $this->newlog('recordEditAccessInternals() check failed. [' . $this->BE_USER->errorMsg . ']', 1);
1062  } elseif (!$this->bypassWorkspaceRestrictions) {
1063  // Workspace related processing:
1064  // If LIVE records cannot be created in the current PID due to workspace restrictions, prepare creation of placeholder-record
1065  if ($res = $this->BE_USER->workspaceAllowLiveRecordsInPID($theRealPid, $table)) {
1066  if ($res < 0) {
1067  $recordAccess = FALSE;
1068  $this->newlog('Stage for versioning root point and users access level did not allow for editing', 1);
1069  }
1070  } else {
1071  // So, if no live records were allowed, we have to create a new version of this record:
1072  if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1073  $createNewVersion = TRUE;
1074  } else {
1075  $recordAccess = FALSE;
1076  $this->newlog('Record could not be created in this workspace in this branch', 1);
1077  }
1078  }
1079  }
1080  }
1081  } else {
1082  debug('Internal ERROR: pid should not be less than zero!');
1083  }
1084  // Yes new record, change $record_status to 'insert'
1085  $status = 'new';
1086  } else {
1087  // Nope... $id is a number
1088  $fieldArray = array();
1089  $recordAccess = $this->checkRecordUpdateAccess($table, $id, $incomingFieldArray, $hookObjectsArr);
1090  if (!$recordAccess) {
1091  $propArr = $this->getRecordProperties($table, $id);
1092  $this->log($table, $id, 2, 0, 1, 'Attempt to modify record \'%s\' (%s) without permission. Or non-existing page.', 2, array($propArr['header'], $table . ':' . $id), $propArr['event_pid']);
1093  } else {
1094  // Next check of the record permissions (internals)
1095  $recordAccess = $this->BE_USER->recordEditAccessInternals($table, $id);
1096  if (!$recordAccess) {
1097  $propArr = $this->getRecordProperties($table, $id);
1098  $this->newlog('recordEditAccessInternals() check failed. [' . $this->BE_USER->errorMsg . ']', 1);
1099  } else {
1100  // Here we fetch the PID of the record that we point to...
1101  $tempdata = $this->recordInfo($table, $id, 'pid' . ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] ? ',t3ver_wsid,t3ver_stage' : ''));
1102  $theRealPid = $tempdata['pid'];
1103  // Use the new id of the versionized record we're trying to write to:
1104  // (This record is a child record of a parent and has already been versionized.)
1105  if ($this->autoVersionIdMap[$table][$id]) {
1106  // For the reason that creating a new version of this record, automatically
1107  // created related child records (e.g. "IRRE"), update the accordant field:
1108  $this->getVersionizedIncomingFieldArray($table, $id, $incomingFieldArray, $registerDBList);
1109  // Use the new id of the copied/versionized record:
1110  $id = $this->autoVersionIdMap[$table][$id];
1111  $recordAccess = TRUE;
1112  $this->autoVersioningUpdate = TRUE;
1113  } elseif (!$this->bypassWorkspaceRestrictions && ($errorCode = $this->BE_USER->workspaceCannotEditRecord($table, $tempdata))) {
1114  $recordAccess = FALSE;
1115  // Versioning is required and it must be offline version!
1116  // Check if there already is a workspace version
1117  $WSversion = BackendUtility::getWorkspaceVersionOfRecord($this->BE_USER->workspace, $table, $id, 'uid,t3ver_oid');
1118  if ($WSversion) {
1119  $id = $WSversion['uid'];
1120  $recordAccess = TRUE;
1121  } elseif ($this->BE_USER->workspaceAllowAutoCreation($table, $id, $theRealPid)) {
1122  $tce = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
1124  $tce->stripslashes_values = 0;
1125  $tce->enableLogging = $this->enableLogging;
1126  // Setting up command for creating a new version of the record:
1127  $cmd = array();
1128  $cmd[$table][$id]['version'] = array(
1129  'action' => 'new',
1130  'treeLevels' => -1,
1131  // Default is to create a version of the individual records... element versioning that is.
1132  'label' => 'Auto-created for WS #' . $this->BE_USER->workspace
1133  );
1134  $tce->start(array(), $cmd);
1135  $tce->process_cmdmap();
1136  $this->errorLog = array_merge($this->errorLog, $tce->errorLog);
1137  // If copying was successful, share the new uids (also of related children):
1138  if ($tce->copyMappingArray[$table][$id]) {
1139  foreach ($tce->copyMappingArray as $origTable => $origIdArray) {
1140  foreach ($origIdArray as $origId => $newId) {
1141  $this->uploadedFileArray[$origTable][$newId] = $this->uploadedFileArray[$origTable][$origId];
1142  $this->autoVersionIdMap[$origTable][$origId] = $newId;
1143  }
1144  }
1145  \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($this->RTEmagic_copyIndex, $tce->RTEmagic_copyIndex);
1146  // See where RTEmagic_copyIndex is used inside fillInFieldArray() for more information...
1147  // Update registerDBList, that holds the copied relations to child records:
1148  $registerDBList = array_merge($registerDBList, $tce->registerDBList);
1149  // For the reason that creating a new version of this record, automatically
1150  // created related child records (e.g. "IRRE"), update the accordant field:
1151  $this->getVersionizedIncomingFieldArray($table, $id, $incomingFieldArray, $registerDBList);
1152  // Use the new id of the copied/versionized record:
1153  $id = $this->autoVersionIdMap[$table][$id];
1154  $recordAccess = TRUE;
1155  $this->autoVersioningUpdate = TRUE;
1156  } else {
1157  $this->newlog('Could not be edited in offline workspace in the branch where found (failure state: \'' . $errorCode . '\'). Auto-creation of version failed!', 1);
1158  }
1159  } else {
1160  $this->newlog('Could not be edited in offline workspace in the branch where found (failure state: \'' . $errorCode . '\'). Auto-creation of version not allowed in workspace!', 1);
1161  }
1162  }
1163  }
1164  }
1165  // The default is 'update'
1166  $status = 'update';
1167  }
1168  // If access was granted above, proceed to create or update record:
1169  if ($recordAccess) {
1170  // Here the "pid" is set IF NOT the old pid was a string pointing to a place in the subst-id array.
1171  list($tscPID) = BackendUtility::getTSCpid($table, $id, $old_pid_value ? $old_pid_value : $fieldArray['pid']);
1172  if ($status === 'new' && $table === 'pages') {
1173  $TSConfig = $this->getTCEMAIN_TSconfig($tscPID);
1174  if (isset($TSConfig['permissions.']) && is_array($TSConfig['permissions.'])) {
1175  $fieldArray = $this->setTSconfigPermissions($fieldArray, $TSConfig['permissions.']);
1176  }
1177  }
1178  // Processing of all fields in incomingFieldArray and setting them in $fieldArray
1179  $fieldArray = $this->fillInFieldArray($table, $id, $fieldArray, $incomingFieldArray, $theRealPid, $status, $tscPID);
1180  if ($createNewVersion) {
1181  // create a placeholder array with already processed field content
1182  $newVersion_placeholderFieldArray = $fieldArray;
1183  }
1184  // NOTICE! All manipulation beyond this point bypasses both "excludeFields" AND possible "MM" relations / file uploads to field!
1185  // Forcing some values unto field array:
1186  // NOTICE: This overriding is potentially dangerous; permissions per field is not checked!!!
1187  $fieldArray = $this->overrideFieldArray($table, $fieldArray);
1188  if ($createNewVersion) {
1189  $newVersion_placeholderFieldArray = $this->overrideFieldArray($table, $newVersion_placeholderFieldArray);
1190  }
1191  // Setting system fields
1192  if ($status == 'new') {
1193  if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1194  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
1195  if ($createNewVersion) {
1196  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
1197  }
1198  }
1199  if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
1200  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $this->userid;
1201  if ($createNewVersion) {
1202  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $this->userid;
1203  }
1204  }
1205  } elseif ($this->checkSimilar) {
1206  // Removing fields which are equal to the current value:
1207  $fieldArray = $this->compareFieldArrayWithCurrentAndUnset($table, $id, $fieldArray);
1208  }
1209  if ($GLOBALS['TCA'][$table]['ctrl']['tstamp'] && count($fieldArray)) {
1210  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
1211  if ($createNewVersion) {
1212  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
1213  }
1214  }
1215  // Set stage to "Editing" to make sure we restart the workflow
1216  if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1217  $fieldArray['t3ver_stage'] = 0;
1218  }
1219  // Hook: processDatamap_postProcessFieldArray
1220  foreach ($hookObjectsArr as $hookObj) {
1221  if (method_exists($hookObj, 'processDatamap_postProcessFieldArray')) {
1222  $hookObj->processDatamap_postProcessFieldArray($status, $table, $id, $fieldArray, $this);
1223  }
1224  }
1225  // Performing insert/update. If fieldArray has been unset by some userfunction (see hook above), don't do anything
1226  // Kasper: Unsetting the fieldArray is dangerous; MM relations might be saved already and files could have been uploaded that are now "lost"
1227  if (is_array($fieldArray)) {
1228  if ($status == 'new') {
1229  // This creates a new version of the record with online placeholder and offline version
1230  if ($createNewVersion) {
1231  $newVersion_placeholderFieldArray['t3ver_label'] = 'INITIAL PLACEHOLDER';
1232  // Setting placeholder state value for temporary record
1233  $newVersion_placeholderFieldArray['t3ver_state'] = (string)new VersionState(VersionState::NEW_PLACEHOLDER);
1234  // Setting workspace - only so display of place holders can filter out those from other workspaces.
1235  $newVersion_placeholderFieldArray['t3ver_wsid'] = $this->BE_USER->workspace;
1236  $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['label']] = $this->getPlaceholderTitleForTableLabel($table);
1237  // Saving placeholder as 'original'
1238  $this->insertDB($table, $id, $newVersion_placeholderFieldArray, FALSE);
1239  // For the actual new offline version, set versioning values to point to placeholder:
1240  $fieldArray['pid'] = -1;
1241  $fieldArray['t3ver_oid'] = $this->substNEWwithIDs[$id];
1242  $fieldArray['t3ver_id'] = 1;
1243  // Setting placeholder state value for version (so it can know it is currently a new version...)
1244  $fieldArray['t3ver_state'] = (string)new VersionState(VersionState::NEW_PLACEHOLDER_VERSION);
1245  $fieldArray['t3ver_label'] = 'First draft version';
1246  $fieldArray['t3ver_wsid'] = $this->BE_USER->workspace;
1247  // When inserted, $this->substNEWwithIDs[$id] will be changed to the uid of THIS version and so the interface will pick it up just nice!
1248  $phShadowId = $this->insertDB($table, $id, $fieldArray, TRUE, 0, TRUE);
1249  if ($phShadowId) {
1250  // Processes fields of the placeholder record:
1251  $this->triggerRemapAction($table, $id, array($this, 'placeholderShadowing'), array($table, $phShadowId));
1252  // Hold auto-versionized ids of placeholders:
1253  $this->autoVersionIdMap[$table][$this->substNEWwithIDs[$id]] = $phShadowId;
1254  }
1255  } else {
1256  $this->insertDB($table, $id, $fieldArray, FALSE, $incomingFieldArray['uid']);
1257  }
1258  } else {
1259  $this->updateDB($table, $id, $fieldArray);
1260  $this->placeholderShadowing($table, $id);
1261  }
1262  }
1263  // Hook: processDatamap_afterDatabaseOperations
1264  // Note: When using the hook after INSERT operations, you will only get the temporary NEW... id passed to your hook as $id,
1265  // but you can easily translate it to the real uid of the inserted record using the $this->substNEWwithIDs array.
1266  $this->hook_processDatamap_afterDatabaseOperations($hookObjectsArr, $status, $table, $id, $fieldArray);
1267  }
1268  }
1269  }
1270  }
1271  }
1272  // Process the stack of relations to remap/correct
1273  $this->processRemapStack();
1274  $this->dbAnalysisStoreExec();
1275  $this->removeRegisteredFiles();
1276  // Hook: processDatamap_afterAllOperations
1277  // Note: When this hook gets called, all operations on the submitted data have been finished.
1278  foreach ($hookObjectsArr as $hookObj) {
1279  if (method_exists($hookObj, 'processDatamap_afterAllOperations')) {
1280  $hookObj->processDatamap_afterAllOperations($this);
1281  }
1282  }
1283  if ($this->isOuterMostInstance()) {
1284  $this->processClearCacheQueue();
1285  $this->resetElementsToBeDeleted();
1286  }
1287  }
1288 
1297  public function placeholderShadowing($table, $id) {
1298  if ($liveRec = BackendUtility::getLiveVersionOfRecord($table, $id, '*')) {
1299  if (VersionState::cast($liveRec['t3ver_state'])->indicatesPlaceholder()) {
1300  $justStoredRecord = BackendUtility::getRecord($table, $id);
1301  $newRecord = array();
1302  $shadowCols = $GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForNewPlaceholders'];
1303  $shadowCols .= ',' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
1304  $shadowCols .= ',' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
1305  $shadowCols .= ',' . $GLOBALS['TCA'][$table]['ctrl']['type'];
1306  $shadowCols .= ',' . $GLOBALS['TCA'][$table]['ctrl']['label'];
1307  $shadowColumns = array_unique(GeneralUtility::trimExplode(',', $shadowCols, TRUE));
1308  foreach ($shadowColumns as $fieldName) {
1309  if ((string)$justStoredRecord[$fieldName] !== (string)$liveRec[$fieldName] && isset($GLOBALS['TCA'][$table]['columns'][$fieldName]) && $fieldName !== 'uid' && $fieldName !== 'pid') {
1310  $newRecord[$fieldName] = $justStoredRecord[$fieldName];
1311  }
1312  }
1313  if (count($newRecord)) {
1314  $this->newlog2('Shadowing done on fields <i>' . implode(',', array_keys($newRecord)) . '</i> in placeholder record ' . $table . ':' . $liveRec['uid'] . ' (offline version UID=' . $id . ')', $table, $liveRec['uid'], $liveRec['pid']);
1315  $this->updateDB($table, $liveRec['uid'], $newRecord);
1316  }
1317  }
1318  }
1319  }
1320 
1328  public function getPlaceholderTitleForTableLabel($table, $placeholderContent = NULL) {
1329  if ($placeholderContent === NULL) {
1330  $placeholderContent = 'PLACEHOLDER';
1331  }
1332 
1333  $labelPlaceholder = '[' . $placeholderContent . ', WS#' . $this->BE_USER->workspace . ']';
1334  $labelField = $GLOBALS['TCA'][$table]['ctrl']['label'];
1335  if (!isset($GLOBALS['TCA'][$table]['columns'][$labelField]['config']['eval'])) {
1336  return $labelPlaceholder;
1337  }
1338  $evalCodesArray = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['columns'][$labelField]['config']['eval'], TRUE);
1339  $transformedLabel = $this->checkValue_input_Eval($labelPlaceholder, $evalCodesArray, '');
1340  return isset($transformedLabel['value']) ? $transformedLabel['value'] : $labelPlaceholder;
1341  }
1342 
1357  public function fillInFieldArray($table, $id, $fieldArray, $incomingFieldArray, $realPid, $status, $tscPID) {
1358  // Initialize:
1359  $originalLanguageRecord = NULL;
1360  $originalLanguage_diffStorage = NULL;
1361  $diffStorageFlag = FALSE;
1362  // Setting 'currentRecord' and 'checkValueRecord':
1363  if (strstr($id, 'NEW')) {
1364  // Must have the 'current' array - not the values after processing below...
1365  $currentRecord = ($checkValueRecord = $fieldArray);
1366  // IF $incomingFieldArray is an array, overlay it.
1367  // 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...
1368  if (is_array($incomingFieldArray) && is_array($checkValueRecord)) {
1369  \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($checkValueRecord, $incomingFieldArray);
1370  }
1371  } else {
1372  // We must use the current values as basis for this!
1373  $currentRecord = ($checkValueRecord = $this->recordInfo($table, $id, '*'));
1374  // This is done to make the pid positive for offline versions; Necessary to have diff-view for pages_language_overlay in workspaces.
1375  BackendUtility::fixVersioningPid($table, $currentRecord);
1376  // Get original language record if available:
1377  if (is_array($currentRecord) && $GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'] && $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $currentRecord[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0 && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] && (int)$currentRecord[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] > 0) {
1378  $lookUpTable = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable'] ?: $table;
1379  $originalLanguageRecord = $this->recordInfo($lookUpTable, $currentRecord[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']], '*');
1380  BackendUtility::workspaceOL($lookUpTable, $originalLanguageRecord);
1381  $originalLanguage_diffStorage = unserialize($currentRecord[$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']]);
1382  }
1383  }
1384  $this->checkValue_currentRecord = $checkValueRecord;
1385  // In the following all incoming value-fields are tested:
1386  // - Are the user allowed to change the field?
1387  // - Is the field uid/pid (which are already set)
1388  // - perms-fields for pages-table, then do special things...
1389  // - If the field is nothing of the above and the field is configured in TCA, the fieldvalues are evaluated by ->checkValue
1390  // If everything is OK, the field is entered into $fieldArray[]
1391  foreach ($incomingFieldArray as $field => $fieldValue) {
1392  if (!in_array(($table . '-' . $field), $this->exclude_array) && !$this->data_disableFields[$table][$id][$field]) {
1393  // The field must be editable.
1394  // Checking if a value for language can be changed:
1395  $languageDeny = $GLOBALS['TCA'][$table]['ctrl']['languageField'] && (string)$GLOBALS['TCA'][$table]['ctrl']['languageField'] === (string)$field && !$this->BE_USER->checkLanguageAccess($fieldValue);
1396  if (!$languageDeny) {
1397  // Stripping slashes - will probably be removed the day $this->stripslashes_values is removed as an option...
1398  if ($this->stripslashes_values) {
1399  if (is_array($fieldValue)) {
1401  } else {
1402  $fieldValue = stripslashes($fieldValue);
1403  }
1404  }
1405  switch ($field) {
1406  case 'uid':
1407 
1408  case 'pid':
1409  // Nothing happens, already set
1410  break;
1411  case 'perms_userid':
1412 
1413  case 'perms_groupid':
1414 
1415  case 'perms_user':
1416 
1417  case 'perms_group':
1418 
1419  case 'perms_everybody':
1420  // Permissions can be edited by the owner or the administrator
1421  if ($table == 'pages' && ($this->admin || $status == 'new' || $this->pageInfo($id, 'perms_userid') == $this->userid)) {
1422  $value = (int)$fieldValue;
1423  switch ($field) {
1424  case 'perms_userid':
1425  $fieldArray[$field] = $value;
1426  break;
1427  case 'perms_groupid':
1428  $fieldArray[$field] = $value;
1429  break;
1430  default:
1431  if ($value >= 0 && $value < pow(2, 5)) {
1432  $fieldArray[$field] = $value;
1433  }
1434  }
1435  }
1436  break;
1437  case 't3ver_oid':
1438 
1439  case 't3ver_id':
1440 
1441  case 't3ver_wsid':
1442 
1443  case 't3ver_state':
1444 
1445  case 't3ver_count':
1446 
1447  case 't3ver_stage':
1448 
1449  case 't3ver_tstamp':
1450  // t3ver_label is not here because it CAN be edited as a regular field!
1451  break;
1452  default:
1453  if (isset($GLOBALS['TCA'][$table]['columns'][$field])) {
1454  // Evaluating the value
1455  $res = $this->checkValue($table, $field, $fieldValue, $id, $status, $realPid, $tscPID);
1456  if (array_key_exists('value', $res)) {
1457  $fieldArray[$field] = $res['value'];
1458  }
1459  // Add the value of the original record to the diff-storage content:
1460  if ($this->updateModeL10NdiffData && $GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']) {
1461  $originalLanguage_diffStorage[$field] = $this->updateModeL10NdiffDataClear ? '' : $originalLanguageRecord[$field];
1462  $diffStorageFlag = TRUE;
1463  }
1464  // If autoversioning is happening we need to perform a nasty hack. The case is parallel to a similar hack inside checkValue_group_select_file().
1465  // When a copy or version is made of a record, a search is made for any RTEmagic* images in fields having the "images" soft reference parser applied.
1466  // That should be TRUE for RTE fields. If any are found they are duplicated to new names and the file reference in the bodytext is updated accordingly.
1467  // However, with auto-versioning the submitted content of the field will just overwrite the corrected values. This leaves a) lost RTEmagic files and b) creates a double reference to the old files.
1468  // The only solution I can come up with is detecting when auto versioning happens, then see if any RTEmagic images was copied and if so make a stupid string-replace of the content !
1469  if ($this->autoVersioningUpdate === TRUE) {
1470  if (is_array($this->RTEmagic_copyIndex[$table][$id][$field])) {
1471  foreach ($this->RTEmagic_copyIndex[$table][$id][$field] as $oldRTEmagicName => $newRTEmagicName) {
1472  $fieldArray[$field] = str_replace(' src="' . $oldRTEmagicName . '"', ' src="' . $newRTEmagicName . '"', $fieldArray[$field]);
1473  }
1474  }
1475  }
1476  } elseif ($GLOBALS['TCA'][$table]['ctrl']['origUid'] === $field) {
1477  // Allow value for original UID to pass by...
1478  $fieldArray[$field] = $fieldValue;
1479  }
1480  }
1481  }
1482  }
1483  }
1484  // Add diff-storage information:
1485  if ($diffStorageFlag && !isset($fieldArray[$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']])) {
1486  // 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...
1487  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']] = serialize($originalLanguage_diffStorage);
1488  }
1489  // Checking for RTE-transformations of fields:
1490  $types_fieldConfig = BackendUtility::getTCAtypes($table, $currentRecord);
1491  $theTypeString = NULL;
1492  if (is_array($types_fieldConfig)) {
1493  foreach ($types_fieldConfig as $vconf) {
1494  $eFile = NULL;
1495  if (isset($vconf['spec']['static_write']) && isset($GLOBALS['TYPO3_CONF_VARS']['BE']['staticFileEditPath'])) {
1496  // only do array_merge if static_write is enabled
1497  $eFile = \TYPO3\CMS\Core\Html\RteHtmlParser::evalWriteFile($vconf['spec']['static_write'], array_merge($currentRecord, $fieldArray));
1498  // RteHtmlParser::evalWriteFile will return a string if an error ocurrs
1499  if (is_string($eFile)) {
1500  $this->log($table, $id, 2, 0, 1, 'Write-file error: \'%s\'', 13, array($eFile), $realPid);
1501  // Unset eFile to make checks cheaper
1502  $eFile = NULL;
1503  }
1504  }
1505  // RTE transformations:
1506  if (!$this->dontProcessTransformations) {
1507  if (isset($fieldArray[$vconf['field']])) {
1508  // Look for transformation flag:
1509  switch ((string) $incomingFieldArray[('_TRANSFORM_' . $vconf['field'])]) {
1510  case 'RTE':
1511  if ($theTypeString === NULL) {
1512  $theTypeString = BackendUtility::getTCAtypeValue($table, $currentRecord);
1513  }
1514  $RTEsetup = $this->BE_USER->getTSConfig('RTE', BackendUtility::getPagesTSconfig($tscPID));
1515  $thisConfig = BackendUtility::RTEsetup($RTEsetup['properties'], $table, $vconf['field'], $theTypeString);
1516  // Set alternative relative path for RTE images/links:
1517  $RTErelPath = isset($eFile) ? dirname($eFile['relEditFile']) : '';
1518  // Get RTE object, draw form and set flag:
1519  $RTEobj = BackendUtility::RTEgetObj();
1520  if (is_object($RTEobj)) {
1521  $fieldArray[$vconf['field']] = $RTEobj->transformContent('db', $fieldArray[$vconf['field']], $table, $vconf['field'], $currentRecord, $vconf['spec'], $thisConfig, $RTErelPath, $currentRecord['pid']);
1522  } else {
1523  debug('NO RTE OBJECT FOUND!');
1524  }
1525  break;
1526  }
1527  }
1528  }
1529  // Write file configuration:
1530  if (isset($eFile)) {
1531  $mixedRec = array_merge($currentRecord, $fieldArray);
1532  $SW_fileContent = GeneralUtility::getUrl($eFile['editFile']);
1533  $parseHTML = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Html\\RteHtmlParser');
1535  $parseHTML->init('', '');
1536  $eFileMarker = $eFile['markerField'] && trim($mixedRec[$eFile['markerField']]) ? trim($mixedRec[$eFile['markerField']]) : '###TYPO3_STATICFILE_EDIT###';
1537  // Must replace the marker if present in content!
1538  $insertContent = str_replace($eFileMarker, '', $mixedRec[$eFile['contentField']]);
1539  $SW_fileNewContent = $parseHTML->substituteSubpart($SW_fileContent, $eFileMarker, LF . $insertContent . LF, 1, 1);
1540  GeneralUtility::writeFile($eFile['editFile'], $SW_fileNewContent);
1541  // Write status:
1542  if (!strstr($id, 'NEW') && $eFile['statusField']) {
1543  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$id, array(
1544  $eFile['statusField'] => $eFile['relEditFile'] . ' updated ' . date('d-m-Y H:i:s') . ', bytes ' . strlen($mixedRec[$eFile['contentField']])
1545  ));
1546  }
1547  }
1548  }
1549  }
1550  // Return fieldArray
1551  return $fieldArray;
1552  }
1553 
1554  /*********************************************
1555  *
1556  * Evaluation of input values
1557  *
1558  ********************************************/
1574  public function checkValue($table, $field, $value, $id, $status, $realPid, $tscPID) {
1575  // Result array
1576  $res = array();
1577  // Processing special case of field pages.doktype
1578  if (($table === 'pages' || $table === 'pages_language_overlay') && $field === 'doktype') {
1579  // If the user may not use this specific doktype, we issue a warning
1580  if (!($this->admin || GeneralUtility::inList($this->BE_USER->groupData['pagetypes_select'], $value))) {
1581  $propArr = $this->getRecordProperties($table, $id);
1582  $this->log($table, $id, 5, 0, 1, 'You cannot change the \'doktype\' of page \'%s\' to the desired value.', 1, array($propArr['header']), $propArr['event_pid']);
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 = isset($GLOBALS['PAGES_TYPES'][$value]['onlyAllowedTables']) ? $GLOBALS['PAGES_TYPES'][$value]['onlyAllowedTables'] : $GLOBALS['PAGES_TYPES']['default']['onlyAllowedTables'];
1588  if ($onlyAllowedTables) {
1589  $theWrongTables = $this->doesPageHaveUnallowedTables($id, $value);
1590  if ($theWrongTables) {
1591  $propArr = $this->getRecordProperties($table, $id);
1592  $this->log($table, $id, 5, 0, 1, '\'doktype\' of page \'%s\' could not be changed because the page contains records from disallowed tables; %s', 2, array($propArr['header'], $theWrongTables), $propArr['event_pid']);
1593  return $res;
1594  }
1595  }
1596  }
1597  }
1598  $curValue = NULL;
1599  if ((int)$id !== 0) {
1600  // Get current value:
1601  $curValueRec = $this->recordInfo($table, $id, $field);
1602  if (isset($curValueRec[$field])) {
1603  $curValue = $curValueRec[$field];
1604  }
1605  }
1606  // Getting config for the field
1607  $tcaFieldConf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
1608  $recFID = $table . ':' . $id . ':' . $field;
1609  // Preform processing:
1610  $res = $this->checkValue_SW($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $field, $this->uploadedFileArray[$table][$id][$field], $tscPID);
1611  return $res;
1612  }
1613 
1634  public function checkValue_SW($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $field, $uploadedFiles, $tscPID, array $additionalData = NULL) {
1635  // Convert to NULL value if defined in TCA
1636  if ($value === NULL && !empty($tcaFieldConf['eval']) && GeneralUtility::inList($tcaFieldConf['eval'], 'null')) {
1637  $res = array('value' => NULL);
1638  return $res;
1639  }
1640 
1641  $PP = array($table, $id, $curValue, $status, $realPid, $recFID, $tscPID);
1642  switch ($tcaFieldConf['type']) {
1643  case 'text':
1644  $res = $this->checkValue_text($res, $value, $tcaFieldConf, $PP, $field);
1645  break;
1646  case 'passthrough':
1647 
1648  case 'user':
1649  $res['value'] = $value;
1650  break;
1651  case 'input':
1652  $res = $this->checkValue_input($res, $value, $tcaFieldConf, $PP, $field);
1653  break;
1654  case 'check':
1655  $res = $this->checkValue_check($res, $value, $tcaFieldConf, $PP, $field);
1656  break;
1657  case 'radio':
1658  $res = $this->checkValue_radio($res, $value, $tcaFieldConf, $PP);
1659  break;
1660  case 'group':
1661 
1662  case 'select':
1663  $res = $this->checkValue_group_select($res, $value, $tcaFieldConf, $PP, $uploadedFiles, $field);
1664  break;
1665  case 'inline':
1666  $res = $this->checkValue_inline($res, $value, $tcaFieldConf, $PP, $field, $additionalData);
1667  break;
1668  case 'flex':
1669  // FlexForms are only allowed for real fields.
1670  if ($field) {
1671  $res = $this->checkValue_flex($res, $value, $tcaFieldConf, $PP, $uploadedFiles, $field);
1672  }
1673  break;
1674  default:
1675  // Do nothing
1676  }
1677  return $res;
1678  }
1679 
1691  public function checkValue_text($res, $value, $tcaFieldConf, $PP, $field = '') {
1692  if (!isset($tcaFieldConf['eval']) || $tcaFieldConf['eval'] === '') {
1693  return array('value' => $value);
1694  }
1695  $cacheId = $this->getFieldEvalCacheIdentifier($tcaFieldConf['eval']);
1696  if ($this->runtimeCache->has($cacheId)) {
1697  $evalCodesArray = $this->runtimeCache->get($cacheId);
1698  } else {
1699  $evalCodesArray = GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], TRUE);
1700  $this->runtimeCache->set($cacheId, $evalCodesArray);
1701  }
1702  return $this->checkValue_text_Eval($value, $evalCodesArray, $tcaFieldConf['is_in']);
1703  }
1704 
1716  public function checkValue_input($res, $value, $tcaFieldConf, $PP, $field = '') {
1717  list($table, $id, $curValue, $status, $realPid, $recFID) = $PP;
1718  // Handle native date/time fields
1719  $isDateOrDateTimeField = FALSE;
1720  if (isset($tcaFieldConf['dbType']) && GeneralUtility::inList('date,datetime', $tcaFieldConf['dbType'])) {
1721  if (empty($value)) {
1722  $value = 0;
1723  } else {
1724  $isDateOrDateTimeField = TRUE;
1725  $dateTimeFormats = $GLOBALS['TYPO3_DB']->getDateTimeFormats($table);
1726  // Convert the date/time into a timestamp for the sake of the checks
1727  $emptyValue = $dateTimeFormats[$tcaFieldConf['dbType']]['empty'];
1728  $format = $dateTimeFormats[$tcaFieldConf['dbType']]['format'];
1729  // At this point in the processing, the timestamps are still based on UTC
1730  $timeZone = new \DateTimeZone('UTC');
1731  $dateTime = \DateTime::createFromFormat('!' . $format, $value, $timeZone);
1732  $value = $value === $emptyValue ? 0 : $dateTime->getTimestamp();
1733  }
1734  }
1735  // Secures the string-length to be less than max.
1736  if ((int)$tcaFieldConf['max'] > 0) {
1737  $value = $GLOBALS['LANG']->csConvObj->substr($GLOBALS['LANG']->charSet, $value, 0, (int)$tcaFieldConf['max']);
1738  }
1739  // Checking range of value:
1740  if ($tcaFieldConf['range'] && $value != $tcaFieldConf['checkbox'] && (int)$value !== (int)$tcaFieldConf['default']) {
1741  if (isset($tcaFieldConf['range']['upper']) && (int)$value > (int)$tcaFieldConf['range']['upper']) {
1742  $value = $tcaFieldConf['range']['upper'];
1743  }
1744  if (isset($tcaFieldConf['range']['lower']) && (int)$value < (int)$tcaFieldConf['range']['lower']) {
1745  $value = $tcaFieldConf['range']['lower'];
1746  }
1747  }
1748 
1749  if (empty($tcaFieldConf['eval'])) {
1750  $res = array('value' => $value);
1751  } else {
1752  // Process evaluation settings:
1753  $cacheId = $this->getFieldEvalCacheIdentifier($tcaFieldConf['eval']);
1754  if ($this->runtimeCache->has($cacheId)) {
1755  $evalCodesArray = $this->runtimeCache->get($cacheId);
1756  } else {
1757  $evalCodesArray = GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], TRUE);
1758  $this->runtimeCache->set($cacheId, $evalCodesArray);
1759  }
1760 
1761  $res = $this->checkValue_input_Eval($value, $evalCodesArray, $tcaFieldConf['is_in']);
1762 
1763  // Process UNIQUE settings:
1764  // Field is NOT set for flexForms - which also means that uniqueInPid and unique is NOT available for flexForm fields! Also getUnique should not be done for versioning and if PID is -1 ($realPid<0) then versioning is happening...
1765  if ($field && $realPid >= 0 && !empty($res['value'])) {
1766  if (in_array('uniqueInPid', $evalCodesArray, TRUE)) {
1767  $res['value'] = $this->getUnique($table, $field, $res['value'], $id, $realPid);
1768  }
1769  if ($res['value'] && in_array('unique', $evalCodesArray, TRUE)) {
1770  $res['value'] = $this->getUnique($table, $field, $res['value'], $id);
1771  }
1772  }
1773  }
1774 
1775  // Handle native date/time fields
1776  if ($isDateOrDateTimeField) {
1777  // Convert the timestamp back to a date/time
1778  $res['value'] = $res['value'] ? date($format, $res['value']) : $emptyValue;
1779  }
1780  return $res;
1781  }
1782 
1794  public function checkValue_check($res, $value, $tcaFieldConf, $PP, $field = '') {
1795  list($table, $id, $curValue, $status, $realPid, $recFID) = $PP;
1796  $itemC = count($tcaFieldConf['items']);
1797  if (!$itemC) {
1798  $itemC = 1;
1799  }
1800  $maxV = pow(2, $itemC) - 1;
1801  if ($value < 0) {
1802  $value = 0;
1803  }
1804  if ($value > $maxV) {
1805  $value = $maxV;
1806  }
1807  if ($field && $realPid >= 0 && $value > 0 && !empty($tcaFieldConf['eval'])) {
1808  $evalCodesArray = GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], TRUE);
1809  $otherRecordsWithSameValue = array();
1810  $maxCheckedRecords = 0;
1811  if (in_array('maximumRecordsCheckedInPid', $evalCodesArray)) {
1812  $otherRecordsWithSameValue = $this->getRecordsWithSameValue($table, $id, $field, $value, $realPid);
1813  $maxCheckedRecords = (int)$tcaFieldConf['validation']['maximumRecordsCheckedInPid'];
1814  }
1815  if (in_array('maximumRecordsChecked', $evalCodesArray)) {
1816  $otherRecordsWithSameValue = $this->getRecordsWithSameValue($table, $id, $field, $value);
1817  $maxCheckedRecords = (int)$tcaFieldConf['validation']['maximumRecordsChecked'];
1818  }
1819 
1820  // there are more than enough records with value "1" in the DB
1821  // if so, set this value to "0" again
1822  if ($maxCheckedRecords && count($otherRecordsWithSameValue) >= $maxCheckedRecords) {
1823  $value = 0;
1824  $this->log($table, $id, 5, 0, 1, 'Could not activate checkbox for field "%s". A total of %s record(s) can have this checkbox activated. Uncheck other records first in order to activate the checkbox of this record.', -1, array($GLOBALS['LANG']->sL(BackendUtility::getItemLabel($table, $field)), $maxCheckedRecords));
1825  }
1826  }
1827  $res['value'] = $value;
1828  return $res;
1829  }
1830 
1841  public function checkValue_radio($res, $value, $tcaFieldConf, $PP) {
1842  list($table, $id, $curValue, $status, $realPid, $recFID) = $PP;
1843  if (is_array($tcaFieldConf['items'])) {
1844  foreach ($tcaFieldConf['items'] as $set) {
1845  if ((string)$set[1] === (string)$value) {
1846  $res['value'] = $value;
1847  break;
1848  }
1849  }
1850  }
1851  return $res;
1852  }
1853 
1866  public function checkValue_group_select($res, $value, $tcaFieldConf, $PP, $uploadedFiles, $field) {
1867  list($table, $id, $curValue, $status, $realPid, $recFID) = $PP;
1868  // Detecting if value sent is an array and if so, implode it around a comma:
1869  if (is_array($value)) {
1870  $value = implode(',', $value);
1871  }
1872  // This converts all occurencies of '&#123;' to the byte 123 in the string - this is needed in very rare cases where filenames with special characters (like ???, umlaud etc) gets sent to the server as HTML entities instead of bytes. The error is done only by MSIE, not Mozilla and Opera.
1873  // Anyways, this should NOT disturb anything else:
1874  $value = $this->convNumEntityToByteValue($value);
1875  // When values are sent as group or select they come as comma-separated values which are exploded by this function:
1876  $valueArray = $this->checkValue_group_select_explodeSelectGroupValue($value);
1877  // If not multiple is set, then remove duplicates:
1878  if (!$tcaFieldConf['multiple']) {
1879  $valueArray = array_unique($valueArray);
1880  }
1881  // If an exclusive key is found, discard all others:
1882  if ($tcaFieldConf['type'] == 'select' && $tcaFieldConf['exclusiveKeys']) {
1883  $exclusiveKeys = GeneralUtility::trimExplode(',', $tcaFieldConf['exclusiveKeys']);
1884  foreach ($valueArray as $kk => $vv) {
1885  // $vv is the item key!
1886  if (in_array($vv, $exclusiveKeys)) {
1887  $valueArray = array($kk => $vv);
1888  break;
1889  }
1890  }
1891  }
1892  // This could be a good spot for parsing the array through a validation-function which checks if the values are alright (except that database references are not in their final form - but that is the point, isn't it?)
1893  // NOTE!!! Must check max-items of files before the later check because that check would just leave out filenames if there are too many!!
1894  $valueArray = $this->applyFiltersToValues($tcaFieldConf, $valueArray);
1895  // Checking for select / authMode, removing elements from $valueArray if any of them is not allowed!
1896  if ($tcaFieldConf['type'] == 'select' && $tcaFieldConf['authMode']) {
1897  $preCount = count($valueArray);
1898  foreach ($valueArray as $kk => $vv) {
1899  if (!$this->BE_USER->checkAuthMode($table, $field, $vv, $tcaFieldConf['authMode'])) {
1900  unset($valueArray[$kk]);
1901  }
1902  }
1903  // 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.
1904  if ($preCount && !count($valueArray)) {
1905  return array();
1906  }
1907  }
1908  // For group types:
1909  if ($tcaFieldConf['type'] == 'group') {
1910  switch ($tcaFieldConf['internal_type']) {
1911  case 'file_reference':
1912 
1913  case 'file':
1914  $valueArray = $this->checkValue_group_select_file($valueArray, $tcaFieldConf, $curValue, $uploadedFiles, $status, $table, $id, $recFID);
1915  break;
1916  case 'db':
1917  $valueArray = $this->checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, 'group', $table, $field);
1918  break;
1919  }
1920  }
1921  // For select types which has a foreign table attached:
1922  if ($tcaFieldConf['type'] == 'select' && $tcaFieldConf['foreign_table']) {
1923  // check, if there is a NEW... id in the value, that should be substituded later
1924  if (strpos($value, 'NEW') !== FALSE) {
1925  $this->remapStackRecords[$table][$id] = array('remapStackIndex' => count($this->remapStack));
1926  $this->addNewValuesToRemapStackChildIds($valueArray);
1927  $this->remapStack[] = array(
1928  'func' => 'checkValue_group_select_processDBdata',
1929  'args' => array($valueArray, $tcaFieldConf, $id, $status, 'select', $table, $field),
1930  'pos' => array('valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 5),
1931  'field' => $field
1932  );
1933  $unsetResult = TRUE;
1934  } else {
1935  $valueArray = $this->checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, 'select', $table, $field);
1936  }
1937  }
1938  if (!$unsetResult) {
1939  $newVal = $this->checkValue_checkMax($tcaFieldConf, $valueArray);
1940  $res['value'] = implode(',', $newVal);
1941  } else {
1942  unset($res['value']);
1943  }
1944  return $res;
1945  }
1946 
1955  protected function applyFiltersToValues(array $tcaFieldConfiguration, array $values) {
1956  if (empty($tcaFieldConfiguration['filter']) || !is_array($tcaFieldConfiguration['filter'])) {
1957  return $values;
1958  }
1959  foreach ($tcaFieldConfiguration['filter'] as $filter) {
1960  if (empty($filter['userFunc'])) {
1961  continue;
1962  }
1963  $parameters = $filter['parameters'] ?: array();
1964  $parameters['values'] = $values;
1965  $parameters['tcaFieldConfig'] = $tcaFieldConfiguration;
1966  $values = GeneralUtility::callUserFunction($filter['userFunc'], $parameters, $this);
1967  if (!is_array($values)) {
1968  throw new \RuntimeException('Failed calling filter userFunc.', 1336051942);
1969  }
1970  }
1971  return $values;
1972  }
1973 
1989  public function checkValue_group_select_file($valueArray, $tcaFieldConf, $curValue, $uploadedFileArray, $status, $table, $id, $recFID) {
1990  // If file handling should NOT be bypassed, do processing:
1991  if (!$this->bypassFileHandling) {
1992  // If any files are uploaded, add them to value array
1993  // Numeric index means that there are multiple files
1994  if (isset($uploadedFileArray[0])) {
1995  $uploadedFiles = $uploadedFileArray;
1996  } else {
1997  // There is only one file
1998  $uploadedFiles = array($uploadedFileArray);
1999  }
2000  foreach ($uploadedFiles as $uploadedFileArray) {
2001  if (!empty($uploadedFileArray['name']) && $uploadedFileArray['tmp_name'] !== 'none') {
2002  $valueArray[] = $uploadedFileArray['tmp_name'];
2003  $this->alternativeFileName[$uploadedFileArray['tmp_name']] = $uploadedFileArray['name'];
2004  }
2005  }
2006  // Creating fileFunc object.
2007  if (!$this->fileFunc) {
2008  $this->fileFunc = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Utility\\File\\BasicFileUtility');
2009  $this->include_filefunctions = 1;
2010  }
2011  // Setting permitted extensions.
2012  $all_files = array();
2013  $all_files['webspace']['allow'] = $tcaFieldConf['allowed'];
2014  $all_files['webspace']['deny'] = $tcaFieldConf['disallowed'] ?: '*';
2015  $all_files['ftpspace'] = $all_files['webspace'];
2016  $this->fileFunc->init('', $all_files);
2017  }
2018  // If there is an upload folder defined:
2019  if ($tcaFieldConf['uploadfolder'] && $tcaFieldConf['internal_type'] == 'file') {
2020  // If filehandling should NOT be bypassed, do processing:
2021  if (!$this->bypassFileHandling) {
2022  // For logging..
2023  $propArr = $this->getRecordProperties($table, $id);
2024  // Get destrination path:
2025  $dest = $this->destPathFromUploadFolder($tcaFieldConf['uploadfolder']);
2026  // If we are updating:
2027  if ($status == 'update') {
2028  // Traverse the input values and convert to absolute filenames in case the update happens to an autoVersionized record.
2029  // Background: This is a horrible workaround! The problem is that when a record is auto-versionized the files of the record get copied and therefore get new names which is overridden with the names from the original record in the incoming data meaning both lost files and double-references!
2030  // The only solution I could come up with (except removing support for managing files when autoversioning) was to convert all relative files to absolute names so they are copied again (and existing files deleted). This should keep references intact but means that some files are copied, then deleted after being copied _again_.
2031  // Actually, the same problem applies to database references in case auto-versioning would include sub-records since in such a case references are remapped - and they would be overridden due to the same principle then.
2032  // Illustration of the problem comes here:
2033  // We have a record 123 with a file logo.gif. We open and edit the files header in a workspace. So a new version is automatically made.
2034  // The versions uid is 456 and the file is copied to "logo_01.gif". But the form data that we sent was based on uid 123 and hence contains the filename "logo.gif" from the original.
2035  // The file management code below will do two things: First it will blindly accept "logo.gif" as a file attached to the record (thus creating a double reference) and secondly it will find that "logo_01.gif" was not in the incoming filelist and therefore should be deleted.
2036  // If we prefix the incoming file "logo.gif" with its absolute path it will be seen as a new file added. Thus it will be copied to "logo_02.gif". "logo_01.gif" will still be deleted but since the files are the same the difference is zero - only more processing and file copying for no reason. But it will work.
2037  if ($this->autoVersioningUpdate === TRUE) {
2038  foreach ($valueArray as $key => $theFile) {
2039  // If it is an already attached file...
2040  if ($theFile === basename($theFile)) {
2041  $valueArray[$key] = PATH_site . $tcaFieldConf['uploadfolder'] . '/' . $theFile;
2042  }
2043  }
2044  }
2045  // Finding the CURRENT files listed, either from MM or from the current record.
2046  $theFileValues = array();
2047  // If MM relations for the files also!
2048  if ($tcaFieldConf['MM']) {
2049  $dbAnalysis = $this->createRelationHandlerInstance();
2051  $dbAnalysis->start('', 'files', $tcaFieldConf['MM'], $id);
2052  foreach ($dbAnalysis->itemArray as $item) {
2053  if ($item['id']) {
2054  $theFileValues[] = $item['id'];
2055  }
2056  }
2057  } else {
2058  $theFileValues = GeneralUtility::trimExplode(',', $curValue, TRUE);
2059  }
2060  $currentFilesForHistory = implode(',', $theFileValues);
2061  // DELETE files: If existing files were found, traverse those and register files for deletion which has been removed:
2062  if (count($theFileValues)) {
2063  // Traverse the input values and for all input values which match an EXISTING value, remove the existing from $theFileValues array (this will result in an array of all the existing files which should be deleted!)
2064  foreach ($valueArray as $key => $theFile) {
2065  if ($theFile && !strstr(GeneralUtility::fixWindowsFilePath($theFile), '/')) {
2066  $theFileValues = GeneralUtility::removeArrayEntryByValue($theFileValues, $theFile);
2067  }
2068  }
2069  // This array contains the filenames in the uploadfolder that should be deleted:
2070  foreach ($theFileValues as $key => $theFile) {
2071  $theFile = trim($theFile);
2072  if (@is_file(($dest . '/' . $theFile))) {
2073  $this->removeFilesStore[] = $dest . '/' . $theFile;
2074  } elseif ($theFile) {
2075  $this->log($table, $id, 5, 0, 1, 'Could not delete file \'%s\' (does not exist). (%s)', 10, array($dest . '/' . $theFile, $recFID), $propArr['event_pid']);
2076  }
2077  }
2078  }
2079  }
2080  // Traverse the submitted values:
2081  foreach ($valueArray as $key => $theFile) {
2082  // Init:
2083  $maxSize = (int)$tcaFieldConf['max_size'];
2084  // Must be cleared. Else a faulty fileref may be inserted if the below code returns an error!
2085  $theDestFile = '';
2086  // a FAL file was added, now resolve the file object and get the absolute path
2087  // @todo in future versions this needs to be modified to handle FAL objects natively
2088  if (!empty($theFile) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($theFile)) {
2089  $fileObject = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFileObject($theFile);
2090  $theFile = $fileObject->getForLocalProcessing(FALSE);
2091  }
2092  // NEW FILES? If the value contains '/' it indicates, that the file
2093  // is new and should be added to the uploadsdir (whether its absolute or relative does not matter here)
2094  if (strstr(GeneralUtility::fixWindowsFilePath($theFile), '/')) {
2095  // Check various things before copying file:
2096  // File and destination must exist
2097  if (@is_dir($dest) && (@is_file($theFile) || @is_uploaded_file($theFile))) {
2098  // Finding size.
2099  if (is_uploaded_file($theFile) && $theFile == $uploadedFileArray['tmp_name']) {
2100  $fileSize = $uploadedFileArray['size'];
2101  } else {
2102  $fileSize = filesize($theFile);
2103  }
2104  // Check file size:
2105  if (!$maxSize || $fileSize <= $maxSize * 1024) {
2106  // Prepare filename:
2107  $theEndFileName = isset($this->alternativeFileName[$theFile]) ? $this->alternativeFileName[$theFile] : $theFile;
2108  $fI = GeneralUtility::split_fileref($theEndFileName);
2109  // Check for allowed extension:
2110  if ($this->fileFunc->checkIfAllowed($fI['fileext'], $dest, $theEndFileName)) {
2111  $theDestFile = $this->fileFunc->getUniqueName($this->fileFunc->cleanFileName($fI['file']), $dest);
2112  // If we have a unique destination filename, then write the file:
2113  if ($theDestFile) {
2114  GeneralUtility::upload_copy_move($theFile, $theDestFile);
2115  // Hook for post-processing the upload action
2116  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processUpload'])) {
2117  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processUpload'] as $classRef) {
2118  $hookObject = GeneralUtility::getUserObj($classRef);
2119  if (!$hookObject instanceof \TYPO3\CMS\Core\DataHandling\DataHandlerProcessUploadHookInterface) {
2120  throw new \UnexpectedValueException('$hookObject must implement interface TYPO3\\CMS\\Core\\DataHandling\\DataHandlerProcessUploadHookInterface', 1279962349);
2121  }
2122  $hookObject->processUpload_postProcessAction($theDestFile, $this);
2123  }
2124  }
2125  $this->copiedFileMap[$theFile] = $theDestFile;
2126  clearstatcache();
2127  if (!@is_file($theDestFile)) {
2128  $this->log($table, $id, 5, 0, 1, 'Copying file \'%s\' failed!: The destination path (%s) may be write protected. Please make it write enabled!. (%s)', 16, array($theFile, dirname($theDestFile), $recFID), $propArr['event_pid']);
2129  }
2130  } else {
2131  $this->log($table, $id, 5, 0, 1, 'Copying file \'%s\' failed!: No destination file (%s) possible!. (%s)', 11, array($theFile, $theDestFile, $recFID), $propArr['event_pid']);
2132  }
2133  } else {
2134  $this->log($table, $id, 5, 0, 1, 'File extension \'%s\' not allowed. (%s)', 12, array($fI['fileext'], $recFID), $propArr['event_pid']);
2135  }
2136  } else {
2137  $this->log($table, $id, 5, 0, 1, 'Filesize (%s) of file \'%s\' exceeds limit (%s). (%s)', 13, array(GeneralUtility::formatSize($fileSize), $theFile, GeneralUtility::formatSize($maxSize * 1024), $recFID), $propArr['event_pid']);
2138  }
2139  } else {
2140  $this->log($table, $id, 5, 0, 1, 'The destination (%s) or the source file (%s) does not exist. (%s)', 14, array($dest, $theFile, $recFID), $propArr['event_pid']);
2141  }
2142  // If the destination file was created, we will set the new filename in
2143  // the value array, otherwise unset the entry in the value array!
2144  if (@is_file($theDestFile)) {
2145  $info = GeneralUtility::split_fileref($theDestFile);
2146  // The value is set to the new filename
2147  $valueArray[$key] = $info['file'];
2148  } else {
2149  // The value is set to the new filename
2150  unset($valueArray[$key]);
2151  }
2152  }
2153  }
2154  }
2155  // If MM relations for the files, we will set the relations as MM records and change the valuearray to contain a single entry with a count of the number of files!
2156  if ($tcaFieldConf['MM']) {
2158  $dbAnalysis = $this->createRelationHandlerInstance();
2159  // Dummy
2160  $dbAnalysis->tableArray['files'] = array();
2161  foreach ($valueArray as $key => $theFile) {
2162  // Explode files
2163  $dbAnalysis->itemArray[]['id'] = $theFile;
2164  }
2165  if ($status == 'update') {
2166  $dbAnalysis->writeMM($tcaFieldConf['MM'], $id, 0);
2167  $newFiles = implode(',', $dbAnalysis->getValueArray());
2168  list(, , $recFieldName) = explode(':', $recFID);
2169  if ($currentFilesForHistory != $newFiles) {
2170  $this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$recFieldName] = $currentFilesForHistory;
2171  $this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$recFieldName] = $newFiles;
2172  } else {
2173  $this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$recFieldName] = '';
2174  $this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$recFieldName] = '';
2175  }
2176  } else {
2177  $this->dbAnalysisStore[] = array($dbAnalysis, $tcaFieldConf['MM'], $id, 0);
2178  }
2179  $valueArray = $dbAnalysis->countItems();
2180  }
2181  } else {
2182  if (count($valueArray)) {
2183  // If filehandling should NOT be bypassed, do processing:
2184  if (!$this->bypassFileHandling) {
2185  // For logging..
2186  $propArr = $this->getRecordProperties($table, $id);
2187  foreach ($valueArray as &$theFile) {
2188  // FAL handling: it's a UID, thus it is resolved to the absolute path
2189  if (!empty($theFile) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($theFile)) {
2190  $fileObject = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFileObject($theFile);
2191  $theFile = $fileObject->getForLocalProcessing(FALSE);
2192  }
2193  if ($this->alternativeFilePath[$theFile]) {
2194  // If alernative File Path is set for the file, then it was an import
2195  // don't import the file if it already exists
2196  if (@is_file((PATH_site . $this->alternativeFilePath[$theFile]))) {
2197  $theFile = PATH_site . $this->alternativeFilePath[$theFile];
2198  } elseif (@is_file($theFile)) {
2199  $dest = dirname(PATH_site . $this->alternativeFilePath[$theFile]);
2200  if (!@is_dir($dest)) {
2201  GeneralUtility::mkdir_deep(PATH_site, dirname($this->alternativeFilePath[$theFile]) . '/');
2202  }
2203  // Init:
2204  $maxSize = (int)$tcaFieldConf['max_size'];
2205  $cmd = '';
2206  // Must be cleared. Else a faulty fileref may be inserted if the below code returns an error!
2207  $theDestFile = '';
2208  $fileSize = filesize($theFile);
2209  // Check file size:
2210  if (!$maxSize || $fileSize <= $maxSize * 1024) {
2211  // Prepare filename:
2212  $theEndFileName = isset($this->alternativeFileName[$theFile]) ? $this->alternativeFileName[$theFile] : $theFile;
2213  $fI = GeneralUtility::split_fileref($theEndFileName);
2214  // Check for allowed extension:
2215  if ($this->fileFunc->checkIfAllowed($fI['fileext'], $dest, $theEndFileName)) {
2216  $theDestFile = PATH_site . $this->alternativeFilePath[$theFile];
2217  // Write the file:
2218  if ($theDestFile) {
2219  GeneralUtility::upload_copy_move($theFile, $theDestFile);
2220  $this->copiedFileMap[$theFile] = $theDestFile;
2221  clearstatcache();
2222  if (!@is_file($theDestFile)) {
2223  $this->log($table, $id, 5, 0, 1, 'Copying file \'%s\' failed!: The destination path (%s) may be write protected. Please make it write enabled!. (%s)', 16, array($theFile, dirname($theDestFile), $recFID), $propArr['event_pid']);
2224  }
2225  } else {
2226  $this->log($table, $id, 5, 0, 1, 'Copying file \'%s\' failed!: No destination file (%s) possible!. (%s)', 11, array($theFile, $theDestFile, $recFID), $propArr['event_pid']);
2227  }
2228  } else {
2229  $this->log($table, $id, 5, 0, 1, 'File extension \'%s\' not allowed. (%s)', 12, array($fI['fileext'], $recFID), $propArr['event_pid']);
2230  }
2231  } else {
2232  $this->log($table, $id, 5, 0, 1, 'Filesize (%s) of file \'%s\' exceeds limit (%s). (%s)', 13, array(GeneralUtility::formatSize($fileSize), $theFile, GeneralUtility::formatSize($maxSize * 1024), $recFID), $propArr['event_pid']);
2233  }
2234  // If the destination file was created, we will set the new filename in the value array, otherwise unset the entry in the value array!
2235  if (@is_file($theDestFile)) {
2236  // The value is set to the new filename
2237  $theFile = $theDestFile;
2238  } else {
2239  // The value is set to the new filename
2240  unset($theFile);
2241  }
2242  }
2243  }
2244  $theFile = GeneralUtility::fixWindowsFilePath($theFile);
2245  if (GeneralUtility::isFirstPartOfStr($theFile, PATH_site)) {
2247  }
2248  }
2249  unset($theFile);
2250  }
2251  }
2252  }
2253  return $valueArray;
2254  }
2255 
2268  public function checkValue_flex($res, $value, $tcaFieldConf, $PP, $uploadedFiles, $field) {
2269  list($table, $id, $curValue, $status, $realPid, $recFID) = $PP;
2270 
2271  if (is_array($value)) {
2272  // This value is necessary for flex form processing to happen on flexform fields in page records when they are copied.
2273  // The problem is, that when copying a page, flexfrom XML comes along in the array for the new record - but since $this->checkValue_currentRecord does not have a uid or pid for that sake, the \TYPO3\CMS\Backend\Utility\BackendUtility::getFlexFormDS() function returns no good DS. For new records we do know the expected PID so therefore we send that with this special parameter. Only active when larger than zero.
2274  $newRecordPidValue = $status == 'new' ? $realPid : 0;
2275  // Get current value array:
2276  $dataStructArray = BackendUtility::getFlexFormDS($tcaFieldConf, $this->checkValue_currentRecord, $table, $field, TRUE, $newRecordPidValue);
2277  $currentValueArray = GeneralUtility::xml2array($curValue);
2278  if (!is_array($currentValueArray)) {
2279  $currentValueArray = array();
2280  }
2281  if (isset($currentValueArray['meta']['currentLangId'])) {
2282  unset($currentValueArray['meta']['currentLangId']);
2283  }
2284  // Remove all old meta for languages...
2285  // Evaluation of input values:
2286  $value['data'] = $this->checkValue_flex_procInData($value['data'], $currentValueArray['data'], $uploadedFiles['data'], $dataStructArray, $PP);
2287  // Create XML and convert charsets from input value:
2288  $xmlValue = $this->checkValue_flexArray2Xml($value, TRUE);
2289  // If we wanted to set UTF fixed:
2290  // $storeInCharset='utf-8';
2291  // $currentCharset=$GLOBALS['LANG']->charSet;
2292  // $xmlValue = $GLOBALS['LANG']->csConvObj->conv($xmlValue,$currentCharset,$storeInCharset,1);
2293  $storeInCharset = $GLOBALS['LANG']->charSet;
2294  // Merge them together IF they are both arrays:
2295  // 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 (provided that the current value was already stored IN the charset that the new value is converted to).
2296  if (is_array($currentValueArray)) {
2297  $arrValue = GeneralUtility::xml2array($xmlValue);
2298 
2299  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['checkFlexFormValue'])) {
2300  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['checkFlexFormValue'] as $classRef) {
2301  $hookObject = GeneralUtility::getUserObj($classRef);
2302  if (method_exists($hookObject, 'checkFlexFormValue_beforeMerge')) {
2303  $hookObject->checkFlexFormValue_beforeMerge($this, $currentValueArray, $arrValue);
2304  }
2305  }
2306  }
2307 
2309  $xmlValue = $this->checkValue_flexArray2Xml($currentValueArray, TRUE);
2310  }
2311  // Action commands (sorting order and removals of elements)
2312  $actionCMDs = GeneralUtility::_GP('_ACTION_FLEX_FORMdata');
2313  if (is_array($actionCMDs[$table][$id][$field]['data'])) {
2314  $arrValue = GeneralUtility::xml2array($xmlValue);
2315  $this->_ACTION_FLEX_FORMdata($arrValue['data'], $actionCMDs[$table][$id][$field]['data']);
2316  $xmlValue = $this->checkValue_flexArray2Xml($arrValue, TRUE);
2317  }
2318  // Create the value XML:
2319  $res['value'] = '';
2320  $res['value'] .= $xmlValue;
2321  } else {
2322  // Passthrough...:
2323  $res['value'] = $value;
2324  }
2325 
2326  return $res;
2327  }
2328 
2337  public function checkValue_flexArray2Xml($array, $addPrologue = FALSE) {
2339  $flexObj = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Configuration\\FlexForm\\FlexFormTools');
2340  return $flexObj->flexArray2Xml($array, $addPrologue);
2341  }
2342 
2351  public function _ACTION_FLEX_FORMdata(&$valueArray, $actionCMDs) {
2352  if (is_array($valueArray) && is_array($actionCMDs)) {
2353  foreach ($actionCMDs as $key => $value) {
2354  if ($key == '_ACTION') {
2355  // First, check if there are "commands":
2356  if (current($actionCMDs[$key]) !== '') {
2357  asort($actionCMDs[$key]);
2358  $newValueArray = array();
2359  foreach ($actionCMDs[$key] as $idx => $order) {
2360  if (substr($idx, 0, 3) == 'ID-') {
2361  $idx = $this->newIndexMap[$idx];
2362  }
2363  // Just one reflection here: It is clear that when removing elements from a flexform, then we will get lost files unless we act on this delete operation by traversing and deleting files that were referred to.
2364  if ($order != 'DELETE') {
2365  $newValueArray[$idx] = $valueArray[$idx];
2366  }
2367  unset($valueArray[$idx]);
2368  }
2369  $valueArray = GeneralUtility::array_merge($newValueArray, $valueArray);
2370  }
2371  } elseif (is_array($actionCMDs[$key]) && isset($valueArray[$key])) {
2372  $this->_ACTION_FLEX_FORMdata($valueArray[$key], $actionCMDs[$key]);
2373  }
2374  }
2375  }
2376  }
2377 
2391  public function checkValue_inline($res, $value, $tcaFieldConf, $PP, $field, array $additionalData = NULL) {
2392  list($table, $id, $curValue, $status, $realPid, $recFID) = $PP;
2393  if (!$tcaFieldConf['foreign_table']) {
2394  // Fatal error, inline fields should always have a foreign_table defined
2395  return FALSE;
2396  }
2397  // When values are sent they come as comma-separated values which are exploded by this function:
2398  $valueArray = GeneralUtility::trimExplode(',', $value);
2399  // Remove duplicates: (should not be needed)
2400  $valueArray = array_unique($valueArray);
2401  // Example for received data:
2402  // $value = 45,NEW4555fdf59d154,12,123
2403  // We need to decide whether we use the stack or can save the relation directly.
2404  if (strpos($value, 'NEW') !== FALSE || !\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($id)) {
2405  $this->remapStackRecords[$table][$id] = array('remapStackIndex' => count($this->remapStack));
2406  $this->addNewValuesToRemapStackChildIds($valueArray);
2407  $this->remapStack[] = array(
2408  'func' => 'checkValue_inline_processDBdata',
2409  'args' => array($valueArray, $tcaFieldConf, $id, $status, $table, $field, $additionalData),
2410  'pos' => array('valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 4),
2411  'additionalData' => $additionalData,
2412  'field' => $field,
2413  );
2414  unset($res['value']);
2415  } elseif ($value || \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($id)) {
2416  $res['value'] = $this->checkValue_inline_processDBdata($valueArray, $tcaFieldConf, $id, $status, $table, $field, $additionalData);
2417  }
2418  return $res;
2419  }
2420 
2430  public function checkValue_checkMax($tcaFieldConf, $valueArray) {
2431  // BTW, checking for min and max items here does NOT make any sense when MM is used because the above function calls will just return an array with a single item (the count) if MM is used... Why didn't I perform the check before? Probably because we could not evaluate the validity of record uids etc... Hmm...
2432  $valueArrayC = count($valueArray);
2433  // NOTE to the comment: It's not really possible to check for too few items, because you must then determine first, if the field is actual used regarding the CType.
2434  $maxI = isset($tcaFieldConf['maxitems']) ? (int)$tcaFieldConf['maxitems'] : 1;
2435  if ($valueArrayC > $maxI) {
2436  $valueArrayC = $maxI;
2437  }
2438  // Checking for not too many elements
2439  // Dumping array to list
2440  $newVal = array();
2441  foreach ($valueArray as $nextVal) {
2442  if ($valueArrayC == 0) {
2443  break;
2444  }
2445  $valueArrayC--;
2446  $newVal[] = $nextVal;
2447  }
2448  return $newVal;
2449  }
2450 
2451  /*********************************************
2452  *
2453  * Helper functions for evaluation functions.
2454  *
2455  ********************************************/
2467  public function getUnique($table, $field, $value, $id, $newPid = 0) {
2468  // Initialize:
2469  $whereAdd = '';
2470  $newValue = '';
2471  if ((int)$newPid) {
2472  $whereAdd .= ' AND pid=' . (int)$newPid;
2473  } else {
2474  $whereAdd .= ' AND pid>=0';
2475  }
2476  // "AND pid>=0" for versioning
2477  $whereAdd .= $this->deleteClause($table);
2478  // If the field is configured in TCA, proceed:
2479  if (is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'][$field])) {
2480  // Look for a record which might already have the value:
2481  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $table) . ' AND uid<>' . (int)$id . $whereAdd);
2482  $counter = 0;
2483  // For as long as records with the test-value existing, try again (with incremented numbers appended).
2484  while ($GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
2485  $newValue = $value . $counter;
2486  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($newValue, $table) . ' AND uid<>' . (int)$id . $whereAdd);
2487  $counter++;
2488  if ($counter > 100) {
2489  break;
2490  }
2491  }
2492  $GLOBALS['TYPO3_DB']->sql_free_result($res);
2493  // If the new value is there:
2494  $value = strlen($newValue) ? $newValue : $value;
2495  }
2496  return $value;
2497  }
2498 
2510  public function getRecordsWithSameValue($tableName, $uid, $fieldName, $value, $pageId = 0) {
2511  $result = array();
2512  if (!empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
2513 
2514  $uid = (int)$uid;
2515  $pageId = (int)$pageId;
2516  $whereStatement = ' AND uid <> ' . $uid . ' AND ' . ($pageId ? 'pid = ' . $pageId : 'pid >= 0');
2517  $result = BackendUtility::getRecordsByField($tableName, $fieldName, $value, $whereStatement);
2518  }
2519  return $result;
2520  }
2521 
2529  public function checkValue_text_Eval($value, $evalArray, $is_in) {
2530  $res = array();
2531  $newValue = $value;
2532  $set = TRUE;
2533  foreach ($evalArray as $func) {
2534  switch ($func) {
2535  case 'trim':
2536  $value = trim($value);
2537  break;
2538  case 'required':
2539  if (!$value) {
2540  $set = FALSE;
2541  }
2542  break;
2543  default:
2544  if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
2545  break;
2546  }
2547  $evalObj = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func] . ':&' . $func);
2548  if (is_object($evalObj) && method_exists($evalObj, 'evaluateFieldValue')) {
2549  $value = $evalObj->evaluateFieldValue($value, $is_in, $set);
2550  }
2551  }
2552  }
2553  if ($set) {
2554  $res['value'] = $value;
2555  }
2556  return $res;
2557  }
2558 
2568  public function checkValue_input_Eval($value, $evalArray, $is_in) {
2569  $res = array();
2570  $newValue = $value;
2571  $set = TRUE;
2572  foreach ($evalArray as $func) {
2573  switch ($func) {
2574  case 'int':
2575 
2576  case 'year':
2577 
2578  case 'time':
2579 
2580  case 'timesec':
2581  $value = (int)$value;
2582  break;
2583  case 'date':
2584 
2585  case 'datetime':
2586  $value = (int)$value;
2587  if ($value > 0 && !$this->dontProcessTransformations) {
2588  $value -= date('Z', $value);
2589  }
2590  break;
2591  case 'double2':
2592  $value = preg_replace('/[^0-9,\\.-]/', '', $value);
2593  $negative = $value[0] === '-';
2594  $value = strtr($value, array(',' => '.', '-' => ''));
2595  if (strpos($value, '.') === FALSE) {
2596  $value .= '.0';
2597  }
2598  $valueArray = explode('.', $value);
2599  $dec = array_pop($valueArray);
2600  $value = join('', $valueArray) . '.' . $dec;
2601  if ($negative) {
2602  $value *= -1;
2603  }
2604  $value = number_format($value, 2, '.', '');
2605  break;
2606  case 'md5':
2607  if (strlen($value) != 32) {
2608  $set = FALSE;
2609  }
2610  break;
2611  case 'trim':
2612  $value = trim($value);
2613  break;
2614  case 'upper':
2615  $value = $GLOBALS['LANG']->csConvObj->conv_case($GLOBALS['LANG']->charSet, $value, 'toUpper');
2616  break;
2617  case 'lower':
2618  $value = $GLOBALS['LANG']->csConvObj->conv_case($GLOBALS['LANG']->charSet, $value, 'toLower');
2619  break;
2620  case 'required':
2621  if (!isset($value) || $value === '') {
2622  $set = FALSE;
2623  }
2624  break;
2625  case 'is_in':
2626  $c = strlen($value);
2627  if ($c) {
2628  $newVal = '';
2629  for ($a = 0; $a < $c; $a++) {
2630  $char = substr($value, $a, 1);
2631  if (strpos($is_in, $char) !== FALSE) {
2632  $newVal .= $char;
2633  }
2634  }
2635  $value = $newVal;
2636  }
2637  break;
2638  case 'nospace':
2639  $value = str_replace(' ', '', $value);
2640  break;
2641  case 'alpha':
2642  $value = preg_replace('/[^a-zA-Z]/', '', $value);
2643  break;
2644  case 'num':
2645  $value = preg_replace('/[^0-9]/', '', $value);
2646  break;
2647  case 'alphanum':
2648  $value = preg_replace('/[^a-zA-Z0-9]/', '', $value);
2649  break;
2650  case 'alphanum_x':
2651  $value = preg_replace('/[^a-zA-Z0-9_-]/', '', $value);
2652  break;
2653  case 'domainname':
2654  if (!preg_match('/^[a-z0-9.\\-]*$/i', $value)) {
2655  $value = GeneralUtility::idnaEncode($value);
2656  }
2657  break;
2658  default:
2659  if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
2660  break;
2661  }
2662  $evalObj = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func] . ':&' . $func);
2663  if (is_object($evalObj) && method_exists($evalObj, 'evaluateFieldValue')) {
2664  $value = $evalObj->evaluateFieldValue($value, $is_in, $set);
2665  }
2666  }
2667  }
2668  if ($set) {
2669  $res['value'] = $value;
2670  }
2671  return $res;
2672  }
2673 
2687  public function checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, $type, $currentTable, $currentField) {
2688  $tables = $type == 'group' ? $tcaFieldConf['allowed'] : $tcaFieldConf['foreign_table'] . ',' . $tcaFieldConf['neg_foreign_table'];
2689  $prep = $type == 'group' ? $tcaFieldConf['prepend_tname'] : $tcaFieldConf['neg_foreign_table'];
2690  $newRelations = implode(',', $valueArray);
2692  $dbAnalysis = $this->createRelationHandlerInstance();
2693  $dbAnalysis->registerNonTableValues = $tcaFieldConf['allowNonIdValues'] ? 1 : 0;
2694  $dbAnalysis->start($newRelations, $tables, '', 0, $currentTable, $tcaFieldConf);
2695  if ($tcaFieldConf['MM']) {
2696  if ($status == 'update') {
2698  $oldRelations_dbAnalysis = $this->createRelationHandlerInstance();
2699  $oldRelations_dbAnalysis->registerNonTableValues = $tcaFieldConf['allowNonIdValues'] ? 1 : 0;
2700  // Db analysis with $id will initialize with the existing relations
2701  $oldRelations_dbAnalysis->start('', $tables, $tcaFieldConf['MM'], $id, $currentTable, $tcaFieldConf);
2702  $oldRelations = implode(',', $oldRelations_dbAnalysis->getValueArray());
2703  $dbAnalysis->writeMM($tcaFieldConf['MM'], $id, $prep);
2704  if ($oldRelations != $newRelations) {
2705  $this->mmHistoryRecords[$currentTable . ':' . $id]['oldRecord'][$currentField] = $oldRelations;
2706  $this->mmHistoryRecords[$currentTable . ':' . $id]['newRecord'][$currentField] = $newRelations;
2707  } else {
2708  $this->mmHistoryRecords[$currentTable . ':' . $id]['oldRecord'][$currentField] = '';
2709  $this->mmHistoryRecords[$currentTable . ':' . $id]['newRecord'][$currentField] = '';
2710  }
2711  } else {
2712  $this->dbAnalysisStore[] = array($dbAnalysis, $tcaFieldConf['MM'], $id, $prep, $currentTable);
2713  }
2714  $valueArray = $dbAnalysis->countItems();
2715  } else {
2716  $valueArray = $dbAnalysis->getValueArray($prep);
2717  if ($type == 'select' && $prep) {
2718  $valueArray = $dbAnalysis->convertPosNeg($valueArray, $tcaFieldConf['foreign_table'], $tcaFieldConf['neg_foreign_table']);
2719  }
2720  }
2721  // 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.
2722  return $valueArray;
2723  }
2724 
2733  $valueArray = GeneralUtility::trimExplode(',', $value, TRUE);
2734  foreach ($valueArray as &$newVal) {
2735  $temp = explode('|', $newVal, 2);
2736  $newVal = str_replace(',', '', str_replace('|', '', rawurldecode($temp[0])));
2737  }
2738  unset($newVal);
2739  return $valueArray;
2740  }
2741 
2758  public function checkValue_flex_procInData($dataPart, $dataPart_current, $uploadedFiles, $dataStructArray, $pParams, $callBackFunc = '', $workspaceOptions = array()) {
2759  if (is_array($dataPart)) {
2760  foreach ($dataPart as $sKey => $sheetDef) {
2761  list($dataStruct, $actualSheet) = GeneralUtility::resolveSheetDefInDS($dataStructArray, $sKey);
2762  if (is_array($dataStruct) && $actualSheet == $sKey && is_array($sheetDef)) {
2763  foreach ($sheetDef as $lKey => $lData) {
2764  $this->checkValue_flex_procInData_travDS($dataPart[$sKey][$lKey], $dataPart_current[$sKey][$lKey], $uploadedFiles[$sKey][$lKey], $dataStruct['ROOT']['el'], $pParams, $callBackFunc, $sKey . '/' . $lKey . '/', $workspaceOptions);
2765  }
2766  }
2767  }
2768  }
2769  return $dataPart;
2770  }
2771 
2788  public function checkValue_flex_procInData_travDS(&$dataValues, $dataValues_current, $uploadedFiles, $DSelements, $pParams, $callBackFunc, $structurePath, $workspaceOptions = array()) {
2789  if (is_array($DSelements)) {
2790  // For each DS element:
2791  foreach ($DSelements as $key => $dsConf) {
2792  // Array/Section:
2793  if ($DSelements[$key]['type'] == 'array') {
2794  if (is_array($dataValues[$key]['el'])) {
2795  if ($DSelements[$key]['section']) {
2796  $newIndexCounter = 0;
2797  foreach ($dataValues[$key]['el'] as $ik => $el) {
2798  if (is_array($el)) {
2799  if (!is_array($dataValues_current[$key]['el'])) {
2800  $dataValues_current[$key]['el'] = array();
2801  }
2802  $theKey = key($el);
2803  if (is_array($dataValues[$key]['el'][$ik][$theKey]['el'])) {
2804  $this->checkValue_flex_procInData_travDS($dataValues[$key]['el'][$ik][$theKey]['el'], is_array($dataValues_current[$key]['el'][$ik]) ? $dataValues_current[$key]['el'][$ik][$theKey]['el'] : array(), $uploadedFiles[$key]['el'][$ik][$theKey]['el'], $DSelements[$key]['el'][$theKey]['el'], $pParams, $callBackFunc, $structurePath . $key . '/el/' . $ik . '/' . $theKey . '/el/', $workspaceOptions);
2805  // If element is added dynamically in the flexform of TCEforms, we map the ID-string to the next numerical index we can have in that particular section of elements:
2806  // The fact that the order changes is not important since order is controlled by a separately submitted index.
2807  if (substr($ik, 0, 3) == 'ID-') {
2808  $newIndexCounter++;
2809  // Set mapping index
2810  $this->newIndexMap[$ik] = (is_array($dataValues_current[$key]['el']) && count($dataValues_current[$key]['el']) ? max(array_keys($dataValues_current[$key]['el'])) : 0) + $newIndexCounter;
2811  // Transfer values
2812  $dataValues[$key]['el'][$this->newIndexMap[$ik]] = $dataValues[$key]['el'][$ik];
2813  // Unset original
2814  unset($dataValues[$key]['el'][$ik]);
2815  }
2816  }
2817  }
2818  }
2819  } else {
2820  if (!isset($dataValues[$key]['el'])) {
2821  $dataValues[$key]['el'] = array();
2822  }
2823  $this->checkValue_flex_procInData_travDS($dataValues[$key]['el'], $dataValues_current[$key]['el'], $uploadedFiles[$key]['el'], $DSelements[$key]['el'], $pParams, $callBackFunc, $structurePath . $key . '/el/', $workspaceOptions);
2824  }
2825  }
2826  } else {
2827  if (is_array($dsConf['TCEforms']['config']) && is_array($dataValues[$key])) {
2828  foreach ($dataValues[$key] as $vKey => $data) {
2829  if ($callBackFunc) {
2830  if (is_object($this->callBackObj)) {
2831  $res = $this->callBackObj->{$callBackFunc}($pParams, $dsConf['TCEforms']['config'], $dataValues[$key][$vKey], $dataValues_current[$key][$vKey], $uploadedFiles[$key][$vKey], $structurePath . $key . '/' . $vKey . '/', $workspaceOptions);
2832  } else {
2833  $res = $this->{$callBackFunc}($pParams, $dsConf['TCEforms']['config'], $dataValues[$key][$vKey], $dataValues_current[$key][$vKey], $uploadedFiles[$key][$vKey], $structurePath . $key . '/' . $vKey . '/', $workspaceOptions);
2834  }
2835  } else {
2836  // Default
2837  list($CVtable, $CVid, $CVcurValue, $CVstatus, $CVrealPid, $CVrecFID, $CVtscPID) = $pParams;
2838 
2839  $additionalData = array(
2840  'flexFormId' => $CVrecFID,
2841  'flexFormPath' => trim(rtrim($structurePath, '/') . '/' . $key . '/' . $vKey, '/'),
2842  );
2843 
2844  $res = $this->checkValue_SW(array(), $dataValues[$key][$vKey], $dsConf['TCEforms']['config'], $CVtable, $CVid, $dataValues_current[$key][$vKey], $CVstatus, $CVrealPid, $CVrecFID, '', $uploadedFiles[$key][$vKey], $CVtscPID, $additionalData);
2845  // Look for RTE transformation of field:
2846  if ($dataValues[$key]['_TRANSFORM_' . $vKey] == 'RTE' && !$this->dontProcessTransformations) {
2847  // Unsetting trigger field - we absolutely don't want that into the data storage!
2848  unset($dataValues[$key]['_TRANSFORM_' . $vKey]);
2849  if (isset($res['value'])) {
2850  // Calculating/Retrieving some values here:
2851  list(, , $recFieldName) = explode(':', $CVrecFID);
2852  $theTypeString = BackendUtility::getTCAtypeValue($CVtable, $this->checkValue_currentRecord);
2853  $specConf = BackendUtility::getSpecConfParts('', $dsConf['TCEforms']['defaultExtras']);
2854  // Find, thisConfig:
2855  $RTEsetup = $this->BE_USER->getTSConfig('RTE', BackendUtility::getPagesTSconfig($CVtscPID));
2856  $thisConfig = BackendUtility::RTEsetup($RTEsetup['properties'], $CVtable, $recFieldName, $theTypeString);
2857  // Get RTE object, draw form and set flag:
2858  $RTEobj = BackendUtility::RTEgetObj();
2859  if (is_object($RTEobj)) {
2860  $res['value'] = $RTEobj->transformContent('db', $res['value'], $CVtable, $recFieldName, $this->checkValue_currentRecord, $specConf, $thisConfig, '', $CVrealPid);
2861  } else {
2862  debug('NO RTE OBJECT FOUND!');
2863  }
2864  }
2865  }
2866  }
2867  // Adding the value:
2868  if (isset($res['value'])) {
2869  $dataValues[$key][$vKey] = $res['value'];
2870  }
2871  // 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.
2872  // 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).
2873  if (substr($vKey, -9) != '.vDEFbase') {
2874  if ($this->clear_flexFormData_vDEFbase) {
2875  $dataValues[$key][$vKey . '.vDEFbase'] = '';
2876  } elseif ($this->updateModeL10NdiffData && $GLOBALS['TYPO3_CONF_VARS']['BE']['flexFormXMLincludeDiffBase'] && $vKey !== 'vDEF' && ((string)$dataValues[$key][$vKey] !== (string)$dataValues_current[$key][$vKey] || !isset($dataValues_current[$key][($vKey . '.vDEFbase')]) || $this->updateModeL10NdiffData === 'FORCE_FFUPD')) {
2877  // 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:
2878  if (isset($dataValues[$key]['vDEF'])) {
2879  $diffValue = $dataValues[$key]['vDEF'];
2880  } else {
2881  // If not found (for translators with no access to the default language) we use the one from the current-value data set:
2882  $diffValue = $dataValues_current[$key]['vDEF'];
2883  }
2884  // 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.
2885  $dataValues[$key][$vKey . '.vDEFbase'] = $this->updateModeL10NdiffDataClear ? '' : $diffValue;
2886  }
2887  }
2888  }
2889  }
2890  }
2891  }
2892  }
2893  }
2894 
2907  protected function checkValue_inline_processDBdata($valueArray, $tcaFieldConf, $id, $status, $table, $field, array $additionalData = NULL) {
2908  $newValue = '';
2909  $foreignTable = $tcaFieldConf['foreign_table'];
2910  $valueArray = $this->applyFiltersToValues($tcaFieldConf, $valueArray);
2911  // Fetch the related child records using \TYPO3\CMS\Core\Database\RelationHandler
2913  $dbAnalysis = $this->createRelationHandlerInstance();
2914  $dbAnalysis->start(implode(',', $valueArray), $foreignTable, '', 0, $table, $tcaFieldConf);
2915  // If the localizationMode is set to 'keep', the children for the localized parent are kept as in the original untranslated record:
2916  $localizationMode = BackendUtility::getInlineLocalizationMode($table, $tcaFieldConf);
2917  if ($localizationMode == 'keep' && $status == 'update') {
2918  // Fetch the current record and determine the original record:
2919  $row = BackendUtility::getRecordWSOL($table, $id);
2920  if (is_array($row)) {
2921  $language = (int)$row[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
2922  $transOrigPointer = (int)$row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
2923  // If language is set (e.g. 1) and also transOrigPointer (e.g. 123), use transOrigPointer as uid:
2924  if ($language > 0 && $transOrigPointer) {
2925  $id = $transOrigPointer;
2926  // If we're in active localizationMode 'keep', prevent from writing data to the field of the parent record:
2927  // (on removing the localized parent, the original (untranslated) children would then also be removed)
2928  $keepTranslation = TRUE;
2929  }
2930  }
2931  }
2932  // IRRE with a pointer field (database normalization):
2933  if ($tcaFieldConf['foreign_field']) {
2934  // if the record was imported, sorting was also imported, so skip this
2935  $skipSorting = $this->callFromImpExp ? TRUE : FALSE;
2936  // update record in intermediate table (sorting & pointer uid to parent record)
2937  $dbAnalysis->writeForeignField($tcaFieldConf, $id, 0, $skipSorting);
2938  $newValue = $keepTranslation ? 0 : $dbAnalysis->countItems(FALSE);
2939  } else {
2940  if ($this->getInlineFieldType($tcaFieldConf) == 'mm') {
2941  // In order to fully support all the MM stuff, directly call checkValue_group_select_processDBdata instead of repeating the needed code here
2942  $valueArray = $this->checkValue_group_select_processDBdata($valueArray, $tcaFieldConf, $id, $status, 'select', $table, $field);
2943  $newValue = $keepTranslation ? 0 : $valueArray[0];
2944  } else {
2945  $valueArray = $dbAnalysis->getValueArray();
2946  // Checking that the number of items is correct:
2947  $valueArray = $this->checkValue_checkMax($tcaFieldConf, $valueArray);
2948  // If a valid translation of the 'keep' mode is active, update relations in the original(!) record:
2949  if ($keepTranslation) {
2950  $this->updateDB($table, $transOrigPointer, array($field => implode(',', $valueArray)));
2951  } else {
2952  $newValue = implode(',', $valueArray);
2953  }
2954  }
2955  }
2956  return $newValue;
2957  }
2958 
2959  /*********************************************
2960  *
2961  * PROCESSING COMMANDS
2962  *
2963  ********************************************/
2970  public function process_cmdmap() {
2971  // Editing frozen:
2972  if ($this->BE_USER->workspace !== 0 && $this->BE_USER->workspaceRec['freeze']) {
2973  $this->newlog('All editing in this workspace has been frozen!', 1);
2974  return FALSE;
2975  }
2976  // Hook initialization:
2977  $hookObjectsArr = array();
2978  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'])) {
2979  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'] as $classRef) {
2980  $hookObj = GeneralUtility::getUserObj($classRef);
2981  if (method_exists($hookObj, 'processCmdmap_beforeStart')) {
2982  $hookObj->processCmdmap_beforeStart($this);
2983  }
2984  $hookObjectsArr[] = $hookObj;
2985  }
2986  }
2987  $pasteDatamap = array();
2988  // Traverse command map:
2989  foreach ($this->cmdmap as $table => $_) {
2990  // Check if the table may be modified!
2991  $modifyAccessList = $this->checkModifyAccessList($table);
2992  if (!$modifyAccessList) {
2993  $id = 0;
2994  $this->log($table, $id, 2, 0, 1, 'Attempt to modify table \'%s\' without permission', 1, array($table));
2995  }
2996  // FIXME: $id not set here (Comment added by Sebastian Kurfürst)
2997  // Check basic permissions and circumstances:
2998  if (isset($GLOBALS['TCA'][$table]) && !$this->tableReadOnly($table) && is_array($this->cmdmap[$table]) && $modifyAccessList) {
2999  // Traverse the command map:
3000  foreach ($this->cmdmap[$table] as $id => $incomingCmdArray) {
3001  if (!is_array($incomingCmdArray)) {
3002  continue;
3003  }
3004  foreach ($incomingCmdArray as $command => $value) {
3005  $pasteUpdate = FALSE;
3006  if (is_array($value) && isset($value['action']) && $value['action'] === 'paste') {
3007  // Extended paste command: $command is set to "move" or "copy"
3008  // $value['update'] holds field/value pairs which should be updated after copy/move operation
3009  // $value['target'] holds original $value (target of move/copy)
3010  $pasteUpdate = $value['update'];
3011  $value = $value['target'];
3012  }
3013  foreach ($hookObjectsArr as $hookObj) {
3014  if (method_exists($hookObj, 'processCmdmap_preProcess')) {
3015  $hookObj->processCmdmap_preProcess($command, $table, $id, $value, $this, $pasteUpdate);
3016  }
3017  }
3018  // Init copyMapping array:
3019  // Must clear this array before call from here to those functions:
3020  // Contains mapping information between new and old id numbers.
3021  $this->copyMappingArray = array();
3022  // process the command
3023  $commandIsProcessed = FALSE;
3024  foreach ($hookObjectsArr as $hookObj) {
3025  if (method_exists($hookObj, 'processCmdmap')) {
3026  $hookObj->processCmdmap($command, $table, $id, $value, $commandIsProcessed, $this, $pasteUpdate);
3027  }
3028  }
3029  // Only execute default commands if a hook hasn't been processed the command already
3030  if (!$commandIsProcessed) {
3031  $procId = $id;
3032  // Branch, based on command
3033  switch ($command) {
3034  case 'move':
3035  $this->moveRecord($table, $id, $value);
3036  break;
3037  case 'copy':
3038  if ($table === 'pages') {
3039  $this->copyPages($id, $value);
3040  } else {
3041  $this->copyRecord($table, $id, $value, 1);
3042  }
3043  $procId = $this->copyMappingArray[$table][$id];
3044  break;
3045  case 'localize':
3046  $this->localize($table, $id, $value);
3047  break;
3048  case 'inlineLocalizeSynchronize':
3049  $this->inlineLocalizeSynchronize($table, $id, $value);
3050  break;
3051  case 'delete':
3052  $this->deleteAction($table, $id);
3053  break;
3054  case 'undelete':
3055  $this->undeleteRecord($table, $id);
3056  break;
3057  }
3058  if (is_array($pasteUpdate)) {
3059  $pasteDatamap[$table][$procId] = $pasteUpdate;
3060  }
3061  }
3062  foreach ($hookObjectsArr as $hookObj) {
3063  if (method_exists($hookObj, 'processCmdmap_postProcess')) {
3064  $hookObj->processCmdmap_postProcess($command, $table, $id, $value, $this, $pasteUpdate, $pasteDatamap);
3065  }
3066  }
3067  // Merging the copy-array info together for remapping purposes.
3068  \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($this->copyMappingArray_merged, $this->copyMappingArray);
3069  }
3070  }
3071  }
3072  }
3074  $copyTCE = $this->getLocalTCE();
3075  $copyTCE->start($pasteDatamap, '', $this->BE_USER);
3076  $copyTCE->process_datamap();
3077  $this->errorLog = array_merge($this->errorLog, $copyTCE->errorLog);
3078  unset($copyTCE);
3079 
3080  // Finally, before exit, check if there are ID references to remap.
3081  // This might be the case if versioning or copying has taken place!
3082  $this->remapListedDBRecords();
3083  $this->processRemapStack();
3084  foreach ($hookObjectsArr as $hookObj) {
3085  if (method_exists($hookObj, 'processCmdmap_afterFinish')) {
3086  $hookObj->processCmdmap_afterFinish($this);
3087  }
3088  }
3089  if ($this->isOuterMostInstance()) {
3090  $this->processClearCacheQueue();
3091  $this->resetNestedElementCalls();
3092  }
3093  }
3094 
3095  /*********************************************
3096  *
3097  * Cmd: Copying
3098  *
3099  ********************************************/
3112  public function copyRecord($table, $uid, $destPid, $first = 0, $overrideValues = array(), $excludeFields = '', $language = 0) {
3113  $uid = ($origUid = (int)$uid);
3114  if (empty($GLOBALS['TCA'][$table]) || $uid === 0) {
3115  return NULL;
3116  }
3117  // Only copy if the table is defined in $GLOBALS['TCA'], a uid is given and the record wasn't copied before:
3118  if (!$this->isRecordCopied($table, $uid)) {
3119  // This checks if the record can be selected which is all that a copy action requires.
3120  if ($this->doesRecordExist($table, $uid, 'show')) {
3121  // Check if table is allowed on destination page
3122  if ($destPid < 0 || $this->isTableAllowedForThisPage($destPid, $table)) {
3123  $fullLanguageCheckNeeded = $table != 'pages';
3124  //Used to check language and general editing rights
3125  if ($language > 0 && $this->BE_USER->checkLanguageAccess($language) || $this->BE_USER->recordEditAccessInternals($table, $uid, FALSE, FALSE, $fullLanguageCheckNeeded)) {
3126  $data = array();
3127  $nonFields = array_unique(GeneralUtility::trimExplode(',', 'uid,perms_userid,perms_groupid,perms_user,perms_group,perms_everybody,t3ver_oid,t3ver_wsid,t3ver_id,t3ver_label,t3ver_state,t3ver_count,t3ver_stage,t3ver_tstamp,' . $excludeFields, TRUE));
3128  // So it copies (and localized) content from workspace...
3129  $row = BackendUtility::getRecordWSOL($table, $uid);
3130  if (is_array($row)) {
3131  // Initializing:
3132  $theNewID = uniqid('NEW', TRUE);
3133  $enableField = isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']) ? $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'] : '';
3134  $headerField = $GLOBALS['TCA'][$table]['ctrl']['label'];
3135  // Getting default data:
3136  $defaultData = $this->newFieldArray($table);
3137  // Getting "copy-after" fields if applicable:
3138  $copyAfterFields = $destPid < 0 ? $this->fixCopyAfterDuplFields($table, $uid, abs($destPid), 0) : array();
3139  // Page TSconfig related:
3140  // 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...
3141  $tscPID = BackendUtility::getTSconfig_pidValue($table, $uid, $destPid);
3142  $TSConfig = $this->getTCEMAIN_TSconfig($tscPID);
3143  $tE = $this->getTableEntries($table, $TSConfig);
3144  // Traverse ALL fields of the selected record:
3145  foreach ($row as $field => $value) {
3146  if (!in_array($field, $nonFields)) {
3147  // Get TCA configuration for the field:
3148  $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
3149  // Preparation/Processing of the value:
3150  // "pid" is hardcoded of course:
3151  if ($field == 'pid') {
3152  $value = $destPid;
3153  } elseif (isset($overrideValues[$field])) {
3154  // Override value...
3155  $value = $overrideValues[$field];
3156  } elseif (isset($copyAfterFields[$field])) {
3157  // Copy-after value if available:
3158  $value = $copyAfterFields[$field];
3159  } elseif ($GLOBALS['TCA'][$table]['ctrl']['setToDefaultOnCopy'] && GeneralUtility::inList($GLOBALS['TCA'][$table]['ctrl']['setToDefaultOnCopy'], $field)) {
3160  $value = $defaultData[$field];
3161  } else {
3162  // Hide at copy may override:
3163  if ($first && $field == $enableField && $GLOBALS['TCA'][$table]['ctrl']['hideAtCopy'] && !$this->neverHideAtCopy && !$tE['disableHideAtCopy']) {
3164  $value = 1;
3165  }
3166  // Prepend label on copy:
3167  if ($first && $field == $headerField && $GLOBALS['TCA'][$table]['ctrl']['prependAtCopy'] && !$tE['disablePrependAtCopy']) {
3168  $value = $this->getCopyHeader($table, $this->resolvePid($table, $destPid), $field, $this->clearPrefixFromValue($table, $value), 0);
3169  }
3170  // Processing based on the TCA config field type (files, references, flexforms...)
3171  $value = $this->copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $tscPID, $language);
3172  }
3173  // Add value to array.
3174  $data[$table][$theNewID][$field] = $value;
3175  }
3176  }
3177  // Overriding values:
3178  if ($GLOBALS['TCA'][$table]['ctrl']['editlock']) {
3179  $data[$table][$theNewID][$GLOBALS['TCA'][$table]['ctrl']['editlock']] = 0;
3180  }
3181  // Setting original UID:
3182  if ($GLOBALS['TCA'][$table]['ctrl']['origUid']) {
3183  $data[$table][$theNewID][$GLOBALS['TCA'][$table]['ctrl']['origUid']] = $uid;
3184  }
3185  // Do the copy by simply submitting the array through TCEmain:
3187  $copyTCE = $this->getLocalTCE();
3188  $copyTCE->start($data, '', $this->BE_USER);
3189  $copyTCE->process_datamap();
3190  // Getting the new UID:
3191  $theNewSQLID = $copyTCE->substNEWwithIDs[$theNewID];
3192  if ($theNewSQLID) {
3193  $this->copyRecord_fixRTEmagicImages($table, BackendUtility::wsMapId($table, $theNewSQLID));
3194  $this->copyMappingArray[$table][$origUid] = $theNewSQLID;
3195  // Keep automatically versionized record information:
3196  if (isset($copyTCE->autoVersionIdMap[$table][$theNewSQLID])) {
3197  $this->autoVersionIdMap[$table][$theNewSQLID] = $copyTCE->autoVersionIdMap[$table][$theNewSQLID];
3198  }
3199  }
3200  // Copy back the cached TSconfig
3201  $this->cachedTSconfig = $copyTCE->cachedTSconfig;
3202  $this->errorLog = array_merge($this->errorLog, $copyTCE->errorLog);
3203  unset($copyTCE);
3204  if ($language == 0) {
3205  //repointing the new translation records to the parent record we just created
3206  $overrideValues[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $theNewSQLID;
3207  $this->copyL10nOverlayRecords($table, $uid, $destPid, $first, $overrideValues, $excludeFields);
3208  }
3209  return $theNewSQLID;
3210  } else {
3211  $this->log($table, $uid, 3, 0, 1, 'Attempt to copy record that did not exist!');
3212  }
3213  } else {
3214  $this->log($table, $uid, 3, 0, 1, 'Attempt to copy record without having permissions to do so. [' . $this->BE_USER->errorMsg . '].');
3215  }
3216  } else {
3217  $this->log($table, $uid, 3, 0, 1, 'Attempt to insert record on a page that can\'t store record type.');
3218  }
3219  } else {
3220  $this->log($table, $uid, 3, 0, 1, 'Attempt to copy record without permission');
3221  }
3222  } elseif (!empty($overrideValues)) {
3223  $this->log($table, $uid, 5, 0, 1, 'Repeated attemp to copy record "' . $table . ':' . $uid . '" with override values');
3224  }
3225  }
3226 
3236  public function copyPages($uid, $destPid) {
3237  // Initialize:
3238  $uid = (int)$uid;
3239  $destPid = (int)$destPid;
3240  // Finding list of tables to copy.
3241  // These are the tables, the user may modify
3242  $copyTablesArray = $this->admin ? $this->compileAdminTables() : explode(',', $this->BE_USER->groupData['tables_modify']);
3243  // If not all tables are allowed then make a list of allowed tables: That is the tables that figure in both allowed tables AND the copyTable-list
3244  if (!strstr($this->copyWhichTables, '*')) {
3245  foreach ($copyTablesArray as $k => $table) {
3246  // Pages are always going...
3247  if (!$table || !GeneralUtility::inList(($this->copyWhichTables . ',pages'), $table)) {
3248  unset($copyTablesArray[$k]);
3249  }
3250  }
3251  }
3252  $copyTablesArray = array_unique($copyTablesArray);
3253  // Begin to copy pages if we're allowed to:
3254  if ($this->admin || in_array('pages', $copyTablesArray)) {
3255  // Copy this page we're on. And set first-flag (this will trigger that the record is hidden if that is configured)!
3256  $theNewRootID = $this->copySpecificPage($uid, $destPid, $copyTablesArray, 1);
3257  // If we're going to copy recursively...:
3258  if ($theNewRootID && $this->copyTree) {
3259  // Get ALL subpages to copy (read-permissions are respected!):
3260  $CPtable = $this->int_pageTreeInfo(array(), $uid, (int)$this->copyTree, $theNewRootID);
3261  // Now copying the subpages:
3262  foreach ($CPtable as $thePageUid => $thePagePid) {
3263  $newPid = $this->copyMappingArray['pages'][$thePagePid];
3264  if (isset($newPid)) {
3265  $this->copySpecificPage($thePageUid, $newPid, $copyTablesArray);
3266  } else {
3267  $this->log('pages', $uid, 5, 0, 1, 'Something went wrong during copying branch');
3268  break;
3269  }
3270  }
3271  }
3272  } else {
3273  $this->log('pages', $uid, 5, 0, 1, 'Attempt to copy page without permission to this table');
3274  }
3275  }
3276 
3287  public function copySpecificPage($uid, $destPid, $copyTablesArray, $first = 0) {
3288  // Copy the page itself:
3289  $theNewRootID = $this->copyRecord('pages', $uid, $destPid, $first);
3290  // If a new page was created upon the copy operation we will proceed with all the tables ON that page:
3291  if ($theNewRootID) {
3292  foreach ($copyTablesArray as $table) {
3293  // All records under the page is copied.
3294  if ($table && is_array($GLOBALS['TCA'][$table]) && $table != 'pages') {
3295  $fields = 'uid';
3296  $languageField = NULL;
3297  $transOrigPointerField = NULL;
3298  if (BackendUtility::isTableLocalizable($table)) {
3299  $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
3300  $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
3301  $fields .= ',' . $languageField . ',' . $transOrigPointerField;
3302  }
3304  $workspaceStatement = '';
3305  } elseif ((int)$this->BE_USER->workspace === 0) {
3306  $workspaceStatement = ' AND t3ver_wsid=0';
3307  } else {
3308  $workspaceStatement = ' AND t3ver_wsid IN (0,' . (int)$this->BE_USER->workspace . ')';
3309  }
3310  $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
3311  $fields,
3312  $table,
3313  'pid=' . (int)$uid . $this->deleteClause($table) . $workspaceStatement,
3314  '',
3315  (!empty($GLOBALS['TCA'][$table]['ctrl']['sortby']) ? $GLOBALS['TCA'][$table]['ctrl']['sortby'] . ' DESC' : ''),
3316  '',
3317  'uid'
3318  );
3319  // Resolve placeholders of workspace versions
3320  if (!empty($rows) && (int)$this->BE_USER->workspace !== 0 && BackendUtility::isTableWorkspaceEnabled($table)) {
3321  $rows = array_reverse(
3322  $this->resolveVersionedRecords(
3323  $table,
3324  $fields,
3325  $GLOBALS['TCA'][$table]['ctrl']['sortby'],
3326  array_keys($rows)
3327  ),
3328  TRUE
3329  );
3330  }
3331 
3332  foreach ($rows as $row) {
3333  // Skip localized records that will be processed in
3334  // copyL10nOverlayRecords() on copying the default language record
3335  $transOrigPointer = $row[$transOrigPointerField];
3336  if ($row[$languageField] > 0 && $transOrigPointer > 0 && isset($rows[$transOrigPointer])) {
3337  continue;
3338  }
3339  // Copying each of the underlying records...
3340  $this->copyRecord($table, $row['uid'], $theNewRootID);
3341  }
3342  }
3343  }
3344  return $theNewRootID;
3345  }
3346  }
3347 
3364  public function copyRecord_raw($table, $uid, $pid, $overrideArray = array(), array $workspaceOptions = array()) {
3365  $uid = (int)$uid;
3366  // Stop any actions if the record is marked to be deleted:
3367  // (this can occur if IRRE elements are versionized and child elements are removed)
3368  if ($this->isElementToBeDeleted($table, $uid)) {
3369  return NULL;
3370  }
3371  // Only copy if the table is defined in TCA, a uid is given and the record wasn't copied before:
3372  if ($GLOBALS['TCA'][$table] && $uid && !$this->isRecordCopied($table, $uid)) {
3373  if ($this->doesRecordExist($table, $uid, 'show')) {
3374  // Set up fields which should not be processed. They are still written - just passed through no-questions-asked!
3375  $nonFields = array('uid', 'pid', 't3ver_id', 't3ver_oid', 't3ver_wsid', 't3ver_label', 't3ver_state', 't3ver_count', 't3ver_stage', 't3ver_tstamp', 'perms_userid', 'perms_groupid', 'perms_user', 'perms_group', 'perms_everybody');
3376  // Select main record:
3377  $row = $this->recordInfo($table, $uid, '*');
3378  if (is_array($row)) {
3379  // Merge in override array.
3380  $row = array_merge($row, $overrideArray);
3381  // Traverse ALL fields of the selected record:
3382  foreach ($row as $field => $value) {
3383  if (!in_array($field, $nonFields)) {
3384  // Get TCA configuration for the field:
3385  $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
3386  if (is_array($conf)) {
3387  // Processing based on the TCA config field type (files, references, flexforms...)
3388  $value = $this->copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $pid, 0, $workspaceOptions);
3389  }
3390  // Add value to array.
3391  $row[$field] = $value;
3392  }
3393  }
3394  // Force versioning related fields:
3395  $row['pid'] = $pid;
3396  // Setting original UID:
3397  if ($GLOBALS['TCA'][$table]['ctrl']['origUid']) {
3398  $row[$GLOBALS['TCA'][$table]['ctrl']['origUid']] = $uid;
3399  }
3400  // Do the copy by internal function
3401  $theNewSQLID = $this->insertNewCopyVersion($table, $row, $pid);
3402  if ($theNewSQLID) {
3403  $this->dbAnalysisStoreExec();
3404  $this->dbAnalysisStore = array();
3405  $this->copyRecord_fixRTEmagicImages($table, BackendUtility::wsMapId($table, $theNewSQLID));
3406  return $this->copyMappingArray[$table][$uid] = $theNewSQLID;
3407  }
3408  } else {
3409  $this->log($table, $uid, 3, 0, 1, 'Attempt to rawcopy/versionize record that did not exist!');
3410  }
3411  } else {
3412  $this->log($table, $uid, 3, 0, 1, 'Attempt to rawcopy/versionize record without copy permission');
3413  }
3414  }
3415  }
3416 
3427  public function insertNewCopyVersion($table, $fieldArray, $realPid) {
3428  $id = uniqid('NEW', TRUE);
3429  // $fieldArray is set as current record.
3430  // 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...
3431  $this->checkValue_currentRecord = $fieldArray;
3432  // Makes sure that transformations aren't processed on the copy.
3433  $backupDontProcessTransformations = $this->dontProcessTransformations;
3434  $this->dontProcessTransformations = TRUE;
3435  // Traverse record and input-process each value:
3436  foreach ($fieldArray as $field => $fieldValue) {
3437  if (isset($GLOBALS['TCA'][$table]['columns'][$field])) {
3438  // Evaluating the value.
3439  $res = $this->checkValue($table, $field, $fieldValue, $id, 'new', $realPid, 0);
3440  if (isset($res['value'])) {
3441  $fieldArray[$field] = $res['value'];
3442  }
3443  }
3444  }
3445  // System fields being set:
3446  if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
3447  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
3448  }
3449  if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
3450  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $this->userid;
3451  }
3452  if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
3453  $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
3454  }
3455  // Finally, insert record:
3456  $this->insertDB($table, $id, $fieldArray, TRUE);
3457  // Resets dontProcessTransformations to the previous state.
3458  $this->dontProcessTransformations = $backupDontProcessTransformations;
3459  // Return new id:
3460  return $this->substNEWwithIDs[$id];
3461  }
3462 
3479  public function copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $realDestPid, $language = 0, array $workspaceOptions = array()) {
3480  // Process references and files, currently that means only the files, prepending absolute paths (so the TCEmain engine will detect the file as new and one that should be made into a copy)
3481  $value = $this->copyRecord_procFilesRefs($conf, $uid, $value);
3482  $inlineSubType = $this->getInlineFieldType($conf);
3483  // Get the localization mode for the current (parent) record (keep|select):
3484  $localizationMode = BackendUtility::getInlineLocalizationMode($table, $field);
3485  // Register if there are references to take care of or MM is used on an inline field (no change to value):
3486  if ($this->isReferenceField($conf) || $inlineSubType == 'mm') {
3487  $allowedTables = $conf['type'] == 'group' ? $conf['allowed'] : $conf['foreign_table'] . ',' . $conf['neg_foreign_table'];
3488  $prependName = $conf['type'] == 'group' ? $conf['prepend_tname'] : $conf['neg_foreign_table'];
3489  $mmTable = isset($conf['MM']) && $conf['MM'] ? $conf['MM'] : '';
3490  $localizeForeignTable = isset($conf['foreign_table']) && BackendUtility::isTableLocalizable($conf['foreign_table']);
3491  $localizeReferences = $localizeForeignTable && isset($conf['localizeReferencesAtParentLocalization']) && $conf['localizeReferencesAtParentLocalization'];
3492  $localizeChildren = $localizeForeignTable && isset($conf['behaviour']['localizeChildrenAtParentLocalization']) && $conf['behaviour']['localizeChildrenAtParentLocalization'];
3494  $dbAnalysis = $this->createRelationHandlerInstance();
3495  $dbAnalysis->start($value, $allowedTables, $mmTable, $uid, $table, $conf);
3496  // Localize referenced records of select fields:
3497  if ($language > 0 && ($localizeReferences && empty($mmTable) || $localizeChildren && $localizationMode === 'select' && $inlineSubType === 'mm')) {
3498  foreach ($dbAnalysis->itemArray as $index => $item) {
3499  // Since select fields can reference many records, check whether there's already a localization:
3500  $recordLocalization = BackendUtility::getRecordLocalization($item['table'], $item['id'], $language);
3501  if ($recordLocalization) {
3502  $dbAnalysis->itemArray[$index]['id'] = $recordLocalization[0]['uid'];
3503  } elseif ($this->isNestedElementCallRegistered($item['table'], $item['id'], 'localize') === FALSE) {
3504  $dbAnalysis->itemArray[$index]['id'] = $this->localize($item['table'], $item['id'], $language);
3505  }
3506  }
3507  $dbAnalysis->purgeItemArray();
3508  $value = implode(',', $dbAnalysis->getValueArray($prependName));
3509  } elseif ($language > 0 && $localizeChildren === FALSE && $localizationMode === 'select' && $inlineSubType === 'mm') {
3510  foreach ($dbAnalysis->itemArray as $index => $item) {
3511  // Since select fields can reference many records, check whether there's already a localization:
3512  $recordLocalization = BackendUtility::getRecordLocalization($item['table'], $item['id'], $language);
3513  if ($recordLocalization) {
3514  $dbAnalysis->itemArray[$index]['id'] = $recordLocalization[0]['uid'];
3515  } elseif ($this->isNestedElementCallRegistered($item['table'], $item['id'], 'localize') === FALSE) {
3516  unset($dbAnalysis->itemArray[$index]);
3517  }
3518  }
3519  $dbAnalysis->purgeItemArray();
3520  $value = implode(',', $dbAnalysis->getValueArray($prependName));
3521  } elseif ($mmTable) {
3522  $dbAnalysis->purgeItemArray();
3523  $value = implode(',', $dbAnalysis->getValueArray($prependName));
3524  }
3525  // Setting the value in this array will notify the remapListedDBRecords() function that this field MAY need references to be corrected
3526  if ($value) {
3527  $this->registerDBList[$table][$uid][$field] = $value;
3528  }
3529  } elseif ($inlineSubType !== FALSE) {
3530  // Localization in mode 'keep', isn't a real localization, but keeps the children of the original parent record:
3531  if ($language > 0 && $localizationMode == 'keep') {
3532  $value = $inlineSubType == 'field' ? 0 : '';
3533  } else {
3534  // Fetch the related child records using \TYPO3\CMS\Core\Database\RelationHandler
3536  $dbAnalysis = $this->createRelationHandlerInstance();
3537  $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
3538  // Walk through the items, copy them and remember the new id:
3539  foreach ($dbAnalysis->itemArray as $k => $v) {
3540  // If language is set and differs from original record, this isn't a copy action but a localization of our parent/ancestor:
3541  if ($language > 0 && BackendUtility::isTableLocalizable($table) && $language != $row[$GLOBALS['TCA'][$table]['ctrl']['languageField']]) {
3542  // If children should be localized when the parent gets localized the first time, just do it:
3543  if ($localizationMode != FALSE && isset($conf['behaviour']['localizeChildrenAtParentLocalization']) && $conf['behaviour']['localizeChildrenAtParentLocalization']) {
3544  $newId = $this->localize($v['table'], $v['id'], $language);
3545  }
3546  } else {
3547  if (!\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($realDestPid)) {
3548  $newId = $this->copyRecord($v['table'], $v['id'], -$v['id']);
3549  // If the destination page id is a NEW string, keep it on the same page
3550  } elseif ($this->BE_USER->workspace > 0 && BackendUtility::isTableWorkspaceEnabled($v['table'])) {
3551  // A filled $workspaceOptions indicated that this call
3552  // has it's origin in previous versionizeRecord() processing
3553  if (count($workspaceOptions)) {
3554  // Versions use live default id, thus the "new"
3555  // id is the original live default child record
3556  $newId = $v['id'];
3557  $this->versionizeRecord(
3558  $v['table'], $v['id'],
3559  (isset($workspaceOptions['label']) ? $workspaceOptions['label'] : 'Auto-created for WS #' . $this->BE_USER->workspace),
3560  (isset($workspaceOptions['delete']) ? $workspaceOptions['delete'] : FALSE)
3561  );
3562  // Otherwise just use plain copyRecord() to create placeholders etc.
3563  } else {
3564  // If a record has been copied already during this request,
3565  // prevent superfluous duplication and use the existing copy
3566  if (isset($this->copyMappingArray[$v['table']][$v['id']])) {
3567  $newId = $this->copyMappingArray[$v['table']][$v['id']];
3568  } else {
3569  $newId = $this->copyRecord($v['table'], $v['id'], $realDestPid);
3570  }
3571  }
3572  } else {
3573  // If a record has been copied already during this request,
3574  // prevent superfluous duplication and use the existing copy
3575  if (isset($this->copyMappingArray[$v['table']][$v['id']])) {
3576  $newId = $this->copyMappingArray[$v['table']][$v['id']];
3577  } else {
3578  $newId = $this->copyRecord_raw($v['table'], $v['id'], $realDestPid, array(), $workspaceOptions);
3579  }
3580  }
3581  }
3582  // If the current field is set on a page record, update the pid of related child records:
3583  if ($table == 'pages') {
3584  $this->registerDBPids[$v['table']][$v['id']] = $uid;
3585  } elseif (isset($this->registerDBPids[$table][$uid])) {
3586  $this->registerDBPids[$v['table']][$v['id']] = $this->registerDBPids[$table][$uid];
3587  }
3588  $dbAnalysis->itemArray[$k]['id'] = $newId;
3589  }
3590  // Store the new values, we will set up the uids for the subtype later on (exception keep localization from original record):
3591  $value = implode(',', $dbAnalysis->getValueArray());
3592  $this->registerDBList[$table][$uid][$field] = $value;
3593  }
3594  }
3595  // 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())
3596  if ($conf['type'] == 'flex') {
3597  // Get current value array:
3598  $dataStructArray = BackendUtility::getFlexFormDS($conf, $row, $table, $field);
3599  $currentValueArray = GeneralUtility::xml2array($value);
3600  // Traversing the XML structure, processing files:
3601  if (is_array($currentValueArray)) {
3602  $currentValueArray['data'] = $this->checkValue_flex_procInData($currentValueArray['data'], array(), array(), $dataStructArray, array($table, $uid, $field, $realDestPid), 'copyRecord_flexFormCallBack', $workspaceOptions);
3603  // Setting value as an array! -> which means the input will be processed according to the 'flex' type when the new copy is created.
3604  $value = $currentValueArray;
3605  }
3606  }
3607  return $value;
3608  }
3609 
3624  public function copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $_1, $_2, $_3 = null, $workspaceOptions = array()) {
3625  // Extract parameters:
3626  list($table, $uid, $field, $realDestPid) = $pParams;
3627  // Process references and files, currently that means only the files, prepending absolute paths:
3628  $dataValue = $this->copyRecord_procFilesRefs($dsConf, $uid, $dataValue);
3629  // If references are set for this field, set flag so they can be corrected later (in ->remapListedDBRecords())
3630  if (($this->isReferenceField($dsConf) || $this->getInlineFieldType($dsConf) !== FALSE) && strlen($dataValue)) {
3631  $dataValue = $this->copyRecord_procBasedOnFieldType($table, $uid, $field, $dataValue, array(), $dsConf, $realDestPid, 0, $workspaceOptions);
3632  $this->registerDBList[$table][$uid][$field] = 'FlexForm_reference';
3633  }
3634  // Return
3635  return array('value' => $dataValue);
3636  }
3637 
3650  public function copyRecord_procFilesRefs($conf, $uid, $value) {
3651  // Prepend absolute paths to files:
3652  if ($conf['type'] == 'group' && ($conf['internal_type'] == 'file' || $conf['internal_type'] == 'file_reference')) {
3653  // Get an array with files as values:
3654  if ($conf['MM']) {
3655  $theFileValues = array();
3657  $dbAnalysis = $this->createRelationHandlerInstance();
3658  $dbAnalysis->start('', 'files', $conf['MM'], $uid);
3659  foreach ($dbAnalysis->itemArray as $somekey => $someval) {
3660  if ($someval['id']) {
3661  $theFileValues[] = $someval['id'];
3662  }
3663  }
3664  } else {
3665  $theFileValues = GeneralUtility::trimExplode(',', $value, TRUE);
3666  }
3667  // Traverse this array of files:
3668  $uploadFolder = $conf['internal_type'] == 'file' ? $conf['uploadfolder'] : '';
3669  $dest = $this->destPathFromUploadFolder($uploadFolder);
3670  $newValue = array();
3671  foreach ($theFileValues as $file) {
3672  if (trim($file)) {
3673  $realFile = str_replace('//', '/', $dest . '/' . trim($file));
3674  if (@is_file($realFile)) {
3675  $newValue[] = $realFile;
3676  }
3677  }
3678  }
3679  // Implode the new filelist into the new value (all files have absolute paths now which means they will get copied when entering TCEmain as new values...)
3680  $value = implode(',', $newValue);
3681  }
3682  // Return the new value:
3683  return $value;
3684  }
3685 
3696  public function copyRecord_fixRTEmagicImages($table, $theNewSQLID) {
3697  // Creating fileFunc object.
3698  if (!$this->fileFunc) {
3699  $this->fileFunc = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Utility\\File\\BasicFileUtility');
3700  $this->include_filefunctions = 1;
3701  }
3702  // Select all RTEmagic files in the reference table from the table/ID
3703  $recs = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'sys_refindex', 'ref_table=' . $GLOBALS['TYPO3_DB']->fullQuoteStr('_FILE', 'sys_refindex') . ' AND ref_string LIKE ' . $GLOBALS['TYPO3_DB']->fullQuoteStr('%/RTEmagic%', 'sys_refindex') . ' AND softref_key=' . $GLOBALS['TYPO3_DB']->fullQuoteStr('images', 'sys_refindex') . ' AND tablename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($table, 'sys_refindex') . ' AND recuid=' . (int)$theNewSQLID, '', 'sorting DESC');
3704  // Traverse the files found and copy them:
3705  if (is_array($recs)) {
3706  foreach ($recs as $rec) {
3707  $filename = basename($rec['ref_string']);
3708  $fileInfo = array();
3709  if (GeneralUtility::isFirstPartOfStr($filename, 'RTEmagicC_')) {
3710  $fileInfo['exists'] = @is_file((PATH_site . $rec['ref_string']));
3711  $fileInfo['original'] = substr($rec['ref_string'], 0, -strlen($filename)) . 'RTEmagicP_' . preg_replace('/\\.[[:alnum:]]+$/', '', substr($filename, 10));
3712  $fileInfo['original_exists'] = @is_file((PATH_site . $fileInfo['original']));
3713  // CODE from tx_impexp and class.rte_images.php adapted for use here:
3714  if ($fileInfo['exists'] && $fileInfo['original_exists']) {
3715  // Initialize; Get directory prefix for file and set the original name:
3716  $dirPrefix = dirname($rec['ref_string']) . '/';
3717  $rteOrigName = basename($fileInfo['original']);
3718  // If filename looks like an RTE file, and the directory is in "uploads/", then process as a RTE file!
3719  if ($rteOrigName && GeneralUtility::isFirstPartOfStr($dirPrefix, 'uploads/') && @is_dir((PATH_site . $dirPrefix))) {
3720  // RTE:
3721  // From the "original" RTE filename, produce a new "original" destination filename which is unused.
3722  $origDestName = $this->fileFunc->getUniqueName($rteOrigName, PATH_site . $dirPrefix);
3723  // Create copy file name:
3724  $pI = pathinfo($rec['ref_string']);
3725  $copyDestName = dirname($origDestName) . '/RTEmagicC_' . substr(basename($origDestName), 10) . '.' . $pI['extension'];
3726  if (!@is_file($copyDestName) && !@is_file($origDestName) && $origDestName === GeneralUtility::getFileAbsFileName($origDestName) && $copyDestName === GeneralUtility::getFileAbsFileName($copyDestName)) {
3727  // Making copies:
3728  GeneralUtility::upload_copy_move(PATH_site . $fileInfo['original'], $origDestName);
3729  GeneralUtility::upload_copy_move(PATH_site . $rec['ref_string'], $copyDestName);
3730  clearstatcache();
3731  // Register this:
3732  $this->RTEmagic_copyIndex[$rec['tablename']][$rec['recuid']][$rec['field']][$rec['ref_string']] = \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($copyDestName);
3733  // Check and update the record using \TYPO3\CMS\Core\Database\ReferenceIndex
3734  if (@is_file($copyDestName)) {
3735  $sysRefObj = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\ReferenceIndex');
3736  $error = $sysRefObj->setReferenceValue($rec['hash'], \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($copyDestName), FALSE, TRUE);
3737  if ($error) {
3738  echo $this->newlog('TYPO3\\CMS\\Core\\Database\\ReferenceIndex::setReferenceValue(): ' . $error, 1);
3739  }
3740  } else {
3741  $this->newlog('File "' . $copyDestName . '" was not created!', 1);
3742  }
3743  } else {
3744  $this->newlog('Could not construct new unique names for file!', 1);
3745  }
3746  } else {
3747  $this->newlog('Maybe directory of file was not within "uploads/"?', 1);
3748  }
3749  } else {
3750  $this->newlog('Trying to copy RTEmagic files (' . $rec['ref_string'] . ' / ' . $fileInfo['original'] . ') but one or both were missing', 1);
3751  }
3752  }
3753  }
3754  }
3755  }
3756 
3769  public function copyL10nOverlayRecords($table, $uid, $destPid, $first = 0, $overrideValues = array(), $excludeFields = '') {
3770  // There's no need to perform this for page-records or for tables that are not localizeable
3771  if (!BackendUtility::isTableLocalizable($table) || !empty($GLOBALS['TCA'][$table]['ctrl']['transForeignTable']) || !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable'])) {
3772  return;
3773  }
3774  $where = '';
3775  if (isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
3776  $where = ' AND t3ver_oid=0';
3777  }
3778  // If $destPid is < 0, get the pid of the record with uid equal to abs($destPid)
3779  $tscPID = BackendUtility::getTSconfig_pidValue($table, $uid, $destPid);
3780  // Get the localized records to be copied
3781  $l10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], $uid, $where);
3782  if (is_array($l10nRecords)) {
3783  // If $destPid < 0, then it is the uid of the original language record we are inserting after
3784  if ($destPid < 0) {
3785  $localizedDestPids = array();
3786  // Get the localized records of the record we are inserting after
3787  $destL10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], abs($destPid), $where);
3788  // Index the localized record uids by language
3789  if (is_array($destL10nRecords)) {
3790  foreach ($destL10nRecords as $record) {
3791  $localizedDestPids[$record[$GLOBALS['TCA'][$table]['ctrl']['languageField']]] = -$record['uid'];
3792  }
3793  }
3794  }
3795  // Copy the localized records after the corresponding localizations of the destination record
3796  foreach ($l10nRecords as $record) {
3797  $localizedDestPid = (int)$localizedDestPids[$record[$GLOBALS['TCA'][$table]['ctrl']['languageField']]];
3798  if ($localizedDestPid < 0) {
3799  $this->copyRecord($table, $record['uid'], $localizedDestPid, $first, $overrideValues, $excludeFields, $record[$GLOBALS['TCA'][$table]['ctrl']['languageField']]);
3800  } else {
3801  $this->copyRecord($table, $record['uid'], $destPid < 0 ? $tscPID : $destPid, $first, $overrideValues, $excludeFields, $record[$GLOBALS['TCA'][$table]['ctrl']['languageField']]);
3802  }
3803  }
3804  }
3805  }
3806 
3807  /*********************************************
3808  *
3809  * Cmd: Moving, Localizing
3810  *
3811  ********************************************/
3821  public function moveRecord($table, $uid, $destPid) {
3822  if ($GLOBALS['TCA'][$table]) {
3823  // In case the record to be moved turns out to be an offline version,
3824  // we have to find the live version and work on that one (this case
3825  // happens for pages with "branch" versioning type)
3826  // @deprecated note: as "branch" versioning is deprecated since TYPO3 4.2, this
3827  // functionality will be removed in TYPO3 4.7 (note by benni: a hook could replace this)
3828  if ($lookForLiveVersion = BackendUtility::getLiveVersionOfRecord($table, $uid, 'uid')) {
3829  $uid = $lookForLiveVersion['uid'];
3830  }
3831  // Initialize:
3832  $destPid = (int)$destPid;
3833  // Get this before we change the pid (for logging)
3834  $propArr = $this->getRecordProperties($table, $uid);
3835  $moveRec = $this->getRecordProperties($table, $uid, TRUE);
3836  // This is the actual pid of the moving to destination
3837  $resolvedPid = $this->resolvePid($table, $destPid);
3838  // 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.
3839  // If the record is a page, then there are two options: If the page is moved within itself, (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.
3840  if ($table != 'pages' || $resolvedPid == $moveRec['pid']) {
3841  // Edit rights for the record...
3842  $mayMoveAccess = $this->checkRecordUpdateAccess($table, $uid);
3843  } else {
3844  $mayMoveAccess = $this->doesRecordExist($table, $uid, 'delete');
3845  }
3846  // Finding out, if the record may be moved TO another place. Here we check insert-rights (non-pages = edit, pages = new), unless the pages are moved on the same pid, then edit-rights are checked
3847  if ($table != 'pages' || $resolvedPid != $moveRec['pid']) {
3848  // Insert rights for the record...
3849  $mayInsertAccess = $this->checkRecordInsertAccess($table, $resolvedPid, 4);
3850  } else {
3851  $mayInsertAccess = $this->checkRecordUpdateAccess($table, $uid);
3852  }
3853  // Checking if there is anything else disallowing moving the record by checking if editing is allowed
3854  $fullLanguageCheckNeeded = $table != 'pages';
3855  $mayEditAccess = $this->BE_USER->recordEditAccessInternals($table, $uid, FALSE, FALSE, $fullLanguageCheckNeeded);
3856  // If moving is allowed, begin the processing:
3857  if ($mayEditAccess) {
3858  if ($mayMoveAccess) {
3859  if ($mayInsertAccess) {
3860  $recordWasMoved = FALSE;
3861  // Move the record via a hook, used e.g. for versioning
3862  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'])) {
3863  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'] as $classRef) {
3864  $hookObj = GeneralUtility::getUserObj($classRef);
3865  if (method_exists($hookObj, 'moveRecord')) {
3866  $hookObj->moveRecord($table, $uid, $destPid, $propArr, $moveRec, $resolvedPid, $recordWasMoved, $this);
3867  }
3868  }
3869  }
3870  // Move the record if a hook hasn't moved it yet
3871  if (!$recordWasMoved) {
3872  $this->moveRecord_raw($table, $uid, $destPid);
3873  }
3874  } else {
3875  $this->log($table, $uid, 4, 0, 1, 'Attempt to move record \'%s\' (%s) without having permissions to insert.', 14, array($propArr['header'], $table . ':' . $uid), $propArr['event_pid']);
3876  }
3877  } else {
3878  $this->log($table, $uid, 4, 0, 1, 'Attempt to move record \'%s\' (%s) without having permissions to do so.', 14, array($propArr['header'], $table . ':' . $uid), $propArr['event_pid']);
3879  }
3880  } else {
3881  $this->log($table, $uid, 4, 0, 1, 'Attempt to move record "%s" (%s) without having permissions to do so. [' . $this->BE_USER->errorMsg . ']', 14, array($propArr['header'], $table . ':' . $uid), $propArr['event_pid']);
3882  }
3883  }
3884  }
3885 
3897  public function moveRecord_raw($table, $uid, $destPid) {
3898  $sortRow = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
3899  $origDestPid = $destPid;
3900  // This is the actual pid of the moving to destination
3901  $resolvedPid = $this->resolvePid($table, $destPid);
3902  // Checking if the pid is negative, but no sorting row is defined. In that case, find the correct pid. Basically this check make the error message 4-13 meaning less... But you can always remove this check if you prefer the error instead of a no-good action (which is to move the record to its own page...)
3903  // $destPid>=0 because we must correct pid in case of versioning "page" types.
3904  if ($destPid < 0 && !$sortRow || $destPid >= 0) {
3905  $destPid = $resolvedPid;
3906  }
3907  // Get this before we change the pid (for logging)
3908  $propArr = $this->getRecordProperties($table, $uid);
3909  $moveRec = $this->getRecordProperties($table, $uid, TRUE);
3910  // Prepare user defined objects (if any) for hooks which extend this function:
3911  $hookObjectsArr = array();
3912  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'])) {
3913  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'] as $classRef) {
3914  $hookObjectsArr[] = GeneralUtility::getUserObj($classRef);
3915  }
3916  }
3917  // Timestamp field:
3918  $updateFields = array();
3919  if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
3920  $updateFields[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
3921  }
3922  // Insert as first element on page (where uid = $destPid)
3923  if ($destPid >= 0) {
3924  if ($table != 'pages' || $this->destNotInsideSelf($destPid, $uid)) {
3925  // Clear cache before moving
3926  list($parentUid) = BackendUtility::getTSCpid($table, $uid, '');
3927  $this->registerRecordIdForPageCacheClearing($table, $uid, $parentUid);
3928  // Setting PID
3929  $updateFields['pid'] = $destPid;
3930  // Table is sorted by 'sortby'
3931  if ($sortRow) {
3932  $sortNumber = $this->getSortNumber($table, $uid, $destPid);
3933  $updateFields[$sortRow] = $sortNumber;
3934  }
3935  // Check for child records that have also to be moved
3936  $this->moveRecord_procFields($table, $uid, $destPid);
3937  // Create query for update:
3938  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$uid, $updateFields);
3939  // Check for the localizations of that element
3940  $this->moveL10nOverlayRecords($table, $uid, $destPid, $destPid);
3941  // Call post processing hooks:
3942  foreach ($hookObjectsArr as $hookObj) {
3943  if (method_exists($hookObj, 'moveRecord_firstElementPostProcess')) {
3944  $hookObj->moveRecord_firstElementPostProcess($table, $uid, $destPid, $moveRec, $updateFields, $this);
3945  }
3946  }
3947  // Logging...
3948  $newPropArr = $this->getRecordProperties($table, $uid);
3949  $oldpagePropArr = $this->getRecordProperties('pages', $propArr['pid']);
3950  $newpagePropArr = $this->getRecordProperties('pages', $destPid);
3951  if ($destPid != $propArr['pid']) {
3952  // Logged to old page
3953  $this->log($table, $uid, 4, $destPid, 0, 'Moved record \'%s\' (%s) to page \'%s\' (%s)', 2, array($propArr['header'], $table . ':' . $uid, $newpagePropArr['header'], $newPropArr['pid']), $propArr['pid']);
3954  // Logged to new page
3955  $this->log($table, $uid, 4, $destPid, 0, 'Moved record \'%s\' (%s) from page \'%s\' (%s)', 3, array($propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']), $destPid);
3956  } else {
3957  // Logged to new page
3958  $this->log($table, $uid, 4, $destPid, 0, 'Moved record \'%s\' (%s) on page \'%s\' (%s)', 4, array($propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']), $destPid);
3959  }
3960  // Clear cache after moving
3961  $this->registerRecordIdForPageCacheClearing($table, $uid);
3962  $this->fixUniqueInPid($table, $uid);
3963  // fixCopyAfterDuplFields
3964  if ($origDestPid < 0) {
3965  $this->fixCopyAfterDuplFields($table, $uid, abs($origDestPid), 1);
3966  }
3967  } else {
3968  $destPropArr = $this->getRecordProperties('pages', $destPid);
3969  $this->log($table, $uid, 4, 0, 1, 'Attempt to move page \'%s\' (%s) to inside of its own rootline (at page \'%s\' (%s))', 10, array($propArr['header'], $uid, $destPropArr['header'], $destPid), $propArr['pid']);
3970  }
3971  } else {
3972  // Put after another record
3973  // Table is being sorted
3974  if ($sortRow) {
3975  // Save the position to which the original record is requested to be moved
3976  $originalRecordDestinationPid = $destPid;
3977  $sortInfo = $this->getSortNumber($table, $uid, $destPid);
3978  // Setting the destPid to the new pid of the record.
3979  $destPid = $sortInfo['pid'];
3980  // If not an array, there was an error (which is already logged)
3981  if (is_array($sortInfo)) {
3982  if ($table != 'pages' || $this->destNotInsideSelf($destPid, $uid)) {
3983  // clear cache before moving
3985  // We now update the pid and sortnumber
3986  $updateFields['pid'] = $destPid;
3987  $updateFields[$sortRow] = $sortInfo['sortNumber'];
3988  // Check for child records that have also to be moved
3989  $this->moveRecord_procFields($table, $uid, $destPid);
3990  // Create query for update:
3991  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$uid, $updateFields);
3992  // Check for the localizations of that element
3993  $this->moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid);
3994  // Call post processing hooks:
3995  foreach ($hookObjectsArr as $hookObj) {
3996  if (method_exists($hookObj, 'moveRecord_afterAnotherElementPostProcess')) {
3997  $hookObj->moveRecord_afterAnotherElementPostProcess($table, $uid, $destPid, $origDestPid, $moveRec, $updateFields, $this);
3998  }
3999  }
4000  // Logging...
4001  $newPropArr = $this->getRecordProperties($table, $uid);
4002  $oldpagePropArr = $this->getRecordProperties('pages', $propArr['pid']);
4003  if ($destPid != $propArr['pid']) {
4004  $newpagePropArr = $this->getRecordProperties('pages', $destPid);
4005  // Logged to old page
4006  $this->log($table, $uid, 4, 0, 0, 'Moved record \'%s\' (%s) to page \'%s\' (%s)', 2, array($propArr['header'], $table . ':' . $uid, $newpagePropArr['header'], $newPropArr['pid']), $propArr['pid']);
4007  // Logged to old page
4008  $this->log($table, $uid, 4, 0, 0, 'Moved record \'%s\' (%s) from page \'%s\' (%s)', 3, array($propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']), $destPid);
4009  } else {
4010  // Logged to old page
4011  $this->log($table, $uid, 4, 0, 0, 'Moved record \'%s\' (%s) on page \'%s\' (%s)', 4, array($propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']), $destPid);
4012  }
4013  // Clear cache after moving
4014  $this->registerRecordIdForPageCacheClearing($table, $uid);
4015  // fixUniqueInPid
4016  $this->fixUniqueInPid($table, $uid);
4017  // fixCopyAfterDuplFields
4018  if ($origDestPid < 0) {
4019  $this->fixCopyAfterDuplFields($table, $uid, abs($origDestPid), 1);
4020  }
4021  } else {
4022  $destPropArr = $this->getRecordProperties('pages', $destPid);
4023  $this->log($table, $uid, 4, 0, 1, 'Attempt to move page \'%s\' (%s) to inside of its own rootline (at page \'%s\' (%s))', 10, array($propArr['header'], $uid, $destPropArr['header'], $destPid), $propArr['pid']);
4024  }
4025  }
4026  } else {
4027  $this->log($table, $uid, 4, 0, 1, 'Attempt to move record \'%s\' (%s) to after another record, although the table has no sorting row.', 13, array($propArr['header'], $table . ':' . $uid), $propArr['event_pid']);
4028  }
4029  }
4030  }
4031 
4042  public function moveRecord_procFields($table, $uid, $destPid) {
4043  $conf = $GLOBALS['TCA'][$table]['columns'];
4044  $row = BackendUtility::getRecordWSOL($table, $uid);
4045  if (is_array($row)) {
4046  foreach ($row as $field => $value) {
4047  $this->moveRecord_procBasedOnFieldType($table, $uid, $destPid, $field, $value, $conf[$field]['config']);
4048  }
4049  }
4050  }
4051 
4064  public function moveRecord_procBasedOnFieldType($table, $uid, $destPid, $field, $value, $conf) {
4065  $moveTable = '';
4066  $moveIds = array();
4067  if ($conf['type'] == 'inline') {
4068  $foreign_table = $conf['foreign_table'];
4069  $moveChildrenWithParent = !isset($conf['behaviour']['disableMovingChildrenWithParent']) || !$conf['behaviour']['disableMovingChildrenWithParent'];
4070  if ($foreign_table && $moveChildrenWithParent) {
4071  $inlineType = $this->getInlineFieldType($conf);
4072  if ($inlineType == 'list' || $inlineType == 'field') {
4073  $moveTable = $foreign_table;
4074  if ($table == 'pages') {
4075  // If the inline elements are related to a page record,
4076  // make sure they reside at that page and not at its parent
4077  $destPid = $uid;
4078  }
4079  $dbAnalysis = $this->createRelationHandlerInstance();
4080  $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
4081  }
4082  }
4083  }
4084  // Move the records
4085  if (isset($dbAnalysis)) {
4086  // Moving records to a positive destination will insert each
4087  // record at the beginning, thus the order is reversed here:
4088  foreach (array_reverse($dbAnalysis->itemArray) as $v) {
4089  $this->moveRecord($v['table'], $v['id'], $destPid);
4090  }
4091  }
4092  }
4093 
4104  public function moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid) {
4105  // There's no need to perform this for page-records or not localizeable tables
4106  if (!BackendUtility::isTableLocalizable($table) || !empty($GLOBALS['TCA'][$table]['ctrl']['transForeignTable']) || !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable'])) {
4107  return;
4108  }
4109  $where = '';
4110  if (isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
4111  $where = ' AND t3ver_oid=0';
4112  }
4113  $l10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], $uid, $where);
4114  if (is_array($l10nRecords)) {
4115  // If $$originalRecordDestinationPid < 0, then it is the uid of the original language record we are inserting after
4116  if ($originalRecordDestinationPid < 0) {
4117  $localizedDestPids = array();
4118  // Get the localized records of the record we are inserting after
4119  $destL10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], abs($originalRecordDestinationPid), $where);
4120  // Index the localized record uids by language
4121  if (is_array($destL10nRecords)) {
4122  foreach ($destL10nRecords as $record) {
4123  $localizedDestPids[$record[$GLOBALS['TCA'][$table]['ctrl']['languageField']]] = -$record['uid'];
4124  }
4125  }
4126  }
4127  // Move the localized records after the corresponding localizations of the destination record
4128  foreach ($l10nRecords as $record) {
4129  $localizedDestPid = (int)$localizedDestPids[$record[$GLOBALS['TCA'][$table]['ctrl']['languageField']]];
4130  if ($localizedDestPid < 0) {
4131  $this->moveRecord($table, $record['uid'], $localizedDestPid);
4132  } else {
4133  $this->moveRecord($table, $record['uid'], $destPid);
4134  }
4135  }
4136  }
4137  }
4138 
4149  public function localize($table, $uid, $language) {
4150  $newId = FALSE;
4151  $uid = (int)$uid;
4152  if ($GLOBALS['TCA'][$table] && $uid && $this->isNestedElementCallRegistered($table, $uid, 'localize') === FALSE) {
4153  $this->registerNestedElementCall($table, $uid, 'localize');
4154  if ($GLOBALS['TCA'][$table]['ctrl']['languageField'] && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] && !$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable'] || $table === 'pages') {
4155  if ($langRec = BackendUtility::getRecord('sys_language', (int)$language, 'uid,title')) {
4156  if ($this->doesRecordExist($table, $uid, 'show')) {
4157  // Getting workspace overlay if possible - this will localize versions in workspace if any
4158  $row = BackendUtility::getRecordWSOL($table, $uid);
4159  if (is_array($row)) {
4160  if ($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] <= 0 || $table === 'pages') {
4161  if ($row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] == 0 || $table === 'pages') {
4162  if ($table === 'pages') {
4163  $pass = $GLOBALS['TCA'][$table]['ctrl']['transForeignTable'] === 'pages_language_overlay' && !BackendUtility::getRecordsByField('pages_language_overlay', 'pid', $uid, (' AND ' . $GLOBALS['TCA']['pages_language_overlay']['ctrl']['languageField'] . '=' . (int)$langRec['uid']));
4164  $Ttable = 'pages_language_overlay';
4165  } else {
4166  $pass = !BackendUtility::getRecordLocalization($table, $uid, $langRec['uid'], ('AND pid=' . (int)$row['pid']));
4167  $Ttable = $table;
4168  }
4169  if ($pass) {
4170  // Initialize:
4171  $overrideValues = array();
4172  $excludeFields = array();
4173  // Set override values:
4174  $overrideValues[$GLOBALS['TCA'][$Ttable]['ctrl']['languageField']] = $langRec['uid'];
4175  $overrideValues[$GLOBALS['TCA'][$Ttable]['ctrl']['transOrigPointerField']] = $uid;
4176  // Copy the type (if defined in both tables) from the original record so that translation has same type as original record
4177  if (isset($GLOBALS['TCA'][$table]['ctrl']['type']) && isset($GLOBALS['TCA'][$Ttable]['ctrl']['type'])) {
4178  $overrideValues[$GLOBALS['TCA'][$Ttable]['ctrl']['type']] = $row[$GLOBALS['TCA'][$table]['ctrl']['type']];
4179  }
4180  // Set exclude Fields:
4181  foreach ($GLOBALS['TCA'][$Ttable]['columns'] as $fN => $fCfg) {
4182  // Check if we are just prefixing:
4183  if ($fCfg['l10n_mode'] == 'prefixLangTitle') {
4184  if (($fCfg['config']['type'] == 'text' || $fCfg['config']['type'] == 'input') && strlen($row[$fN])) {
4185  list($tscPID) = BackendUtility::getTSCpid($table, $uid, '');
4186  $TSConfig = $this->getTCEMAIN_TSconfig($tscPID);
4187  if (isset($TSConfig['translateToMessage']) && !empty($TSConfig['translateToMessage'])) {
4188  $translateToMsg = $GLOBALS['LANG'] ? $GLOBALS['LANG']->sL($TSConfig['translateToMessage']) : $TSConfig['translateToMessage'];
4189  $translateToMsg = @sprintf($translateToMsg, $langRec['title']);
4190  }
4191  if (!strlen($translateToMsg)) {
4192  $translateToMsg = 'Translate to ' . $langRec['title'] . ':';
4193  }
4194  $overrideValues[$fN] = '[' . $translateToMsg . '] ' . $row[$fN];
4195  }
4196  } elseif (GeneralUtility::inList('exclude,noCopy,mergeIfNotBlank', $fCfg['l10n_mode']) && $fN != $GLOBALS['TCA'][$Ttable]['ctrl']['languageField'] && $fN != $GLOBALS['TCA'][$Ttable]['ctrl']['transOrigPointerField']) {
4197  // Otherwise, do not copy field (unless it is the language field or
4198  // pointer to the original language)
4199  $excludeFields[] = $fN;
4200  }
4201  }
4202  if ($Ttable === $table) {
4203  // Get the uid of record after which this localized record should be inserted
4204  $previousUid = $this->getPreviousLocalizedRecordUid($table, $uid, $row['pid'], $language);
4205  // Execute the copy:
4206  $newId = $this->copyRecord($table, $uid, -$previousUid, 1, $overrideValues, implode(',', $excludeFields), $language);
4207  $autoVersionNewId = $this->getAutoVersionId($table, $newId);
4208  if (is_null($autoVersionNewId) === FALSE) {
4209  $this->triggerRemapAction($table, $newId, array($this, 'placeholderShadowing'), array($table, $autoVersionNewId), TRUE);
4210  }
4211  } else {
4212  // Create new record:
4214  $copyTCE = $this->getLocalTCE();
4215  $copyTCE->start(array($Ttable => array('NEW' => $overrideValues)), '', $this->BE_USER);
4216  $copyTCE->process_datamap();
4217  // Getting the new UID as if it had been copied:
4218  $theNewSQLID = $copyTCE->substNEWwithIDs['NEW'];
4219  if ($theNewSQLID) {
4220  // If is by design that $Ttable is used and not $table! See "l10nmgr" extension. Could be debated, but this is what I chose for this "pseudo case"
4221  $this->copyMappingArray[$Ttable][$uid] = $theNewSQLID;
4222  $newId = $theNewSQLID;
4223  }
4224  }
4225  } else {
4226  $this->newlog('Localization failed; There already was a localization for this language of the record!', 1);
4227  }
4228  } else {
4229  $this->newlog('Localization failed; Source record contained a reference to an original default record (which is strange)!', 1);
4230  }
4231  } else {
4232  $this->newlog('Localization failed; Source record had another language than "Default" or "All" defined!', 1);
4233  }
4234  } else {
4235  $this->newlog('Attempt to localize record that did not exist!', 1);
4236  }
4237  } else {
4238  $this->newlog('Attempt to localize record without permission', 1);
4239  }
4240  } else {
4241  $this->newlog('Sys language UID "' . $language . '" not found valid!', 1);
4242  }
4243  } else {
4244  $this->newlog('Localization failed; "languageField" and "transOrigPointerField" must be defined for the table!', 1);
4245  }
4246  }
4247  return $newId;
4248  }
4249 
4258  protected function inlineLocalizeSynchronize($table, $id, $command) {
4259  // <field>, (localize | synchronize | <uid>):
4260  $parts = GeneralUtility::trimExplode(',', $command);
4261  $field = $parts[0];
4262  $type = $parts[1];
4263  if ($field && (GeneralUtility::inList('localize,synchronize', $type) || \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($type)) && isset($GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
4264  $config = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
4265  $foreignTable = $config['foreign_table'];
4266  $localizationMode = BackendUtility::getInlineLocalizationMode($table, $config);
4267  if ($localizationMode == 'select') {
4268  $parentRecord = BackendUtility::getRecordWSOL($table, $id);
4269  $language = (int)$parentRecord[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
4270  $transOrigPointer = (int)$parentRecord[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
4271  $transOrigTable = BackendUtility::getOriginalTranslationTable($table);
4272  $childTransOrigPointerField = $GLOBALS['TCA'][$foreignTable]['ctrl']['transOrigPointerField'];
4273 
4274  if ($parentRecord && is_array($parentRecord) && $language > 0 && $transOrigPointer) {
4275  $inlineSubType = $this->getInlineFieldType($config);
4276  $transOrigRecord = BackendUtility::getRecordWSOL($transOrigTable, $transOrigPointer);
4277 
4278  if ($inlineSubType !== FALSE) {
4279  $removeArray = array();
4280  $mmTable = $inlineSubType == 'mm' && isset($config['MM']) && $config['MM'] ? $config['MM'] : '';
4281  // Fetch children from original language parent:
4283  $dbAnalysisOriginal = $this->createRelationHandlerInstance();
4284  $dbAnalysisOriginal->start($transOrigRecord[$field], $foreignTable, $mmTable, $transOrigRecord['uid'], $transOrigTable, $config);
4285  $elementsOriginal = array();
4286  foreach ($dbAnalysisOriginal->itemArray as $item) {
4287  $elementsOriginal[$item['id']] = $item;
4288  }
4289  unset($dbAnalysisOriginal);
4290  // Fetch children from current localized parent:
4292  $dbAnalysisCurrent = $this->createRelationHandlerInstance();
4293  $dbAnalysisCurrent->start($parentRecord[$field], $foreignTable, $mmTable, $id, $table, $config);
4294  // Perform synchronization: Possibly removal of already localized records:
4295  if ($type == 'synchronize') {
4296  foreach ($dbAnalysisCurrent->itemArray as $index => $item) {
4297  $childRecord = BackendUtility::getRecordWSOL($item['table'], $item['id']);
4298  if (isset($childRecord[$childTransOrigPointerField]) && $childRecord[$childTransOrigPointerField] > 0) {
4299  $childTransOrigPointer = $childRecord[$childTransOrigPointerField];
4300  // If snychronization is requested, child record was translated once, but original record does not exist anymore, remove it:
4301  if (!isset($elementsOriginal[$childTransOrigPointer])) {
4302  unset($dbAnalysisCurrent->itemArray[$index]);
4303  $removeArray[$item['table']][$item['id']]['delete'] = 1;
4304  }
4305  }
4306  }
4307  }
4308  // Perform synchronization/localization: Possibly add unlocalized records for original language:
4309  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($type) && isset($elementsOriginal[$type])) {
4310  $item = $elementsOriginal[$type];
4311  $item['id'] = $this->localize($item['table'], $item['id'], $language);
4312  $item['id'] = $this->overlayAutoVersionId($item['table'], $item['id']);
4313  $dbAnalysisCurrent->itemArray[] = $item;
4314  } elseif (GeneralUtility::inList('localize,synchronize', $type)) {
4315  foreach ($elementsOriginal as $originalId => $item) {
4316  $item['id'] = $this->localize($item['table'], $item['id'], $language);
4317  $item['id'] = $this->overlayAutoVersionId($item['table'], $item['id']);
4318  $dbAnalysisCurrent->itemArray[] = $item;
4319  }
4320  }
4321  // Store the new values, we will set up the uids for the subtype later on (exception keep localization from original record):
4322  $value = implode(',', $dbAnalysisCurrent->getValueArray());
4323  $this->registerDBList[$table][$id][$field] = $value;
4324  // Remove child records (if synchronization requested it):
4325  if (is_array($removeArray) && count($removeArray)) {
4326  $tce = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
4327  $tce->stripslashes_values = FALSE;
4328  $tce->enableLogging = $this->enableLogging;
4329  $tce->start(array(), $removeArray);
4330  $tce->process_cmdmap();
4331  unset($tce);
4332  }
4333  // Handle, reorder and store relations:
4334  if ($inlineSubType == 'list') {
4335  $updateFields = array($field => $value);
4336  } elseif ($inlineSubType == 'field') {
4337  $dbAnalysisCurrent->writeForeignField($config, $id);
4338  $updateFields = array($field => $dbAnalysisCurrent->countItems(FALSE));
4339  } elseif ($inlineSubType == 'mm') {
4340  $dbAnalysisCurrent->writeMM($config['MM'], $id);
4341  $updateFields = array($field => $dbAnalysisCurrent->countItems(FALSE));
4342  }
4343  // Update field referencing to child records of localized parent record:
4344  if (is_array($updateFields) && count($updateFields)) {
4345  $this->updateDB($table, $id, $updateFields);
4346  }
4347  }
4348  }
4349  }
4350  }
4351  }
4352 
4353  /*********************************************
4354  *
4355  * Cmd: Deleting
4356  *
4357  ********************************************/
4366  public function deleteAction($table, $id) {
4367  $recordToDelete = BackendUtility::getRecord($table, $id);
4368  // Record asked to be deleted was found:
4369  if (is_array($recordToDelete)) {
4370  $recordWasDeleted = FALSE;
4371  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'])) {
4372  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'] as $classRef) {
4373  $hookObj = GeneralUtility::getUserObj($classRef);
4374  if (method_exists($hookObj, 'processCmdmap_deleteAction')) {
4375  $hookObj->processCmdmap_deleteAction($table, $id, $recordToDelete, $recordWasDeleted, $this);
4376  }
4377  }
4378  }
4379  // Delete the record if a hook hasn't deleted it yet
4380  if (!$recordWasDeleted) {
4381  $this->deleteEl($table, $id);
4382  }
4383  }
4384  }
4385 
4396  public function deleteEl($table, $uid, $noRecordCheck = FALSE, $forceHardDelete = FALSE) {
4397  if ($table == 'pages') {
4398  $this->deletePages($uid, $noRecordCheck, $forceHardDelete);
4399  } else {
4400  $this->deleteVersionsForRecord($table, $uid, $forceHardDelete);
4401  $this->deleteRecord($table, $uid, $noRecordCheck, $forceHardDelete);
4402  }
4403  }
4404 
4414  public function deleteVersionsForRecord($table, $uid, $forceHardDelete) {
4415  $versions = BackendUtility::selectVersionsOfRecord($table, $uid, 'uid,pid,t3ver_wsid,t3ver_state', $this->BE_USER->workspace ?: NULL);
4416  if (is_array($versions)) {
4417  foreach ($versions as $verRec) {
4418  if (!$verRec['_CURRENT_VERSION']) {
4419  if ($table == 'pages') {
4420  $this->deletePages($verRec['uid'], TRUE, $forceHardDelete);
4421  } else {
4422  $this->deleteRecord($table, $verRec['uid'], TRUE, $forceHardDelete);
4423  }
4424 
4425  // Delete move-placeholder
4426  $versionState = VersionState::cast($verRec['t3ver_state']);
4427  if ($versionState->equals(VersionState::MOVE_POINTER)) {
4428  $versionMovePlaceholder = BackendUtility::getMovePlaceholder($table, $uid, 'uid', $verRec['t3ver_wsid']);
4429  if (!empty($versionMovePlaceholder)) {
4430  $this->deleteEl($table, $versionMovePlaceholder['uid'], TRUE, $forceHardDelete);
4431  }
4432  }
4433  }
4434  }
4435  }
4436  }
4437 
4446  public function undeleteRecord($table, $uid) {
4447  if ($this->isRecordUndeletable($table, $uid)) {
4448  $this->deleteRecord($table, $uid, TRUE, FALSE, TRUE);
4449  }
4450  }
4451 
4466  public function deleteRecord($table, $uid, $noRecordCheck = FALSE, $forceHardDelete = FALSE, $undeleteRecord = FALSE) {
4467  // Checking if there is anything else disallowing deleting the record by checking if editing is allowed
4468  $deletedRecord = ($forceHardDelete || $undeleteRecord);
4469  $mayEditAccess = $this->BE_USER->recordEditAccessInternals($table, $uid, FALSE, $deletedRecord, TRUE);
4470  $uid = (int)$uid;
4471  if ($GLOBALS['TCA'][$table] && $uid) {
4472  if ($mayEditAccess) {
4473  if ($noRecordCheck || $this->doesRecordExist($table, $uid, 'delete')) {
4474  // Clear cache before deleting the record, else the correct page cannot be identified by clear_cache
4475  list($parentUid) = BackendUtility::getTSCpid($table, $uid, '');
4476  $this->registerRecordIdForPageCacheClearing($table, $uid, $parentUid);
4477  $propArr = $this->getRecordProperties($table, $uid);
4478  $pagePropArr = $this->getRecordProperties('pages', $propArr['pid']);
4479  $deleteRow = $GLOBALS['TCA'][$table]['ctrl']['delete'];
4480  if ($deleteRow && !$forceHardDelete) {
4481  $value = $undeleteRecord ? 0 : 1;
4482  $updateFields = array(
4483  $deleteRow => $value
4484  );
4485  if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
4486  $updateFields[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
4487  }
4488  // If the table is sorted, then the sorting number is set very high
4489  if ($GLOBALS['TCA'][$table]['ctrl']['sortby'] && !$undeleteRecord) {
4490  $updateFields[$GLOBALS['TCA'][$table]['ctrl']['sortby']] = 1000000000;
4491  }
4492  // before (un-)deleting this record, check for child records or references
4493  $this->deleteRecord_procFields($table, $uid, $undeleteRecord);
4494  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$uid, $updateFields);
4495  // Delete all l10n records aswell, impossible during undelete because it might bring too many records back to life
4496  if (!$undeleteRecord) {
4497  $this->deleteL10nOverlayRecords($table, $uid);
4498  }
4499  } else {
4500  // Fetches all fields with flexforms and look for files to delete:
4501  foreach ($GLOBALS['TCA'][$table]['columns'] as $fieldName => $cfg) {
4502  $conf = $cfg['config'];
4503  switch ($conf['type']) {
4504  case 'flex':
4505  $flexObj = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Configuration\\FlexForm\\FlexFormTools');
4506  $flexObj->traverseFlexFormXMLData($table, $fieldName, BackendUtility::getRecordRaw($table, 'uid=' . (int)$uid), $this, 'deleteRecord_flexFormCallBack');
4507  break;
4508  }
4509  }
4510  // Fetches all fields that holds references to files
4511  $fileFieldArr = $this->extFileFields($table);
4512  if (count($fileFieldArr)) {
4513  $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery(implode(',', $fileFieldArr), $table, 'uid=' . (int)$uid);
4514  if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
4515  $fArray = $fileFieldArr;
4516  // MISSING: Support for MM file relations!
4517  foreach ($fArray as $theField) {
4518  // This deletes files that belonged to this record.
4519  $this->extFileFunctions($table, $theField, $row[$theField], 'deleteAll');
4520  }
4521  } else {
4522  $this->log($table, $uid, 3, 0, 100, 'Delete: Zero rows in result when trying to read filenames from record which should be deleted');
4523  }
4524  $GLOBALS['TYPO3_DB']->sql_free_result($mres);
4525  }
4526  // Delete the hard way...:
4527  $GLOBALS['TYPO3_DB']->exec_DELETEquery($table, 'uid=' . (int)$uid);
4528  $this->deleteL10nOverlayRecords($table, $uid);
4529  }
4530  // 1 means insert, 3 means delete
4531  $state = $undeleteRecord ? 1 : 3;
4532  if (!$GLOBALS['TYPO3_DB']->sql_error()) {
4533  if ($forceHardDelete) {
4534  $message = 'Record \'%s\' (%s) was deleted unrecoverable from page \'%s\' (%s)';
4535  } else {
4536  $message = $state == 1 ? 'Record \'%s\' (%s) was restored on page \'%s\' (%s)' : 'Record \'%s\' (%s) was deleted from page \'%s\' (%s)';
4537  }
4538  $this->log($table, $uid, $state, 0, 0, $message, 0, array(
4539  $propArr['header'],
4540  $table . ':' . $uid,
4541  $pagePropArr['header'],
4542  $propArr['pid']
4543  ), $propArr['event_pid']);
4544  } else {
4545  $this->log($table, $uid, $state, 0, 100, $GLOBALS['TYPO3_DB']->sql_error());
4546  }
4547  // Update reference index:
4548  $this->updateRefIndex($table, $uid);
4549  // If there are entries in the updateRefIndexStack
4550  if (is_array($this->updateRefIndexStack[$table]) && is_array($this->updateRefIndexStack[$table][$uid])) {
4551  while ($args = array_pop($this->updateRefIndexStack[$table][$uid])) {
4552  // $args[0]: table, $args[1]: uid
4553  $this->updateRefIndex($args[0], $args[1]);
4554  }
4555  unset($this->updateRefIndexStack[$table][$uid]);
4556  }
4557  } else {
4558  $this->log($table, $uid, 3, 0, 1, 'Attempt to delete record without delete-permissions');
4559  }
4560  } else {
4561  $this->log($table, $uid, 3, 0, 1, 'Attempt to delete record without delete-permissions. [' . $this->BE_USER->errorMsg . ']');
4562  }
4563  }
4564  }
4565 
4577  public function deleteRecord_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, $pObj) {
4578  // Use reference index object to find files in fields:
4579  $refIndexObj = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\ReferenceIndex');
4580  $files = $refIndexObj->getRelations_procFiles($dataValue, $dsArr['TCEforms']['config'], $PA['uid']);
4581  // Traverse files and delete them if the field is a regular file field (and not a file_reference field)
4582  if (is_array($files) && $dsArr['TCEforms']['config']['internal_type'] === 'file') {
4583  foreach ($files as $dat) {
4584  if (@is_file($dat['ID_absFile'])) {
4585  $file = $this->getResourceFactory()->retrieveFileOrFolderObject($dat['ID_absFile']);
4586  $file->delete();
4587  } else {
4588  $this->log('', 0, 3, 0, 100, 'Delete: Referenced file \'' . $dat['ID_absFile'] . '\' that was supposed to be deleted together with its record which didn\'t exist');
4589  }
4590  }
4591  }
4592  }
4593 
4603  public function deletePages($uid, $force = FALSE, $forceHardDelete = FALSE) {
4604  $uid = (int)$uid;
4605  if ($uid === 0) {
4606  $this->newlog2('Deleting all pages starting from the root-page is disabled.', 'pages', 0, 0, 2);
4607  return;
4608  }
4609  // Getting list of pages to delete:
4610  if ($force) {
4611  // Returns the branch WITHOUT permission checks (0 secures that)
4612  $brExist = $this->doesBranchExist('', $uid, 0, 1);
4613  $res = GeneralUtility::trimExplode(',', $brExist . $uid, TRUE);
4614  } else {
4615  $res = $this->canDeletePage($uid);
4616  }
4617  // Perform deletion if not error:
4618  if (is_array($res)) {
4619  foreach ($res as $deleteId) {
4620  $this->deleteSpecificPage($deleteId, $forceHardDelete);
4621  }
4622  } else {
4623  $this->newlog($res, 1);
4624  }
4625  }
4626 
4637  public function deleteSpecificPage($uid, $forceHardDelete = FALSE) {
4638  $uid = (int)$uid;
4639  if ($uid) {
4640  foreach ($GLOBALS['TCA'] as $table => $_) {
4641  if ($table != 'pages') {
4642  $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'pid=' . (int)$uid . $this->deleteClause($table));
4643  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
4644  $this->copyMovedRecordToNewLocation($table, $row['uid']);
4645  $this->deleteVersionsForRecord($table, $row['uid'], $forceHardDelete);
4646  $this->deleteRecord($table, $row['uid'], TRUE, $forceHardDelete);
4647  }
4648  $GLOBALS['TYPO3_DB']->sql_free_result($mres);
4649  }
4650  }
4651  $this->copyMovedRecordToNewLocation('pages', $uid);
4652  $this->deleteVersionsForRecord('pages', $uid, $forceHardDelete);
4653  $this->deleteRecord('pages', $uid, TRUE, $forceHardDelete);
4654  }
4655  }
4656 
4670  protected function copyMovedRecordToNewLocation($table, $uid) {
4671  if ($this->BE_USER->workspace > 0) {
4672  $originalRecord = BackendUtility::getRecord($table, $uid);
4673  $movePlaceholder = BackendUtility::getMovePlaceholder($table, $uid);
4674  // Check whether target page to copied to is different to current page
4675  // Cloning on the same page is superfluous and does not help at all
4676  if (!empty($originalRecord) && !empty($movePlaceholder) && (int)$originalRecord['pid'] !== (int)$movePlaceholder['pid']) {
4677  // If move placeholder exists, copy to new location
4678  // This will create a New placeholder on the new location
4679  // and a version for this new placeholder
4680  $command = array(
4681  $table => array(
4682  $uid => array(
4683  'copy' => '-' . $movePlaceholder['uid']
4684  )
4685  )
4686  );
4687  $dataHandler = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
4688  $dataHandler->stripslashes_values = FALSE;
4689  $dataHandler->enableLogging = $this->enableLogging;
4690  $dataHandler->neverHideAtCopy = TRUE;
4691  $dataHandler->start(array(), $command);
4692  $dataHandler->process_cmdmap();
4693  unset($dataHandler);
4694 
4695  // Delete move placeholder
4696  $this->deleteRecord($table, $movePlaceholder['uid'], TRUE, TRUE);
4697  }
4698  }
4699  }
4700 
4708  public function canDeletePage($uid) {
4709  // If we may at all delete this page
4710  if ($this->doesRecordExist('pages', $uid, 'delete')) {
4711  if ($this->deleteTree) {
4712  // Returns the branch
4713  $brExist = $this->doesBranchExist('', $uid, $this->pMap['delete'], 1);
4714  // Checks if we had permissions
4715  if ($brExist != -1) {
4716  if ($this->noRecordsFromUnallowedTables($brExist . $uid)) {
4717  $pagesInBranch = GeneralUtility::trimExplode(',', $brExist . $uid, TRUE);
4718  foreach ($pagesInBranch as $pageInBranch) {
4719  if (!$this->BE_USER->recordEditAccessInternals('pages', $pageInBranch, FALSE, FALSE, TRUE)) {
4720  return 'Attempt to delete page which has prohibited localizations.';
4721  }
4722  }
4723  return $pagesInBranch;
4724  } else {
4725  return 'Attempt to delete records from disallowed tables';
4726  }
4727  } else {
4728  return 'Attempt to delete pages in branch without permissions';
4729  }
4730  } else {
4731  // returns the branch
4732  $brExist = $this->doesBranchExist('', $uid, $this->pMap['delete'], 1);
4733  // Checks if branch exists
4734  if ($brExist == '') {
4735  if ($this->noRecordsFromUnallowedTables($uid)) {
4736  if ($this->BE_USER->recordEditAccessInternals('pages', $uid, FALSE, FALSE, TRUE)) {
4737  return array($uid);
4738  } else {
4739  return 'Attempt to delete page which has prohibited localizations.';
4740  }
4741  } else {
4742  return 'Attempt to delete records from disallowed tables';
4743  }
4744  } else {
4745  return 'Attempt to delete page which has subpages';
4746  }
4747  }
4748  } else {
4749  return 'Attempt to delete page without permissions';
4750  }
4751  }
4752 
4761  public function cannotDeleteRecord($table, $id) {
4762  if ($table === 'pages') {
4763  $res = $this->canDeletePage($id);
4764  return is_array($res) ? FALSE : $res;
4765  } else {
4766  return $this->doesRecordExist($table, $id, 'delete') ? FALSE : 'No permission to delete record';
4767  }
4768  }
4769 
4777  public function isRecordUndeletable($table, $uid) {
4778  $result = FALSE;
4779  $record = BackendUtility::getRecord($table, $uid, 'pid', '', FALSE);
4780  if ($record['pid']) {
4781  $page = BackendUtility::getRecord('pages', $record['pid'], 'deleted, title, uid', '', FALSE);
4782  // The page containing the record is not deleted, thus the record can be undeleted:
4783  if (!$page['deleted']) {
4784  $result = TRUE;
4785  } else {
4786  $this->log($table, $uid, 'isRecordUndeletable', '', 1, 'Record cannot be undeleted since the page containing it is deleted! Undelete page "' . $page['title'] . ' (UID: ' . $page['uid'] . ')" first');
4787  }
4788  } else {
4789  // The page containing the record is on rootlevel, so there is no parent record to check, and the record can be undeleted:
4790  $result = TRUE;
4791  }
4792  return $result;
4793  }
4794 
4806  public function deleteRecord_procFields($table, $uid, $undeleteRecord = FALSE) {
4807  $conf = $GLOBALS['TCA'][$table]['columns'];
4808  $row = BackendUtility::getRecord($table, $uid, '*', '', FALSE);
4809  if (empty($row)) {
4810  return;
4811  }
4812  foreach ($row as $field => $value) {
4813  $this->deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf[$field]['config'], $undeleteRecord);
4814  }
4815  }
4816 
4831  public function deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf, $undeleteRecord = FALSE) {
4832  if ($conf['type'] == 'inline') {
4833  $foreign_table = $conf['foreign_table'];
4834  if ($foreign_table) {
4835  $inlineType = $this->getInlineFieldType($conf);
4836  if ($inlineType == 'list' || $inlineType == 'field') {
4838  $dbAnalysis = $this->createRelationHandlerInstance();
4839  $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
4840  $dbAnalysis->undeleteRecord = TRUE;
4841 
4842  $enableCascadingDelete = TRUE;
4843  // non type save comparison is intended!
4844  if (isset($conf['behaviour']['enableCascadingDelete']) && $conf['behaviour']['enableCascadingDelete'] == FALSE) {
4845  $enableCascadingDelete = FALSE;
4846  }
4847 
4848  // Walk through the items and remove them
4849  foreach ($dbAnalysis->itemArray as $v) {
4850  if (!$undeleteRecord) {
4851  if ($enableCascadingDelete) {
4852  $this->deleteAction($v['table'], $v['id']);
4853  }
4854  } else {
4855  $this->undeleteRecord($v['table'], $v['id']);
4856  }
4857  }
4858  }
4859  }
4860  } elseif ($this->isReferenceField($conf)) {
4861  $allowedTables = $conf['type'] == 'group' ? $conf['allowed'] : $conf['foreign_table'] . ',' . $conf['neg_foreign_table'];
4862  $prependName = $conf['type'] == 'group' ? $conf['prepend_tname'] : $conf['neg_foreign_table'];
4863  $dbAnalysis = $this->createRelationHandlerInstance();
4864  $dbAnalysis->start($value, $allowedTables, $conf['MM'], $uid, $table, $conf);
4865  foreach ($dbAnalysis->itemArray as $v) {
4866  $this->updateRefIndexStack[$table][$uid][] = array($v['table'], $v['id']);
4867  }
4868  }
4869  }
4870 
4879  public function deleteL10nOverlayRecords($table, $uid) {
4880  // Check whether table can be localized or has a different table defined to store localizations:
4881  if (!BackendUtility::isTableLocalizable($table) || !empty($GLOBALS['TCA'][$table]['ctrl']['transForeignTable']) || !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable'])) {
4882  return;
4883  }
4884  $where = '';
4885  if (isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
4886  $where = ' AND t3ver_oid=0';
4887  }
4888  $l10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], $uid, $where);
4889  if (is_array($l10nRecords)) {
4890  foreach ($l10nRecords as $record) {
4891  // Ignore workspace delete placeholders. Those records have been marked for
4892  // deletion before - deleting them again in a workspace would revert that state.
4893  if ($this->BE_USER->workspace > 0 && BackendUtility::isTableWorkspaceEnabled($table)) {
4894  BackendUtility::workspaceOL($table, $record);
4895  if (VersionState::cast($record['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
4896  continue;
4897  }
4898  }
4899  $this->deleteAction($table, (int)$record['t3ver_oid'] > 0 ? (int)$record['t3ver_oid'] : (int)$record['uid']);
4900  }
4901  }
4902  }
4903 
4904  /*********************************************
4905  *
4906  * Cmd: Versioning
4907  *
4908  ********************************************/
4921  public function versionizeRecord($table, $id, $label, $delete = FALSE) {
4922  $id = (int)$id;
4923  // Stop any actions if the record is marked to be deleted:
4924  // (this can occur if IRRE elements are versionized and child elements are removed)
4925  if ($this->isElementToBeDeleted($table, $id)) {
4926  return NULL;
4927  }
4928  if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $id > 0) {
4929  if ($this->doesRecordExist($table, $id, 'show')) {
4930  // Select main record:
4931  $row = $this->recordInfo($table, $id, 'pid,t3ver_id,t3ver_state');
4932  if (is_array($row)) {
4933  // Record must be online record
4934  if ($row['pid'] >= 0) {
4935  // Record must not be placeholder for moving.
4936  if (!VersionState::cast($row['t3ver_state'])->equals(VersionState::MOVE_PLACEHOLDER)) {
4937  if (!$delete || !$this->cannotDeleteRecord($table, $id)) {
4938  // Look for next version number:
4939  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('t3ver_id', $table, '((pid=-1 && t3ver_oid=' . $id . ') OR uid=' . $id . ')' . $this->deleteClause($table), '', 't3ver_id DESC', '1');
4940  list($highestVerNumber) = $GLOBALS['TYPO3_DB']->sql_fetch_row($res);
4941  $GLOBALS['TYPO3_DB']->sql_free_result($res);
4942  // Look for version number of the current:
4943  $subVer = $row['t3ver_id'] . '.' . ($highestVerNumber + 1);
4944  // Set up the values to override when making a raw-copy:
4945  $overrideArray = array(
4946  't3ver_id' => $highestVerNumber + 1,
4947  't3ver_oid' => $id,
4948  't3ver_label' => $label ?: $subVer . ' / ' . date('d-m-Y H:m:s'),
4949  't3ver_wsid' => $this->BE_USER->workspace,
4950  't3ver_state' => (string)($delete ? new VersionState(VersionState::DELETE_PLACEHOLDER) : new VersionState(VersionState::DEFAULT_STATE)),
4951  't3ver_count' => 0,
4952  't3ver_stage' => 0,
4953  't3ver_tstamp' => 0
4954  );
4955  if ($GLOBALS['TCA'][$table]['ctrl']['editlock']) {
4956  $overrideArray[$GLOBALS['TCA'][$table]['ctrl']['editlock']] = 0;
4957  }
4958  // Checking if the record already has a version in the current workspace of the backend user
4959  if ($this->BE_USER->workspace !== 0) {
4960  // Look for version already in workspace:
4961  $versionRecord = BackendUtility::getWorkspaceVersionOfRecord($this->BE_USER->workspace, $table, $id, 'uid');
4962  }
4963  // Create new version of the record and return the new uid
4964  if (empty($versionRecord['uid'])) {
4965  // Create raw-copy and return result:
4966  // The information of the label to be used for the workspace record
4967  // as well as the information whether the record shall be removed
4968  // must be forwarded (creating remove placeholders on a workspace are
4969  // done by copying the record and override several fields).
4970  $workspaceOptions = array(
4971  'delete' => $delete,
4972  'label' => $label,
4973  );
4974  return $this->copyRecord_raw($table, $id, -1, $overrideArray, $workspaceOptions);
4975  // Reuse the existing record and return its uid
4976  // (prior to TYPO3 CMS 6.2, an error was thrown here, which
4977  // did not make much sense since the information is available)
4978  } else {
4979  return $versionRecord['uid'];
4980  }
4981  } else {
4982  $this->newlog('Record cannot be deleted: ' . $this->cannotDeleteRecord($table, $id), 1);
4983  }
4984  } else {
4985  $this->newlog('Record cannot be versioned because it is a placeholder for a moving operation', 1);
4986  }
4987  } else {
4988  $this->newlog('Record "' . $table . ':' . $id . '" you wanted to versionize was already a version in archive (pid=-1)!', 1);
4989  }
4990  } else {
4991  $this->newlog('Record "' . $table . ':' . $id . '" you wanted to versionize did not exist!', 1);
4992  }
4993  } else {
4994  $this->newlog('You didnt have correct permissions to make a new version (copy) of this record "' . $table . '" / ' . $id, 1);
4995  }
4996  } else {
4997  $this->newlog('Versioning is not supported for this table "' . $table . '" / ' . $id, 1);
4998  }
4999  }
5000 
5011  public function version_remapMMForVersionSwap($table, $id, $swapWith) {
5012  // Actually, selecting the records fully is only need if flexforms are found inside... This could be optimized ...
5013  $currentRec = BackendUtility::getRecord($table, $id);
5014  $swapRec = BackendUtility::getRecord($table, $swapWith);
5015  $this->version_remapMMForVersionSwap_reg = array();
5016  foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $fConf) {
5017  $conf = $fConf['config'];
5018  if ($this->isReferenceField($conf)) {
5019  $allowedTables = $conf['type'] == 'group' ? $conf['allowed'] : $conf['foreign_table'] . ',' . $conf['neg_foreign_table'];
5020  $prependName = $conf['type'] == 'group' ? $conf['prepend_tname'] : $conf['neg_foreign_table'];
5021  if ($conf['MM']) {
5023  $dbAnalysis = $this->createRelationHandlerInstance();
5024  $dbAnalysis->start('', $allowedTables, $conf['MM'], $id, $table, $conf);
5025  if (count($dbAnalysis->getValueArray($prependName))) {
5026  $this->version_remapMMForVersionSwap_reg[$id][$field] = array($dbAnalysis, $conf['MM'], $prependName);
5027  }
5029  $dbAnalysis = $this->createRelationHandlerInstance();
5030  $dbAnalysis->start('', $allowedTables, $conf['MM'], $swapWith, $table, $conf);
5031  if (count($dbAnalysis->getValueArray($prependName))) {
5032  $this->version_remapMMForVersionSwap_reg[$swapWith][$field] = array($dbAnalysis, $conf['MM'], $prependName);
5033  }
5034  }
5035  } elseif ($conf['type'] == 'flex') {
5036  // Current record
5037  $dataStructArray = BackendUtility::getFlexFormDS($conf, $currentRec, $table, $field);
5038  $currentValueArray = GeneralUtility::xml2array($currentRec[$field]);
5039  if (is_array($currentValueArray)) {
5040  $this->checkValue_flex_procInData($currentValueArray['data'], array(), array(), $dataStructArray, array($table, $id, $field), 'version_remapMMForVersionSwap_flexFormCallBack');
5041  }
5042  // Swap record
5043  $dataStructArray = BackendUtility::getFlexFormDS($conf, $swapRec, $table, $field);
5044  $currentValueArray = GeneralUtility::xml2array($swapRec[$field]);
5045  if (is_array($currentValueArray)) {
5046  $this->checkValue_flex_procInData($currentValueArray['data'], array(), array(), $dataStructArray, array($table, $swapWith, $field), 'version_remapMMForVersionSwap_flexFormCallBack');
5047  }
5048  }
5049  }
5050  // Execute:
5051  $this->version_remapMMForVersionSwap_execSwap($table, $id, $swapWith);
5052  }
5053 
5067  public function version_remapMMForVersionSwap_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path) {
5068  // Extract parameters:
5069  list($table, $uid, $field) = $pParams;
5070  if ($this->isReferenceField($dsConf)) {
5071  $allowedTables = $dsConf['type'] == 'group' ? $dsConf['allowed'] : $dsConf['foreign_table'] . ',' . $dsConf['neg_foreign_table'];
5072  $prependName = $dsConf['type'] == 'group' ? $dsConf['prepend_tname'] : $dsConf['neg_foreign_table'];
5073  if ($dsConf['MM']) {
5075  $dbAnalysis = $this->createRelationHandlerInstance();
5076  $dbAnalysis->start('', $allowedTables, $dsConf['MM'], $uid, $table, $dsConf);
5077  $this->version_remapMMForVersionSwap_reg[$uid][$field . '/' . $path] = array($dbAnalysis, $dsConf['MM'], $prependName);
5078  }
5079  }
5080  }
5081 
5093  public function version_remapMMForVersionSwap_execSwap($table, $id, $swapWith) {
5094  if (is_array($this->version_remapMMForVersionSwap_reg[$id])) {
5095  foreach ($this->version_remapMMForVersionSwap_reg[$id] as $field => $str) {
5096  $str[0]->remapMM($str[1], $id, -$id, $str[2]);
5097  }
5098  }
5099  if (is_array($this->version_remapMMForVersionSwap_reg[$swapWith])) {
5100  foreach ($this->version_remapMMForVersionSwap_reg[$swapWith] as $field => $str) {
5101  $str[0]->remapMM($str[1], $swapWith, $id, $str[2]);
5102  }
5103  }
5104  if (is_array($this->version_remapMMForVersionSwap_reg[$id])) {
5105  foreach ($this->version_remapMMForVersionSwap_reg[$id] as $field => $str) {
5106  $str[0]->remapMM($str[1], -$id, $swapWith, $str[2]);
5107  }
5108  }
5109  }
5110 
5111  /*********************************************
5112  *
5113  * Cmd: Helper functions
5114  *
5115  ********************************************/
5116 
5124  protected function getLocalTCE($stripslashesValues = FALSE, $dontProcessTransformations = TRUE) {
5125  $copyTCE = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
5126  $copyTCE->stripslashes_values = $stripslashesValues;
5127  $copyTCE->enableLogging = $this->enableLogging;
5128  $copyTCE->copyTree = $this->copyTree;
5129  // Copy forth the cached TSconfig
5130  $copyTCE->cachedTSconfig = $this->cachedTSconfig;
5131  // Transformations should NOT be carried out during copy
5132  $copyTCE->dontProcessTransformations = $dontProcessTransformations;
5133  return $copyTCE;
5134  }
5135 
5142  public function remapListedDBRecords() {
5143  if (count($this->registerDBList)) {
5144  foreach ($this->registerDBList as $table => $records) {
5145  foreach ($records as $uid => $fields) {
5146  $newData = array();
5147  $theUidToUpdate = $this->copyMappingArray_merged[$table][$uid];
5148  $theUidToUpdate_saveTo = BackendUtility::wsMapId($table, $theUidToUpdate);
5149  foreach ($fields as $fieldName => $value) {
5150  $conf = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
5151  switch ($conf['type']) {
5152  case 'group':
5153 
5154  case 'select':
5155  $vArray = $this->remapListedDBRecords_procDBRefs($conf, $value, $theUidToUpdate, $table);
5156  if (is_array($vArray)) {
5157  $newData[$fieldName] = implode(',', $vArray);
5158  }
5159  break;
5160  case 'flex':
5161  if ($value == 'FlexForm_reference') {
5162  // This will fetch the new row for the element
5163  $origRecordRow = $this->recordInfo($table, $theUidToUpdate, '*');
5164  if (is_array($origRecordRow)) {
5165  BackendUtility::workspaceOL($table, $origRecordRow);
5166  // Get current data structure and value array:
5167  $dataStructArray = BackendUtility::getFlexFormDS($conf, $origRecordRow, $table, $fieldName);
5168  $currentValueArray = GeneralUtility::xml2array($origRecordRow[$fieldName]);
5169  // Do recursive processing of the XML data:
5170  $currentValueArray['data'] = $this->checkValue_flex_procInData($currentValueArray['data'], array(), array(), $dataStructArray, array($table, $theUidToUpdate, $fieldName), 'remapListedDBRecords_flexFormCallBack');
5171  // The return value should be compiled back into XML, ready to insert directly in the field (as we call updateDB() directly later):
5172  if (is_array($currentValueArray['data'])) {
5173  $newData[$fieldName] = $this->checkValue_flexArray2Xml($currentValueArray, TRUE);
5174  }
5175  }
5176  }
5177  break;
5178  case 'inline':
5179  $this->remapListedDBRecords_procInline($conf, $value, $uid, $table);
5180  break;
5181  default:
5182  debug('Field type should not appear here: ' . $conf['type']);
5183  }
5184  }
5185  // If any fields were changed, those fields are updated!
5186  if (count($newData)) {
5187  $this->updateDB($table, $theUidToUpdate_saveTo, $newData);
5188  }
5189  }
5190  }
5191  }
5192  }
5193 
5206  public function remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2) {
5207  // Extract parameters:
5208  list($table, $uid, $field) = $pParams;
5209  // If references are set for this field, set flag so they can be corrected later:
5210  if ($this->isReferenceField($dsConf) && strlen($dataValue)) {
5211  $vArray = $this->remapListedDBRecords_procDBRefs($dsConf, $dataValue, $uid, $table);
5212  if (is_array($vArray)) {
5213  $dataValue = implode(',', $vArray);
5214  }
5215  }
5216  // Return
5217  return array('value' => $dataValue);
5218  }
5219 
5231  public function remapListedDBRecords_procDBRefs($conf, $value, $MM_localUid, $table) {
5232  // Initialize variables
5233  // Will be set TRUE if an upgrade should be done...
5234  $set = FALSE;
5235  // Allowed tables for references.
5236  $allowedTables = $conf['type'] == 'group' ? $conf['allowed'] : $conf['foreign_table'] . ',' . $conf['neg_foreign_table'];
5237  // Table name to prepend the UID
5238  $prependName = $conf['type'] == 'group' ? $conf['prepend_tname'] : '';
5239  // Which tables that should possibly not be remapped
5240  $dontRemapTables = GeneralUtility::trimExplode(',', $conf['dontRemapTablesOnCopy'], TRUE);
5241  // Convert value to list of references:
5242  $dbAnalysis = $this->createRelationHandlerInstance();
5243  $dbAnalysis->registerNonTableValues = $conf['type'] == 'select' && $conf['allowNonIdValues'] ? 1 : 0;
5244  $dbAnalysis->start($value, $allowedTables, $conf['MM'], $MM_localUid, $table, $conf);
5245  // Traverse those references and map IDs:
5246  foreach ($dbAnalysis->itemArray as $k => $v) {
5247  $mapID = $this->copyMappingArray_merged[$v['table']][$v['id']];
5248  if ($mapID && !in_array($v['table'], $dontRemapTables)) {
5249  $dbAnalysis->itemArray[$k]['id'] = $mapID;
5250  $set = TRUE;
5251  }
5252  }
5253  if (!empty($conf['MM'])) {
5254  // Purge invalid items (live/version)
5255  $dbAnalysis->purgeItemArray();
5256  if ($dbAnalysis->isPurged()) {
5257  $set = TRUE;
5258  }
5259 
5260  // If record has been versioned/copied in this process, handle invalid relations of the live record
5261  $liveId = BackendUtility::getLiveVersionIdOfRecord($table, $MM_localUid);
5262  if (!empty($this->copyMappingArray_merged[$table])) {
5263  $originalId = array_search($MM_localUid, $this->copyMappingArray_merged[$table]);
5264  }
5265  if (!empty($liveId) && !empty($originalId) && (int)$liveId === (int)$originalId) {
5266  $liveRelations = $this->createRelationHandlerInstance();
5267  $liveRelations->setWorkspaceId(0);
5268  $liveRelations->start('', $allowedTables, $conf['MM'], $liveId, $table, $conf);
5269  // Purge invalid relations in the live workspace ("0")
5270  $liveRelations->purgeItemArray(0);
5271  if ($liveRelations->isPurged()) {
5272  $liveRelations->writeMM($conf['MM'], $liveId, $prependName);
5273  }
5274  }
5275  }
5276  // If a change has been done, set the new value(s)
5277  if ($set) {
5278  if ($conf['MM']) {
5279  $dbAnalysis->writeMM($conf['MM'], $MM_localUid, $prependName);
5280  } else {
5281  $vArray = $dbAnalysis->getValueArray($prependName);
5282  if ($conf['type'] == 'select') {
5283  $vArray = $dbAnalysis->convertPosNeg($vArray, $conf['foreign_table'], $conf['neg_foreign_table']);
5284  }
5285  return $vArray;
5286  }
5287  }
5288  }
5289 
5300  public function remapListedDBRecords_procInline($conf, $value, $uid, $table) {
5301  $theUidToUpdate = $this->copyMappingArray_merged[$table][$uid];
5302  if ($conf['foreign_table']) {
5303  $inlineType = $this->getInlineFieldType($conf);
5304  if ($inlineType == 'mm') {
5305  $this->remapListedDBRecords_procDBRefs($conf, $value, $theUidToUpdate, $table);
5306  } elseif ($inlineType !== FALSE) {
5308  $dbAnalysis = $this->createRelationHandlerInstance();
5309  $dbAnalysis->start($value, $conf['foreign_table'], '', 0, $table, $conf);
5310 
5311  // Keep original (live) item array and update values for specific versioned records
5312  $originalItemArray = $dbAnalysis->itemArray;
5313  foreach ($dbAnalysis->itemArray as &$item) {
5314  $versionedId = $this->getAutoVersionId($item['table'], $item['id']);
5315  if (!empty($versionedId)) {
5316  $item['id'] = $versionedId;
5317  }
5318  }
5319 
5320  // Update child records if using pointer fields ('foreign_field'):
5321  if ($inlineType == 'field') {
5322  $dbAnalysis->writeForeignField($conf, $uid, $theUidToUpdate);
5323  }
5324  // If the current field is set on a page record, update the pid of related child records:
5325  if ($table == 'pages') {
5326  $thePidToUpdate = $theUidToUpdate;
5327  } elseif (isset($this->registerDBPids[$table][$uid])) {
5328  $thePidToUpdate = $this->registerDBPids[$table][$uid];
5329  $thePidToUpdate = $this->copyMappingArray_merged['pages'][$thePidToUpdate];
5330  }
5331  // Update child records if change to pid is required (only if the current record is not on a workspace):
5332  if ($thePidToUpdate) {
5333  $updateValues = array('pid' => $thePidToUpdate);
5334  foreach ($originalItemArray as $v) {
5335  if ($v['id'] && $v['table'] && is_null(BackendUtility::getLiveVersionIdOfRecord($v['table'], $v['id']))) {
5336  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($v['table'], 'uid=' . (int)$v['id'], $updateValues);
5337  }
5338  }
5339  }
5340  }
5341  }
5342  }
5343 
5351  public function processRemapStack() {
5352  // Processes the remap stack:
5353  if (is_array($this->remapStack)) {
5354  $remapFlexForms = array();
5355 
5356  foreach ($this->remapStack as $remapAction) {
5357  // If no position index for the arguments was set, skip this remap action:
5358  if (!is_array($remapAction['pos'])) {
5359  continue;
5360  }
5361  // Load values from the argument array in remapAction:
5362  $field = $remapAction['field'];
5363  $id = $remapAction['args'][$remapAction['pos']['id']];
5364  $rawId = $id;
5365  $table = $remapAction['args'][$remapAction['pos']['table']];
5366  $valueArray = $remapAction['args'][$remapAction['pos']['valueArray']];
5367  $tcaFieldConf = $remapAction['args'][$remapAction['pos']['tcaFieldConf']];
5368  $additionalData = $remapAction['additionalData'];
5369  // The record is new and has one or more new ids (in case of versioning/workspaces):
5370  if (strpos($id, 'NEW') !== FALSE) {
5371  // Replace NEW...-ID with real uid:
5372  $id = $this->substNEWwithIDs[$id];
5373  // If the new parent record is on a non-live workspace or versionized, it has another new id:
5374  if (isset($this->autoVersionIdMap[$table][$id])) {
5375  $id = $this->autoVersionIdMap[$table][$id];
5376  }
5377  $remapAction['args'][$remapAction['pos']['id']] = $id;
5378  }
5379  // Replace relations to NEW...-IDs in field value (uids of child records):
5380  if (is_array($valueArray)) {
5381  $foreign_table = $tcaFieldConf['foreign_table'];
5382  foreach ($valueArray as $key => $value) {
5383  if (strpos($value, 'NEW') !== FALSE) {
5384  $value = $this->substNEWwithIDs[$value];
5385  // The record is new, but was also auto-versionized and has another new id:
5386  if (isset($this->autoVersionIdMap[$foreign_table][$value])) {
5387  $value = $this->autoVersionIdMap[$foreign_table][$value];
5388  }
5389  // Set a hint that this was a new child record:
5390  $this->newRelatedIDs[$foreign_table][] = $value;
5391  $valueArray[$key] = $value;
5392  }
5393  }
5394  $remapAction['args'][$remapAction['pos']['valueArray']] = $valueArray;
5395  }
5396  // Process the arguments with the defined function:
5397  $newValue = call_user_func_array(array($this, $remapAction['func']), $remapAction['args']);
5398  // If array is returned, check for maxitems condition, if string is returned this was already done:
5399  if (is_array($newValue)) {
5400  $newValue = implode(',', $this->checkValue_checkMax($tcaFieldConf, $newValue));
5401  }
5402  // Update in database (list of children (csv) or number of relations (foreign_field)):
5403  if (!empty($field)) {
5404  $this->updateDB($table, $id, array($field => $newValue));
5405  // Collect data to update FlexForms
5406  } elseif (!empty($additionalData['flexFormId']) && !empty($additionalData['flexFormPath'])) {
5407  $flexFormId = $additionalData['flexFormId'];
5408  $flexFormPath = $additionalData['flexFormPath'];
5409 
5410  if (!isset($remapFlexForms[$flexFormId])) {
5411  $remapFlexForms[$flexFormId] = array();
5412  }
5413 
5414  $remapFlexForms[$flexFormId][$flexFormPath] = $newValue;
5415  }
5416  // Process waiting Hook: processDatamap_afterDatabaseOperations:
5417  if (isset($this->remapStackRecords[$table][$rawId]['processDatamap_afterDatabaseOperations'])) {
5418  $hookArgs = $this->remapStackRecords[$table][$rawId]['processDatamap_afterDatabaseOperations'];
5419  // Update field with remapped data:
5420  $hookArgs['fieldArray'][$field] = $newValue;
5421  // Process waiting hook objects:
5422  $hookObjectsArr = $hookArgs['hookObjectsArr'];
5423  foreach ($hookObjectsArr as $hookObj) {
5424  if (method_exists($hookObj, 'processDatamap_afterDatabaseOperations')) {
5425  $hookObj->processDatamap_afterDatabaseOperations($hookArgs['status'], $table, $rawId, $hookArgs['fieldArray'], $this);
5426  }
5427  }
5428  }
5429  }
5430 
5431  if ($remapFlexForms) {
5432  foreach ($remapFlexForms as $flexFormId => $modifications) {
5433  $this->updateFlexFormData($flexFormId, $modifications);
5434  }
5435  }
5436  }
5437  // Processes the remap stack actions:
5438  if ($this->remapStackActions) {
5439  foreach ($this->remapStackActions as $action) {
5440  if (isset($action['callback']) && isset($action['arguments'])) {
5441  call_user_func_array($action['callback'], $action['arguments']);
5442  }
5443  }
5444  }
5445  // Processes the reference index updates of the remap stack:
5446  foreach ($this->remapStackRefIndex as $table => $idArray) {
5447  foreach ($idArray as $id) {
5448  $this->updateRefIndex($table, $id);
5449  unset($this->remapStackRefIndex[$table][$id]);
5450  }
5451  }
5452  // Reset:
5453  $this->remapStack = array();
5454  $this->remapStackRecords = array();
5455  $this->remapStackActions = array();
5456  $this->remapStackRefIndex = array();
5457  }
5458 
5466  protected function updateFlexFormData($flexFormId, array $modifications) {
5467  list ($table, $uid, $field) = explode(':', $flexFormId, 3);
5468 
5469  if (!MathUtility::canBeInterpretedAsInteger($uid) && !empty($this->substNEWwithIDs[$uid])) {
5470  $uid = $this->substNEWwithIDs[$uid];
5471  }
5472 
5473  $record = $this->recordInfo($table, $uid, '*');
5474 
5475  if (!$table || !$uid || !$field || !is_array($record)) {
5476  return;
5477  }
5478 
5479  BackendUtility::workspaceOL($table, $record);
5480 
5481  // Get current data structure and value array:
5482  $valueStructure = GeneralUtility::xml2array($record[$field]);
5483 
5484  // Do recursive processing of the XML data:
5485  foreach ($modifications as $path => $value) {
5486  $valueStructure['data'] = \TYPO3\CMS\Core\Utility\ArrayUtility::setValueByPath(
5487  $valueStructure['data'], $path, $value
5488  );
5489  }
5490 
5491  if (is_array($valueStructure['data'])) {
5492  // The return value should be compiled back into XML
5493  $values = array(
5494  $field => $this->checkValue_flexArray2Xml($valueStructure, TRUE),
5495  );
5496 
5497  $this->updateDB($table, $uid, $values);
5498  }
5499  }
5500 
5516  protected function triggerRemapAction($table, $id, array $callback, array $arguments, $forceRemapStackActions = FALSE) {
5517  // Check whether the affected record is marked to be remapped:
5518  if (!$forceRemapStackActions && !isset($this->remapStackRecords[$table][$id]) && !isset($this->remapStackChildIds[$id])) {
5519  call_user_func_array($callback, $arguments);
5520  } else {
5521  $this->addRemapAction($table, $id, $callback, $arguments);
5522  }
5523  }
5524 
5534  public function addRemapAction($table, $id, array $callback, array $arguments) {
5535  $this->remapStackActions[] = array(
5536  'affects' => array(
5537  'table' => $table,
5538  'id' => $id
5539  ),
5540  'callback' => $callback,
5541  'arguments' => $arguments
5542  );
5543  }
5544 
5552  public function addRemapStackRefIndex($table, $id) {
5553  $this->remapStackRefIndex[$table][$id] = $id;
5554  }
5555 
5569  public function getVersionizedIncomingFieldArray($table, $id, &$incomingFieldArray, &$registerDBList) {
5570  if (is_array($registerDBList[$table][$id])) {
5571  foreach ($incomingFieldArray as $field => $value) {
5572  $fieldConf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
5573  if ($registerDBList[$table][$id][$field] && ($foreignTable = $fieldConf['foreign_table'])) {
5574  $newValueArray = array();
5575  $origValueArray = explode(',', $value);
5576  // Update the uids of the copied records, but also take care about new records:
5577  foreach ($origValueArray as $childId) {
5578  $newValueArray[] = $this->autoVersionIdMap[$foreignTable][$childId] ? $this->autoVersionIdMap[$foreignTable][$childId] : $childId;
5579  }
5580  // Set the changed value to the $incomingFieldArray
5581  $incomingFieldArray[$field] = implode(',', $newValueArray);
5582  }
5583  }
5584  // Clean up the $registerDBList array:
5585  unset($registerDBList[$table][$id]);
5586  if (!count($registerDBList[$table])) {
5587  unset($registerDBList[$table]);
5588  }
5589  }
5590  }
5591 
5592  /*****************************
5593  *
5594  * Access control / Checking functions
5595  *
5596  *****************************/
5604  public function checkModifyAccessList($table) {
5605  $res = $this->admin || !$this->tableAdminOnly($table) && GeneralUtility::inList($this->BE_USER->groupData['tables_modify'], $table);
5606  // Hook 'checkModifyAccessList': Post-processing of the state of access
5607  foreach ($this->getCheckModifyAccessListHookObjects() as $hookObject) {
5609  $hookObject->checkModifyAccessList($res, $table, $this);
5610  }
5611  return $res;
5612  }
5613 
5622  public function isRecordInWebMount($table, $id) {
5623  if (!isset($this->isRecordInWebMount_Cache[($table . ':' . $id)])) {
5624  $recP = $this->getRecordProperties($table, $id);
5625  $this->isRecordInWebMount_Cache[$table . ':' . $id] = $this->isInWebMount($recP['event_pid']);
5626  }
5627  return $this->isRecordInWebMount_Cache[$table . ':' . $id];
5628  }
5629 
5637  public function isInWebMount($pid) {
5638  if (!isset($this->isInWebMount_Cache[$pid])) {
5639  $this->isInWebMount_Cache[$pid] = $this->BE_USER->isInWebMount($pid);
5640  }
5641  return $this->isInWebMount_Cache[$pid];
5642  }
5643 
5654  public function checkRecordUpdateAccess($table, $id, $data = FALSE, &$hookObjectsArr = FALSE) {
5655  $res = NULL;
5656  if (is_array($hookObjectsArr)) {
5657  foreach ($hookObjectsArr as $hookObj) {
5658  if (method_exists($hookObj, 'checkRecordUpdateAccess')) {
5659  $res = $hookObj->checkRecordUpdateAccess($table, $id, $data, $res, $this);
5660  }
5661  }
5662  }
5663  if ($res === 1 || $res === 0) {
5664  return $res;
5665  } else {
5666  $res = 0;
5667  }
5668  if ($GLOBALS['TCA'][$table] && (int)$id > 0) {
5669  // If information is cached, return it
5670  if (isset($this->recUpdateAccessCache[$table][$id])) {
5671  return $this->recUpdateAccessCache[$table][$id];
5672  } elseif ($this->doesRecordExist($table, $id, 'edit')) {
5673  $res = 1;
5674  }
5675  // Cache the result
5676  $this->recUpdateAccessCache[$table][$id] = $res;
5677  }
5678  return $res;
5679  }
5680 
5691  public function checkRecordInsertAccess($insertTable, $pid, $action = 1) {
5692  $pid = (int)$pid;
5693  if ($pid < 0) {
5694  return FALSE;
5695  }
5696  // If information is cached, return it
5697  if (isset($this->recInsertAccessCache[$insertTable][$pid])) {
5698  return $this->recInsertAccessCache[$insertTable][$pid];
5699  }
5700 
5701  $res = FALSE;
5702  if ($insertTable === 'pages') {
5703  $perms = $this->pMap['new'];
5704  // @todo: find a more generic way to handle content relations of a page (without needing content editing access to that page)
5705  } elseif (($insertTable === 'sys_file_reference') && array_key_exists('pages', $this->datamap)) {
5706  $perms = $this->pMap['edit'];
5707  } else {
5708  $perms = $this->pMap['editcontent'];
5709  }
5710  $pageExists = (bool)$this->doesRecordExist('pages', $pid, $perms);
5711  // 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
5712  if ($pageExists || $pid === 0 && ($this->admin || BackendUtility::isRootLevelRestrictionIgnored($insertTable))) {
5713  // Check permissions
5714  if ($this->isTableAllowedForThisPage($pid, $insertTable)) {
5715  $res = TRUE;
5716  // Cache the result
5717  $this->recInsertAccessCache[$insertTable][$pid] = $res;
5718  } else {
5719  $propArr = $this->getRecordProperties('pages', $pid);
5720  $this->log($insertTable, $pid, $action, 0, 1, 'Attempt to insert record on page \'%s\' (%s) where this table, %s, is not allowed', 11, array($propArr['header'], $pid, $insertTable), $propArr['event_pid']);
5721  }
5722  } else {
5723  $propArr = $this->getRecordProperties('pages', $pid);
5724  $this->log($insertTable, $pid, $action, 0, 1, 'Attempt to insert a record on page \'%s\' (%s) from table \'%s\' without permissions. Or non-existing page.', 12, array($propArr['header'], $pid, $insertTable), $propArr['event_pid']);
5725  }
5726  return $res;
5727  }
5728 
5737  public function isTableAllowedForThisPage($page_uid, $checkTable) {
5738  $page_uid = (int)$page_uid;
5739  $rootLevelSetting = (int)$GLOBALS['TCA'][$checkTable]['ctrl']['rootLevel'];
5740  // 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.
5741  if ($checkTable !== 'pages' && $rootLevelSetting !== -1 && ($rootLevelSetting xor !$page_uid)) {
5742  return FALSE;
5743  }
5744  $allowed = FALSE;
5745  // Check root-level
5746  if (!$page_uid) {
5747  if ($this->admin || BackendUtility::isRootLevelRestrictionIgnored($checkTable)) {
5748  $allowed = TRUE;
5749  }
5750  } else {
5751  // Check non-root-level
5752  $doktype = $this->pageInfo($page_uid, 'doktype');
5753  $allowedTableList = isset($GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'])
5754  ? $GLOBALS['PAGES_TYPES'][$doktype]['allowedTables']
5755  : $GLOBALS['PAGES_TYPES']['default']['allowedTables'];
5756  $allowedArray = GeneralUtility::trimExplode(',', $allowedTableList, TRUE);
5757  // If all tables or the table is listed as a allowed type, return TRUE
5758  if (strpos($allowedTableList, '*') !== FALSE || in_array($checkTable, $allowedArray)) {
5759  $allowed = TRUE;
5760  }
5761  }
5762  return $allowed;
5763  }
5764 
5776  public function doesRecordExist($table, $id, $perms) {
5777  $id = (int)$id;
5778  if ($this->bypassAccessCheckForRecords) {
5779  return is_array(BackendUtility::getRecordRaw($table, 'uid=' . $id, 'uid'));
5780  }
5781  // Processing the incoming $perms (from possible string to integer that can be AND'ed)
5782  if (!\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($perms)) {
5783  if ($table != 'pages') {
5784  switch ($perms) {
5785  case 'edit':
5786 
5787  case 'delete':
5788 
5789  case 'new':
5790  // This holds it all in case the record is not page!!
5791  if ($table === 'sys_file_reference' && array_key_exists('pages', $this->datamap)) {
5792  $perms = 'edit';
5793  } else {
5794  $perms = 'editcontent';
5795  }
5796  break;
5797  }
5798  }
5799  $perms = (int)$this->pMap[$perms];
5800  } else {
5801  $perms = (int)$perms;
5802  }
5803  if (!$perms) {
5804  throw new \RuntimeException('Internal ERROR: no permissions to check for non-admin user', 1270853920);
5805  }
5806  // For all tables: Check if record exists:
5807  $isWebMountRestrictionIgnored = BackendUtility::isWebMountRestrictionIgnored($table);
5808  if (is_array($GLOBALS['TCA'][$table]) && $id > 0 && ($isWebMountRestrictionIgnored || $this->isRecordInWebMount($table, $id) || $this->admin)) {
5809  if ($table != 'pages') {
5810  // Find record without checking page:
5811  $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,pid', $table, 'uid=' . (int)$id . $this->deleteClause($table));
5812  // THIS SHOULD CHECK FOR editlock I think!
5813  $output = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres);
5814  BackendUtility::fixVersioningPid($table, $output, TRUE);
5815  // If record found, check page as well:
5816  if (is_array($output)) {
5817  // Looking up the page for record:
5818  $mres = $this->doesRecordExist_pageLookUp($output['pid'], $perms);
5819  $pageRec = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres);
5820  // 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):
5821  $isRootLevelRestrictionIgnored = BackendUtility::isRootLevelRestrictionIgnored($table);
5822  if (is_array($pageRec) || !$output['pid'] && ($isRootLevelRestrictionIgnored || $this->admin)) {
5823  return TRUE;
5824  }
5825  }
5826  return FALSE;
5827  } else {
5828  $mres = $this->doesRecordExist_pageLookUp($id, $perms);
5829  return $GLOBALS['TYPO3_DB']->sql_num_rows($mres);
5830  }
5831  }
5832  }
5833 
5844  public function doesRecordExist_pageLookUp($id, $perms) {
5845  return $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', 'pages', 'uid=' . (int)$id . $this->deleteClause('pages') . ($perms && !$this->admin ? ' AND ' . $this->BE_USER->getPagePermsClause($perms) : '') . (!$this->admin && $GLOBALS['TCA']['pages']['ctrl']['editlock'] && $perms & 2 + 4 + 16 ? ' AND ' . $GLOBALS['TCA']['pages']['ctrl']['editlock'] . '=0' : ''));
5846  }
5847 
5862  public function doesBranchExist($inList, $pid, $perms, $recurse) {
5863  $pid = (int)$pid;
5864  $perms = (int)$perms;
5865  if ($pid >= 0) {
5866  $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid, perms_userid, perms_groupid, perms_user, perms_group, perms_everybody', 'pages', 'pid=' . (int)$pid . $this->deleteClause('pages'), '', 'sorting');
5867  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
5868  // IF admin, then it's OK
5869  if ($this->admin || $this->BE_USER->doesUserHaveAccess($row, $perms)) {
5870  $inList .= $row['uid'] . ',';
5871  if ($recurse) {
5872  // Follow the subpages recursively...
5873  $inList = $this->doesBranchExist($inList, $row['uid'], $perms, $recurse);
5874  if ($inList == -1) {
5875  return -1;
5876  }
5877  }
5878  } else {
5879  // No permissions
5880  return -1;
5881  }
5882  }
5883  $GLOBALS['TYPO3_DB']->sql_free_result($mres);
5884  }
5885  return $inList;
5886  }
5887 
5895  public function tableReadOnly($table) {
5896  // Returns TRUE if table is readonly
5897  return $GLOBALS['TCA'][$table]['ctrl']['readOnly'] ? TRUE : FALSE;
5898  }
5899 
5907  public function tableAdminOnly($table) {
5908  // Returns TRUE if table is admin-only
5909  return $GLOBALS['TCA'][$table]['ctrl']['adminOnly'] ? TRUE : FALSE;
5910  }
5911 
5921  public function destNotInsideSelf($dest, $id) {
5922  $loopCheck = 100;
5923  $dest = (int)$dest;
5924  $id = (int)$id;
5925  if ($dest == $id) {
5926  return FALSE;
5927  }
5928  while ($dest != 0 && $loopCheck > 0) {
5929  $loopCheck--;
5930  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('pid, uid, t3ver_oid,t3ver_wsid', 'pages', 'uid=' . (int)$dest . $this->deleteClause('pages'));
5931  if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
5932  BackendUtility::fixVersioningPid('pages', $row);
5933  if ($row['pid'] == $id) {
5934  return FALSE;
5935  } else {
5936  $dest = $row['pid'];
5937  }
5938  } else {
5939  return FALSE;
5940  }
5941  $GLOBALS['TYPO3_DB']->sql_free_result($res);
5942  }
5943  return TRUE;
5944  }
5945 
5953  public function getExcludeListArray() {
5954  $list = array();
5955  foreach ($GLOBALS['TCA'] as $table => $_) {
5956  if (isset($GLOBALS['TCA'][$table]['columns'])) {
5957  foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $config) {
5958  if ($config['exclude'] && !GeneralUtility::inList($this->BE_USER->groupData['non_exclude_fields'], ($table . ':' . $field))) {
5959  $list[] = $table . '-' . $field;
5960  }
5961  }
5962  }
5963  }
5964  return $list;
5965  }
5966 
5975  public function doesPageHaveUnallowedTables($page_uid, $doktype) {
5976  $page_uid = (int)$page_uid;
5977  if (!$page_uid) {
5978  // Not a number. Probably a new page
5979  return FALSE;
5980  }
5981  $allowedTableList = isset($GLOBALS['PAGES_TYPES'][$doktype]['allowedTables']) ? $GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'] : $GLOBALS['PAGES_TYPES']['default']['allowedTables'];
5982  $allowedArray = GeneralUtility::trimExplode(',', $allowedTableList, TRUE);
5983  // If all tables is OK the return TRUE
5984  if (strstr($allowedTableList, '*')) {
5985  // OK...
5986  return FALSE;
5987  }
5988  $tableList = array();
5989  foreach ($GLOBALS['TCA'] as $table => $_) {
5990  // If the table is not in the allowed list, check if there are records...
5991  if (!in_array($table, $allowedArray)) {
5992  $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', $table, 'pid=' . (int)$page_uid);
5993  if ($count) {
5994  $tableList[] = $table;
5995  }
5996  }
5997  }
5998  return implode(',', $tableList);
5999  }
6000 
6001  /*****************************
6002  *
6003  * Information lookup
6004  *
6005  *****************************/
6015  public function pageInfo($id, $field) {
6016  if (!isset($this->pageCache[$id])) {
6017  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'pages', 'uid=' . (int)$id);
6018  if ($GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
6019  $this->pageCache[$id] = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
6020  }
6021  $GLOBALS['TYPO3_DB']->sql_free_result($res);
6022  }
6023  return $this->pageCache[$id][$field];
6024  }
6025 
6035  public function recordInfo($table, $id, $fieldList) {
6036  // Skip, if searching for NEW records or there's no TCA table definition
6037  if ((int)$id === 0 || !isset($GLOBALS['TCA'][$table])) {
6038  return NULL;
6039  }
6041  $db = $GLOBALS['TYPO3_DB'];
6042  $result = $db->exec_SELECTgetSingleRow($fieldList, $table, 'uid=' . (int)$id);
6043  return $result ?: NULL;
6044  }
6045 
6058  public function getRecordProperties($table, $id, $noWSOL = FALSE) {
6059  $row = $table == 'pages' && !$id ? array('title' => '[root-level]', 'uid' => 0, 'pid' => 0) : $this->recordInfo($table, $id, '*');
6060  if (!$noWSOL) {
6061  BackendUtility::workspaceOL($table, $row);
6062  }
6063  return $this->getRecordPropertiesFromRow($table, $row);
6064  }
6065 
6074  public function getRecordPropertiesFromRow($table, $row) {
6075  if ($GLOBALS['TCA'][$table]) {
6076  BackendUtility::fixVersioningPid($table, $row);
6077  $out = array(
6078  'header' => BackendUtility::getRecordTitle($table, $row),
6079  'pid' => $row['pid'],
6080  'event_pid' => $this->eventPid($table, isset($row['_ORIG_pid']) ? $row['t3ver_oid'] : $row['uid'], $row['pid']),
6081  't3ver_state' => $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] ? $row['t3ver_state'] : '',
6082  '_ORIG_pid' => $row['_ORIG_pid']
6083  );
6084  return $out;
6085  }
6086  }
6087 
6095  public function eventPid($table, $uid, $pid) {
6096  return $table == 'pages' ? $uid : $pid;
6097  }
6098 
6099  /*********************************************
6100  *
6101  * Storing data to Database Layer
6102  *
6103  ********************************************/
6114  public function updateDB($table, $id, $fieldArray) {
6115  if (is_array($fieldArray) && is_array($GLOBALS['TCA'][$table]) && (int)$id) {
6116  // Do NOT update the UID field, ever!
6117  unset($fieldArray['uid']);
6118  if (count($fieldArray)) {
6119  $fieldArray = $this->insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray);
6120  // Execute the UPDATE query:
6121  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$id, $fieldArray);
6122  // If succees, do...:
6123  if (!$GLOBALS['TYPO3_DB']->sql_error()) {
6124  if ($this->checkStoredRecords) {
6125  $newRow = $this->checkStoredRecord($table, $id, $fieldArray, 2);
6126  }
6127  // Update reference index:
6128  $this->updateRefIndex($table, $id);
6129  // Set log entry:
6130  $propArr = $this->getRecordPropertiesFromRow($table, $newRow);
6131  $theLogId = $this->log($table, $id, 2, $propArr['pid'], 0, 'Record \'%s\' (%s) was updated.' . ($propArr['_ORIG_pid'] == -1 ? ' (Offline version).' : ' (Online).'), 10, array($propArr['header'], $table . ':' . $id), $propArr['event_pid']);
6132  // Set History data:
6133  $this->setHistory($table, $id, $theLogId);
6134  // Clear cache for relevant pages:
6135  $this->registerRecordIdForPageCacheClearing($table, $id);
6136  // Unset the pageCache for the id if table was page.
6137  if ($table == 'pages') {
6138  unset($this->pageCache[$id]);
6139  }
6140  } else {
6141  $this->log($table, $id, 2, 0, 2, 'SQL error: \'%s\' (%s)', 12, array($GLOBALS['TYPO3_DB']->sql_error(), $table . ':' . $id));
6142  }
6143  }
6144  }
6145  }
6146 
6160  public function insertDB($table, $id, $fieldArray, $newVersion = FALSE, $suggestedUid = 0, $dontSetNewIdIndex = FALSE) {
6161  if (is_array($fieldArray) && is_array($GLOBALS['TCA'][$table]) && isset($fieldArray['pid'])) {
6162  // Do NOT insert the UID field, ever!
6163  unset($fieldArray['uid']);
6164  if (count($fieldArray)) {
6165  // Check for "suggestedUid".
6166  // This feature is used by the import functionality to force a new record to have a certain UID value.
6167  // This is only recommended for use when the destination server is a passive mirrow of another server.
6168  // As a security measure this feature is available only for Admin Users (for now)
6169  $suggestedUid = (int)$suggestedUid;
6170  if ($this->BE_USER->isAdmin() && $suggestedUid && $this->suggestedInsertUids[$table . ':' . $suggestedUid]) {
6171  // When the value of ->suggestedInsertUids[...] is "DELETE" it will try to remove the previous record
6172  if ($this->suggestedInsertUids[$table . ':' . $suggestedUid] === 'DELETE') {
6173  // DELETE:
6174  $GLOBALS['TYPO3_DB']->exec_DELETEquery($table, 'uid=' . (int)$suggestedUid);
6175  }
6176  $fieldArray['uid'] = $suggestedUid;
6177  }
6178  $fieldArray = $this->insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray);
6179  // Execute the INSERT query:
6180  $GLOBALS['TYPO3_DB']->exec_INSERTquery($table, $fieldArray);
6181  // If succees, do...:
6182  if (!$GLOBALS['TYPO3_DB']->sql_error()) {
6183  // Set mapping for NEW... -> real uid:
6184  // the NEW_id now holds the 'NEW....' -id
6185  $NEW_id = $id;
6186  $id = $GLOBALS['TYPO3_DB']->sql_insert_id();
6187  if (!$dontSetNewIdIndex) {
6188  $this->substNEWwithIDs[$NEW_id] = $id;
6189  $this->substNEWwithIDs_table[$NEW_id] = $table;
6190  }
6191  // Checking the record is properly saved and writing to log
6192  if ($this->checkStoredRecords) {
6193  $newRow = $this->checkStoredRecord($table, $id, $fieldArray, 1);
6194  }
6195  // Update reference index:
6196  $this->updateRefIndex($table, $id);
6197  if ($newVersion) {
6198  $propArr = $this->getRecordPropertiesFromRow($table, $newRow);
6199  $this->log($table, $id, 1, 0, 0, 'New version created of table \'%s\', uid \'%s\'. UID of new version is \'%s\'', 10, array($table, $fieldArray['t3ver_oid'], $id), $propArr['event_pid'], $NEW_id);
6200  } else {
6201  $propArr = $this->getRecordPropertiesFromRow($table, $newRow);
6202  $page_propArr = $this->getRecordProperties('pages', $propArr['pid']);
6203  $this->log($table, $id, 1, 0, 0, 'Record \'%s\' (%s) was inserted on page \'%s\' (%s)', 10, array($propArr['header'], $table . ':' . $id, $page_propArr['header'], $newRow['pid']), $newRow['pid'], $NEW_id);
6204  // Clear cache for relavant pages:
6205  $this->registerRecordIdForPageCacheClearing($table, $id);
6206  }
6207  return $id;
6208  } else {
6209  $this->log($table, $id, 1, 0, 2, 'SQL error: \'%s\' (%s)', 12, array($GLOBALS['TYPO3_DB']->sql_error(), $table . ':' . $id));
6210  }
6211  }
6212  }
6213  }
6214 
6226  public function checkStoredRecord($table, $id, $fieldArray, $action) {
6227  $id = (int)$id;
6228  if (is_array($GLOBALS['TCA'][$table]) && $id) {
6229  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $table, 'uid=' . (int)$id);
6230  if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
6231  // Traverse array of values that was inserted into the database and compare with the actually stored value:
6232  $errorString = array();
6233  foreach ($fieldArray as $key => $value) {
6234  if ($this->checkStoredRecords_loose && !$value && !$row[$key]) {
6235 
6236  } elseif ((string)$value !== (string)$row[$key]) {
6237  $errorString[] = $key;
6238  }
6239  }
6240  // Set log message if there were fields with unmatching values:
6241  if (count($errorString)) {
6242  $this->log($table, $id, $action, 0, 1, 'These fields are not properly updated in database: (' . implode(',', $errorString) . ') Probably value mismatch with fieldtype.');
6243  }
6244  // Return selected rows:
6245  return $row;
6246  }
6247  $GLOBALS['TYPO3_DB']->sql_free_result($res);
6248  }
6249  }
6250 
6260  public function setHistory($table, $id, $logId) {
6261  if (isset($this->historyRecords[$table . ':' . $id]) && (int)$logId > 0) {
6262  $fields_values = array();
6263  $fields_values['history_data'] = serialize($this->historyRecords[$table . ':' . $id]);
6264  $fields_values['fieldlist'] = implode(',', array_keys($this->historyRecords[$table . ':' . $id]['newRecord']));
6265  $fields_values['tstamp'] = $GLOBALS['EXEC_TIME'];
6266  $fields_values['tablename'] = $table;
6267  $fields_values['recuid'] = $id;
6268  $fields_values['sys_log_uid'] = $logId;
6269  $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_history', $fields_values);
6270  }
6271  }
6272 
6282  public function updateRefIndex($table, $id) {
6284  $refIndexObj = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\ReferenceIndex');
6286  $refIndexObj->setWorkspaceId($this->BE_USER->workspace);
6287  }
6288  $refIndexObj->updateRefIndexTable($table, $id);
6289  }
6290 
6291  /*********************************************
6292  *
6293  * Misc functions
6294  *
6295  ********************************************/
6306  public function getSortNumber($table, $uid, $pid) {
6307  if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['sortby']) {
6308  $sortRow = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
6309  // Sorting number is in the top
6310  if ($pid >= 0) {
6311  // Fetches the first record under this pid
6312  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($sortRow . ',pid,uid', $table, 'pid=' . (int)$pid . $this->deleteClause($table), '', $sortRow . ' ASC', '1');
6313  // There was an element
6314  if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
6315  // The top record was the record it self, so we return its current sortnumber
6316  if ($row['uid'] == $uid) {
6317  return $row[$sortRow];
6318  }
6319  // If the pages sortingnumber < 1 we must resort the records under this pid
6320  if ($row[$sortRow] < 1) {
6321  $this->resorting($table, $pid, $sortRow, 0);
6322  // First sorting number after resorting
6323  return $this->sortIntervals;
6324  } else {
6325  // Sorting number between current top element and zero
6326  return floor($row[$sortRow] / 2);
6327  }
6328  } else {
6329  // No pages, so we choose the default value as sorting-number
6330  // First sorting number if no elements.
6331  return $this->sortIntervals;
6332  }
6333  } else {
6334  // Sorting number is inside the list
6335  // Fetches the record which is supposed to be the prev record
6336  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($sortRow . ',pid,uid', $table, 'uid=' . abs($pid) . $this->deleteClause($table));
6337  // There was a record
6338  if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
6339  // Look, if the record UID happens to be an offline record. If so, find its live version. Offline uids will be used when a page is versionized as "branch" so this is when we must correct - otherwise a pid of "-1" and a wrong sort-row number is returned which we don't want.
6340  if ($lookForLiveVersion = BackendUtility::getLiveVersionOfRecord($table, $row['uid'], $sortRow . ',pid,uid')) {
6341  $row = $lookForLiveVersion;
6342  }
6343  // Fetch move placeholder, since it might point to a new page in the current workspace
6344  if ($movePlaceholder = BackendUtility::getMovePlaceholder($table, $row['uid'], 'uid,pid,' . $sortRow)) {
6345  $row = $movePlaceholder;
6346  }
6347  // If the record should be inserted after itself, keep the current sorting information:
6348  if ($row['uid'] == $uid) {
6349  $sortNumber = $row[$sortRow];
6350  } else {
6351  $subres = $GLOBALS['TYPO3_DB']->exec_SELECTquery($sortRow . ',pid,uid', $table, 'pid=' . (int)$row['pid'] . ' AND ' . $sortRow . '>=' . (int)$row[$sortRow] . $this->deleteClause($table), '', $sortRow . ' ASC', '2');
6352  // Fetches the next record in order to calculate the in-between sortNumber
6353  // There was a record afterwards
6354  if ($GLOBALS['TYPO3_DB']->sql_num_rows($subres) == 2) {
6355  // Forward to the second result...
6356  $GLOBALS['TYPO3_DB']->sql_fetch_assoc($subres);
6357  // There was a record afterwards
6358  $subrow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($subres);
6359  // The sortNumber is found in between these values
6360  $sortNumber = $row[$sortRow] + floor(($subrow[$sortRow] - $row[$sortRow]) / 2);
6361  // The sortNumber happend NOT to be between the two surrounding numbers, so we'll have to resort the list
6362  if ($sortNumber <= $row[$sortRow] || $sortNumber >= $subrow[$sortRow]) {
6363  // By this special param, resorting reserves and returns the sortnumber after the uid
6364  $sortNumber = $this->resorting($table, $row['pid'], $sortRow, $row['uid']);
6365  }
6366  } else {
6367  // If after the last record in the list, we just add the sortInterval to the last sortvalue
6368  $sortNumber = $row[$sortRow] + $this->sortIntervals;
6369  }
6370  $GLOBALS['TYPO3_DB']->sql_free_result($subres);
6371  }
6372  return array('pid' => $row['pid'], 'sortNumber' => $sortNumber);
6373  } else {
6374  $propArr = $this->getRecordProperties($table, $uid);
6375  // OK, dont insert $propArr['event_pid'] here...
6376  $this->log($table, $uid, 4, 0, 1, 'Attempt to move record \'%s\' (%s) to after a non-existing record (uid=%s)', 1, array($propArr['header'], $table . ':' . $uid, abs($pid)), $propArr['pid']);
6377  // There MUST be a page or else this cannot work
6378  return FALSE;
6379  }
6380  }
6381  }
6382  }
6383 
6397  public function resorting($table, $pid, $sortRow, $return_SortNumber_After_This_Uid) {
6398  if ($GLOBALS['TCA'][$table] && $sortRow && $GLOBALS['TCA'][$table]['ctrl']['sortby'] == $sortRow) {
6399  $returnVal = 0;
6400  $intervals = $this->sortIntervals;
6401  $i = $intervals * 2;
6402  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'pid=' . (int)$pid . $this->deleteClause($table), '', $sortRow . ' ASC');
6403  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
6404  $uid = (int)$row['uid'];
6405  if ($uid) {
6406  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$uid, array($sortRow => $i));
6407  // This is used to return a sortingValue if the list is resorted because of inserting records inside the list and not in the top
6408  if ($uid == $return_SortNumber_After_This_Uid) {
6409  $i = $i + $intervals;
6410  $returnVal = $i;
6411  }
6412  } else {
6413  die('Fatal ERROR!! No Uid at resorting.');
6414  }
6415  $i = $i + $intervals;
6416  }
6417  $GLOBALS['TYPO3_DB']->sql_free_result($res);
6418  return $returnVal;
6419  }
6420  }
6421 
6432  protected function getPreviousLocalizedRecordUid($table, $uid, $pid, $language) {
6433  $previousLocalizedRecordUid = $uid;
6434  if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['sortby']) {
6435  $sortRow = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
6436  $select = $sortRow . ',pid,uid';
6437  // For content elements, we also need the colPos
6438  if ($table === 'tt_content') {
6439  $select .= ',colPos';
6440  }
6441  // Get the sort value of the default language record
6442  $row = BackendUtility::getRecord($table, $uid, $select);
6443  if (is_array($row)) {
6444  // Find the previous record in default language on the same page
6445  $where = 'pid=' . (int)$pid . ' AND ' . 'sys_language_uid=0' . ' AND ' . $sortRow . '<' . (int)$row[$sortRow];
6446  // Respect the colPos for content elements
6447  if ($table === 'tt_content') {
6448  $where .= ' AND colPos=' . (int)$row['colPos'];
6449  }
6450  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($select, $table, $where . $this->deleteClause($table), '', $sortRow . ' DESC', '1');
6451  // If there is an element, find its localized record in specified localization language
6452  if ($previousRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
6453  $previousLocalizedRecord = BackendUtility::getRecordLocalization($table, $previousRow['uid'], $language);
6454  if (is_array($previousLocalizedRecord[0])) {
6455  $previousLocalizedRecordUid = $previousLocalizedRecord[0]['uid'];
6456  }
6457  }
6458  $GLOBALS['TYPO3_DB']->sql_free_result($res);
6459  }
6460  }
6461  return $previousLocalizedRecordUid;
6462  }
6463 
6473  public function setTSconfigPermissions($fieldArray, $TSConfig_p) {
6474  if ((string)$TSConfig_p['userid'] !== '') {
6475  $fieldArray['perms_userid'] = (int)$TSConfig_p['userid'];
6476  }
6477  if ((string)$TSConfig_p['groupid'] !== '') {
6478  $fieldArray['perms_groupid'] = (int)$TSConfig_p['groupid'];
6479  }
6480  if ((string)$TSConfig_p['user'] !== '') {
6481  $fieldArray['perms_user'] = \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($TSConfig_p['user']) ? $TSConfig_p['user'] : $this->assemblePermissions($TSConfig_p['user']);
6482  }
6483  if ((string)$TSConfig_p['group'] !== '') {
6484  $fieldArray['perms_group'] = \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($TSConfig_p['group']) ? $TSConfig_p['group'] : $this->assemblePermissions($TSConfig_p['group']);
6485  }
6486  if ((string)$TSConfig_p['everybody'] !== '') {
6487  $fieldArray['perms_everybody'] = \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($TSConfig_p['everybody']) ? $TSConfig_p['everybody'] : $this->assemblePermissions($TSConfig_p['everybody']);
6488  }
6489  return $fieldArray;
6490  }
6491 
6500  public function newFieldArray($table) {
6501  $fieldArray = array();
6502  if (is_array($GLOBALS['TCA'][$table]['columns'])) {
6503  foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $content) {
6504  if (isset($this->defaultValues[$table][$field])) {
6505  $fieldArray[$field] = $this->defaultValues[$table][$field];
6506  } elseif (isset($content['config']['default'])) {
6507  $fieldArray[$field] = $content['config']['default'];
6508  }
6509  }
6510  }
6511  // Set default permissions for a page.
6512  if ($table === 'pages') {
6513  $fieldArray['perms_userid'] = $this->userid;
6514  $fieldArray['perms_groupid'] = (int)$this->BE_USER->firstMainGroup;
6515  $fieldArray['perms_user'] = $this->assemblePermissions($this->defaultPermissions['user']);
6516  $fieldArray['perms_group'] = $this->assemblePermissions($this->defaultPermissions['group']);
6517  $fieldArray['perms_everybody'] = $this->assemblePermissions($this->defaultPermissions['everybody']);
6518  }
6519  return $fieldArray;
6520  }
6521 
6530  public function addDefaultPermittedLanguageIfNotSet($table, &$incomingFieldArray) {
6531  // Checking languages:
6532  if ($GLOBALS['TCA'][$table]['ctrl']['languageField']) {
6533  if (!isset($incomingFieldArray[$GLOBALS['TCA'][$table]['ctrl']['languageField']])) {
6534  // Language field must be found in input row - otherwise it does not make sense.
6535  $rows = array_merge(array(array('uid' => 0)), $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', 'sys_language', 'pid=0' . BackendUtility::deleteClause('sys_language')), array(array('uid' => -1)));
6536  foreach ($rows as $r) {
6537  if ($this->BE_USER->checkLanguageAccess($r['uid'])) {
6538  $incomingFieldArray[$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $r['uid'];
6539  break;
6540  }
6541  }
6542  }
6543  }
6544  }
6545 
6554  public function overrideFieldArray($table, $data) {
6555  if (is_array($this->overrideValues[$table])) {
6556  $data = array_merge($data, $this->overrideValues[$table]);
6557  }
6558  return $data;
6559  }
6560 
6571  public function compareFieldArrayWithCurrentAndUnset($table, $id, $fieldArray) {
6572  // Fetch the original record:
6573  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $table, 'uid=' . (int)$id);
6574  $currentRecord = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
6575  // If the current record exists (which it should...), begin comparison:
6576  if (is_array($currentRecord)) {
6577  // Read all field types:
6578  $c = 0;
6579  $cRecTypes = array();
6580  foreach ($currentRecord as $col => $val) {
6581  $cRecTypes[$col] = $GLOBALS['TYPO3_DB']->sql_field_type($res, $c);
6582  $c++;
6583  }
6584  // Free result:
6585  $GLOBALS['TYPO3_DB']->sql_free_result($res);
6586  // Unset the fields which are similar:
6587  foreach ($fieldArray as $col => $val) {
6588  $fieldConfiguration = $GLOBALS['TCA'][$table]['columns'][$col]['config'];
6589  $isNullField = (!empty($fieldConfiguration['eval']) && GeneralUtility::inList($fieldConfiguration['eval'], 'null'));
6590 
6591  // Unset fields if stored and submitted values are equal - except the current field holds MM relations.
6592  // In general this avoids to store superfluous data which also will be visualized in the editing history.
6593  if (!$fieldConfiguration['MM'] && $this->isSubmittedValueEqualToStoredValue($val, $currentRecord[$col], $cRecTypes[$col], $isNullField)) {
6594  unset($fieldArray[$col]);
6595  } else {
6596  if (!isset($this->mmHistoryRecords[($table . ':' . $id)]['oldRecord'][$col])) {
6597  $this->historyRecords[$table . ':' . $id]['oldRecord'][$col] = $currentRecord[$col];
6598  } elseif ($this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$col] != $this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$col]) {
6599  $this->historyRecords[$table . ':' . $id]['oldRecord'][$col] = $this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$col];
6600  }
6601  if (!isset($this->mmHistoryRecords[($table . ':' . $id)]['newRecord'][$col])) {
6602  $this->historyRecords[$table . ':' . $id]['newRecord'][$col] = $fieldArray[$col];
6603  } elseif ($this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$col] != $this->mmHistoryRecords[$table . ':' . $id]['oldRecord'][$col]) {
6604  $this->historyRecords[$table . ':' . $id]['newRecord'][$col] = $this->mmHistoryRecords[$table . ':' . $id]['newRecord'][$col];
6605  }
6606  }
6607  }
6608  } else {
6609  // If the current record does not exist this is an error anyways and we just return an empty array here.
6610  $fieldArray = array();
6611  }
6612  return $fieldArray;
6613  }
6614 
6627  protected function isSubmittedValueEqualToStoredValue($submittedValue, $storedValue, $storedType, $allowNull = FALSE) {
6628  // No NULL values are allowed, this is the regular behaviour.
6629  // Thus, check whether strings are the same or whether integer values are empty ("0" or "").
6630  if (!$allowNull) {
6631  $result = (string)$submittedValue === (string)$storedValue || $storedType === 'int' && (int)$storedValue === (int)$submittedValue;
6632  // Null values are allowed, but currently there's a real (not NULL) value.
6633  // Thus, ensure no NULL value was submitted and fallback to the regular behaviour.
6634  } elseif ($storedValue !== NULL) {
6635  $result = (
6636  $submittedValue !== NULL
6637  && $this->isSubmittedValueEqualToStoredValue($submittedValue, $storedValue, $storedType, FALSE)
6638  );
6639  // Null values are allowed, and currently there's a NULL value.
6640  // Thus, check whether a NULL value was submitted.
6641  } else {
6642  $result = ($submittedValue === NULL);
6643  }
6644 
6645  return $result;
6646  }
6647 
6656  public function assemblePermissions($string) {
6657  $keyArr = GeneralUtility::trimExplode(',', $string, TRUE);
6658  $value = 0;
6659  foreach ($keyArr as $key) {
6660  if ($key && isset($this->pMap[$key])) {
6661  $value |= $this->pMap[$key];
6662  }
6663  }
6664  return $value;
6665  }
6666 
6674  public function rmComma($input) {
6675  return rtrim($input, ',');
6676  }
6677 
6685  public function convNumEntityToByteValue($input) {
6686  $token = md5(microtime());
6687  $parts = explode($token, preg_replace('/(&#([0-9]+);)/', $token . '\\2' . $token, $input));
6688  foreach ($parts as $k => $v) {
6689  if ($k % 2) {
6690  $v = (int)$v;
6691  // Just to make sure that control bytes are not converted.
6692  if ($v > 32) {
6693  $parts[$k] = chr((int)$v);
6694  }
6695  }
6696  }
6697  return implode('', $parts);
6698  }
6699 
6707  public function destPathFromUploadFolder($folder) {
6708  return PATH_site . $folder;
6709  }
6710 
6718  public function disableDeleteClause() {
6719  $this->disableDeleteClause = TRUE;
6720  }
6721 
6729  public function deleteClause($table) {
6730  // Returns the proper delete-clause if any for a table from TCA
6731  if (!$this->disableDeleteClause && $GLOBALS['TCA'][$table]['ctrl']['delete']) {
6732  return ' AND ' . $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['delete'] . '=0';
6733  } else {
6734  return '';
6735  }
6736  }
6737 
6746  protected function getOriginalParentOfRecord($table, $uid) {
6747  if (isset(self::$recordPidsForDeletedRecords[$table][$uid])) {
6748  return self::$recordPidsForDeletedRecords[$table][$uid];
6749  }
6750  list($parentUid) = BackendUtility::getTSCpid($table, $uid, '');
6751  return array($parentUid);
6752  }
6753 
6761  public function getTCEMAIN_TSconfig($tscPID) {
6762  if (!isset($this->cachedTSconfig[$tscPID])) {
6763  $this->cachedTSconfig[$tscPID] = $this->BE_USER->getTSConfig('TCEMAIN', BackendUtility::getPagesTSconfig($tscPID));
6764  }
6765  return $this->cachedTSconfig[$tscPID]['properties'];
6766  }
6767 
6777  public function getTableEntries($table, $TSconfig) {
6778  $tA = is_array($TSconfig['table.'][$table . '.']) ? $TSconfig['table.'][$table . '.'] : array();
6779  $dA = is_array($TSconfig['default.']) ? $TSconfig['default.'] : array();
6781  return $dA;
6782  }
6783 
6792  public function getPID($table, $uid) {
6793  $res_tmp = $GLOBALS['TYPO3_DB']->exec_SELECTquery('pid', $table, 'uid=' . (int)$uid);
6794  if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res_tmp)) {
6795  return $row['pid'];
6796  }
6797  }
6798 
6806  public function dbAnalysisStoreExec() {
6807  foreach ($this->dbAnalysisStore as $action) {
6808  $id = BackendUtility::wsMapId($action[4], \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($action[2]) ? $action[2] : $this->substNEWwithIDs[$action[2]]);
6809  if ($id) {
6810  $action[0]->writeMM($action[1], $id, $action[3]);
6811  }
6812  }
6813  }
6814 
6821  public function removeRegisteredFiles() {
6822  foreach ($this->removeFilesStore as $file) {
6823  if (@is_file($file)) {
6824  $file = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
6825  $file->delete();
6826  }
6827  }
6828  }
6829 
6841  public function int_pageTreeInfo($CPtable, $pid, $counter, $rootID) {
6842  if ($counter) {
6843  if ((int)$this->BE_USER->workspace === 0) {
6844  $workspaceStatement = ' AND t3ver_wsid=0';
6845  } else {
6846  $workspaceStatement = ' AND t3ver_wsid IN (0,' . (int)$this->BE_USER->workspace . ')';
6847  }
6848  $addW = !$this->admin ? ' AND ' . $this->BE_USER->getPagePermsClause($this->pMap['show']) : '';
6849  $pages = $this->databaseConnection->exec_SELECTgetRows(
6850  'uid',
6851  'pages',
6852  'pid=' . (int)$pid . $this->deleteClause('pages') . $workspaceStatement . $addW,
6853  '',
6854  'sorting DESC',
6855  '',
6856  'uid'
6857  );
6858 
6859  // Resolve placeholders of workspace versions
6860  if (!empty($pages) && (int)$this->BE_USER->workspace !== 0) {
6861  $pages = array_reverse(
6862  $this->resolveVersionedRecords(
6863  'pages',
6864  'uid',
6865  'sorting',
6866  array_keys($pages)
6867  ),
6868  TRUE
6869  );
6870  }
6871 
6872  foreach ($pages as $page) {
6873  if ($page['uid'] != $rootID) {
6874  $CPtable[$page['uid']] = $pid;
6875  // If the uid is NOT the rootID of the copyaction and if we are supposed to walk further down
6876  if ($counter - 1) {
6877  $CPtable = $this->int_pageTreeInfo($CPtable, $page['uid'], $counter - 1, $rootID);
6878  }
6879  }
6880  }
6881  }
6882  return $CPtable;
6883  }
6884 
6891  public function compileAdminTables() {
6892  return array_keys($GLOBALS['TCA']);
6893  }
6894 
6903  public function fixUniqueInPid($table, $uid) {
6904  if (empty($GLOBALS['TCA'][$table])) {
6905  return;
6906  }
6907  $curData = $this->recordInfo($table, $uid, '*');
6908  $newData = array();
6909  foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $conf) {
6910  if ($conf['config']['type'] === 'input' && (string)$curData[$field] !== '') {
6911  $evalCodesArray = GeneralUtility::trimExplode(',', $conf['config']['eval'], TRUE);
6912  if (in_array('uniqueInPid', $evalCodesArray)) {
6913  $newV = $this->getUnique($table, $field, $curData[$field], $uid, $curData['pid']);
6914  if ((string)$newV !== (string)$curData[$field]) {
6915  $newData[$field] = $newV;
6916  }
6917  }
6918  }
6919  }
6920  // IF there are changed fields, then update the database
6921  if (!empty($newData)) {
6922  $this->updateDB($table, $uid, $newData);
6923  }
6924  }
6925 
6938  public function fixCopyAfterDuplFields($table, $uid, $prevUid, $update, $newData = array()) {
6939  if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['copyAfterDuplFields']) {
6940  $prevData = $this->recordInfo($table, $prevUid, '*');
6941  $theFields = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['copyAfterDuplFields'], TRUE);
6942  foreach ($theFields as $field) {
6943  if ($GLOBALS['TCA'][$table]['columns'][$field] && ($update || !isset($newData[$field]))) {
6944  $newData[$field] = $prevData[$field];
6945  }
6946  }
6947  if ($update && count($newData)) {
6948  $this->updateDB($table, $uid, $newData);
6949  }
6950  }
6951  return $newData;
6952  }
6953 
6961  public function extFileFields($table) {
6962  $listArr = array();
6963  if (isset($GLOBALS['TCA'][$table]['columns'])) {
6964  foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $configArr) {
6965  if ($configArr['config']['type'] == 'group' && ($configArr['config']['internal_type'] == 'file' || $configArr['config']['internal_type'] == 'file_reference')) {
6966  $listArr[] = $field;
6967  }
6968  }
6969  }
6970  return $listArr;
6971  }
6972 
6980  public function getUniqueFields($table) {
6982  $listArr = array();
6983  if ($GLOBALS['TCA'][$table]['columns']) {
6984  foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $configArr) {
6985  if ($configArr['config']['type'] === 'input') {
6986  $evalCodesArray = GeneralUtility::trimExplode(',', $configArr['config']['eval'], TRUE);
6987  if (in_array('uniqueInPid', $evalCodesArray) || in_array('unique', $evalCodesArray)) {
6988  $listArr[] = $field;
6989  }
6990  }
6991  }
6992  }
6993  return $listArr;
6994  }
6995 
7003  public function isReferenceField($conf) {
7004  return $conf['type'] == 'group' && $conf['internal_type'] == 'db' || $conf['type'] == 'select' && $conf['foreign_table'];
7005  }
7006 
7015  public function getInlineFieldType($conf) {
7016  if ($conf['type'] !== 'inline' || !$conf['foreign_table']) {
7017  return FALSE;
7018  }
7019  if ($conf['foreign_field']) {
7020  // The reference to the parent is stored in a pointer field in the child record
7021  return 'field';
7022  } elseif ($conf['MM']) {
7023  // Regular MM intermediate table is used to store data
7024  return 'mm';
7025  } else {
7026  // An item list (separated by comma) is stored (like select type is doing)
7027  return 'list';
7028  }
7029  }
7030 
7043  public function getCopyHeader($table, $pid, $field, $value, $count, $prevTitle = '') {
7044  // Set title value to check for:
7045  if ($count) {
7046  $checkTitle = $value . rtrim((' ' . sprintf($this->prependLabel($table), $count)));
7047  } else {
7048  $checkTitle = $value;
7049  }
7050  // Do check:
7051  if ($prevTitle != $checkTitle || $count < 100) {
7052  $rowCount = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', $table, 'pid=' . (int)$pid . ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($checkTitle, $table) . $this->deleteClause($table));
7053  if ($rowCount) {
7054  return $this->getCopyHeader($table, $pid, $field, $value, $count + 1, $checkTitle);
7055  }
7056  }
7057  // Default is to just return the current input title if no other was returned before:
7058  return $checkTitle;
7059  }
7060 
7069  public function prependLabel($table) {
7070  if (is_object($GLOBALS['LANG'])) {
7071  $label = $GLOBALS['LANG']->sL($GLOBALS['TCA'][$table]['ctrl']['prependAtCopy']);
7072  } else {
7073  list($label) = explode('|', $GLOBALS['TCA'][$table]['ctrl']['prependAtCopy']);
7074  }
7075  return $label;
7076  }
7077 
7086  public function resolvePid($table, $pid) {
7087  $pid = (int)$pid;
7088  if ($pid < 0) {
7089  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('pid', $table, 'uid=' . abs($pid));
7090  $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
7091  $GLOBALS['TYPO3_DB']->sql_free_result($res);
7092  // Look, if the record UID happens to be an offline record. If so, find its live version.
7093  // Offline uids will be used when a page is versionized as "branch" so this is when we
7094  // must correct - otherwise a pid of "-1" and a wrong sort-row number
7095  // is returned which we don't want.
7096  if ($lookForLiveVersion = BackendUtility::getLiveVersionOfRecord($table, abs($pid), 'pid')) {
7097  $row = $lookForLiveVersion;
7098  }
7099  $pid = (int)$row['pid'];
7100  }
7101  return $pid;
7102  }
7103 
7112  public function clearPrefixFromValue($table, $value) {
7113  $regex = '/' . sprintf(quotemeta($this->prependLabel($table)), '[0-9]*') . '$/';
7114  return @preg_replace($regex, '', $value);
7115  }
7116 
7127  public function extFileFunctions($table, $field, $filelist, $func) {
7128  $uploadFolder = $GLOBALS['TCA'][$table]['columns'][$field]['config']['uploadfolder'];
7129  if ($uploadFolder && trim($filelist) && $GLOBALS['TCA'][$table]['columns'][$field]['config']['internal_type'] == 'file') {
7130  $uploadPath = $this->destPathFromUploadFolder($uploadFolder);
7131  $fileArray = explode(',', $filelist);
7132  foreach ($fileArray as $theFile) {
7133  $theFile = trim($theFile);
7134  if ($theFile) {
7135  switch ($func) {
7136  case 'deleteAll':
7137  $theFileFullPath = $uploadPath . '/' . $theFile;
7138  if (@is_file($theFileFullPath)) {
7139  $file = $this->getResourceFactory()->retrieveFileOrFolderObject($theFileFullPath);
7140  $file->delete();
7141  } else {
7142  $this->log($table, 0, 3, 0, 100, 'Delete: Referenced file that was supposed to be deleted together with it\'s record didn\'t exist');
7143  }
7144  break;
7145  }
7146  }
7147  }
7148  }
7149  }
7150 
7158  public function noRecordsFromUnallowedTables($inList) {
7159  $inList = trim($this->rmComma(trim($inList)));
7160  if ($inList && !$this->admin) {
7161  foreach ($GLOBALS['TCA'] as $table => $_) {
7162  $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', $table, 'pid IN (' . $inList . ')' . BackendUtility::deleteClause($table));
7163  if ($count && ($this->tableReadOnly($table) || !$this->checkModifyAccessList($table))) {
7164  return FALSE;
7165  }
7166  }
7167  }
7168  return TRUE;
7169  }
7170 
7179  public function isRecordCopied($table, $uid) {
7180  // If the record was copied:
7181  if (isset($this->copyMappingArray[$table][$uid])) {
7182  return TRUE;
7183  } elseif (isset($this->copyMappingArray[$table]) && in_array($uid, array_values($this->copyMappingArray[$table]))) {
7184  return TRUE;
7185  }
7186  return FALSE;
7187  }
7188 
7189  /******************************
7190  *
7191  * Clearing cache
7192  *
7193  ******************************/
7194 
7206  public function registerRecordIdForPageCacheClearing($table, $uid, $pid = null) {
7207  if (!is_array(static::$recordsToClearCacheFor[$table])) {
7208  static::$recordsToClearCacheFor[$table] = array();
7209  }
7210  static::$recordsToClearCacheFor[$table][] = (int)$uid;
7211  if ($pid !== null) {
7212  if (!is_array(static::$recordPidsForDeletedRecords[$table])) {
7213  static::$recordPidsForDeletedRecords[$table] = array();
7214  }
7215  static::$recordPidsForDeletedRecords[$table][$uid][] = (int)$pid;
7216  }
7217  }
7218 
7229  public function clear_cache($table, $uid) {
7231  $originalValues = static::$recordsToClearCacheFor;
7232  // Clear the queue temporarily
7233  static::$recordsToClearCacheFor = array();
7234  static::$recordsToClearCacheFor[$table] = array();
7235  static::$recordsToClearCacheFor[$table][] = (int)$uid;
7236 
7237  $this->processClearCacheQueue();
7238  // Reset the queue to its original value again
7239  static::$recordsToClearCacheFor = $originalValues;
7240  }
7241 
7246  protected function processClearCacheQueue() {
7247  $tagsToClear = array();
7248  $clearCacheCommands = array();
7249 
7250  foreach (static::$recordsToClearCacheFor as $table => $uids) {
7251  foreach (array_unique($uids) as $uid) {
7252  if (!isset($GLOBALS['TCA'][$table]) || $uid <= 0) {
7253  return;
7254  }
7255  // For move commands we may get more than 1 parent.
7256  $pageUids = $this->getOriginalParentOfRecord($table, $uid);
7257  foreach ($pageUids as $originalParent) {
7258  list($tagsToClearFromPrepare, $clearCacheCommandsFromPrepare)
7259  = $this->prepareCacheFlush($table, $uid, $originalParent);
7260  $tagsToClear = array_merge($tagsToClear, $tagsToClearFromPrepare);
7261  $clearCacheCommands = array_merge($clearCacheCommands, $clearCacheCommandsFromPrepare);
7262  }
7263  }
7264  }
7265 
7267  $cacheManager = $this->getCacheManager();
7268  foreach ($tagsToClear as $tag => $_) {
7269  $cacheManager->flushCachesInGroupByTag('pages', $tag);
7270  }
7271 
7272  // Execute collected clear cache commands from page TSConfig
7273  foreach ($clearCacheCommands as $command) {
7274  $this->clear_cacheCmd($command);
7275  }
7276 
7277  // Reset the cache clearing array
7278  static::$recordsToClearCacheFor = array();
7279 
7280  // Reset the original pid array
7281  static::$recordPidsForDeletedRecords = array();
7282  }
7283 
7293  protected function prepareCacheFlush($table, $uid, $pid) {
7294  $tagsToClear = array();
7295  $clearCacheCommands = array();
7296  $pageUid = 0;
7297  // Get Page TSconfig relavant:
7298  $TSConfig = $this->getTCEMAIN_TSconfig($pid);
7299  if (empty($TSConfig['clearCache_disable'])) {
7300  // If table is "pages":
7301  $pageIdsThatNeedCacheFlush = array();
7302  if ($table === 'pages' || $table === 'pages_language_overlay') {
7303  if ($table === 'pages_language_overlay') {
7304  $pageUid = $this->getPID($table, $uid);
7305  } else {
7306  $pageUid = $uid;
7307  }
7308  // Builds list of pages on the SAME level as this page (siblings)
7309  $res_tmp = $GLOBALS['TYPO3_DB']->exec_SELECTquery('A.pid AS pid, B.uid AS uid', 'pages A, pages B', 'A.uid=' . (int)$pageUid . ' AND B.pid=A.pid AND B.deleted=0 AND A.pid >= 0');
7310  $pid_tmp = 0;
7311  while ($row_tmp = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res_tmp)) {
7312  $pageIdsThatNeedCacheFlush[] = (int)$row_tmp['uid'];
7313  $pid_tmp = (int)$row_tmp['pid'];
7314  // Add children as well:
7315  if ($TSConfig['clearCache_pageSiblingChildren']) {
7316  $res_tmp2 = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', 'pages', 'pid=' . (int)$row_tmp['uid'] . ' AND deleted=0');
7317  while ($row_tmp2 = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res_tmp2)) {
7318  $pageIdsThatNeedCacheFlush[] = (int)$row_tmp2['uid'];
7319  }
7320  $GLOBALS['TYPO3_DB']->sql_free_result($res_tmp2);
7321  }
7322  }
7323  $GLOBALS['TYPO3_DB']->sql_free_result($res_tmp);
7324  // Finally, add the parent page as well:
7325  if ($pid_tmp > 0) {
7326  $pageIdsThatNeedCacheFlush[] = (int)$pid_tmp;
7327  }
7328  // Add grand-parent as well:
7329  if ($TSConfig['clearCache_pageGrandParent']) {
7330  $res_tmp = $GLOBALS['TYPO3_DB']->exec_SELECTquery('pid', 'pages', 'uid=' . (int)$pid_tmp);
7331  if ($row_tmp = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res_tmp)) {
7332  $pageIdsThatNeedCacheFlush[] = (int)$row_tmp['pid'];
7333  }
7334  $GLOBALS['TYPO3_DB']->sql_free_result($res_tmp);
7335  }
7336  } else {
7337  // For other tables than "pages", delete cache for the records "parent page".
7338  $pageIdsThatNeedCacheFlush[] = $pageUid = (int)$this->getPID($table, $uid);
7339  }
7340  // Call pre-processing function for clearing of cache for page ids:
7341  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval'])) {
7342  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval'] as $funcName) {
7343  $_params = array('pageIdArray' => &$pageIdsThatNeedCacheFlush, 'table' => $table, 'uid' => $uid, 'functionID' => 'clear_cache()');
7344  // Returns the array of ids to clear, FALSE if nothing should be cleared! Never an empty array!
7345  GeneralUtility::callUserFunction($funcName, $_params, $this);
7346  }
7347  }
7348  // Delete cache for selected pages:
7349  foreach ($pageIdsThatNeedCacheFlush as $pageId) {
7350  // Workspaces always use "-1" as the page id which do not
7351  // point to real pages and caches at all. Flushing caches for
7352  // those records does not make sense and decreases performance
7353  if ($pageId >= 0) {
7354  $tagsToClear['pageId_' . $pageId] = TRUE;
7355  }
7356  }
7357  // Queue delete cache for current table and record
7358  $tagsToClear[$table] = TRUE;
7359  $tagsToClear[$table . '_' . $uid] = TRUE;
7360  }
7361  // Clear cache for pages entered in TSconfig:
7362  if (!empty($TSConfig['clearCacheCmd'])) {
7363  $commands = GeneralUtility::trimExplode(',', $TSConfig['clearCacheCmd'], TRUE);
7364  $clearCacheCommands = array_unique($commands);
7365  }
7366  // Call post processing function for clear-cache:
7367  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'])) {
7368  $_params = array('table' => $table, 'uid' => $uid, 'uid_page' => $pageUid, 'TSConfig' => $TSConfig);
7369  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'] as $_funcRef) {
7370  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
7371  }
7372  }
7373  return array(
7374  $tagsToClear,
7375  $clearCacheCommands
7376  );
7377  }
7378 
7418  public function clear_cacheCmd($cacheCmd) {
7419  if (is_object($this->BE_USER)) {
7420  $this->BE_USER->writelog(3, 1, 0, 0, 'User %s has cleared the cache (cacheCmd=%s)', array($this->BE_USER->user['username'], $cacheCmd));
7421  }
7422  // Clear cache for either ALL pages or ALL tables!
7423  switch (strtolower($cacheCmd)) {
7424  case 'pages':
7425  if ($this->admin || $this->BE_USER->getTSConfigVal('options.clearCache.pages')) {
7426  $this->getCacheManager()->flushCachesInGroup('pages');
7427  }
7428  break;
7429  case 'all':
7430  if ($this->admin || $this->BE_USER->getTSConfigVal('options.clearCache.all')) {
7431  // Clear cache group "all" of caching framework caches
7432  $this->getCacheManager()->flushCachesInGroup('all');
7433  $GLOBALS['TYPO3_DB']->exec_TRUNCATEquery('cache_treelist');
7434  // Clearing additional cache tables:
7435  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearAllCache_additionalTables'])) {
7436  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearAllCache_additionalTables'] as $tableName) {
7437  GeneralUtility::deprecationLog('Hook clearAllCache_additionalTables in DataHandler is deprecated in 6.2 and will be removed two versions later. Use the caching framework with database backend instead.');
7438  if (!preg_match('/[^[:alnum:]_]/', $tableName) && substr($tableName, -5) === 'cache') {
7439  $GLOBALS['TYPO3_DB']->exec_TRUNCATEquery($tableName);
7440  } else {
7441  throw new \RuntimeException('TYPO3 Fatal Error: Trying to flush table "' . $tableName . '" with "Clear All Cache"', 1270853922);
7442  }
7443  }
7444  }
7445  }
7446 
7447  break;
7448  case 'temp_cached':
7449  case 'system':
7450  if ($this->admin || $this->BE_USER->getTSConfigVal('options.clearCache.system')
7451  || ((bool) $GLOBALS['TYPO3_CONF_VARS']['SYS']['clearCacheSystem'] === TRUE && $this->admin)) {
7452  $this->getCacheManager()->flushCachesInGroup('system');
7453  }
7454  break;
7455  }
7456 
7457  $tagsToFlush = array();
7458  // Clear cache for a page ID!
7459  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($cacheCmd)) {
7460  $list_cache = array($cacheCmd);
7461  // Call pre-processing function for clearing of cache for page ids:
7462  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval'])) {
7463  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearPageCacheEval'] as $funcName) {
7464  $_params = array('pageIdArray' => &$list_cache, 'cacheCmd' => $cacheCmd, 'functionID' => 'clear_cacheCmd()');
7465  // Returns the array of ids to clear, FALSE if nothing should be cleared! Never an empty array!
7466  GeneralUtility::callUserFunction($funcName, $_params, $this);
7467  }
7468  }
7469  // Delete cache for selected pages:
7470  if (is_array($list_cache)) {
7471  foreach ($list_cache as $pageId) {
7472  $tagsToFlush[] = 'pageId_' . (int)$pageId;
7473  }
7474  }
7475  }
7476  // flush cache by tag
7477  if (GeneralUtility::isFirstPartOfStr(strtolower($cacheCmd), 'cachetag:')) {
7478  $cacheTag = substr($cacheCmd, 9);
7479  $tagsToFlush[] = $cacheTag;
7480  }
7481  // process caching framwork operations
7482  if (count($tagsToFlush) > 0) {
7483  foreach (array_unique($tagsToFlush) as $tag) {
7484  $this->getCacheManager()->flushCachesInGroupByTag('pages', $tag);
7485  }
7486  }
7487 
7488  // Call post processing function for clear-cache:
7489  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'])) {
7490  $_params = array('cacheCmd' => strtolower($cacheCmd));
7491  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'] as $_funcRef) {
7492  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
7493  }
7494  }
7495  }
7496 
7497  /*****************************
7498  *
7499  * Logging
7500  *
7501  *****************************/
7518  public function log($table, $recuid, $action, $recpid, $error, $details, $details_nr = -1, $data = array(), $event_pid = -1, $NEWid = '') {
7519  if (!$this->enableLogging) {
7520  return 0;
7521  }
7522  // Type value for tce_db.php
7523  $type = 1;
7524  if (!$this->storeLogMessages) {
7525  $details = '';
7526  }
7527  if ($error > 0) {
7528  $detailMessage = $details;
7529  if (is_array($data)) {
7530  $detailMessage = vsprintf($details, $data);
7531  }
7532  $this->errorLog[] = '[' . $type . '.' . $action . '.' . $details_nr . ']: ' . $detailMessage;
7533  }
7534  return $this->BE_USER->writelog($type, $action, $error, $details_nr, $details, $data, $table, $recuid, $recpid, $event_pid, $NEWid);
7535  }
7536 
7546  public function newlog($message, $error = 0) {
7547  return $this->log('', 0, 0, 0, $error, '[newlog()] ' . $message, -1);
7548  }
7549 
7562  public function newlog2($message, $table, $uid, $pid = FALSE, $error = 0) {
7563  if ($pid === FALSE) {
7564  $propArr = $this->getRecordProperties($table, $uid);
7565  $pid = $propArr['pid'];
7566  }
7567  return $this->log($table, $uid, 0, 0, $error, $message, -1, array(), $this->eventPid($table, $uid, $pid));
7568  }
7569 
7577  public function printLogErrorMessages($redirect) {
7578  $res_log = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_log', 'type=1 AND action<256 AND userid=' . (int)$this->BE_USER->user['uid'] . ' AND tstamp=' . (int)$GLOBALS['EXEC_TIME'] . ' AND error<>0');
7579  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res_log)) {
7580  $log_data = unserialize($row['log_data']);
7581  $msg = $row['error'] . ': ' . sprintf($row['details'], $log_data[0], $log_data[1], $log_data[2], $log_data[3], $log_data[4]);
7582  $flashMessage = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessage', htmlspecialchars($msg), '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR, TRUE);
7584  $flashMessageService = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessageService');
7586  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
7587  $defaultFlashMessageQueue->enqueue($flashMessage);
7588  }
7589  $GLOBALS['TYPO3_DB']->sql_free_result($res_log);
7590  }
7591 
7592  /*****************************
7593  *
7594  * Internal (do not use outside Core!)
7595  *
7596  *****************************/
7604  public function internal_clearPageCache() {
7606  $this->getCacheManager()->flushCachesInGroup('pages');
7607  }
7608 
7619  public function insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray) {
7620  $result = $fieldArray;
7621  foreach ($fieldArray as $field => $value) {
7622  switch ($GLOBALS['TCA'][$table]['columns'][$field]['config']['type']) {
7623  case 'inline':
7624  if ($GLOBALS['TCA'][$table]['columns'][$field]['config']['foreign_field']) {
7625  if (!\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($value)) {
7626  $result[$field] = count(GeneralUtility::trimExplode(',', $value, TRUE));
7627  }
7628  }
7629  break;
7630  }
7631  }
7632  return $result;
7633  }
7634 
7642  public function getAutoVersionId($table, $id) {
7643  $result = NULL;
7644  if (isset($this->autoVersionIdMap[$table][$id])) {
7645  $result = $this->autoVersionIdMap[$table][$id];
7646  }
7647  return $result;
7648  }
7649 
7657  protected function overlayAutoVersionId($table, $id) {
7658  $autoVersionId = $this->getAutoVersionId($table, $id);
7659  if (is_null($autoVersionId) === FALSE) {
7660  $id = $autoVersionId;
7661  }
7662  return $id;
7663  }
7664 
7671  protected function addNewValuesToRemapStackChildIds(array $idValues) {
7672  foreach ($idValues as $idValue) {
7673  if (strpos($idValue, 'NEW') === 0) {
7674  $this->remapStackChildIds[$idValue] = TRUE;
7675  }
7676  }
7677  }
7678 
7689  protected function resolveVersionedRecords($tableName, $fieldNames, $sortingField, array $liveIds) {
7691  $resolver = GeneralUtility::makeInstance(
7692  'TYPO3\\CMS\\Core\\DataHandling\\PlainDataResolver',
7693  $tableName,
7694  $liveIds,
7695  $sortingField
7696  );
7697 
7698  $resolver->setWorkspaceId($this->BE_USER->workspace);
7699  $resolver->setKeepDeletePlaceholder(FALSE);
7700  $resolver->setKeepMovePlaceholder(FALSE);
7701  $resolver->setKeepLiveIds(TRUE);
7702  $recordIds = $resolver->get();
7703 
7704  $records = array();
7705  foreach ($recordIds as $recordId) {
7706  $records[$recordId] = BackendUtility::getRecord($tableName, $recordId, $fieldNames);
7707  }
7708 
7709  return $records;
7710  }
7711 
7719  protected function getOuterMostInstance() {
7720  if (!isset($this->outerMostInstance)) {
7721  $stack = array_reverse(debug_backtrace());
7722  foreach ($stack as $stackItem) {
7723  if (isset($stackItem['object']) && $stackItem['object'] instanceof \TYPO3\CMS\Core\DataHandling\DataHandler) {
7724  $this->outerMostInstance = $stackItem['object'];
7725  break;
7726  }
7727  }
7728  }
7729  return $this->outerMostInstance;
7730  }
7731 
7739  public function isOuterMostInstance() {
7740  return $this->getOuterMostInstance() === $this;
7741  }
7742 
7748  protected function getMemoryCache() {
7749  return $this->getCacheManager()->getCache('cache_runtime');
7750  }
7751 
7752 
7761  protected function isNestedElementCallRegistered($table, $id, $identifier) {
7762  $nestedElementCalls = (array)$this->runtimeCache->get($this->cachePrefixNestedElementCalls);
7763  return isset($nestedElementCalls[$identifier][$table][$id]);
7764  }
7765 
7775  protected function registerNestedElementCall($table, $id, $identifier) {
7776  $nestedElementCalls = (array)$this->runtimeCache->get($this->cachePrefixNestedElementCalls);
7777  $nestedElementCalls[$identifier][$table][$id] = TRUE;
7778  $this->runtimeCache->set($this->cachePrefixNestedElementCalls, $nestedElementCalls);
7779  }
7780 
7786  protected function resetNestedElementCalls() {
7787  $this->runtimeCache->remove($this->cachePrefixNestedElementCalls);
7788  }
7789 
7801  protected function isElementToBeDeleted($table, $id) {
7802  $elementsToBeDeleted = (array)$this->runtimeCache->get('core-datahandler-elementsToBeDeleted');
7803  return isset($elementsToBeDeleted[$table][$id]);
7804  }
7805 
7812  protected function registerElementsToBeDeleted() {
7813  $elementsToBeDeleted = (array)$this->runtimeCache->get('core-datahandler-elementsToBeDeleted');
7814  $this->runtimeCache->set('core-datahandler-elementsToBeDeleted', array_merge($elementsToBeDeleted, $this->getCommandMapElements('delete')));
7815  }
7816 
7823  protected function resetElementsToBeDeleted() {
7824  $this->runtimeCache->remove('core-datahandler-elementsToBeDeleted');
7825  }
7826 
7834  protected function unsetElementsToBeDeleted(array $elements) {
7835  $elements = GeneralUtility::arrayDiffAssocRecursive($elements, $this->getCommandMapElements('delete'));
7836  foreach ($elements as $key => $value) {
7837  if (empty($value)) {
7838  unset($elements[$key]);
7839  }
7840  }
7841  return $elements;
7842  }
7843 
7850  protected function getCommandMapElements($needle) {
7851  $elements = array();
7852  foreach ($this->cmdmap as $tableName => $idArray) {
7853  foreach ($idArray as $id => $commandArray) {
7854  foreach ($commandArray as $command => $value) {
7855  if ($value && $command == $needle) {
7856  $elements[$tableName][$id] = TRUE;
7857  }
7858  }
7859  }
7860  }
7861  return $elements;
7862  }
7863 
7870  protected function controlActiveElements() {
7871  if (!empty($this->control['active'])) {
7872  $this->setNullValues(
7873  $this->control['active'],
7874  $this->datamap
7875  );
7876  }
7877  }
7878 
7888  protected function setNullValues(array $active, array &$haystack) {
7889  foreach ($active as $key => $value) {
7890  // Nested data is processes recursively
7891  if (is_array($value)) {
7892  $this->setNullValues(
7893  $value,
7894  $haystack[$key]
7895  );
7896  // Field has not been activated in the user interface,
7897  // thus a NULL value shall be stored in the database
7898  } elseif ($value == 0) {
7899  $haystack[$key] = NULL;
7900  }
7901  }
7902  }
7903 
7910  protected function getFieldEvalCacheIdentifier($additionalIdentifier) {
7911  return 'core-datahandler-eval-' . md5($additionalIdentifier);
7912  }
7913 
7919  protected function createRelationHandlerInstance() {
7920  return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler');
7921  }
7922 
7928  protected function getCacheManager() {
7929  return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Cache\\CacheManager');
7930  }
7931 
7936  protected function getResourceFactory() {
7937  return \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance();
7938  }
7939 
7940 }
version_remapMMForVersionSwap_execSwap($table, $id, $swapWith)
getRecordProperties($table, $id, $noWSOL=FALSE)
updateFlexFormData($flexFormId, array $modifications)
static stripSlashesOnArray(array &$theArray)
static getTSconfig_pidValue($table, $uid, $pid)
int_pageTreeInfo($CPtable, $pid, $counter, $rootID)
copyRecord_fixRTEmagicImages($table, $theNewSQLID)
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=TRUE, $includeEmptyValues=TRUE, $enableUnsetFeature=TRUE)
moveRecord_procBasedOnFieldType($table, $uid, $destPid, $field, $value, $conf)
applyFiltersToValues(array $tcaFieldConfiguration, array $values)
checkValue_flex($res, $value, $tcaFieldConf, $PP, $uploadedFiles, $field)
static getRecordWSOL($table, $uid, $fields=' *', $where='', $useDeleteClause=TRUE, $unsetMovePointers=FALSE)
doesBranchExist($inList, $pid, $perms, $recurse)
static mkdir_deep($directory, $deepDirectory='')
static getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields=' *')
hook_processDatamap_afterDatabaseOperations(&$hookObjectsArr, &$status, &$table, &$id, &$fieldArray)
getPreviousLocalizedRecordUid($table, $uid, $pid, $language)
static writeFile($file, $content, $changePermissions=FALSE)
$parameters
Definition: FileDumpEID.php:15
getUnique($table, $field, $value, $id, $newPid=0)
static setValueByPath(array $array, $path, $value, $delimiter='/')
checkValue_text_Eval($value, $evalArray, $is_in)
insertNewCopyVersion($table, $fieldArray, $realPid)
static getItemLabel($table, $col, $printAllWrap='')
static getRecordsByField($theTable, $theField, $theValue, $whereClause='', $groupBy='', $orderBy='', $limit='', $useDeleteClause=TRUE)
checkStoredRecord($table, $id, $fieldArray, $action)
copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $_1, $_2, $_3=null, $workspaceOptions=array())
checkValue($table, $field, $value, $id, $status, $realPid, $tscPID)
static isFirstPartOfStr($str, $partStr)
fixCopyAfterDuplFields($table, $uid, $prevUid, $update, $newData=array())
setTSconfigPermissions($fieldArray, $TSConfig_p)
static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=FALSE)
moveRecord_procFields($table, $uid, $destPid)
checkValue_radio($res, $value, $tcaFieldConf, $PP)
versionizeRecord($table, $id, $label, $delete=FALSE)
checkValue_inline($res, $value, $tcaFieldConf, $PP, $field, array $additionalData=NULL)
$uid
Definition: server.php:36
setNullValues(array $active, array &$haystack)
deleteVersionsForRecord($table, $uid, $forceHardDelete)
static getUserObj($classRef, $checkPrefix='', $silent=FALSE)
registerNestedElementCall($table, $id, $identifier)
static getMovePlaceholder($table, $uid, $fields=' *', $workspace=NULL)
moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid)
checkValue_check($res, $value, $tcaFieldConf, $PP, $field='')
start($data, $cmd, $altUserObject='')
deleteRecord_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, $pObj)
triggerRemapAction($table, $id, array $callback, array $arguments, $forceRemapStackActions=FALSE)
getRecordsWithSameValue($tableName, $uid, $fieldName, $value, $pageId=0)
die
Definition: index.php:6
static trimExplode($delim, $string, $removeEmptyValues=FALSE, $limit=0)
copyRecord_raw($table, $uid, $pid, $overrideArray=array(), array $workspaceOptions=array())
static getFlexFormDS($conf, $row, $table, $fieldName='', $WSOL=TRUE, $newRecordPidValue=0)
checkValue_flex_procInData_travDS(&$dataValues, $dataValues_current, $uploadedFiles, $DSelements, $pParams, $callBackFunc, $structurePath, $workspaceOptions=array())
deleteRecord($table, $uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE, $undeleteRecord=FALSE)
resorting($table, $pid, $sortRow, $return_SortNumber_After_This_Uid)
static callUserFunction($funcName, &$params, &$ref, $checkPrefix='', $errorMode=0)
addRemapAction($table, $id, array $callback, array $arguments)
checkValue_SW($res, $value, $tcaFieldConf, $table, $id, $curValue, $status, $realPid, $recFID, $field, $uploadedFiles, $tscPID, array $additionalData=NULL)
insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray)
static getRecordTitle($table, $row, $prep=FALSE, $forceResult=TRUE)
updateDB($table, $id, $fieldArray)
static getInlineLocalizationMode($table, $fieldOrConfig)
static split_fileref($fileNameWithPath)
checkRecordUpdateAccess($table, $id, $data=FALSE, &$hookObjectsArr=FALSE)
static resolveSheetDefInDS($dataStructArray, $sheet='sDEF')
checkValue_input_Eval($value, $evalArray, $is_in)
getVersionizedIncomingFieldArray($table, $id, &$incomingFieldArray, &$registerDBList)
isTableAllowedForThisPage($page_uid, $checkTable)
compareFieldArrayWithCurrentAndUnset($table, $id, $fieldArray)
remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
registerRecordIdForPageCacheClearing($table, $uid, $pid=null)
if($list_of_literals) if(!empty($literals)) if(!empty($literals)) $result
Analyse literals to prepend the N char to them if their contents aren&#39;t numeric.
static getUrl($url, $includeHeader=0, $requestHeaders=FALSE, &$report=NULL)
checkValue_flex_procInData($dataPart, $dataPart_current, $uploadedFiles, $dataStructArray, $pParams, $callBackFunc='', $workspaceOptions=array())
static getRecordLocalization($table, $uid, $language, $andWhereClause='')
deleteEl($table, $uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE)
checkRecordInsertAccess($insertTable, $pid, $action=1)
getPlaceholderTitleForTableLabel($table, $placeholderContent=NULL)
deleteSpecificPage($uid, $forceHardDelete=FALSE)
log($table, $recuid, $action, $recpid, $error, $details, $details_nr=-1, $data=array(), $event_pid=-1, $NEWid='')
checkValue_checkMax($tcaFieldConf, $valueArray)
static getTCAtypes($table, $rec, $useFieldNameAsKey=0)
addDefaultPermittedLanguageIfNotSet($table, &$incomingFieldArray)
static getRecordRaw($table, $where='', $fields=' *')
isSubmittedValueEqualToStoredValue($submittedValue, $storedValue, $storedType, $allowNull=FALSE)
doesPageHaveUnallowedTables($page_uid, $doktype)
static RTEsetup($RTEprop, $table, $field, $type='')
static formatSize($sizeInBytes, $labels='')
debug($variable='', $name=' *variable *', $line=' *line *', $file=' *file *', $recursiveDepth=3, $debugLevel=E_DEBUG)
static array_merge(array $arr1, array $arr2)
static selectVersionsOfRecord($table, $uid, $fields=' *', $workspace=0, $includeDeletedRecords=FALSE, $row=NULL)
copySpecificPage($uid, $destPid, $copyTablesArray, $first=0)
getCopyHeader($table, $pid, $field, $value, $count, $prevTitle='')
static fixVersioningPid($table, &$rr, $ignoreWorkspaceMatch=FALSE)
_ACTION_FLEX_FORMdata(&$valueArray, $actionCMDs)
deleteRecord_procFields($table, $uid, $undeleteRecord=FALSE)
static getSpecConfParts($str, $defaultExtras)
checkValue_text($res, $value, $tcaFieldConf, $PP, $field='')
static getLiveVersionOfRecord($table, $uid, $fields=' *')
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]
static removeArrayEntryByValue(array $array, $cmpValue)
process_uploads_traverseArray(&$outputArr, $inputArr, $keyToSet)
static xml2array($string, $NSprefix='', $reportDocTag=FALSE)
moveRecord_raw($table, $uid, $destPid)
static getFileAbsFileName($filename, $onlyRelative=TRUE, $relToTYPO3_mainDir=FALSE)
deletePages($uid, $force=FALSE, $forceHardDelete=FALSE)
static evalWriteFile($pArr, $currentRecord)
static getPagesTSconfig($id, $rootLine=NULL, $returnPartArray=FALSE)
static arrayDiffAssocRecursive(array $array1, array $array2)
copyL10nOverlayRecords($table, $uid, $destPid, $first=0, $overrideValues=array(), $excludeFields='')
checkValue_group_select($res, $value, $tcaFieldConf, $PP, $uploadedFiles, $field)
remapListedDBRecords_procDBRefs($conf, $value, $MM_localUid, $table)
extFileFunctions($table, $field, $filelist, $func)
static deleteClause($table, $tableAlias='')
isNestedElementCallRegistered($table, $id, $identifier)
insertDB($table, $id, $fieldArray, $newVersion=FALSE, $suggestedUid=0, $dontSetNewIdIndex=FALSE)
checkValue_input($res, $value, $tcaFieldConf, $PP, $field='')
static upload_copy_move($source, $destination)
newlog2($message, $table, $uid, $pid=FALSE, $error=0)
getLocalTCE($stripslashesValues=FALSE, $dontProcessTransformations=TRUE)
getFieldEvalCacheIdentifier($additionalIdentifier)