TYPO3 CMS  TYPO3_7-6
Import.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Impexp;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
32 
36 class Import extends ImportExport
37 {
44  public $suggestedInsertUids = [];
45 
51  public $enableLogging = false;
52 
60  public $import_newId = [];
61 
67  public $import_newId_pids = [];
68 
74  public $import_data = [];
75 
81  protected $storageObjects = [];
82 
88  protected $legacyImport = false;
89 
93  protected $legacyImportFolder = null;
94 
100  protected $legacyImportTargetPath = '_imported/';
101 
108  'tt_content' => [
109  'image' => [
110  'titleTexts' => 'titleText',
111  'description' => 'imagecaption',
112  'links' => 'image_link',
113  'alternativeTexts' => 'altText'
114  ],
115  'media' => [
116  'description' => 'imagecaption',
117  ]
118  ],
119  'pages' => [
120  'media' => []
121  ],
122  'pages_language_overlay' => [
123  'media' => []
124  ]
125  ];
126 
134 
138  protected $filesPathForImport = null;
139 
143  protected $unlinkFiles = [];
144 
148  protected $alternativeFileName = [];
149 
153  protected $alternativeFilePath = [];
154 
158  protected $filePathMap = [];
159 
160  /**************************
161  * Initialize
162  *************************/
163 
169  public function init()
170  {
171  parent::init();
172  $this->mode = 'import';
173  }
174 
175  /***********************
176  * Import
177  ***********************/
178 
184  protected function initializeImport()
185  {
186  // Set this flag to indicate that an import is being/has been done.
187  $this->doesImport = 1;
188  // Initialize:
189  // These vars MUST last for the whole section not being cleared. They are used by the method setRelations() which are called at the end of the import session.
190  $this->import_mapId = [];
191  $this->import_newId = [];
192  $this->import_newId_pids = [];
193  // Temporary files stack initialized:
194  $this->unlinkFiles = [];
195  $this->alternativeFileName = [];
196  $this->alternativeFilePath = [];
197 
198  $this->initializeStorageObjects();
199  }
200 
206  protected function initializeStorageObjects()
207  {
209  $storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
210  $this->storageObjects = $storageRepository->findAll();
211  }
212 
219  public function importData($pid)
220  {
221  $this->initializeImport();
222 
223  // Write sys_file_storages first
225  // Write sys_file records and write the binary file data
226  $this->writeSysFileRecords();
227  // Write records, first pages, then the rest
228  // Fields with "hard" relations to database, files and flexform fields are kept empty during this run
229  $this->writeRecords_pages($pid);
230  $this->writeRecords_records($pid);
231  // Finally all the file and DB record references must be fixed. This is done after all records have supposedly been written to database:
232  // $this->import_mapId will indicate two things: 1) that a record WAS written to db and 2) that it has got a new id-number.
233  $this->setRelations();
234  // And when all DB relations are in place, we can fix file and DB relations in flexform fields (since data structures often depends on relations to a DS record):
235  $this->setFlexFormRelations();
236  // Unlink temporary files:
237  $this->unlinkTempFiles();
238  // Finally, traverse all records and process softreferences with substitution attributes.
239  $this->processSoftReferences();
240  // After all migrate records using sys_file_reference now
241  if ($this->legacyImport) {
243  }
244  }
245 
251  protected function writeSysFileStorageRecords()
252  {
253  if (!isset($this->dat['header']['records']['sys_file_storage'])) {
254  return;
255  }
256  $sysFileStorageUidsToBeResetToDefaultStorage = [];
257  foreach ($this->dat['header']['records']['sys_file_storage'] as $sysFileStorageUid => $_) {
258  $storageRecord = $this->dat['records']['sys_file_storage:' . $sysFileStorageUid]['data'];
259  // continue with Local, writable and online storage only
260  if ($storageRecord['driver'] === 'Local' && $storageRecord['is_writable'] && $storageRecord['is_online']) {
261  foreach ($this->storageObjects as $localStorage) {
262  if ($this->isEquivalentObjectStorage($localStorage, $storageRecord)) {
263  $this->import_mapId['sys_file_storage'][$sysFileStorageUid] = $localStorage->getUid();
264  break;
265  }
266  }
267 
268  if (!isset($this->import_mapId['sys_file_storage'][$sysFileStorageUid])) {
269  // Local, writable and online storage. Is allowed to be used to later write files in.
270  // Does currently not exist so add the record.
271  $this->addSingle('sys_file_storage', $sysFileStorageUid, 0);
272  }
273  } else {
274  // Storage with non Local drivers could be imported but must not be used to saves files in, because you
275  // could not be sure, that this is supported. The default storage will be used in this case.
276  // It could happen that non writable and non online storage will be created as dupes because you could not
277  // check the detailed configuration options at this point
278  $this->addSingle('sys_file_storage', $sysFileStorageUid, 0);
279  $sysFileStorageUidsToBeResetToDefaultStorage[] = $sysFileStorageUid;
280  }
281  }
282 
283  // Importing the added ones
284  $tce = $this->getNewTCE();
285  // Because all records are being submitted in their correct order with positive pid numbers - and so we should reverse submission order internally.
286  $tce->reverseOrder = 1;
287  $tce->isImporting = true;
288  $tce->start($this->import_data, []);
289  $tce->process_datamap();
290  $this->addToMapId($tce->substNEWwithIDs);
291 
292  $defaultStorageUid = null;
293  // get default storage
294  $defaultStorage = ResourceFactory::getInstance()->getDefaultStorage();
295  if ($defaultStorage !== null) {
296  $defaultStorageUid = $defaultStorage->getUid();
297  }
298  foreach ($sysFileStorageUidsToBeResetToDefaultStorage as $sysFileStorageUidToBeResetToDefaultStorage) {
299  $this->import_mapId['sys_file_storage'][$sysFileStorageUidToBeResetToDefaultStorage] = $defaultStorageUid;
300  }
301 
302  // unset the sys_file_storage records to prevent an import in writeRecords_records
303  unset($this->dat['header']['records']['sys_file_storage']);
304  }
305 
314  protected function isEquivalentObjectStorage(ResourceStorage $storageObject, array $storageRecord)
315  {
316  // compare the properties: driver, writable and online
317  if ($storageObject->getDriverType() === $storageRecord['driver']
318  && (bool)$storageObject->isWritable() === (bool)$storageRecord['is_writable']
319  && (bool)$storageObject->isOnline() === (bool)$storageRecord['is_online']
320  ) {
321  $storageRecordConfiguration = ResourceFactory::getInstance()
322  ->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
323  $storageObjectConfiguration = $storageObject->getConfiguration();
324  // compare the properties: pathType and basePath
325  if ($storageRecordConfiguration['pathType'] === $storageObjectConfiguration['pathType']
326  && $storageRecordConfiguration['basePath'] === $storageObjectConfiguration['basePath']
327  ) {
328  return true;
329  }
330  }
331  return false;
332  }
333 
339  public function checkImportPrerequisites()
340  {
341  $messages = [];
342 
343  // Check #1: Extension dependencies
344  $extKeysToInstall = [];
345  foreach ($this->dat['header']['extensionDependencies'] as $extKey) {
346  if (!empty($extKey) && !ExtensionManagementUtility::isLoaded($extKey)) {
347  $extKeysToInstall[] = $extKey;
348  }
349  }
350  if (!empty($extKeysToInstall)) {
351  $messages['missingExtensions'] = 'Before you can install this T3D file you need to install the extensions "'
352  . implode('", "', $extKeysToInstall) . '".';
353  }
354 
355  // Check #2: If the path for every local storage object exists.
356  // Else files can't get moved into a newly imported storage.
357  if (!empty($this->dat['header']['records']['sys_file_storage'])) {
358  foreach ($this->dat['header']['records']['sys_file_storage'] as $sysFileStorageUid => $_) {
359  $storageRecord = $this->dat['records']['sys_file_storage:' . $sysFileStorageUid]['data'];
360  // continue with Local, writable and online storage only
361  if ($storageRecord['driver'] === 'Local'
362  && $storageRecord['is_writable']
363  && $storageRecord['is_online']
364  ) {
365  foreach ($this->storageObjects as $localStorage) {
366  if ($this->isEquivalentObjectStorage($localStorage, $storageRecord)) {
367  // There is already an existing storage
368  break;
369  }
370 
371  // The storage from the import does not have an equivalent storage
372  // in the current instance (same driver, same path, etc.). Before
373  // the storage record can get inserted later on take care the path
374  // it points to really exists and is accessible.
375  $storageRecordUid = $storageRecord['uid'];
376  // Unset the storage record UID when trying to create the storage object
377  // as the record does not already exist in DB. The constructor of the
378  // storage object will check whether the target folder exists and set the
379  // isOnline flag depending on the outcome.
380  $storageRecord['uid'] = 0;
381  $resourceStorage = ResourceFactory::getInstance()->createStorageObject($storageRecord);
382  if (!$resourceStorage->isOnline()) {
383  $configuration = $resourceStorage->getConfiguration();
384  $messages['resourceStorageFolderMissing_' . $storageRecordUid] =
385  'The resource storage "'
386  . $resourceStorage->getName()
387  . $configuration['basePath']
388  . '" does not exist. Please create the directory prior to starting the import!';
389  }
390  }
391  }
392  }
393  }
394 
395  return $messages;
396  }
397 
403  protected function writeSysFileRecords()
404  {
405  if (!isset($this->dat['header']['records']['sys_file'])) {
406  return;
407  }
408  $this->addGeneralErrorsByTable('sys_file');
409 
410  // fetch fresh storage records from database
411  $storageRecords = $this->fetchStorageRecords();
412 
413  $defaultStorage = ResourceFactory::getInstance()->getDefaultStorage();
414 
415  $sanitizedFolderMappings = [];
416 
417  foreach ($this->dat['header']['records']['sys_file'] as $sysFileUid => $_) {
418  $fileRecord = $this->dat['records']['sys_file:' . $sysFileUid]['data'];
419 
420  $temporaryFile = null;
421  // check if there is the right file already in the local folder
422  if ($this->filesPathForImport !== null) {
423  if (is_file($this->filesPathForImport . '/' . $fileRecord['sha1']) && sha1_file($this->filesPathForImport . '/' . $fileRecord['sha1']) === $fileRecord['sha1']) {
424  $temporaryFile = $this->filesPathForImport . '/' . $fileRecord['sha1'];
425  }
426  }
427 
428  // save file to disk
429  if ($temporaryFile === null) {
430  $fileId = md5($fileRecord['storage'] . ':' . $fileRecord['identifier_hash']);
431  $temporaryFile = $this->writeTemporaryFileFromData($fileId);
432  if ($temporaryFile === null) {
433  // error on writing the file. Error message was already added
434  continue;
435  }
436  }
437 
438  $originalStorageUid = $fileRecord['storage'];
439  $useStorageFromStorageRecords = false;
440 
441  // replace storage id, if an alternative one was registered
442  if (isset($this->import_mapId['sys_file_storage'][$fileRecord['storage']])) {
443  $fileRecord['storage'] = $this->import_mapId['sys_file_storage'][$fileRecord['storage']];
444  $useStorageFromStorageRecords = true;
445  }
446 
447  if (empty($fileRecord['storage']) && !$this->isFallbackStorage($fileRecord['storage'])) {
448  // no storage for the file is defined, mostly because of a missing default storage.
449  $this->error('Error: No storage for the file "' . $fileRecord['identifier'] . '" with storage uid "' . $originalStorageUid . '"');
450  continue;
451  }
452 
453  // using a storage from the local storage is only allowed, if the uid is present in the
454  // mapping. Only in this case we could be sure, that it's a local, online and writable storage.
455  if ($useStorageFromStorageRecords && isset($storageRecords[$fileRecord['storage']])) {
457  $storage = ResourceFactory::getInstance()->getStorageObject($fileRecord['storage'], $storageRecords[$fileRecord['storage']]);
458  } elseif ($this->isFallbackStorage($fileRecord['storage'])) {
459  $storage = ResourceFactory::getInstance()->getStorageObject(0);
460  } elseif ($defaultStorage !== null) {
461  $storage = $defaultStorage;
462  } else {
463  $this->error('Error: No storage available for the file "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
464  continue;
465  }
466 
467  $newFile = null;
468 
469  // check, if there is an identical file
470  try {
471  if ($storage->hasFile($fileRecord['identifier'])) {
472  $file = $storage->getFile($fileRecord['identifier']);
473  if ($file->getSha1() === $fileRecord['sha1']) {
474  $newFile = $file;
475  }
476  }
477  } catch (Exception $e) {
478  }
479 
480  if ($newFile === null) {
481  $folderName = PathUtility::dirname(ltrim($fileRecord['identifier'], '/'));
482  if (in_array($folderName, $sanitizedFolderMappings)) {
483  $folderName = $sanitizedFolderMappings[$folderName];
484  }
485  if (!$storage->hasFolder($folderName)) {
486  try {
487  $importFolder = $storage->createFolder($folderName);
488  if ($importFolder->getIdentifier() !== $folderName && !in_array($folderName, $sanitizedFolderMappings)) {
489  $sanitizedFolderMappings[$folderName] = $importFolder->getIdentifier();
490  }
491  } catch (Exception $e) {
492  $this->error('Error: Folder "' . $folderName . '" could not be created for file "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
493  continue;
494  }
495  } else {
496  $importFolder = $storage->getFolder($folderName);
497  }
498 
499  try {
501  $newFile = $storage->addFile($temporaryFile, $importFolder, $fileRecord['name']);
502  } catch (Exception $e) {
503  $this->error('Error: File could not be added to the storage: "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
504  continue;
505  }
506 
507  if ($newFile->getSha1() !== $fileRecord['sha1']) {
508  $this->error('Error: The hash of the written file is not identical to the import data! File could be corrupted! File: "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
509  }
510  }
511 
512  // save the new uid in the import id map
513  $this->import_mapId['sys_file'][$fileRecord['uid']] = $newFile->getUid();
514  $this->fixUidLocalInSysFileReferenceRecords($fileRecord['uid'], $newFile->getUid());
515  }
516 
517  // unset the sys_file records to prevent an import in writeRecords_records
518  unset($this->dat['header']['records']['sys_file']);
519  // remove all sys_file_reference records that point to file records which are unknown
520  // in the system to prevent exceptions
522  }
523 
531  {
532  if (!isset($this->dat['header']['records']['sys_file_reference'])) {
533  return;
534  }
535 
536  foreach ($this->dat['header']['records']['sys_file_reference'] as $sysFileReferenceUid => $_) {
537  $fileReferenceRecord = $this->dat['records']['sys_file_reference:' . $sysFileReferenceUid]['data'];
538  if (!in_array($fileReferenceRecord['uid_local'], $this->import_mapId['sys_file'])) {
539  unset($this->dat['header']['records']['sys_file_reference'][$sysFileReferenceUid]);
540  unset($this->dat['records']['sys_file_reference:' . $sysFileReferenceUid]);
541  $this->error('Error: sys_file_reference record ' . (int)$sysFileReferenceUid
542  . ' with relation to sys_file record ' . (int)$fileReferenceRecord['uid_local']
543  . ', which is not part of the import data, was not imported.'
544  );
545  }
546  }
547  }
548 
555  protected function isFallbackStorage($storageId)
556  {
557  return $storageId === 0 || $storageId === '0';
558  }
559 
576  protected function fixUidLocalInSysFileReferenceRecords($oldFileUid, $newFileUid)
577  {
578  if (!isset($this->dat['header']['records']['sys_file_reference'])) {
579  return;
580  }
581 
582  foreach ($this->dat['header']['records']['sys_file_reference'] as $sysFileReferenceUid => $_) {
583  if (!isset($this->dat['records']['sys_file_reference:' . $sysFileReferenceUid]['hasBeenMapped'])
584  && $this->dat['records']['sys_file_reference:' . $sysFileReferenceUid]['data']['uid_local'] == $oldFileUid
585  ) {
586  $this->dat['records']['sys_file_reference:' . $sysFileReferenceUid]['hasBeenMapped'] = true;
587  $this->dat['records']['sys_file_reference:' . $sysFileReferenceUid]['data']['uid_local'] = $newFileUid;
588  }
589  }
590  }
591 
597  protected function initializeLegacyImportFolder()
598  {
600  $folder = $this->getBackendUser()->getDefaultUploadFolder();
601  if ($folder === false) {
602  $this->error('Error: the backend users default upload folder is missing! No files will be imported!');
603  }
604  if (!$folder->hasFolder($this->legacyImportTargetPath)) {
605  try {
606  $this->legacyImportFolder = $folder->createFolder($this->legacyImportTargetPath);
607  } catch (Exception $e) {
608  $this->error('Error: the import folder in the default upload folder could not be created! No files will be imported!');
609  }
610  } else {
611  $this->legacyImportFolder = $folder->getSubfolder($this->legacyImportTargetPath);
612  }
613  }
614 
621  protected function fetchStorageRecords()
622  {
623  $whereClause = BackendUtility::BEenableFields('sys_file_storage');
624  $whereClause .= BackendUtility::deleteClause('sys_file_storage');
625 
626  $rows = $this->getDatabaseConnection()->exec_SELECTgetRows(
627  '*',
628  'sys_file_storage',
629  '1=1' . $whereClause,
630  '',
631  '',
632  '',
633  'uid'
634  );
635 
636  return $rows;
637  }
638 
646  protected function writeTemporaryFileFromData($fileId, $dataKey = 'files_fal')
647  {
648  $temporaryFilePath = null;
649  if (is_array($this->dat[$dataKey][$fileId])) {
650  $temporaryFilePathInternal = GeneralUtility::tempnam('import_temp_');
651  GeneralUtility::writeFile($temporaryFilePathInternal, $this->dat[$dataKey][$fileId]['content']);
652  clearstatcache();
653  if (@is_file($temporaryFilePathInternal)) {
654  $this->unlinkFiles[] = $temporaryFilePathInternal;
655  if (filesize($temporaryFilePathInternal) == $this->dat[$dataKey][$fileId]['filesize']) {
656  $temporaryFilePath = $temporaryFilePathInternal;
657  } else {
658  $this->error('Error: temporary file ' . $temporaryFilePathInternal . ' had a size (' . filesize($temporaryFilePathInternal) . ') different from the original (' . $this->dat[$dataKey][$fileId]['filesize'] . ')');
659  }
660  } else {
661  $this->error('Error: temporary file ' . $temporaryFilePathInternal . ' was not written as it should have been!');
662  }
663  } else {
664  $this->error('Error: No file found for ID ' . $fileId);
665  }
666  return $temporaryFilePath;
667  }
668 
676  public function writeRecords_pages($pid)
677  {
678  // First, write page structure if any:
679  if (is_array($this->dat['header']['records']['pages'])) {
680  $this->addGeneralErrorsByTable('pages');
681  // $pageRecords is a copy of the pages array in the imported file. Records here are unset one by one when the addSingle function is called.
682  $pageRecords = $this->dat['header']['records']['pages'];
683  $this->import_data = [];
684  // First add page tree if any
685  if (is_array($this->dat['header']['pagetree'])) {
686  $pagesFromTree = $this->flatInversePageTree($this->dat['header']['pagetree']);
687  foreach ($pagesFromTree as $uid) {
688  $thisRec = $this->dat['header']['records']['pages'][$uid];
689  // PID: Set the main $pid, unless a NEW-id is found
690  $setPid = isset($this->import_newId_pids[$thisRec['pid']]) ? $this->import_newId_pids[$thisRec['pid']] : $pid;
691  $this->addSingle('pages', $uid, $setPid);
692  unset($pageRecords[$uid]);
693  }
694  }
695  // Then add all remaining pages not in tree on root level:
696  if (!empty($pageRecords)) {
697  $remainingPageUids = array_keys($pageRecords);
698  foreach ($remainingPageUids as $pUid) {
699  $this->addSingle('pages', $pUid, $pid);
700  }
701  }
702  // Now write to database:
703  $tce = $this->getNewTCE();
704  $tce->isImporting = true;
705  $this->callHook('before_writeRecordsPages', [
706  'tce' => &$tce,
707  'data' => &$this->import_data
708  ]);
709  $tce->suggestedInsertUids = $this->suggestedInsertUids;
710  $tce->start($this->import_data, []);
711  $tce->process_datamap();
712  $this->callHook('after_writeRecordsPages', [
713  'tce' => &$tce
714  ]);
715  // post-processing: Registering new ids (end all tcemain sessions with this)
716  $this->addToMapId($tce->substNEWwithIDs);
717  // In case of an update, order pages from the page tree correctly:
718  if ($this->update && is_array($this->dat['header']['pagetree'])) {
719  $this->writeRecords_pages_order();
720  }
721  }
722  }
723 
732  public function writeRecords_pages_order()
733  {
734  $cmd_data = [];
735  // Get uid-pid relations and traverse them in order to map to possible new IDs
736  $pidsFromTree = $this->flatInversePageTree_pid($this->dat['header']['pagetree']);
737  foreach ($pidsFromTree as $origPid => $newPid) {
738  if ($newPid >= 0 && $this->dontIgnorePid('pages', $origPid)) {
739  // If the page had a new id (because it was created) use that instead!
740  if (substr($this->import_newId_pids[$origPid], 0, 3) === 'NEW') {
741  if ($this->import_mapId['pages'][$origPid]) {
742  $mappedPid = $this->import_mapId['pages'][$origPid];
743  $cmd_data['pages'][$mappedPid]['move'] = $newPid;
744  }
745  } else {
746  $cmd_data['pages'][$origPid]['move'] = $newPid;
747  }
748  }
749  }
750  // Execute the move commands if any:
751  if (!empty($cmd_data)) {
752  $tce = $this->getNewTCE();
753  $this->callHook('before_writeRecordsPagesOrder', [
754  'tce' => &$tce,
755  'data' => &$cmd_data
756  ]);
757  $tce->start([], $cmd_data);
758  $tce->process_cmdmap();
759  $this->callHook('after_writeRecordsPagesOrder', [
760  'tce' => &$tce
761  ]);
762  }
763  }
764 
774  public function flatInversePageTree_pid($idH, $a = [], $pid = -1)
775  {
776  if (is_array($idH)) {
777  $idH = array_reverse($idH);
778  foreach ($idH as $v) {
779  $a[$v['uid']] = $pid;
780  if (is_array($v['subrow'])) {
781  $a = $this->flatInversePageTree_pid($v['subrow'], $a, $v['uid']);
782  }
783  }
784  }
785  return $a;
786  }
787 
795  public function writeRecords_records($pid)
796  {
797  // Write the rest of the records
798  $this->import_data = [];
799  if (is_array($this->dat['header']['records'])) {
800  foreach ($this->dat['header']['records'] as $table => $recs) {
801  $this->addGeneralErrorsByTable($table);
802  if ($table != 'pages') {
803  foreach ($recs as $uid => $thisRec) {
804  // PID: Set the main $pid, unless a NEW-id is found
805  $setPid = isset($this->import_mapId['pages'][$thisRec['pid']])
806  ? (int)$this->import_mapId['pages'][$thisRec['pid']]
807  : (int)$pid;
808  if (is_array($GLOBALS['TCA'][$table]) && isset($GLOBALS['TCA'][$table]['ctrl']['rootLevel'])) {
809  $rootLevelSetting = (int)$GLOBALS['TCA'][$table]['ctrl']['rootLevel'];
810  if ($rootLevelSetting === 1) {
811  $setPid = 0;
812  } elseif ($rootLevelSetting === 0 && $setPid === 0) {
813  $this->error('Error: Record type ' . $table . ' is not allowed on pid 0');
814  continue;
815  }
816  }
817  // Add record:
818  $this->addSingle($table, $uid, $setPid);
819  }
820  }
821  }
822  } else {
823  $this->error('Error: No records defined in internal data array.');
824  }
825  // Now write to database:
826  $tce = $this->getNewTCE();
827  $this->callHook('before_writeRecordsRecords', [
828  'tce' => &$tce,
829  'data' => &$this->import_data
830  ]);
831  $tce->suggestedInsertUids = $this->suggestedInsertUids;
832  // Because all records are being submitted in their correct order with positive pid numbers - and so we should reverse submission order internally.
833  $tce->reverseOrder = 1;
834  $tce->isImporting = true;
835  $tce->start($this->import_data, []);
836  $tce->process_datamap();
837  $this->callHook('after_writeRecordsRecords', [
838  'tce' => &$tce
839  ]);
840  // post-processing: Removing files and registering new ids (end all tcemain sessions with this)
841  $this->addToMapId($tce->substNEWwithIDs);
842  // In case of an update, order pages from the page tree correctly:
843  if ($this->update) {
844  $this->writeRecords_records_order($pid);
845  }
846  }
847 
857  public function writeRecords_records_order($mainPid)
858  {
859  $cmd_data = [];
860  if (is_array($this->dat['header']['pagetree'])) {
861  $pagesFromTree = $this->flatInversePageTree($this->dat['header']['pagetree']);
862  } else {
863  $pagesFromTree = [];
864  }
865  if (is_array($this->dat['header']['pid_lookup'])) {
866  foreach ($this->dat['header']['pid_lookup'] as $pid => $recList) {
867  $newPid = isset($this->import_mapId['pages'][$pid]) ? $this->import_mapId['pages'][$pid] : $mainPid;
869  foreach ($recList as $tableName => $uidList) {
870  // If $mainPid===$newPid then we are on root level and we can consider to move pages as well!
871  // (they will not be in the page tree!)
872  if (($tableName != 'pages' || !$pagesFromTree[$pid]) && is_array($uidList)) {
873  $uidList = array_reverse(array_keys($uidList));
874  foreach ($uidList as $uid) {
875  if ($this->dontIgnorePid($tableName, $uid)) {
876  $cmd_data[$tableName][$uid]['move'] = $newPid;
877  } else {
878  }
879  }
880  }
881  }
882  }
883  }
884  }
885  // Execute the move commands if any:
886  if (!empty($cmd_data)) {
887  $tce = $this->getNewTCE();
888  $this->callHook('before_writeRecordsRecordsOrder', [
889  'tce' => &$tce,
890  'data' => &$cmd_data
891  ]);
892  $tce->start([], $cmd_data);
893  $tce->process_cmdmap();
894  $this->callHook('after_writeRecordsRecordsOrder', [
895  'tce' => &$tce
896  ]);
897  }
898  }
899 
911  public function addSingle($table, $uid, $pid)
912  {
913  if ($this->import_mode[$table . ':' . $uid] === 'exclude') {
914  return;
915  }
916  $record = $this->dat['records'][$table . ':' . $uid]['data'];
917  if (is_array($record)) {
918  if ($this->update && $this->doesRecordExist($table, $uid) && $this->import_mode[$table . ':' . $uid] !== 'as_new') {
919  $ID = $uid;
920  } elseif ($table === 'sys_file_metadata' && $record['sys_language_uid'] == '0' && $this->import_mapId['sys_file'][$record['file']]) {
921  // on adding sys_file records the belonging sys_file_metadata record was also created
922  // if there is one the record need to be overwritten instead of creating a new one.
923  $recordInDatabase = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
924  'uid',
925  'sys_file_metadata',
926  'file = ' . $this->import_mapId['sys_file'][$record['file']] . ' AND sys_language_uid = 0 AND pid = 0'
927  );
928  // if no record could be found, $this->import_mapId['sys_file'][$record['file']] is pointing
929  // to a file, that was already there, thus a new metadata record should be created
930  if (is_array($recordInDatabase)) {
931  $this->import_mapId['sys_file_metadata'][$record['uid']] = $recordInDatabase['uid'];
932  $ID = $recordInDatabase['uid'];
933  } else {
934  $ID = StringUtility::getUniqueId('NEW');
935  }
936  } else {
937  $ID = StringUtility::getUniqueId('NEW');
938  }
939  $this->import_newId[$table . ':' . $ID] = ['table' => $table, 'uid' => $uid];
940  if ($table == 'pages') {
941  $this->import_newId_pids[$uid] = $ID;
942  }
943  // Set main record data:
944  $this->import_data[$table][$ID] = $record;
945  $this->import_data[$table][$ID]['tx_impexp_origuid'] = $this->import_data[$table][$ID]['uid'];
946  // Reset permission data:
947  if ($table === 'pages') {
948  // Have to reset the user/group IDs so pages are owned by importing user. Otherwise strange things may happen for non-admins!
949  unset($this->import_data[$table][$ID]['perms_userid']);
950  unset($this->import_data[$table][$ID]['perms_groupid']);
951  }
952  // PID and UID:
953  unset($this->import_data[$table][$ID]['uid']);
954  // Updates:
956  unset($this->import_data[$table][$ID]['pid']);
957  } else {
958  // Inserts:
959  $this->import_data[$table][$ID]['pid'] = $pid;
960  if (($this->import_mode[$table . ':' . $uid] === 'force_uid' && $this->update || $this->force_all_UIDS) && $this->getBackendUser()->isAdmin()) {
961  $this->import_data[$table][$ID]['uid'] = $uid;
962  $this->suggestedInsertUids[$table . ':' . $uid] = 'DELETE';
963  }
964  }
965  // Setting db/file blank:
966  foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) {
967  switch ((string)$config['type']) {
968  case 'db':
969 
970  case 'file':
971  // Fixed later in ->setRelations() [because we need to know ALL newly created IDs before we can map relations!]
972  // In the meantime we set NO values for relations.
973  //
974  // BUT for field uid_local of table sys_file_reference the relation MUST not be cleared here,
975  // because the value is already the uid of the right imported sys_file record.
976  // @see fixUidLocalInSysFileReferenceRecords()
977  // If it's empty or a uid to another record the FileExtensionFilter will throw an exception or
978  // delete the reference record if the file extension of the related record doesn't match.
979  if ($table !== 'sys_file_reference' && $field !== 'uid_local') {
980  $this->import_data[$table][$ID][$field] = '';
981  }
982  break;
983  case 'flex':
984  // Fixed later in setFlexFormRelations()
985  // In the meantime we set NO value for flexforms - this is mainly because file references
986  // inside will not be processed properly; In fact references will point to no file
987  // or existing files (in which case there will be double-references which is a big problem of course!)
988  $this->import_data[$table][$ID][$field] = '';
989  break;
990  }
991  }
992  } elseif ($table . ':' . $uid != 'pages:0') {
993  // On root level we don't want this error message.
994  $this->error('Error: no record was found in data array!');
995  }
996  }
997 
1005  public function addToMapId($substNEWwithIDs)
1006  {
1007  foreach ($this->import_data as $table => $recs) {
1008  foreach ($recs as $id => $value) {
1009  $old_uid = $this->import_newId[$table . ':' . $id]['uid'];
1010  if (isset($substNEWwithIDs[$id])) {
1011  $this->import_mapId[$table][$old_uid] = $substNEWwithIDs[$id];
1012  } elseif ($this->update) {
1013  // Map same ID to same ID....
1014  $this->import_mapId[$table][$old_uid] = $id;
1015  } else {
1016  // if $this->import_mapId contains already the right mapping, skip the error msg.
1017  // See special handling of sys_file_metadata in addSingle() => nothing to do
1018  if (!($table === 'sys_file_metadata' && isset($this->import_mapId[$table][$old_uid]) && $this->import_mapId[$table][$old_uid] == $id)) {
1019  $this->error('Possible error: ' . $table . ':' . $old_uid . ' had no new id assigned to it. This indicates that the record was not added to database during import. Please check changelog!');
1020  }
1021  }
1022  }
1023  }
1024  }
1025 
1031  public function getNewTCE()
1032  {
1033  $tce = GeneralUtility::makeInstance(DataHandler::class);
1034  $tce->stripslashes_values = false;
1035  $tce->dontProcessTransformations = 1;
1036  $tce->enableLogging = $this->enableLogging;
1037  $tce->alternativeFileName = $this->alternativeFileName;
1038  $tce->alternativeFilePath = $this->alternativeFilePath;
1039  return $tce;
1040  }
1041 
1047  public function unlinkTempFiles()
1048  {
1049  foreach ($this->unlinkFiles as $fileName) {
1050  if (GeneralUtility::isFirstPartOfStr($fileName, PATH_site . 'typo3temp/')) {
1052  clearstatcache();
1053  if (is_file($fileName)) {
1054  $this->error('Error: ' . $fileName . ' was NOT unlinked as it should have been!');
1055  }
1056  } else {
1057  $this->error('Error: ' . $fileName . ' was not in temp-path. Not removed!');
1058  }
1059  }
1060  $this->unlinkFiles = [];
1061  }
1062 
1063  /***************************
1064  * Import / Relations setting
1065  ***************************/
1066 
1075  public function setRelations()
1076  {
1077  $updateData = [];
1078  // import_newId contains a register of all records that was in the import memorys "records" key
1079  foreach ($this->import_newId as $nId => $dat) {
1080  $table = $dat['table'];
1081  $uid = $dat['uid'];
1082  // original UID - NOT the new one!
1083  // If the record has been written and received a new id, then proceed:
1084  if (is_array($this->import_mapId[$table]) && isset($this->import_mapId[$table][$uid])) {
1085  $thisNewUid = BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]);
1086  if (is_array($this->dat['records'][$table . ':' . $uid]['rels'])) {
1087  $thisNewPageUid = 0;
1088  if ($this->legacyImport) {
1089  if ($table != 'pages') {
1090  $oldPid = $this->dat['records'][$table . ':' . $uid]['data']['pid'];
1091  $thisNewPageUid = BackendUtility::wsMapId($table, $this->import_mapId['pages'][$oldPid]);
1092  } else {
1093  $thisNewPageUid = $thisNewUid;
1094  }
1095  }
1096  // Traverse relation fields of each record
1097  foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) {
1098  // uid_local of sys_file_reference needs no update because the correct reference uid was already written
1099  // @see ImportExport::fixUidLocalInSysFileReferenceRecords()
1100  if ($table === 'sys_file_reference' && $field === 'uid_local') {
1101  continue;
1102  }
1103  switch ((string)$config['type']) {
1104  case 'db':
1105  if (is_array($config['itemArray']) && !empty($config['itemArray'])) {
1106  $itemConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
1107  $valArray = $this->setRelations_db($config['itemArray'], $itemConfig);
1108  $updateData[$table][$thisNewUid][$field] = implode(',', $valArray);
1109  }
1110  break;
1111  case 'file':
1112  if (is_array($config['newValueFiles']) && !empty($config['newValueFiles'])) {
1113  $valArr = [];
1114  foreach ($config['newValueFiles'] as $fI) {
1115  $valArr[] = $this->import_addFileNameToBeCopied($fI);
1116  }
1117  if ($this->legacyImport && $this->legacyImportFolder === null && isset($this->legacyImportMigrationTables[$table][$field])) {
1118  // Do nothing - the legacy import folder is missing
1119  } elseif ($this->legacyImport && $this->legacyImportFolder !== null && isset($this->legacyImportMigrationTables[$table][$field])) {
1120  $refIds = [];
1121  foreach ($valArr as $tempFile) {
1122  $fileName = $this->alternativeFileName[$tempFile];
1123  $fileObject = null;
1124 
1125  try {
1126  // check, if there is alreay the same file in the folder
1127  if ($this->legacyImportFolder->hasFile($fileName)) {
1128  $fileStorage = $this->legacyImportFolder->getStorage();
1129  $file = $fileStorage->getFile($this->legacyImportFolder->getIdentifier() . $fileName);
1130  if ($file->getSha1() === sha1_file($tempFile)) {
1131  $fileObject = $file;
1132  }
1133  }
1134  } catch (Exception $e) {
1135  }
1136 
1137  if ($fileObject === null) {
1138  try {
1139  $fileObject = $this->legacyImportFolder->addFile($tempFile, $fileName, DuplicationBehavior::RENAME);
1140  } catch (Exception $e) {
1141  $this->error('Error: no file could be added to the storage for file name' . $this->alternativeFileName[$tempFile]);
1142  }
1143  }
1144  if ($fileObject !== null) {
1145  $refId = StringUtility::getUniqueId('NEW');
1146  $refIds[] = $refId;
1147  $updateData['sys_file_reference'][$refId] = [
1148  'uid_local' => $fileObject->getUid(),
1149  'uid_foreign' => $thisNewUid, // uid of your content record
1150  'tablenames' => $table,
1151  'fieldname' => $field,
1152  'pid' => $thisNewPageUid, // parent id of the parent page
1153  'table_local' => 'sys_file',
1154  ];
1155  }
1156  }
1157  $updateData[$table][$thisNewUid][$field] = implode(',', $refIds);
1158  if (!empty($this->legacyImportMigrationTables[$table][$field])) {
1159  $this->legacyImportMigrationRecords[$table][$thisNewUid][$field] = $refIds;
1160  }
1161  } else {
1162  $updateData[$table][$thisNewUid][$field] = implode(',', $valArr);
1163  }
1164  }
1165  break;
1166  }
1167  }
1168  } else {
1169  $this->error('Error: no record was found in data array!');
1170  }
1171  } else {
1172  $this->error('Error: this records is NOT created it seems! (' . $table . ':' . $uid . ')');
1173  }
1174  }
1175  if (!empty($updateData)) {
1176  $tce = $this->getNewTCE();
1177  $tce->isImporting = true;
1178  $this->callHook('before_setRelation', [
1179  'tce' => &$tce,
1180  'data' => &$updateData
1181  ]);
1182  $tce->start($updateData, []);
1183  $tce->process_datamap();
1184  // Replace the temporary "NEW" ids with the final ones.
1185  foreach ($this->legacyImportMigrationRecords as $table => $records) {
1186  foreach ($records as $uid => $fields) {
1187  foreach ($fields as $field => $referenceIds) {
1188  foreach ($referenceIds as $key => $referenceId) {
1189  $this->legacyImportMigrationRecords[$table][$uid][$field][$key] = $tce->substNEWwithIDs[$referenceId];
1190  }
1191  }
1192  }
1193  }
1194  $this->callHook('after_setRelations', [
1195  'tce' => &$tce
1196  ]);
1197  }
1198  }
1199 
1207  public function setRelations_db($itemArray, $itemConfig)
1208  {
1209  $valArray = [];
1210  foreach ($itemArray as $relDat) {
1211  if (is_array($this->import_mapId[$relDat['table']]) && isset($this->import_mapId[$relDat['table']][$relDat['id']])) {
1212  // Since non FAL file relation type group internal_type file_reference are handled as reference to
1213  // sys_file records Datahandler requires the value as uid of the the related sys_file record only
1214  if ($itemConfig['type'] === 'group' && $itemConfig['internal_type'] === 'file_reference') {
1215  $value = $this->import_mapId[$relDat['table']][$relDat['id']];
1216  } elseif ($itemConfig['type'] === 'input' && isset($itemConfig['wizards']['link'])) {
1217  // If an input field has a relation to a sys_file record this need to be converted back to
1218  // the public path. But use getPublicUrl here, because could normally only be a local file path.
1219  $fileUid = $this->import_mapId[$relDat['table']][$relDat['id']];
1220  // Fallback value
1221  $value = 'file:' . $fileUid;
1222  try {
1223  $file = ResourceFactory::getInstance()->retrieveFileOrFolderObject($fileUid);
1224  } catch (\Exception $e) {
1225  $file = null;
1226  }
1227  if ($file instanceof FileInterface) {
1228  $value = $file->getPublicUrl();
1229  }
1230  } else {
1231  $value = $relDat['table'] . '_' . $this->import_mapId[$relDat['table']][$relDat['id']];
1232  }
1233  $valArray[] = $value;
1234  } elseif ($this->isTableStatic($relDat['table']) || $this->isExcluded($relDat['table'], $relDat['id']) || $relDat['id'] < 0) {
1235  // Checking for less than zero because some select types could contain negative values,
1236  // eg. fe_groups (-1, -2) and sys_language (-1 = ALL languages). This must be handled on both export and import.
1237  $valArray[] = $relDat['table'] . '_' . $relDat['id'];
1238  } else {
1239  $this->error('Lost relation: ' . $relDat['table'] . ':' . $relDat['id']);
1240  }
1241  }
1242  return $valArray;
1243  }
1244 
1251  public function import_addFileNameToBeCopied($fI)
1252  {
1253  if (is_array($this->dat['files'][$fI['ID']])) {
1254  $tmpFile = null;
1255  // check if there is the right file already in the local folder
1256  if ($this->filesPathForImport !== null) {
1257  if (is_file($this->filesPathForImport . '/' . $this->dat['files'][$fI['ID']]['content_md5']) &&
1258  md5_file($this->filesPathForImport . '/' . $this->dat['files'][$fI['ID']]['content_md5']) === $this->dat['files'][$fI['ID']]['content_md5']) {
1259  $tmpFile = $this->filesPathForImport . '/' . $this->dat['files'][$fI['ID']]['content_md5'];
1260  }
1261  }
1262  if ($tmpFile === null) {
1263  $tmpFile = GeneralUtility::tempnam('import_temp_');
1264  GeneralUtility::writeFile($tmpFile, $this->dat['files'][$fI['ID']]['content']);
1265  }
1266  clearstatcache();
1267  if (@is_file($tmpFile)) {
1268  $this->unlinkFiles[] = $tmpFile;
1269  if (filesize($tmpFile) == $this->dat['files'][$fI['ID']]['filesize']) {
1270  $this->alternativeFileName[$tmpFile] = $fI['filename'];
1271  $this->alternativeFilePath[$tmpFile] = $this->dat['files'][$fI['ID']]['relFileRef'];
1272  return $tmpFile;
1273  } else {
1274  $this->error('Error: temporary file ' . $tmpFile . ' had a size (' . filesize($tmpFile) . ') different from the original (' . $this->dat['files'][$fI['ID']]['filesize'] . ')');
1275  }
1276  } else {
1277  $this->error('Error: temporary file ' . $tmpFile . ' was not written as it should have been!');
1278  }
1279  } else {
1280  $this->error('Error: No file found for ID ' . $fI['ID']);
1281  }
1282  return null;
1283  }
1284 
1292  public function setFlexFormRelations()
1293  {
1294  $updateData = [];
1295  // import_newId contains a register of all records that was in the import memorys "records" key
1296  foreach ($this->import_newId as $nId => $dat) {
1297  $table = $dat['table'];
1298  $uid = $dat['uid'];
1299  // original UID - NOT the new one!
1300  // If the record has been written and received a new id, then proceed:
1301  if (!isset($this->import_mapId[$table][$uid])) {
1302  $this->error('Error: this records is NOT created it seems! (' . $table . ':' . $uid . ')');
1303  continue;
1304  }
1305 
1306  if (!is_array($this->dat['records'][$table . ':' . $uid]['rels'])) {
1307  $this->error('Error: no record was found in data array!');
1308  continue;
1309  }
1310  $thisNewUid = BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]);
1311  // Traverse relation fields of each record
1312  foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) {
1313  switch ((string)$config['type']) {
1314  case 'flex':
1315  // Get XML content and set as default value (string, non-processed):
1316  $updateData[$table][$thisNewUid][$field] = $this->dat['records'][$table . ':' . $uid]['data'][$field];
1317  // If there has been registered relations inside the flex form field, run processing on the content:
1318  if (!empty($config['flexFormRels']['db']) || !empty($config['flexFormRels']['file'])) {
1319  $origRecordRow = BackendUtility::getRecord($table, $thisNewUid, '*');
1320  // This will fetch the new row for the element (which should be updated with any references to data structures etc.)
1321  $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
1322  if (is_array($origRecordRow) && is_array($conf) && $conf['type'] === 'flex') {
1323  // Get current data structure and value array:
1324  $dataStructArray = BackendUtility::getFlexFormDS($conf, $origRecordRow, $table, $field);
1325  $currentValueArray = GeneralUtility::xml2array($updateData[$table][$thisNewUid][$field]);
1326  // Do recursive processing of the XML data:
1327  $iteratorObj = GeneralUtility::makeInstance(DataHandler::class);
1328  $iteratorObj->callBackObj = $this;
1329  $currentValueArray['data'] = $iteratorObj->checkValue_flex_procInData(
1330  $currentValueArray['data'],
1331  [],
1332  [],
1333  $dataStructArray,
1334  [$table, $thisNewUid, $field, $config],
1335  'remapListedDBRecords_flexFormCallBack'
1336  );
1337  // The return value is set as an array which means it will be processed by tcemain for file and DB references!
1338  if (is_array($currentValueArray['data'])) {
1339  $updateData[$table][$thisNewUid][$field] = $currentValueArray;
1340  }
1341  }
1342  }
1343  break;
1344  }
1345  }
1346  }
1347  if (!empty($updateData)) {
1348  $tce = $this->getNewTCE();
1349  $tce->isImporting = true;
1350  $this->callHook('before_setFlexFormRelations', [
1351  'tce' => &$tce,
1352  'data' => &$updateData
1353  ]);
1354  $tce->start($updateData, []);
1355  $tce->process_datamap();
1356  $this->callHook('after_setFlexFormRelations', [
1357  'tce' => &$tce
1358  ]);
1359  }
1360  }
1361 
1374  public function remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)
1375  {
1376  // Extract parameters:
1377  list(, , , $config) = $pParams;
1378  // In case the $path is used as index without a trailing slash we will remove that
1379  if (!is_array($config['flexFormRels']['db'][$path]) && is_array($config['flexFormRels']['db'][rtrim($path, '/')])) {
1380  $path = rtrim($path, '/');
1381  }
1382  if (is_array($config['flexFormRels']['db'][$path])) {
1383  $valArray = $this->setRelations_db($config['flexFormRels']['db'][$path], $dsConf);
1384  $dataValue = implode(',', $valArray);
1385  }
1386  if (is_array($config['flexFormRels']['file'][$path])) {
1387  $valArr = [];
1388  foreach ($config['flexFormRels']['file'][$path] as $fI) {
1389  $valArr[] = $this->import_addFileNameToBeCopied($fI);
1390  }
1391  $dataValue = implode(',', $valArr);
1392  }
1393  return ['value' => $dataValue];
1394  }
1395 
1396  /**************************
1397  * Import / Soft References
1398  *************************/
1399 
1405  public function processSoftReferences()
1406  {
1407  // Initialize:
1408  $inData = [];
1409  // Traverse records:
1410  if (is_array($this->dat['header']['records'])) {
1411  foreach ($this->dat['header']['records'] as $table => $recs) {
1412  foreach ($recs as $uid => $thisRec) {
1413  // If there are soft references defined, traverse those:
1414  if (isset($GLOBALS['TCA'][$table]) && is_array($thisRec['softrefs'])) {
1415  // First traversal is to collect softref configuration and split them up based on fields.
1416  // This could probably also have been done with the "records" key instead of the header.
1417  $fieldsIndex = [];
1418  foreach ($thisRec['softrefs'] as $softrefDef) {
1419  // If a substitution token is set:
1420  if ($softrefDef['field'] && is_array($softrefDef['subst']) && $softrefDef['subst']['tokenID']) {
1421  $fieldsIndex[$softrefDef['field']][$softrefDef['subst']['tokenID']] = $softrefDef;
1422  }
1423  }
1424  // The new id:
1425  $thisNewUid = BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]);
1426  // Now, if there are any fields that require substitution to be done, lets go for that:
1427  foreach ($fieldsIndex as $field => $softRefCfgs) {
1428  if (is_array($GLOBALS['TCA'][$table]['columns'][$field])) {
1429  $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
1430  if ($conf['type'] === 'flex') {
1431  // This will fetch the new row for the element (which should be updated with any references to data structures etc.)
1432  $origRecordRow = BackendUtility::getRecord($table, $thisNewUid, '*');
1433  if (is_array($origRecordRow)) {
1434  // Get current data structure and value array:
1435  $dataStructArray = BackendUtility::getFlexFormDS($conf, $origRecordRow, $table, $field);
1436  $currentValueArray = GeneralUtility::xml2array($origRecordRow[$field]);
1437  // Do recursive processing of the XML data:
1439  $iteratorObj = GeneralUtility::makeInstance(DataHandler::class);
1440  $iteratorObj->callBackObj = $this;
1441  $currentValueArray['data'] = $iteratorObj->checkValue_flex_procInData($currentValueArray['data'], [], [], $dataStructArray, [$table, $uid, $field, $softRefCfgs], 'processSoftReferences_flexFormCallBack');
1442  // The return value is set as an array which means it will be processed by tcemain for file and DB references!
1443  if (is_array($currentValueArray['data'])) {
1444  $inData[$table][$thisNewUid][$field] = $currentValueArray;
1445  }
1446  }
1447  } else {
1448  // Get tokenizedContent string and proceed only if that is not blank:
1449  $tokenizedContent = $this->dat['records'][$table . ':' . $uid]['rels'][$field]['softrefs']['tokenizedContent'];
1450  if (strlen($tokenizedContent) && is_array($softRefCfgs)) {
1451  $inData[$table][$thisNewUid][$field] = $this->processSoftReferences_substTokens($tokenizedContent, $softRefCfgs, $table, $uid);
1452  }
1453  }
1454  }
1455  }
1456  }
1457  }
1458  }
1459  }
1460  // Now write to database:
1461  $tce = $this->getNewTCE();
1462  $tce->isImporting = true;
1463  $this->callHook('before_processSoftReferences', [
1464  'tce' => $tce,
1465  'data' => &$inData
1466  ]);
1467  $tce->enableLogging = true;
1468  $tce->start($inData, []);
1469  $tce->process_datamap();
1470  $this->callHook('after_processSoftReferences', [
1471  'tce' => $tce
1472  ]);
1473  }
1474 
1487  public function processSoftReferences_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)
1488  {
1489  // Extract parameters:
1490  list($table, $origUid, $field, $softRefCfgs) = $pParams;
1491  if (is_array($softRefCfgs)) {
1492  // First, find all soft reference configurations for this structure path (they are listed flat in the header):
1493  $thisSoftRefCfgList = [];
1494  foreach ($softRefCfgs as $sK => $sV) {
1495  if ($sV['structurePath'] === $path) {
1496  $thisSoftRefCfgList[$sK] = $sV;
1497  }
1498  }
1499  // If any was found, do processing:
1500  if (!empty($thisSoftRefCfgList)) {
1501  // Get tokenizedContent string and proceed only if that is not blank:
1502  $tokenizedContent = $this->dat['records'][$table . ':' . $origUid]['rels'][$field]['flexFormRels']['softrefs'][$path]['tokenizedContent'];
1503  if (strlen($tokenizedContent)) {
1504  $dataValue = $this->processSoftReferences_substTokens($tokenizedContent, $thisSoftRefCfgList, $table, $origUid);
1505  }
1506  }
1507  }
1508  // Return
1509  return ['value' => $dataValue];
1510  }
1511 
1521  public function processSoftReferences_substTokens($tokenizedContent, $softRefCfgs, $table, $uid)
1522  {
1523  // traverse each softref type for this field:
1524  foreach ($softRefCfgs as $cfg) {
1525  // Get token ID:
1526  $tokenID = $cfg['subst']['tokenID'];
1527  // Default is current token value:
1528  $insertValue = $cfg['subst']['tokenValue'];
1529  // Based on mode:
1530  switch ((string)$this->softrefCfg[$tokenID]['mode']) {
1531  case 'exclude':
1532  // Exclude is a simple passthrough of the value
1533  break;
1534  case 'editable':
1535  // Editable always picks up the value from this input array:
1536  $insertValue = $this->softrefInputValues[$tokenID];
1537  break;
1538  default:
1539  // Mapping IDs/creating files: Based on type, look up new value:
1540  switch ((string)$cfg['subst']['type']) {
1541  case 'file':
1542  // Create / Overwrite file:
1543  $insertValue = $this->processSoftReferences_saveFile($cfg['subst']['relFileName'], $cfg, $table, $uid);
1544  break;
1545  case 'db':
1546  default:
1547  // Trying to map database element if found in the mapID array:
1548  list($tempTable, $tempUid) = explode(':', $cfg['subst']['recordRef']);
1549  if (isset($this->import_mapId[$tempTable][$tempUid])) {
1550  $insertValue = BackendUtility::wsMapId($tempTable, $this->import_mapId[$tempTable][$tempUid]);
1551  // Look if reference is to a page and the original token value was NOT an integer - then we assume is was an alias and try to look up the new one!
1552  if ($tempTable === 'pages' && !MathUtility::canBeInterpretedAsInteger($cfg['subst']['tokenValue'])) {
1553  $recWithUniqueValue = BackendUtility::getRecord($tempTable, $insertValue, 'alias');
1554  if ($recWithUniqueValue['alias']) {
1555  $insertValue = $recWithUniqueValue['alias'];
1556  }
1557  } elseif (strpos($cfg['subst']['tokenValue'], ':') !== false) {
1558  list($tokenKey) = explode(':', $cfg['subst']['tokenValue']);
1559  $insertValue = $tokenKey . ':' . $insertValue;
1560  }
1561  }
1562  }
1563  }
1564  // Finally, swap the soft reference token in tokenized content with the insert value:
1565  $tokenizedContent = str_replace('{softref:' . $tokenID . '}', $insertValue, $tokenizedContent);
1566  }
1567  return $tokenizedContent;
1568  }
1569 
1579  public function processSoftReferences_saveFile($relFileName, $cfg, $table, $uid)
1580  {
1581  if ($fileHeaderInfo = $this->dat['header']['files'][$cfg['file_ID']]) {
1582  // Initialize; Get directory prefix for file and find possible RTE filename
1583  $dirPrefix = PathUtility::dirname($relFileName) . '/';
1584  $rteOrigName = $this->getRTEoriginalFilename(PathUtility::basename($relFileName));
1585  // If filename looks like an RTE file, and the directory is in "uploads/", then process as a RTE file!
1586  if ($rteOrigName && GeneralUtility::isFirstPartOfStr($dirPrefix, 'uploads/')) {
1587  // RTE:
1588  // First, find unique RTE file name:
1589  if (@is_dir((PATH_site . $dirPrefix))) {
1590  // From the "original" RTE filename, produce a new "original" destination filename which is unused.
1591  // Even if updated, the image should be unique. Currently the problem with this is that it leaves a lot of unused RTE images...
1592  $fileProcObj = $this->getFileProcObj();
1593  $origDestName = $fileProcObj->getUniqueName($rteOrigName, PATH_site . $dirPrefix);
1594  // Create copy file name:
1595  $pI = pathinfo($relFileName);
1596  $copyDestName = PathUtility::dirname($origDestName) . '/RTEmagicC_' . substr(PathUtility::basename($origDestName), 10) . '.' . $pI['extension'];
1597  if (
1598  !@is_file($copyDestName) && !@is_file($origDestName)
1599  && $origDestName === GeneralUtility::getFileAbsFileName($origDestName)
1600  && $copyDestName === GeneralUtility::getFileAbsFileName($copyDestName)
1601  ) {
1602  if ($this->dat['header']['files'][$fileHeaderInfo['RTE_ORIG_ID']]) {
1603  if ($this->legacyImport) {
1604  $fileName = PathUtility::basename($copyDestName);
1605  $this->writeSysFileResourceForLegacyImport($fileName, $cfg['file_ID']);
1606  $relFileName = $this->filePathMap[$cfg['file_ID']] . '" data-htmlarea-file-uid="' . $fileName . '" data-htmlarea-file-table="sys_file';
1607  // Also save the original file
1608  $originalFileName = PathUtility::basename($origDestName);
1609  $this->writeSysFileResourceForLegacyImport($originalFileName, $fileHeaderInfo['RTE_ORIG_ID']);
1610  } else {
1611  // Write the copy and original RTE file to the respective filenames:
1612  $this->writeFileVerify($copyDestName, $cfg['file_ID'], true);
1613  $this->writeFileVerify($origDestName, $fileHeaderInfo['RTE_ORIG_ID'], true);
1614  // Return the relative path of the copy file name:
1615  return PathUtility::stripPathSitePrefix($copyDestName);
1616  }
1617  } else {
1618  $this->error('ERROR: Could not find original file ID');
1619  }
1620  } else {
1621  $this->error('ERROR: The destination filenames "' . $copyDestName . '" and "' . $origDestName . '" either existed or have non-valid names');
1622  }
1623  } else {
1624  $this->error('ERROR: "' . PATH_site . $dirPrefix . '" was not a directory, so could not process file "' . $relFileName . '"');
1625  }
1626  } elseif (GeneralUtility::isFirstPartOfStr($dirPrefix, $this->fileadminFolderName . '/')) {
1627  // File in fileadmin/ folder:
1628  // Create file (and possible resources)
1629  $newFileName = $this->processSoftReferences_saveFile_createRelFile($dirPrefix, PathUtility::basename($relFileName), $cfg['file_ID'], $table, $uid);
1630  if (strlen($newFileName)) {
1631  $relFileName = $newFileName;
1632  } else {
1633  $this->error('ERROR: No new file created for "' . $relFileName . '"');
1634  }
1635  } else {
1636  $this->error('ERROR: Sorry, cannot operate on non-RTE files which are outside the fileadmin folder.');
1637  }
1638  } else {
1639  $this->error('ERROR: Could not find file ID in header.');
1640  }
1641  // Return (new) filename relative to PATH_site:
1642  return $relFileName;
1643  }
1644 
1655  public function processSoftReferences_saveFile_createRelFile($origDirPrefix, $fileName, $fileID, $table, $uid)
1656  {
1657  // If the fileID map contains an entry for this fileID then just return the relative filename of that entry;
1658  // we don't want to write another unique filename for this one!
1659  if (isset($this->fileIDMap[$fileID])) {
1660  return PathUtility::stripPathSitePrefix($this->fileIDMap[$fileID]);
1661  }
1662  if ($this->legacyImport) {
1663  // set dirPrefix to fileadmin because the right target folder is set and checked for permissions later
1664  $dirPrefix = $this->fileadminFolderName . '/';
1665  } else {
1666  // Verify FileMount access to dir-prefix. Returns the best alternative relative path if any
1667  $dirPrefix = $this->verifyFolderAccess($origDirPrefix);
1668  }
1669  if ($dirPrefix && (!$this->update || $origDirPrefix === $dirPrefix) && $this->checkOrCreateDir($dirPrefix)) {
1670  $fileHeaderInfo = $this->dat['header']['files'][$fileID];
1671  $updMode = $this->update && $this->import_mapId[$table][$uid] === $uid && $this->import_mode[$table . ':' . $uid] !== 'as_new';
1672  // Create new name for file:
1673  // Must have same ID in map array (just for security, is not really needed) and NOT be set "as_new".
1674 
1675  // Write main file:
1676  if ($this->legacyImport) {
1677  $fileWritten = $this->writeSysFileResourceForLegacyImport($fileName, $fileID);
1678  if ($fileWritten) {
1679  $newName = 'file:' . $fileName;
1680  return $newName;
1681  // no support for HTML/CSS file resources attached ATM - see below
1682  }
1683  } else {
1684  if ($updMode) {
1685  $newName = PATH_site . $dirPrefix . $fileName;
1686  } else {
1687  // Create unique filename:
1688  $fileProcObj = $this->getFileProcObj();
1689  $newName = $fileProcObj->getUniqueName($fileName, PATH_site . $dirPrefix);
1690  }
1691  if ($this->writeFileVerify($newName, $fileID)) {
1692  // If the resource was an HTML/CSS file with resources attached, we will write those as well!
1693  if (is_array($fileHeaderInfo['EXT_RES_ID'])) {
1694  $tokenizedContent = $this->dat['files'][$fileID]['tokenizedContent'];
1695  $tokenSubstituted = false;
1696  $fileProcObj = $this->getFileProcObj();
1697  if ($updMode) {
1698  foreach ($fileHeaderInfo['EXT_RES_ID'] as $res_fileID) {
1699  if ($this->dat['files'][$res_fileID]['filename']) {
1700  // Resolve original filename:
1701  $relResourceFileName = $this->dat['files'][$res_fileID]['parentRelFileName'];
1702  $absResourceFileName = GeneralUtility::resolveBackPath(PATH_site . $origDirPrefix . $relResourceFileName);
1703  $absResourceFileName = GeneralUtility::getFileAbsFileName($absResourceFileName);
1704  if ($absResourceFileName && GeneralUtility::isFirstPartOfStr($absResourceFileName, PATH_site . $this->fileadminFolderName . '/')) {
1705  $destDir = PathUtility::stripPathSitePrefix(PathUtility::dirname($absResourceFileName) . '/');
1706  if ($this->verifyFolderAccess($destDir, true) && $this->checkOrCreateDir($destDir)) {
1707  $this->writeFileVerify($absResourceFileName, $res_fileID);
1708  } else {
1709  $this->error('ERROR: Could not create file in directory "' . $destDir . '"');
1710  }
1711  } else {
1712  $this->error('ERROR: Could not resolve path for "' . $relResourceFileName . '"');
1713  }
1714  $tokenizedContent = str_replace('{EXT_RES_ID:' . $res_fileID . '}', $relResourceFileName, $tokenizedContent);
1715  $tokenSubstituted = true;
1716  }
1717  }
1718  } else {
1719  // Create the resouces directory name (filename without extension, suffixed "_FILES")
1720  $resourceDir = PathUtility::dirname($newName) . '/' . preg_replace('/\\.[^.]*$/', '', PathUtility::basename($newName)) . '_FILES';
1721  if (GeneralUtility::mkdir($resourceDir)) {
1722  foreach ($fileHeaderInfo['EXT_RES_ID'] as $res_fileID) {
1723  if ($this->dat['files'][$res_fileID]['filename']) {
1724  $absResourceFileName = $fileProcObj->getUniqueName($this->dat['files'][$res_fileID]['filename'], $resourceDir);
1725  $relResourceFileName = substr($absResourceFileName, strlen(PathUtility::dirname($resourceDir)) + 1);
1726  $this->writeFileVerify($absResourceFileName, $res_fileID);
1727  $tokenizedContent = str_replace('{EXT_RES_ID:' . $res_fileID . '}', $relResourceFileName, $tokenizedContent);
1728  $tokenSubstituted = true;
1729  }
1730  }
1731  }
1732  }
1733  // If substitutions has been made, write the content to the file again:
1734  if ($tokenSubstituted) {
1735  GeneralUtility::writeFile($newName, $tokenizedContent);
1736  }
1737  }
1738  return PathUtility::stripPathSitePrefix($newName);
1739  }
1740  }
1741  }
1742  return null;
1743  }
1744 
1753  public function writeFileVerify($fileName, $fileID, $bypassMountCheck = false)
1754  {
1755  $fileProcObj = $this->getFileProcObj();
1756  if (!$fileProcObj->actionPerms['addFile']) {
1757  $this->error('ERROR: You did not have sufficient permissions to write the file "' . $fileName . '"');
1758  return false;
1759  }
1760  // Just for security, check again. Should actually not be necessary.
1761  if (!$fileProcObj->checkPathAgainstMounts($fileName) && !$bypassMountCheck) {
1762  $this->error('ERROR: Filename "' . $fileName . '" was not allowed in destination path!');
1763  return false;
1764  }
1765  $fI = GeneralUtility::split_fileref($fileName);
1766  if (!$fileProcObj->checkIfAllowed($fI['fileext'], $fI['path'], $fI['file']) && (!$this->allowPHPScripts || !$this->getBackendUser()->isAdmin())) {
1767  $this->error('ERROR: Filename "' . $fileName . '" failed against extension check or deny-pattern!');
1768  return false;
1769  }
1770  if (!GeneralUtility::getFileAbsFileName($fileName)) {
1771  $this->error('ERROR: Filename "' . $fileName . '" was not a valid relative file path!');
1772  return false;
1773  }
1774  if (!$this->dat['files'][$fileID]) {
1775  $this->error('ERROR: File ID "' . $fileID . '" could not be found');
1776  return false;
1777  }
1778  GeneralUtility::writeFile($fileName, $this->dat['files'][$fileID]['content']);
1779  $this->fileIDMap[$fileID] = $fileName;
1780  if (md5(GeneralUtility::getUrl($fileName)) == $this->dat['files'][$fileID]['content_md5']) {
1781  return true;
1782  } else {
1783  $this->error('ERROR: File content "' . $fileName . '" was corrupted');
1784  return false;
1785  }
1786  }
1787 
1797  protected function writeSysFileResourceForLegacyImport(&$fileName, $fileId)
1798  {
1799  if ($this->legacyImportFolder === null) {
1800  return false;
1801  }
1802 
1803  if (!isset($this->dat['files'][$fileId])) {
1804  $this->error('ERROR: File ID "' . $fileId . '" could not be found');
1805  return false;
1806  }
1807 
1808  $temporaryFile = $this->writeTemporaryFileFromData($fileId, 'files');
1809  if ($temporaryFile === null) {
1810  // error on writing the file. Error message was already added
1811  return false;
1812  }
1813 
1814  $importFolder = $this->legacyImportFolder;
1815 
1816  if (isset($this->dat['files'][$fileId]['relFileName'])) {
1817  $relativeFilePath = PathUtility::dirname($this->dat['files'][$fileId]['relFileName']);
1818 
1819  if (!$this->legacyImportFolder->hasFolder($relativeFilePath)) {
1820  $this->legacyImportFolder->createFolder($relativeFilePath);
1821  }
1822  $importFolder = $this->legacyImportFolder->getSubfolder($relativeFilePath);
1823  }
1824 
1825  $fileObject = null;
1826 
1827  try {
1828  // check, if there is alreay the same file in the folder
1829  if ($importFolder->hasFile($fileName)) {
1830  $fileStorage = $importFolder->getStorage();
1831  $file = $fileStorage->getFile($importFolder->getIdentifier() . $fileName);
1832  if ($file->getSha1() === sha1_file($temporaryFile)) {
1833  $fileObject = $file;
1834  }
1835  }
1836  } catch (Exception $e) {
1837  }
1838 
1839  if ($fileObject === null) {
1840  try {
1841  $fileObject = $importFolder->addFile($temporaryFile, $fileName, DuplicationBehavior::RENAME);
1842  } catch (Exception $e) {
1843  $this->error('Error: no file could be added to the storage for file name ' . $this->alternativeFileName[$temporaryFile]);
1844  }
1845  }
1846 
1847  if (md5_file(PATH_site . $fileObject->getPublicUrl()) == $this->dat['files'][$fileId]['content_md5']) {
1848  $fileName = $fileObject->getUid();
1849  $this->fileIDMap[$fileId] = $fileName;
1850  $this->filePathMap[$fileId] = $fileObject->getPublicUrl();
1851  return true;
1852  } else {
1853  $this->error('ERROR: File content "' . $this->dat['files'][$fileId]['relFileName'] . '" was corrupted');
1854  }
1855 
1856  return false;
1857  }
1858 
1864  protected function migrateLegacyImportRecords()
1865  {
1866  $updateData= [];
1867 
1868  foreach ($this->legacyImportMigrationRecords as $table => $records) {
1869  foreach ($records as $uid => $fields) {
1870  $row = BackendUtility::getRecord($table, $uid);
1871  if (empty($row)) {
1872  continue;
1873  }
1874 
1875  foreach ($fields as $field => $referenceIds) {
1876  $fieldConfiguration = $this->legacyImportMigrationTables[$table][$field];
1877 
1878  if (isset($fieldConfiguration['titleTexts'])) {
1879  $titleTextField = $fieldConfiguration['titleTexts'];
1880  if (isset($row[$titleTextField]) && $row[$titleTextField] !== '') {
1881  $titleTextContents = explode(LF, $row[$titleTextField]);
1882  $updateData[$table][$uid][$titleTextField] = '';
1883  }
1884  }
1885 
1886  if (isset($fieldConfiguration['alternativeTexts'])) {
1887  $alternativeTextField = $fieldConfiguration['alternativeTexts'];
1888  if (isset($row[$alternativeTextField]) && $row[$alternativeTextField] !== '') {
1889  $alternativeTextContents = explode(LF, $row[$alternativeTextField]);
1890  $updateData[$table][$uid][$alternativeTextField] = '';
1891  }
1892  }
1893  if (isset($fieldConfiguration['description'])) {
1894  $descriptionField = $fieldConfiguration['description'];
1895  if ($row[$descriptionField] !== '') {
1896  $descriptionContents = explode(LF, $row[$descriptionField]);
1897  $updateData[$table][$uid][$descriptionField] = '';
1898  }
1899  }
1900  if (isset($fieldConfiguration['links'])) {
1901  $linkField = $fieldConfiguration['links'];
1902  if ($row[$linkField] !== '') {
1903  $linkContents = explode(LF, $row[$linkField]);
1904  $updateData[$table][$uid][$linkField] = '';
1905  }
1906  }
1907 
1908  foreach ($referenceIds as $key => $referenceId) {
1909  if (isset($titleTextContents[$key])) {
1910  $updateData['sys_file_reference'][$referenceId]['title'] = trim($titleTextContents[$key]);
1911  }
1912  if (isset($alternativeTextContents[$key])) {
1913  $updateData['sys_file_reference'][$referenceId]['alternative'] = trim($alternativeTextContents[$key]);
1914  }
1915  if (isset($descriptionContents[$key])) {
1916  $updateData['sys_file_reference'][$referenceId]['description'] = trim($descriptionContents[$key]);
1917  }
1918  if (isset($linkContents[$key])) {
1919  $updateData['sys_file_reference'][$referenceId]['link'] = trim($linkContents[$key]);
1920  }
1921  }
1922  }
1923  }
1924  }
1925 
1926  // update
1927  $tce = $this->getNewTCE();
1928  $tce->isImporting = true;
1929  $tce->start($updateData, []);
1930  $tce->process_datamap();
1931  }
1932 
1939  public function checkOrCreateDir($dirPrefix)
1940  {
1941  // Split dir path and remove first directory (which should be "fileadmin")
1942  $filePathParts = explode('/', $dirPrefix);
1943  $firstDir = array_shift($filePathParts);
1944  if ($firstDir === $this->fileadminFolderName && GeneralUtility::getFileAbsFileName($dirPrefix)) {
1945  $pathAcc = '';
1946  foreach ($filePathParts as $dirname) {
1947  $pathAcc .= '/' . $dirname;
1948  if (strlen($dirname)) {
1949  if (!@is_dir((PATH_site . $this->fileadminFolderName . $pathAcc))) {
1950  if (!GeneralUtility::mkdir((PATH_site . $this->fileadminFolderName . $pathAcc))) {
1951  $this->error('ERROR: Directory could not be created....B');
1952  return false;
1953  }
1954  }
1955  } elseif ($dirPrefix === $this->fileadminFolderName . $pathAcc) {
1956  return true;
1957  } else {
1958  $this->error('ERROR: Directory could not be created....A');
1959  }
1960  }
1961  }
1962  return false;
1963  }
1964 
1965  /**************************
1966  * File Input
1967  *************************/
1968 
1976  public function loadFile($filename, $all = false)
1977  {
1978  if (!@is_file($filename)) {
1979  $this->error('Filename not found: ' . $filename);
1980  return false;
1981  }
1982  $fI = pathinfo($filename);
1983  if (@is_dir($filename . '.files')) {
1984  if (GeneralUtility::isAllowedAbsPath($filename . '.files')) {
1985  // copy the folder lowlevel to typo3temp, because the files would be deleted after import
1986  $temporaryFolderName = $this->getTemporaryFolderName();
1987  GeneralUtility::copyDirectory($filename . '.files', $temporaryFolderName);
1988  $this->filesPathForImport = $temporaryFolderName;
1989  } else {
1990  $this->error('External import files for the given import source is currently not supported.');
1991  }
1992  }
1993  if (strtolower($fI['extension']) == 'xml') {
1994  // XML:
1995  $xmlContent = GeneralUtility::getUrl($filename);
1996  if (strlen($xmlContent)) {
1997  $this->dat = GeneralUtility::xml2array($xmlContent, '', true);
1998  if (is_array($this->dat)) {
1999  if ($this->dat['_DOCUMENT_TAG'] === 'T3RecordDocument' && is_array($this->dat['header']) && is_array($this->dat['records'])) {
2000  $this->loadInit();
2001  return true;
2002  } else {
2003  $this->error('XML file did not contain proper XML for TYPO3 Import');
2004  }
2005  } else {
2006  $this->error('XML could not be parsed: ' . $this->dat);
2007  }
2008  } else {
2009  $this->error('Error opening file: ' . $filename);
2010  }
2011  } else {
2012  // T3D
2013  if ($fd = fopen($filename, 'rb')) {
2014  $this->dat['header'] = $this->getNextFilePart($fd, 1, 'header');
2015  if ($all) {
2016  $this->dat['records'] = $this->getNextFilePart($fd, 1, 'records');
2017  $this->dat['files'] = $this->getNextFilePart($fd, 1, 'files');
2018  $this->dat['files_fal'] = $this->getNextFilePart($fd, 1, 'files_fal');
2019  }
2020  $this->loadInit();
2021  return true;
2022  } else {
2023  $this->error('Error opening file: ' . $filename);
2024  }
2025  fclose($fd);
2026  }
2027  return false;
2028  }
2029 
2040  public function getNextFilePart($fd, $unserialize = false, $name = '')
2041  {
2042  $initStrLen = 32 + 1 + 1 + 1 + 10 + 1;
2043  // Getting header data
2044  $initStr = fread($fd, $initStrLen);
2045  if (empty($initStr)) {
2046  $this->error('File does not contain data for "' . $name . '"');
2047  return null;
2048  }
2049  $initStrDat = explode(':', $initStr);
2050  if (strstr($initStrDat[0], 'Warning')) {
2051  $this->error('File read error: Warning message in file. (' . $initStr . fgets($fd) . ')');
2052  return null;
2053  }
2054  if ((string)$initStrDat[3] !== '') {
2055  $this->error('File read error: InitString had a wrong length. (' . $name . ')');
2056  return null;
2057  }
2058  $datString = fread($fd, (int)$initStrDat[2]);
2059  fread($fd, 1);
2060  if (md5($datString) === $initStrDat[0]) {
2061  if ($initStrDat[1]) {
2062  if ($this->compress) {
2063  $datString = gzuncompress($datString);
2064  } else {
2065  $this->error('Content read error: This file requires decompression, but this server does not offer gzcompress()/gzuncompress() functions.');
2066  return null;
2067  }
2068  }
2069  return $unserialize ? unserialize($datString) : $datString;
2070  } else {
2071  $this->error('MD5 check failed (' . $name . ')');
2072  }
2073  return null;
2074  }
2075 
2083  public function loadContent($filecontent)
2084  {
2085  $pointer = 0;
2086  $this->dat['header'] = $this->getNextContentPart($filecontent, $pointer, 1, 'header');
2087  $this->dat['records'] = $this->getNextContentPart($filecontent, $pointer, 1, 'records');
2088  $this->dat['files'] = $this->getNextContentPart($filecontent, $pointer, 1, 'files');
2089  $this->loadInit();
2090  }
2091 
2101  public function getNextContentPart($filecontent, &$pointer, $unserialize = false, $name = '')
2102  {
2103  $initStrLen = 32 + 1 + 1 + 1 + 10 + 1;
2104  // getting header data
2105  $initStr = substr($filecontent, $pointer, $initStrLen);
2106  $pointer += $initStrLen;
2107  $initStrDat = explode(':', $initStr);
2108  if ((string)$initStrDat[3] !== '') {
2109  $this->error('Content read error: InitString had a wrong length. (' . $name . ')');
2110  return null;
2111  }
2112  $datString = substr($filecontent, $pointer, (int)$initStrDat[2]);
2113  $pointer += (int)$initStrDat[2] + 1;
2114  if (md5($datString) === $initStrDat[0]) {
2115  if ($initStrDat[1]) {
2116  if ($this->compress) {
2117  $datString = gzuncompress($datString);
2118  return $unserialize ? unserialize($datString) : $datString;
2119  } else {
2120  $this->error('Content read error: This file requires decompression, but this server does not offer gzcompress()/gzuncompress() functions.');
2121  }
2122  }
2123  } else {
2124  $this->error('MD5 check failed (' . $name . ')');
2125  }
2126  return null;
2127  }
2128 
2134  public function loadInit()
2135  {
2136  $this->relStaticTables = (array)$this->dat['header']['relStaticTables'];
2137  $this->excludeMap = (array)$this->dat['header']['excludeMap'];
2138  $this->softrefCfg = (array)$this->dat['header']['softrefCfg'];
2139  $this->fixCharsets();
2140  if (
2141  isset($this->dat['header']['meta']['TYPO3_version'])
2142  && VersionNumberUtility::convertVersionNumberToInteger($this->dat['header']['meta']['TYPO3_version']) < 6000000
2143  ) {
2144  $this->legacyImport = true;
2145  $this->initializeLegacyImportFolder();
2146  }
2147  }
2148 
2155  public function fixCharsets()
2156  {
2157  $importCharset = $this->dat['header']['charset'];
2158  if ($importCharset) {
2159  if ($importCharset !== $this->getLanguageService()->charSet) {
2160  $this->error('CHARSET: Converting charset of input file (' . $importCharset . ') to the system charset (' . $this->getLanguageService()->charSet . ')');
2161  // Convert meta data:
2162  if (is_array($this->dat['header']['meta'])) {
2163  $this->getLanguageService()->csConvObj->convArray($this->dat['header']['meta'], $importCharset, $this->getLanguageService()->charSet);
2164  }
2165  // Convert record headers:
2166  if (is_array($this->dat['header']['records'])) {
2167  $this->getLanguageService()->csConvObj->convArray($this->dat['header']['records'], $importCharset, $this->getLanguageService()->charSet);
2168  }
2169  // Convert records themselves:
2170  if (is_array($this->dat['records'])) {
2171  $this->getLanguageService()->csConvObj->convArray($this->dat['records'], $importCharset, $this->getLanguageService()->charSet);
2172  }
2173  }
2174  } else {
2175  $this->error('CHARSET: No charset found in import file!');
2176  }
2177  }
2178 }
processSoftReferences_saveFile_createRelFile($origDirPrefix, $fileName, $fileID, $table, $uid)
Definition: Import.php:1655
static unlink_tempfile($uploadedTempFileName)
processSoftReferences_saveFile($relFileName, $cfg, $table, $uid)
Definition: Import.php:1579
fixUidLocalInSysFileReferenceRecords($oldFileUid, $newFileUid)
Definition: Import.php:576
static isFirstPartOfStr($str, $partStr)
remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)
Definition: Import.php:1374
static BEenableFields($table, $inv=false)
writeRecords_records_order($mainPid)
Definition: Import.php:857
removeSysFileReferenceRecordsFromImportDataWithRelationToMissingFile()
Definition: Import.php:530
processSoftReferences_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)
Definition: Import.php:1487
flatInversePageTree_pid($idH, $a=[], $pid=-1)
Definition: Import.php:774
addToMapId($substNEWwithIDs)
Definition: Import.php:1005
static getFlexFormDS($conf, $row, $table, $fieldName='', $WSOL=true, $newRecordPidValue=0)
static copyDirectory($source, $destination)
isFallbackStorage($storageId)
Definition: Import.php:555
loadFile($filename, $all=false)
Definition: Import.php:1976
static split_fileref($fileNameWithPath)
processSoftReferences_substTokens($tokenizedContent, $softRefCfgs, $table, $uid)
Definition: Import.php:1521
addSingle($table, $uid, $pid)
Definition: Import.php:911
static tempnam($filePrefix, $fileSuffix='')
getNextFilePart($fd, $unserialize=false, $name='')
Definition: Import.php:2040
doesRecordExist($table, $uid, $fields='')
setRelations_db($itemArray, $itemConfig)
Definition: Import.php:1207
loadContent($filecontent)
Definition: Import.php:2083
static getUrl($url, $includeHeader=0, $requestHeaders=false, &$report=null)
writeFileVerify($fileName, $fileID, $bypassMountCheck=false)
Definition: Import.php:1753
static xml2array($string, $NSprefix='', $reportDocTag=false)
checkOrCreateDir($dirPrefix)
Definition: Import.php:1939
$uid
Definition: server.php:38
getNextContentPart($filecontent, &$pointer, $unserialize=false, $name='')
Definition: Import.php:2101
import_addFileNameToBeCopied($fI)
Definition: Import.php:1251
static getFileAbsFileName($filename, $onlyRelative=true, $relToTYPO3_mainDir=false)
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
writeSysFileResourceForLegacyImport(&$fileName, $fileId)
Definition: Import.php:1797
static writeFile($file, $content, $changePermissions=false)
static deleteClause($table, $tableAlias='')
verifyFolderAccess($dirPrefix, $noAlternative=false)
writeTemporaryFileFromData($fileId, $dataKey='files_fal')
Definition: Import.php:646
isEquivalentObjectStorage(ResourceStorage $storageObject, array $storageRecord)
Definition: Import.php:314