TYPO3  7.6
ImportExport.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 
28 
63 {
69  public $showStaticRelations = false;
70 
76  public $fileadminFolderName = '';
77 
83  public $mode = '';
84 
90  public $update = false;
91 
97  public $doesImport = false;
98 
106  public $display_import_pid_record = array();
107 
114  public $suggestedInsertUids = array();
115 
121  public $import_mode = array();
122 
128  public $global_ignore_pid = false;
129 
135  public $force_all_UIDS = false;
136 
142  public $showDiff = false;
143 
149  public $allowPHPScripts = false;
150 
156  public $enableLogging = false;
157 
163  public $softrefInputValues = array();
164 
170  public $fileIDMap = array();
171 
177  public $maxFileSize = 1000000;
178 
184  public $maxRecordSize = 1000000;
185 
191  public $maxExportSize = 10000000;
192 
199  public $relOnlyTables = array();
200 
207  public $relStaticTables = array();
208 
214  public $excludeMap = array();
215 
221  public $softrefCfg = array();
222 
228  public $extensionDependencies = array();
229 
235  public $dontCompress = false;
236 
242  public $includeExtFileResources = false;
243 
249  public $extFileResourceExtensions = 'html,htm,css';
250 
256  public $import_mapId = array();
257 
265  public $import_newId = array();
266 
272  public $import_newId_pids = array();
273 
279  public $import_data = array();
280 
286  public $errorLog = array();
287 
293  public $cache_getRecordPath = array();
294 
300  public $checkPID_cache = array();
301 
308  public $compress = false;
309 
315  public $dat = array();
316 
322  protected $fileProcObj = null;
323 
330  protected $recordTypesIncludeFields = array();
331 
337  protected $defaultRecordIncludeFields = array('uid', 'pid');
338 
344  protected $storageObjects = array();
345 
351  protected $legacyImport = false;
352 
356  protected $legacyImportFolder = null;
357 
363  protected $legacyImportTargetPath = '_imported/';
364 
370  protected $legacyImportMigrationTables = array(
371  'tt_content' => array(
372  'image' => array(
373  'titleTexts' => 'titleText',
374  'description' => 'imagecaption',
375  'links' => 'image_link',
376  'alternativeTexts' => 'altText'
377  ),
378  'media' => array(
379  'description' => 'imagecaption',
380  )
381  ),
382  'pages' => array(
383  'media' => array()
384  ),
385  'pages_language_overlay' => array(
386  'media' => array()
387  )
388  );
389 
390 
397  protected $legacyImportMigrationRecords = array();
398 
402  protected $saveFilesOutsideExportFile = false;
403 
408 
412  protected $filesPathForImport = null;
413 
417  protected $unlinkFiles = array();
418 
422  protected $alternativeFileName = array();
423 
427  protected $alternativeFilePath = array();
428 
432  protected $filePathMap = array();
433 
437  protected $remainHeader = array();
438 
442  protected $iconFactory;
443 
447  public function __construct()
448  {
449  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
450  }
451 
452  /**************************
453  * Initialize
454  *************************/
455 
463  public function init($dontCompress = false, $mode = '')
464  {
465  $this->compress = function_exists('gzcompress');
466  $this->dontCompress = $dontCompress;
467  $this->mode = $mode;
468  $this->fileadminFolderName = !empty($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir']) ? rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') : 'fileadmin';
469  }
470 
471  /**************************
472  * Export / Init + Meta Data
473  *************************/
474 
480  public function setHeaderBasics()
481  {
482  // Initializing:
483  if (is_array($this->softrefCfg)) {
484  foreach ($this->softrefCfg as $key => $value) {
485  if (!strlen($value['mode'])) {
486  unset($this->softrefCfg[$key]);
487  }
488  }
489  }
490  // Setting in header memory:
491  // Version of file format
492  $this->dat['header']['XMLversion'] = '1.0';
493  // Initialize meta data array (to put it in top of file)
494  $this->dat['header']['meta'] = array();
495  // Add list of tables to consider static
496  $this->dat['header']['relStaticTables'] = $this->relStaticTables;
497  // The list of excluded records
498  $this->dat['header']['excludeMap'] = $this->excludeMap;
499  // Soft Reference mode for elements
500  $this->dat['header']['softrefCfg'] = $this->softrefCfg;
501  // List of extensions the import depends on.
502  $this->dat['header']['extensionDependencies'] = $this->extensionDependencies;
503  }
504 
511  public function setCharset($charset)
512  {
513  $this->dat['header']['charset'] = $charset;
514  }
515 
527  public function setMetaData($title, $description, $notes, $packager_username, $packager_name, $packager_email)
528  {
529  $this->dat['header']['meta'] = array(
530  'title' => $title,
531  'description' => $description,
532  'notes' => $notes,
533  'packager_username' => $packager_username,
534  'packager_name' => $packager_name,
535  'packager_email' => $packager_email,
536  'TYPO3_version' => TYPO3_version,
537  'created' => strftime('%A %e. %B %Y', $GLOBALS['EXEC_TIME'])
538  );
539  }
540 
549  {
550  $this->saveFilesOutsideExportFile = $saveFilesOutsideExportFile;
551  }
552 
559  public function addThumbnail($imgFilepath)
560  {
561  if (@is_file($imgFilepath)) {
562  $imgInfo = @getimagesize($imgFilepath);
563  if (is_array($imgInfo)) {
564  $fileContent = GeneralUtility::getUrl($imgFilepath);
565  $this->dat['header']['thumbnail'] = array(
566  'imgInfo' => $imgInfo,
567  'content' => $fileContent,
568  'filesize' => strlen($fileContent),
569  'filemtime' => filemtime($imgFilepath),
570  'filename' => PathUtility::basename($imgFilepath)
571  );
572  }
573  }
574  }
575 
576  /**************************
577  * Export / Init Page tree
578  *************************/
579 
586  public function setPageTree($idH)
587  {
588  $this->dat['header']['pagetree'] = $this->unsetExcludedSections($idH);
589  return $this->flatInversePageTree($this->dat['header']['pagetree']);
590  }
591 
600  public function unsetExcludedSections($idH)
601  {
602  if (is_array($idH)) {
603  foreach ($idH as $k => $v) {
604  if ($this->excludeMap['pages:' . $idH[$k]['uid']]) {
605  unset($idH[$k]);
606  } elseif (is_array($idH[$k]['subrow'])) {
607  $idH[$k]['subrow'] = $this->unsetExcludedSections($idH[$k]['subrow']);
608  }
609  }
610  }
611  return $idH;
612  }
613 
622  public function flatInversePageTree($idH, $a = array())
623  {
624  if (is_array($idH)) {
625  $idH = array_reverse($idH);
626  foreach ($idH as $k => $v) {
627  $a[$v['uid']] = $v['uid'];
628  if (is_array($v['subrow'])) {
629  $a = $this->flatInversePageTree($v['subrow'], $a);
630  }
631  }
632  }
633  return $a;
634  }
635 
645  public function flatInversePageTree_pid($idH, $a = array(), $pid = -1)
646  {
647  if (is_array($idH)) {
648  $idH = array_reverse($idH);
649  foreach ($idH as $v) {
650  $a[$v['uid']] = $pid;
651  if (is_array($v['subrow'])) {
652  $a = $this->flatInversePageTree_pid($v['subrow'], $a, $v['uid']);
653  }
654  }
655  }
656  return $a;
657  }
658 
659  /**************************
660  * Export
661  *************************/
662 
671  {
672  foreach ($recordTypesIncludeFields as $table => $fields) {
673  if (!is_array($fields)) {
674  throw new \TYPO3\CMS\Core\Exception('The include fields for record type ' . htmlspecialchars($table) . ' are not defined by an array.', 1391440658);
675  }
676  $this->setRecordTypeIncludeFields($table, $fields);
677  }
678  }
679 
687  public function setRecordTypeIncludeFields($table, array $fields)
688  {
689  $this->recordTypesIncludeFields[$table] = $fields;
690  }
691 
701  public function export_addRecord($table, $row, $relationLevel = 0)
702  {
703  BackendUtility::workspaceOL($table, $row);
704  if ((string)$table !== '' && is_array($row) && $row['uid'] > 0 && !$this->excludeMap[($table . ':' . $row['uid'])]) {
705  if ($this->checkPID($table === 'pages' ? $row['uid'] : $row['pid'])) {
706  if (!isset($this->dat['records'][($table . ':' . $row['uid'])])) {
707  // Prepare header info:
708  $row = $this->filterRecordFields($table, $row);
709  $headerInfo = array();
710  $headerInfo['uid'] = $row['uid'];
711  $headerInfo['pid'] = $row['pid'];
712  $headerInfo['title'] = GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($table, $row), 40);
713  $headerInfo['size'] = strlen(serialize($row));
714  if ($relationLevel) {
715  $headerInfo['relationLevel'] = $relationLevel;
716  }
717  // If record content is not too large in size, set the header content and add the rest:
718  if ($headerInfo['size'] < $this->maxRecordSize) {
719  // Set the header summary:
720  $this->dat['header']['records'][$table][$row['uid']] = $headerInfo;
721  // Create entry in the PID lookup:
722  $this->dat['header']['pid_lookup'][$row['pid']][$table][$row['uid']] = 1;
723  // Initialize reference index object:
724  $refIndexObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ReferenceIndex::class);
725  // Yes to workspace overlays for exporting....
726  $refIndexObj->WSOL = true;
727  $relations = $refIndexObj->getRelations($table, $row);
728  $relations = $this->fixFileIDsInRelations($relations);
729  $relations = $this->removeSoftrefsHavingTheSameDatabaseRelation($relations);
730  // Data:
731  $this->dat['records'][$table . ':' . $row['uid']] = array();
732  $this->dat['records'][$table . ':' . $row['uid']]['data'] = $row;
733  $this->dat['records'][$table . ':' . $row['uid']]['rels'] = $relations;
734  $this->errorLog = array_merge($this->errorLog, $refIndexObj->errorLog);
735  // Merge error logs.
736  // Add information about the relations in the record in the header:
737  $this->dat['header']['records'][$table][$row['uid']]['rels'] = $this->flatDBrels($this->dat['records'][$table . ':' . $row['uid']]['rels']);
738  // Add information about the softrefs to header:
739  $this->dat['header']['records'][$table][$row['uid']]['softrefs'] = $this->flatSoftRefs($this->dat['records'][$table . ':' . $row['uid']]['rels']);
740  } else {
741  $this->error('Record ' . $table . ':' . $row['uid'] . ' was larger than maxRecordSize (' . GeneralUtility::formatSize($this->maxRecordSize) . ')');
742  }
743  } else {
744  $this->error('Record ' . $table . ':' . $row['uid'] . ' already added.');
745  }
746  } else {
747  $this->error('Record ' . $table . ':' . $row['uid'] . ' was outside your DB mounts!');
748  }
749  }
750  }
751 
759  protected function fixFileIDsInRelations(array $relations)
760  {
761  foreach ($relations as $field => $relation) {
762  if (isset($relation['type']) && $relation['type'] === 'file') {
763  foreach ($relation['newValueFiles'] as $key => $fileRelationData) {
764  $absoluteFilePath = $fileRelationData['ID_absFile'];
765  if (GeneralUtility::isFirstPartOfStr($absoluteFilePath, PATH_site)) {
766  $relatedFilePath = PathUtility::stripPathSitePrefix($absoluteFilePath);
767  $relations[$field]['newValueFiles'][$key]['ID'] = md5($relatedFilePath);
768  }
769  }
770  }
771  if ($relation['type'] === 'flex') {
772  if (is_array($relation['flexFormRels']['file'])) {
773  foreach ($relation['flexFormRels']['file'] as $key => $subList) {
774  foreach ($subList as $subKey => $fileRelationData) {
775  $absoluteFilePath = $fileRelationData['ID_absFile'];
776  if (GeneralUtility::isFirstPartOfStr($absoluteFilePath, PATH_site)) {
777  $relatedFilePath = PathUtility::stripPathSitePrefix($absoluteFilePath);
778  $relations[$field]['flexFormRels']['file'][$key][$subKey]['ID'] = md5($relatedFilePath);
779  }
780  }
781  }
782  }
783  }
784  }
785  return $relations;
786  }
787 
797  protected function removeSoftrefsHavingTheSameDatabaseRelation($relations)
798  {
799  $fixedRelations = array();
800  foreach ($relations as $field => $relation) {
801  $newRelation = $relation;
802  if (isset($newRelation['type']) && $newRelation['type'] === 'db') {
803  foreach ($newRelation['itemArray'] as $key => $dbRelationData) {
804  if ($dbRelationData['table'] === 'sys_file') {
805  if (isset($newRelation['softrefs']['keys']['typolink'])) {
806  foreach ($newRelation['softrefs']['keys']['typolink'] as $softrefKey => $softRefData) {
807  if ($softRefData['subst']['type'] === 'file') {
808  $file = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->retrieveFileOrFolderObject($softRefData['subst']['relFileName']);
809  if ($file instanceof \TYPO3\CMS\Core\Resource\File) {
810  if ($file->getUid() == $dbRelationData['id']) {
811  unset($newRelation['softrefs']['keys']['typolink'][$softrefKey]);
812  }
813  }
814  }
815  }
816  if (empty($newRelation['softrefs']['keys']['typolink'])) {
817  unset($newRelation['softrefs']);
818  }
819  }
820  }
821  }
822  }
823  $fixedRelations[$field] = $newRelation;
824  }
825  return $fixedRelations;
826  }
827 
828 
839  public function export_addDBRelations($relationLevel = 0)
840  {
841  // Traverse all "rels" registered for "records"
842  if (!is_array($this->dat['records'])) {
843  $this->error('There were no records available.');
844  return array();
845  }
846  $addR = array();
847  foreach ($this->dat['records'] as $k => $value) {
848  if (!is_array($this->dat['records'][$k])) {
849  continue;
850  }
851  foreach ($this->dat['records'][$k]['rels'] as $fieldname => $vR) {
852  // For all DB types of relations:
853  if ($vR['type'] == 'db') {
854  foreach ($vR['itemArray'] as $fI) {
855  $this->export_addDBRelations_registerRelation($fI, $addR);
856  }
857  }
858  // For all flex/db types of relations:
859  if ($vR['type'] == 'flex') {
860  // DB relations in flex form fields:
861  if (is_array($vR['flexFormRels']['db'])) {
862  foreach ($vR['flexFormRels']['db'] as $subList) {
863  foreach ($subList as $fI) {
864  $this->export_addDBRelations_registerRelation($fI, $addR);
865  }
866  }
867  }
868  // DB oriented soft references in flex form fields:
869  if (is_array($vR['flexFormRels']['softrefs'])) {
870  foreach ($vR['flexFormRels']['softrefs'] as $subList) {
871  foreach ($subList['keys'] as $spKey => $elements) {
872  foreach ($elements as $el) {
873  if ($el['subst']['type'] === 'db' && $this->includeSoftref($el['subst']['tokenID'])) {
874  list($tempTable, $tempUid) = explode(':', $el['subst']['recordRef']);
875  $fI = array(
876  'table' => $tempTable,
877  'id' => $tempUid
878  );
879  $this->export_addDBRelations_registerRelation($fI, $addR, $el['subst']['tokenID']);
880  }
881  }
882  }
883  }
884  }
885  }
886  // In any case, if there are soft refs:
887  if (is_array($vR['softrefs']['keys'])) {
888  foreach ($vR['softrefs']['keys'] as $spKey => $elements) {
889  foreach ($elements as $el) {
890  if ($el['subst']['type'] === 'db' && $this->includeSoftref($el['subst']['tokenID'])) {
891  list($tempTable, $tempUid) = explode(':', $el['subst']['recordRef']);
892  $fI = array(
893  'table' => $tempTable,
894  'id' => $tempUid
895  );
896  $this->export_addDBRelations_registerRelation($fI, $addR, $el['subst']['tokenID']);
897  }
898  }
899  }
900  }
901  }
902  }
903 
904  // Now, if there were new records to add, do so:
905  if (!empty($addR)) {
906  foreach ($addR as $fI) {
907  // Get and set record:
908  $row = BackendUtility::getRecord($fI['table'], $fI['id']);
909  if (is_array($row)) {
910  $this->export_addRecord($fI['table'], $row, $relationLevel + 1);
911  }
912  // Set status message
913  // Relation pointers always larger than zero except certain "select" types with
914  // negative values pointing to uids - but that is not supported here.
915  if ($fI['id'] > 0) {
916  $rId = $fI['table'] . ':' . $fI['id'];
917  if (!isset($this->dat['records'][$rId])) {
918  $this->dat['records'][$rId] = 'NOT_FOUND';
919  $this->error('Relation record ' . $rId . ' was not found!');
920  }
921  }
922  }
923  }
924  // Return overview of relations found and added
925  return $addR;
926  }
927 
937  public function export_addDBRelations_registerRelation($fI, &$addR, $tokenID = '')
938  {
939  $rId = $fI['table'] . ':' . $fI['id'];
940  if (
941  isset($GLOBALS['TCA'][$fI['table']]) && !$this->isTableStatic($fI['table']) && !$this->isExcluded($fI['table'], $fI['id'])
942  && (!$tokenID || $this->includeSoftref($tokenID)) && $this->inclRelation($fI['table'])
943  ) {
944  if (!isset($this->dat['records'][$rId])) {
945  // Set this record to be included since it is not already.
946  $addR[$rId] = $fI;
947  }
948  }
949  }
950 
959  {
960  // Traverse all "rels" registered for "records"
961  if (!is_array($this->dat['records'])) {
962  $this->error('There were no records available.');
963  return;
964  }
965  foreach ($this->dat['records'] as $k => $value) {
966  if (!isset($this->dat['records'][$k]['rels']) || !is_array($this->dat['records'][$k]['rels'])) {
967  continue;
968  }
969  foreach ($this->dat['records'][$k]['rels'] as $fieldname => $vR) {
970  // For all file type relations:
971  if ($vR['type'] == 'file') {
972  foreach ($vR['newValueFiles'] as $key => $fI) {
973  $this->export_addFile($fI, $k, $fieldname);
974  // Remove the absolute reference to the file so it doesn't expose absolute paths from source server:
975  unset($this->dat['records'][$k]['rels'][$fieldname]['newValueFiles'][$key]['ID_absFile']);
976  }
977  }
978  // For all flex type relations:
979  if ($vR['type'] == 'flex') {
980  if (is_array($vR['flexFormRels']['file'])) {
981  foreach ($vR['flexFormRels']['file'] as $key => $subList) {
982  foreach ($subList as $subKey => $fI) {
983  $this->export_addFile($fI, $k, $fieldname);
984  // Remove the absolute reference to the file so it doesn't expose absolute paths from source server:
985  unset($this->dat['records'][$k]['rels'][$fieldname]['flexFormRels']['file'][$key][$subKey]['ID_absFile']);
986  }
987  }
988  }
989  // DB oriented soft references in flex form fields:
990  if (is_array($vR['flexFormRels']['softrefs'])) {
991  foreach ($vR['flexFormRels']['softrefs'] as $key => $subList) {
992  foreach ($subList['keys'] as $spKey => $elements) {
993  foreach ($elements as $subKey => $el) {
994  if ($el['subst']['type'] === 'file' && $this->includeSoftref($el['subst']['tokenID'])) {
995  // Create abs path and ID for file:
996  $ID_absFile = GeneralUtility::getFileAbsFileName(PATH_site . $el['subst']['relFileName']);
997  $ID = md5($el['subst']['relFileName']);
998  if ($ID_absFile) {
999  if (!$this->dat['files'][$ID]) {
1000  $fI = array(
1001  'filename' => PathUtility::basename($ID_absFile),
1002  'ID_absFile' => $ID_absFile,
1003  'ID' => $ID,
1004  'relFileName' => $el['subst']['relFileName']
1005  );
1006  $this->export_addFile($fI, '_SOFTREF_');
1007  }
1008  $this->dat['records'][$k]['rels'][$fieldname]['flexFormRels']['softrefs'][$key]['keys'][$spKey][$subKey]['file_ID'] = $ID;
1009  }
1010  }
1011  }
1012  }
1013  }
1014  }
1015  }
1016  // In any case, if there are soft refs:
1017  if (is_array($vR['softrefs']['keys'])) {
1018  foreach ($vR['softrefs']['keys'] as $spKey => $elements) {
1019  foreach ($elements as $subKey => $el) {
1020  if ($el['subst']['type'] === 'file' && $this->includeSoftref($el['subst']['tokenID'])) {
1021  // Create abs path and ID for file:
1022  $ID_absFile = GeneralUtility::getFileAbsFileName(PATH_site . $el['subst']['relFileName']);
1023  $ID = md5($el['subst']['relFileName']);
1024  if ($ID_absFile) {
1025  if (!$this->dat['files'][$ID]) {
1026  $fI = array(
1027  'filename' => PathUtility::basename($ID_absFile),
1028  'ID_absFile' => $ID_absFile,
1029  'ID' => $ID,
1030  'relFileName' => $el['subst']['relFileName']
1031  );
1032  $this->export_addFile($fI, '_SOFTREF_');
1033  }
1034  $this->dat['records'][$k]['rels'][$fieldname]['softrefs']['keys'][$spKey][$subKey]['file_ID'] = $ID;
1035  }
1036  }
1037  }
1038  }
1039  }
1040  }
1041  }
1042  }
1043 
1050  {
1051  if (!isset($this->dat['header']['records']['sys_file']) || !is_array($this->dat['header']['records']['sys_file'])) {
1052  return;
1053  }
1054  foreach ($this->dat['header']['records']['sys_file'] as $sysFileUid => $_) {
1055  $recordData = $this->dat['records']['sys_file:' . $sysFileUid]['data'];
1056  $file = ResourceFactory::getInstance()->createFileObject($recordData);
1057  $this->export_addSysFile($file);
1058  }
1059  }
1060 
1067  public function export_addSysFile(\TYPO3\CMS\Core\Resource\File $file)
1068  {
1069  if ($file->getProperty('size') >= $this->maxFileSize) {
1070  $this->error('File ' . $file->getPublicUrl() . ' was larger (' . GeneralUtility::formatSize($file->getProperty('size')) . ') than the maxFileSize (' . GeneralUtility::formatSize($this->maxFileSize) . ')! Skipping.');
1071  return;
1072  }
1073  $fileContent = '';
1074  try {
1075  if (!$this->saveFilesOutsideExportFile) {
1076  $fileContent = $file->getContents();
1077  } else {
1078  $file->checkActionPermission('read');
1079  }
1080  } catch (\Exception $e) {
1081  $this->error('Error when trying to add file ' . $file->getCombinedIdentifier() . ': ' . $e->getMessage());
1082  return;
1083  }
1084  $fileUid = $file->getUid();
1085  $fileInfo = $file->getStorage()->getFileInfo($file);
1086  // we sadly have to cast it to string here, because the size property is also returning a string
1087  $fileSize = (string)$fileInfo['size'];
1088  if ($fileSize !== $file->getProperty('size')) {
1089  $this->error('File size of ' . $file->getCombinedIdentifier() . ' is not up-to-date in index! File added with current size.');
1090  $this->dat['records']['sys_file:' . $fileUid]['data']['size'] = $fileSize;
1091  }
1092  $fileSha1 = $file->getStorage()->hashFile($file, 'sha1');
1093  if ($fileSha1 !== $file->getProperty('sha1')) {
1094  $this->error('File sha1 hash of ' . $file->getCombinedIdentifier() . ' is not up-to-date in index! File added on current sha1.');
1095  $this->dat['records']['sys_file:' . $fileUid]['data']['sha1'] = $fileSha1;
1096  }
1097 
1098  $fileRec = array();
1099  $fileRec['filesize'] = $fileSize;
1100  $fileRec['filename'] = $file->getProperty('name');
1101  $fileRec['filemtime'] = $file->getProperty('modification_date');
1102 
1103  // build unique id based on the storage and the file identifier
1104  $fileId = md5($file->getStorage()->getUid() . ':' . $file->getProperty('identifier_hash'));
1105 
1106  // Setting this data in the header
1107  $this->dat['header']['files_fal'][$fileId] = $fileRec;
1108 
1109  if (!$this->saveFilesOutsideExportFile) {
1110  // ... and finally add the heavy stuff:
1111  $fileRec['content'] = $fileContent;
1112  } else {
1113  GeneralUtility::upload_copy_move($file->getForLocalProcessing(false), $this->getTemporaryFilesPathForExport() . $file->getProperty('sha1'));
1114  }
1115  $fileRec['content_sha1'] = $fileSha1;
1116 
1117  $this->dat['files_fal'][$fileId] = $fileRec;
1118  }
1119 
1120 
1129  public function export_addFile($fI, $recordRef = '', $fieldname = '')
1130  {
1131  if (!@is_file($fI['ID_absFile'])) {
1132  $this->error($fI['ID_absFile'] . ' was not a file! Skipping.');
1133  return;
1134  }
1135  if (filesize($fI['ID_absFile']) >= $this->maxFileSize) {
1136  $this->error($fI['ID_absFile'] . ' was larger (' . GeneralUtility::formatSize(filesize($fI['ID_absFile'])) . ') than the maxFileSize (' . GeneralUtility::formatSize($this->maxFileSize) . ')! Skipping.');
1137  return;
1138  }
1139  $fileInfo = stat($fI['ID_absFile']);
1140  $fileRec = array();
1141  $fileRec['filesize'] = $fileInfo['size'];
1142  $fileRec['filename'] = PathUtility::basename($fI['ID_absFile']);
1143  $fileRec['filemtime'] = $fileInfo['mtime'];
1144  //for internal type file_reference
1145  $fileRec['relFileRef'] = PathUtility::stripPathSitePrefix($fI['ID_absFile']);
1146  if ($recordRef) {
1147  $fileRec['record_ref'] = $recordRef . '/' . $fieldname;
1148  }
1149  if ($fI['relFileName']) {
1150  $fileRec['relFileName'] = $fI['relFileName'];
1151  }
1152  // Setting this data in the header
1153  $this->dat['header']['files'][$fI['ID']] = $fileRec;
1154  // ... and for the recordlisting, why not let us know WHICH relations there was...
1155  if ($recordRef && $recordRef !== '_SOFTREF_') {
1156  $refParts = explode(':', $recordRef, 2);
1157  if (!is_array($this->dat['header']['records'][$refParts[0]][$refParts[1]]['filerefs'])) {
1158  $this->dat['header']['records'][$refParts[0]][$refParts[1]]['filerefs'] = array();
1159  }
1160  $this->dat['header']['records'][$refParts[0]][$refParts[1]]['filerefs'][] = $fI['ID'];
1161  }
1162  $fileMd5 = md5_file($fI['ID_absFile']);
1163  if (!$this->saveFilesOutsideExportFile) {
1164  // ... and finally add the heavy stuff:
1165  $fileRec['content'] = GeneralUtility::getUrl($fI['ID_absFile']);
1166  } else {
1167  GeneralUtility::upload_copy_move($fI['ID_absFile'], $this->getTemporaryFilesPathForExport() . $fileMd5);
1168  }
1169  $fileRec['content_md5'] = $fileMd5;
1170  $this->dat['files'][$fI['ID']] = $fileRec;
1171  // For soft references, do further processing:
1172  if ($recordRef === '_SOFTREF_') {
1173  // RTE files?
1174  if ($RTEoriginal = $this->getRTEoriginalFilename(PathUtility::basename($fI['ID_absFile']))) {
1175  $RTEoriginal_absPath = PathUtility::dirname($fI['ID_absFile']) . '/' . $RTEoriginal;
1176  if (@is_file($RTEoriginal_absPath)) {
1177  $RTEoriginal_ID = md5($RTEoriginal_absPath);
1178  $fileInfo = stat($RTEoriginal_absPath);
1179  $fileRec = array();
1180  $fileRec['filesize'] = $fileInfo['size'];
1181  $fileRec['filename'] = PathUtility::basename($RTEoriginal_absPath);
1182  $fileRec['filemtime'] = $fileInfo['mtime'];
1183  $fileRec['record_ref'] = '_RTE_COPY_ID:' . $fI['ID'];
1184  $this->dat['header']['files'][$fI['ID']]['RTE_ORIG_ID'] = $RTEoriginal_ID;
1185  // Setting this data in the header
1186  $this->dat['header']['files'][$RTEoriginal_ID] = $fileRec;
1187  $fileMd5 = md5_file($RTEoriginal_absPath);
1188  if (!$this->saveFilesOutsideExportFile) {
1189  // ... and finally add the heavy stuff:
1190  $fileRec['content'] = GeneralUtility::getUrl($RTEoriginal_absPath);
1191  } else {
1192  GeneralUtility::upload_copy_move($RTEoriginal_absPath, $this->getTemporaryFilesPathForExport() . $fileMd5);
1193  }
1194  $fileRec['content_md5'] = $fileMd5;
1195  $this->dat['files'][$RTEoriginal_ID] = $fileRec;
1196  } else {
1197  $this->error('RTE original file "' . PathUtility::stripPathSitePrefix($RTEoriginal_absPath) . '" was not found!');
1198  }
1199  }
1200  // Files with external media?
1201  // This is only done with files grabbed by a softreference parser since it is deemed improbable that hard-referenced files should undergo this treatment.
1202  $html_fI = pathinfo(PathUtility::basename($fI['ID_absFile']));
1203  if ($this->includeExtFileResources && GeneralUtility::inList($this->extFileResourceExtensions, strtolower($html_fI['extension']))) {
1204  $uniquePrefix = '###' . md5($GLOBALS['EXEC_TIME']) . '###';
1205  if (strtolower($html_fI['extension']) === 'css') {
1206  $prefixedMedias = explode($uniquePrefix, preg_replace('/(url[[:space:]]*\\([[:space:]]*["\']?)([^"\')]*)(["\']?[[:space:]]*\\))/i', '\\1' . $uniquePrefix . '\\2' . $uniquePrefix . '\\3', $fileRec['content']));
1207  } else {
1208  // html, htm:
1209  $htmlParser = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Html\HtmlParser::class);
1210  $prefixedMedias = explode($uniquePrefix, $htmlParser->prefixResourcePath($uniquePrefix, $fileRec['content'], array(), $uniquePrefix));
1211  }
1212  $htmlResourceCaptured = false;
1213  foreach ($prefixedMedias as $k => $v) {
1214  if ($k % 2) {
1215  $EXTres_absPath = GeneralUtility::resolveBackPath(PathUtility::dirname($fI['ID_absFile']) . '/' . $v);
1216  $EXTres_absPath = GeneralUtility::getFileAbsFileName($EXTres_absPath);
1217  if ($EXTres_absPath && GeneralUtility::isFirstPartOfStr($EXTres_absPath, PATH_site . $this->fileadminFolderName . '/') && @is_file($EXTres_absPath)) {
1218  $htmlResourceCaptured = true;
1219  $EXTres_ID = md5($EXTres_absPath);
1220  $this->dat['header']['files'][$fI['ID']]['EXT_RES_ID'][] = $EXTres_ID;
1221  $prefixedMedias[$k] = '{EXT_RES_ID:' . $EXTres_ID . '}';
1222  // Add file to memory if it is not set already:
1223  if (!isset($this->dat['header']['files'][$EXTres_ID])) {
1224  $fileInfo = stat($EXTres_absPath);
1225  $fileRec = array();
1226  $fileRec['filesize'] = $fileInfo['size'];
1227  $fileRec['filename'] = PathUtility::basename($EXTres_absPath);
1228  $fileRec['filemtime'] = $fileInfo['mtime'];
1229  $fileRec['record_ref'] = '_EXT_PARENT_:' . $fI['ID'];
1230  // Media relative to the HTML file.
1231  $fileRec['parentRelFileName'] = $v;
1232  // Setting this data in the header
1233  $this->dat['header']['files'][$EXTres_ID] = $fileRec;
1234  // ... and finally add the heavy stuff:
1235  $fileRec['content'] = GeneralUtility::getUrl($EXTres_absPath);
1236  $fileRec['content_md5'] = md5($fileRec['content']);
1237  $this->dat['files'][$EXTres_ID] = $fileRec;
1238  }
1239  }
1240  }
1241  }
1242  if ($htmlResourceCaptured) {
1243  $this->dat['files'][$fI['ID']]['tokenizedContent'] = implode('', $prefixedMedias);
1244  }
1245  }
1246  }
1247  }
1248 
1258  {
1259  if (!$this->saveFilesOutsideExportFile) {
1260  throw new \RuntimeException('You need to set saveFilesOutsideExportFile to TRUE before you want to get the temporary files path for export.', 1401205213);
1261  }
1262  if ($this->temporaryFilesPathForExport === null) {
1263  $temporaryFolderName = $this->getTemporaryFolderName();
1264  $this->temporaryFilesPathForExport = $temporaryFolderName . '/';
1265  }
1267  }
1268 
1273  protected function getTemporaryFolderName()
1274  {
1275  $temporaryPath = PATH_site . 'typo3temp/';
1276  do {
1277  $temporaryFolderName = $temporaryPath . 'export_temp_files_' . mt_rand(1, PHP_INT_MAX);
1278  } while (is_dir($temporaryFolderName));
1279  GeneralUtility::mkdir($temporaryFolderName);
1280  return $temporaryFolderName;
1281  }
1282 
1290  public function flatDBrels($dbrels)
1291  {
1292  $list = array();
1293  foreach ($dbrels as $dat) {
1294  if ($dat['type'] == 'db') {
1295  foreach ($dat['itemArray'] as $i) {
1296  $list[$i['table'] . ':' . $i['id']] = $i;
1297  }
1298  }
1299  if ($dat['type'] == 'flex' && is_array($dat['flexFormRels']['db'])) {
1300  foreach ($dat['flexFormRels']['db'] as $subList) {
1301  foreach ($subList as $i) {
1302  $list[$i['table'] . ':' . $i['id']] = $i;
1303  }
1304  }
1305  }
1306  }
1307  return $list;
1308  }
1309 
1316  public function flatSoftRefs($dbrels)
1317  {
1318  $list = array();
1319  foreach ($dbrels as $field => $dat) {
1320  if (is_array($dat['softrefs']['keys'])) {
1321  foreach ($dat['softrefs']['keys'] as $spKey => $elements) {
1322  if (is_array($elements)) {
1323  foreach ($elements as $subKey => $el) {
1324  $lKey = $field . ':' . $spKey . ':' . $subKey;
1325  $list[$lKey] = array_merge(array('field' => $field, 'spKey' => $spKey), $el);
1326  // Add file_ID key to header - slightly "risky" way of doing this because if the calculation
1327  // changes for the same value in $this->records[...] this will not work anymore!
1328  if ($el['subst'] && $el['subst']['relFileName']) {
1329  $list[$lKey]['file_ID'] = md5(PATH_site . $el['subst']['relFileName']);
1330  }
1331  }
1332  }
1333  }
1334  }
1335  if ($dat['type'] == 'flex' && is_array($dat['flexFormRels']['softrefs'])) {
1336  foreach ($dat['flexFormRels']['softrefs'] as $structurePath => $subSoftrefs) {
1337  if (is_array($subSoftrefs['keys'])) {
1338  foreach ($subSoftrefs['keys'] as $spKey => $elements) {
1339  foreach ($elements as $subKey => $el) {
1340  $lKey = $field . ':' . $structurePath . ':' . $spKey . ':' . $subKey;
1341  $list[$lKey] = array_merge(array('field' => $field, 'spKey' => $spKey, 'structurePath' => $structurePath), $el);
1342  // Add file_ID key to header - slightly "risky" way of doing this because if the calculation
1343  // changes for the same value in $this->records[...] this will not work anymore!
1344  if ($el['subst'] && $el['subst']['relFileName']) {
1345  $list[$lKey]['file_ID'] = md5(PATH_site . $el['subst']['relFileName']);
1346  }
1347  }
1348  }
1349  }
1350  }
1351  }
1352  }
1353  return $list;
1354  }
1355 
1364  protected function filterRecordFields($table, array $row)
1365  {
1366  if (isset($this->recordTypesIncludeFields[$table])) {
1367  $includeFields = array_unique(array_merge(
1368  $this->recordTypesIncludeFields[$table],
1369  $this->defaultRecordIncludeFields
1370  ));
1371  $newRow = array();
1372  foreach ($row as $key => $value) {
1373  if (in_array($key, $includeFields)) {
1374  $newRow[$key] = $value;
1375  }
1376  }
1377  } else {
1378  $newRow = $row;
1379  }
1380  return $newRow;
1381  }
1382 
1383 
1384  /**************************
1385  * File Output
1386  *************************/
1387 
1394  public function compileMemoryToFileContent($type = '')
1395  {
1396  if ($type == 'xml') {
1397  $out = $this->createXML();
1398  } else {
1399  $compress = $this->doOutputCompress();
1400  $out = '';
1401  // adding header:
1402  $out .= $this->addFilePart(serialize($this->dat['header']), $compress);
1403  // adding records:
1404  $out .= $this->addFilePart(serialize($this->dat['records']), $compress);
1405  // adding files:
1406  $out .= $this->addFilePart(serialize($this->dat['files']), $compress);
1407  // adding files_fal:
1408  $out .= $this->addFilePart(serialize($this->dat['files_fal']), $compress);
1409  }
1410  return $out;
1411  }
1412 
1418  public function createXML()
1419  {
1420  // Options:
1421  $options = array(
1422  'alt_options' => array(
1423  '/header' => array(
1424  'disableTypeAttrib' => true,
1425  'clearStackPath' => true,
1426  'parentTagMap' => array(
1427  'files' => 'file',
1428  'files_fal' => 'file',
1429  'records' => 'table',
1430  'table' => 'rec',
1431  'rec:rels' => 'relations',
1432  'relations' => 'element',
1433  'filerefs' => 'file',
1434  'pid_lookup' => 'page_contents',
1435  'header:relStaticTables' => 'static_tables',
1436  'static_tables' => 'tablename',
1437  'excludeMap' => 'item',
1438  'softrefCfg' => 'softrefExportMode',
1439  'extensionDependencies' => 'extkey',
1440  'softrefs' => 'softref_element'
1441  ),
1442  'alt_options' => array(
1443  '/pagetree' => array(
1444  'disableTypeAttrib' => true,
1445  'useIndexTagForNum' => 'node',
1446  'parentTagMap' => array(
1447  'node:subrow' => 'node'
1448  )
1449  ),
1450  '/pid_lookup/page_contents' => array(
1451  'disableTypeAttrib' => true,
1452  'parentTagMap' => array(
1453  'page_contents' => 'table'
1454  ),
1455  'grandParentTagMap' => array(
1456  'page_contents/table' => 'item'
1457  )
1458  )
1459  )
1460  ),
1461  '/records' => array(
1462  'disableTypeAttrib' => true,
1463  'parentTagMap' => array(
1464  'records' => 'tablerow',
1465  'tablerow:data' => 'fieldlist',
1466  'tablerow:rels' => 'related',
1467  'related' => 'field',
1468  'field:itemArray' => 'relations',
1469  'field:newValueFiles' => 'filerefs',
1470  'field:flexFormRels' => 'flexform',
1471  'relations' => 'element',
1472  'filerefs' => 'file',
1473  'flexform:db' => 'db_relations',
1474  'flexform:file' => 'file_relations',
1475  'flexform:softrefs' => 'softref_relations',
1476  'softref_relations' => 'structurePath',
1477  'db_relations' => 'path',
1478  'file_relations' => 'path',
1479  'path' => 'element',
1480  'keys' => 'softref_key',
1481  'softref_key' => 'softref_element'
1482  ),
1483  'alt_options' => array(
1484  '/records/tablerow/fieldlist' => array(
1485  'useIndexTagForAssoc' => 'field'
1486  )
1487  )
1488  ),
1489  '/files' => array(
1490  'disableTypeAttrib' => true,
1491  'parentTagMap' => array(
1492  'files' => 'file'
1493  )
1494  ),
1495  '/files_fal' => array(
1496  'disableTypeAttrib' => true,
1497  'parentTagMap' => array(
1498  'files_fal' => 'file'
1499  )
1500  )
1501  )
1502  );
1503  // Creating XML file from $outputArray:
1504  $charset = $this->dat['header']['charset'] ?: 'utf-8';
1505  $XML = '<?xml version="1.0" encoding="' . $charset . '" standalone="yes" ?>' . LF;
1506  $XML .= GeneralUtility::array2xml($this->dat, '', 0, 'T3RecordDocument', 0, $options);
1507  return $XML;
1508  }
1509 
1515  public function doOutputCompress()
1516  {
1517  return $this->compress && !$this->dontCompress;
1518  }
1519 
1527  public function addFilePart($data, $compress = false)
1528  {
1529  if ($compress) {
1530  $data = gzcompress($data);
1531  }
1532  return md5($data) . ':' . ($compress ? '1' : '0') . ':' . str_pad(strlen($data), 10, '0', STR_PAD_LEFT) . ':' . $data . ':';
1533  }
1534 
1535  /***********************
1536  * Import
1537  ***********************/
1538 
1544  protected function initializeImport()
1545  {
1546  // Set this flag to indicate that an import is being/has been done.
1547  $this->doesImport = 1;
1548  // Initialize:
1549  // 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.
1550  $this->import_mapId = array();
1551  $this->import_newId = array();
1552  $this->import_newId_pids = array();
1553  // Temporary files stack initialized:
1554  $this->unlinkFiles = array();
1555  $this->alternativeFileName = array();
1556  $this->alternativeFilePath = array();
1557 
1558  $this->initializeStorageObjects();
1559  }
1560 
1566  protected function initializeStorageObjects()
1567  {
1569  $storageRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\StorageRepository::class);
1570  $this->storageObjects = $storageRepository->findAll();
1571  }
1572 
1579  public function importData($pid)
1580  {
1581  $this->initializeImport();
1582 
1583  // Write sys_file_storages first
1584  $this->writeSysFileStorageRecords();
1585  // Write sys_file records and write the binary file data
1586  $this->writeSysFileRecords();
1587  // Write records, first pages, then the rest
1588  // Fields with "hard" relations to database, files and flexform fields are kept empty during this run
1589  $this->writeRecords_pages($pid);
1590  $this->writeRecords_records($pid);
1591  // Finally all the file and DB record references must be fixed. This is done after all records have supposedly been written to database:
1592  // $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.
1593  $this->setRelations();
1594  // 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):
1595  $this->setFlexFormRelations();
1596  // Unlink temporary files:
1597  $this->unlinkTempFiles();
1598  // Finally, traverse all records and process softreferences with substitution attributes.
1599  $this->processSoftReferences();
1600  // After all migrate records using sys_file_reference now
1601  if ($this->legacyImport) {
1602  $this->migrateLegacyImportRecords();
1603  }
1604  }
1605 
1611  protected function writeSysFileStorageRecords()
1612  {
1613  if (!isset($this->dat['header']['records']['sys_file_storage'])) {
1614  return;
1615  }
1616  $sysFileStorageUidsToBeResetToDefaultStorage = array();
1617  foreach ($this->dat['header']['records']['sys_file_storage'] as $sysFileStorageUid => $_) {
1618  $storageRecord = $this->dat['records']['sys_file_storage:' . $sysFileStorageUid]['data'];
1619  // continue with Local, writable and online storage only
1620  if ($storageRecord['driver'] === 'Local' && $storageRecord['is_writable'] && $storageRecord['is_online']) {
1621  $useThisStorageUidInsteadOfTheOneInImport = 0;
1623  foreach ($this->storageObjects as $localStorage) {
1624  // check the available storage for Local, writable and online ones
1625  if ($localStorage->getDriverType() === 'Local' && $localStorage->isWritable() && $localStorage->isOnline()) {
1626  // check if there is already an identical storage present (same pathType and basePath)
1627  $storageRecordConfiguration = ResourceFactory::getInstance()->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
1628  $localStorageRecordConfiguration = $localStorage->getConfiguration();
1629  if (
1630  $storageRecordConfiguration['pathType'] === $localStorageRecordConfiguration['pathType']
1631  && $storageRecordConfiguration['basePath'] === $localStorageRecordConfiguration['basePath']
1632  ) {
1633  // same storage is already present
1634  $useThisStorageUidInsteadOfTheOneInImport = $localStorage->getUid();
1635  break;
1636  }
1637  }
1638  }
1639  if ($useThisStorageUidInsteadOfTheOneInImport > 0) {
1640  // same storage is already present; map the to be imported one to the present one
1641  $this->import_mapId['sys_file_storage'][$sysFileStorageUid] = $useThisStorageUidInsteadOfTheOneInImport;
1642  } else {
1643  // Local, writable and online storage. Is allowed to be used to later write files in.
1644  $this->addSingle('sys_file_storage', $sysFileStorageUid, 0);
1645  }
1646  } else {
1647  // Storage with non Local drivers could be imported but must not be used to saves files in, because you
1648  // could not be sure, that this is supported. The default storage will be used in this case.
1649  // It could happen that non writable and non online storage will be created as dupes because you could not
1650  // check the detailed configuration options at this point
1651  $this->addSingle('sys_file_storage', $sysFileStorageUid, 0);
1652  $sysFileStorageUidsToBeResetToDefaultStorage[] = $sysFileStorageUid;
1653  }
1654  }
1655 
1656  // Importing the added ones
1657  $tce = $this->getNewTCE();
1658  // Because all records are being submitted in their correct order with positive pid numbers - and so we should reverse submission order internally.
1659  $tce->reverseOrder = 1;
1660  $tce->isImporting = true;
1661  $tce->start($this->import_data, array());
1662  $tce->process_datamap();
1663  $this->addToMapId($tce->substNEWwithIDs);
1664 
1665  $defaultStorageUid = null;
1666  // get default storage
1667  $defaultStorage = ResourceFactory::getInstance()->getDefaultStorage();
1668  if ($defaultStorage !== null) {
1669  $defaultStorageUid = $defaultStorage->getUid();
1670  }
1671  foreach ($sysFileStorageUidsToBeResetToDefaultStorage as $sysFileStorageUidToBeResetToDefaultStorage) {
1672  $this->import_mapId['sys_file_storage'][$sysFileStorageUidToBeResetToDefaultStorage] = $defaultStorageUid;
1673  }
1674 
1675  // unset the sys_file_storage records to prevent an import in writeRecords_records
1676  unset($this->dat['header']['records']['sys_file_storage']);
1677  }
1678 
1684  protected function writeSysFileRecords()
1685  {
1686  if (!isset($this->dat['header']['records']['sys_file'])) {
1687  return;
1688  }
1689  $this->addGeneralErrorsByTable('sys_file');
1690 
1691  // fetch fresh storage records from database
1692  $storageRecords = $this->fetchStorageRecords();
1693 
1694  $defaultStorage = ResourceFactory::getInstance()->getDefaultStorage();
1695 
1696  $sanitizedFolderMappings = array();
1697 
1698  foreach ($this->dat['header']['records']['sys_file'] as $sysFileUid => $_) {
1699  $fileRecord = $this->dat['records']['sys_file:' . $sysFileUid]['data'];
1700 
1701  $temporaryFile = null;
1702  // check if there is the right file already in the local folder
1703  if ($this->filesPathForImport !== null) {
1704  if (is_file($this->filesPathForImport . '/' . $fileRecord['sha1']) && sha1_file($this->filesPathForImport . '/' . $fileRecord['sha1']) === $fileRecord['sha1']) {
1705  $temporaryFile = $this->filesPathForImport . '/' . $fileRecord['sha1'];
1706  }
1707  }
1708 
1709  // save file to disk
1710  if ($temporaryFile === null) {
1711  $fileId = md5($fileRecord['storage'] . ':' . $fileRecord['identifier_hash']);
1712  $temporaryFile = $this->writeTemporaryFileFromData($fileId);
1713  if ($temporaryFile === null) {
1714  // error on writing the file. Error message was already added
1715  continue;
1716  }
1717  }
1718 
1719  $originalStorageUid = $fileRecord['storage'];
1720  $useStorageFromStorageRecords = false;
1721 
1722  // replace storage id, if an alternative one was registered
1723  if (isset($this->import_mapId['sys_file_storage'][$fileRecord['storage']])) {
1724  $fileRecord['storage'] = $this->import_mapId['sys_file_storage'][$fileRecord['storage']];
1725  $useStorageFromStorageRecords = true;
1726  }
1727 
1728  if (empty($fileRecord['storage']) && !$this->isFallbackStorage($fileRecord['storage'])) {
1729  // no storage for the file is defined, mostly because of a missing default storage.
1730  $this->error('Error: No storage for the file "' . $fileRecord['identifier'] . '" with storage uid "' . $originalStorageUid . '"');
1731  continue;
1732  }
1733 
1734  // using a storage from the local storage is only allowed, if the uid is present in the
1735  // mapping. Only in this case we could be sure, that it's a local, online and writable storage.
1736  if ($useStorageFromStorageRecords && isset($storageRecords[$fileRecord['storage']])) {
1738  $storage = ResourceFactory::getInstance()->getStorageObject($fileRecord['storage'], $storageRecords[$fileRecord['storage']]);
1739  } elseif ($this->isFallbackStorage($fileRecord['storage'])) {
1740  $storage = ResourceFactory::getInstance()->getStorageObject(0);
1741  } elseif ($defaultStorage !== null) {
1742  $storage = $defaultStorage;
1743  } else {
1744  $this->error('Error: No storage available for the file "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
1745  continue;
1746  }
1747 
1748  $newFile = null;
1749 
1750  // check, if there is an identical file
1751  try {
1752  if ($storage->hasFile($fileRecord['identifier'])) {
1753  $file = $storage->getFile($fileRecord['identifier']);
1754  if ($file->getSha1() === $fileRecord['sha1']) {
1755  $newFile = $file;
1756  }
1757  }
1758  } catch (Exception $e) {
1759  }
1760 
1761  if ($newFile === null) {
1762  $folderName = PathUtility::dirname(ltrim($fileRecord['identifier'], '/'));
1763  if (in_array($folderName, $sanitizedFolderMappings)) {
1764  $folderName = $sanitizedFolderMappings[$folderName];
1765  }
1766  if (!$storage->hasFolder($folderName)) {
1767  try {
1768  $importFolder = $storage->createFolder($folderName);
1769  if ($importFolder->getIdentifier() !== $folderName && !in_array($folderName, $sanitizedFolderMappings)) {
1770  $sanitizedFolderMappings[$folderName] = $importFolder->getIdentifier();
1771  }
1772  } catch (Exception $e) {
1773  $this->error('Error: Folder could not be created for file "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
1774  continue;
1775  }
1776  } else {
1777  $importFolder = $storage->getFolder($folderName);
1778  }
1779 
1780  try {
1782  $newFile = $storage->addFile($temporaryFile, $importFolder, $fileRecord['name']);
1783  } catch (Exception $e) {
1784  $this->error('Error: File could not be added to the storage: "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
1785  continue;
1786  }
1787 
1788  if ($newFile->getSha1() !== $fileRecord['sha1']) {
1789  $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'] . '"');
1790  }
1791  }
1792 
1793  // save the new uid in the import id map
1794  $this->import_mapId['sys_file'][$fileRecord['uid']] = $newFile->getUid();
1795  $this->fixUidLocalInSysFileReferenceRecords($fileRecord['uid'], $newFile->getUid());
1796  }
1797 
1798  // unset the sys_file records to prevent an import in writeRecords_records
1799  unset($this->dat['header']['records']['sys_file']);
1800  }
1801 
1808  protected function isFallbackStorage($storageId)
1809  {
1810  return $storageId === 0 || $storageId === '0';
1811  }
1812 
1829  protected function fixUidLocalInSysFileReferenceRecords($oldFileUid, $newFileUid)
1830  {
1831  if (!isset($this->dat['header']['records']['sys_file_reference'])) {
1832  return;
1833  }
1834 
1835  foreach ($this->dat['header']['records']['sys_file_reference'] as $sysFileReferenceUid => $_) {
1836  $fileReferenceRecord = $this->dat['records']['sys_file_reference:' . $sysFileReferenceUid]['data'];
1837  if ($fileReferenceRecord['uid_local'] == $oldFileUid) {
1838  $fileReferenceRecord['uid_local'] = $newFileUid;
1839  $this->dat['records']['sys_file_reference:' . $sysFileReferenceUid]['data'] = $fileReferenceRecord;
1840  }
1841  }
1842  }
1843 
1849  protected function initializeLegacyImportFolder()
1850  {
1852  $folder = $GLOBALS['BE_USER']->getDefaultUploadFolder();
1853  if ($folder === false) {
1854  $this->error('Error: the backend users default upload folder is missing! No files will be imported!');
1855  }
1856  if (!$folder->hasFolder($this->legacyImportTargetPath)) {
1857  try {
1858  $this->legacyImportFolder = $folder->createFolder($this->legacyImportTargetPath);
1859  } catch (\TYPO3\CMS\Core\Exception $e) {
1860  $this->error('Error: the import folder in the default upload folder could not be created! No files will be imported!');
1861  }
1862  } else {
1863  $this->legacyImportFolder = $folder->getSubFolder($this->legacyImportTargetPath);
1864  }
1865  }
1866 
1873  protected function fetchStorageRecords()
1874  {
1875  $whereClause = BackendUtility::BEenableFields('sys_file_storage');
1876  $whereClause .= BackendUtility::deleteClause('sys_file_storage');
1877 
1878  $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
1879  '*',
1880  'sys_file_storage',
1881  '1=1' . $whereClause,
1882  '',
1883  '',
1884  '',
1885  'uid'
1886  );
1887 
1888  return $rows;
1889  }
1890 
1898  protected function writeTemporaryFileFromData($fileId, $dataKey = 'files_fal')
1899  {
1900  $temporaryFilePath = null;
1901  if (is_array($this->dat[$dataKey][$fileId])) {
1902  $temporaryFilePathInternal = GeneralUtility::tempnam('import_temp_');
1903  GeneralUtility::writeFile($temporaryFilePathInternal, $this->dat[$dataKey][$fileId]['content']);
1904  clearstatcache();
1905  if (@is_file($temporaryFilePathInternal)) {
1906  $this->unlinkFiles[] = $temporaryFilePathInternal;
1907  if (filesize($temporaryFilePathInternal) == $this->dat[$dataKey][$fileId]['filesize']) {
1908  $temporaryFilePath = $temporaryFilePathInternal;
1909  } else {
1910  $this->error('Error: temporary file ' . $temporaryFilePathInternal . ' had a size (' . filesize($temporaryFilePathInternal) . ') different from the original (' . $this->dat[$dataKey][$fileId]['filesize'] . ')', 1);
1911  }
1912  } else {
1913  $this->error('Error: temporary file ' . $temporaryFilePathInternal . ' was not written as it should have been!', 1);
1914  }
1915  } else {
1916  $this->error('Error: No file found for ID ' . $fileId, 1);
1917  }
1918  return $temporaryFilePath;
1919  }
1920 
1928  public function writeRecords_pages($pid)
1929  {
1930  // First, write page structure if any:
1931  if (is_array($this->dat['header']['records']['pages'])) {
1932  $this->addGeneralErrorsByTable('pages');
1933  // $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.
1934  $pageRecords = $this->dat['header']['records']['pages'];
1935  $this->import_data = array();
1936  // First add page tree if any
1937  if (is_array($this->dat['header']['pagetree'])) {
1938  $pagesFromTree = $this->flatInversePageTree($this->dat['header']['pagetree']);
1939  foreach ($pagesFromTree as $uid) {
1940  $thisRec = $this->dat['header']['records']['pages'][$uid];
1941  // PID: Set the main $pid, unless a NEW-id is found
1942  $setPid = isset($this->import_newId_pids[$thisRec['pid']]) ? $this->import_newId_pids[$thisRec['pid']] : $pid;
1943  $this->addSingle('pages', $uid, $setPid);
1944  unset($pageRecords[$uid]);
1945  }
1946  }
1947  // Then add all remaining pages not in tree on root level:
1948  if (!empty($pageRecords)) {
1949  $remainingPageUids = array_keys($pageRecords);
1950  foreach ($remainingPageUids as $pUid) {
1951  $this->addSingle('pages', $pUid, $pid);
1952  }
1953  }
1954  // Now write to database:
1955  $tce = $this->getNewTCE();
1956  $tce->isImporting = true;
1957  $this->callHook('before_writeRecordsPages', array(
1958  'tce' => &$tce,
1959  'data' => &$this->import_data
1960  ));
1961  $tce->suggestedInsertUids = $this->suggestedInsertUids;
1962  $tce->start($this->import_data, array());
1963  $tce->process_datamap();
1964  $this->callHook('after_writeRecordsPages', array(
1965  'tce' => &$tce
1966  ));
1967  // post-processing: Registering new ids (end all tcemain sessions with this)
1968  $this->addToMapId($tce->substNEWwithIDs);
1969  // In case of an update, order pages from the page tree correctly:
1970  if ($this->update && is_array($this->dat['header']['pagetree'])) {
1971  $this->writeRecords_pages_order($pid);
1972  }
1973  }
1974  }
1975 
1985  public function writeRecords_pages_order($pid)
1986  {
1987  $cmd_data = array();
1988  // Get uid-pid relations and traverse them in order to map to possible new IDs
1989  $pidsFromTree = $this->flatInversePageTree_pid($this->dat['header']['pagetree']);
1990  foreach ($pidsFromTree as $origPid => $newPid) {
1991  if ($newPid >= 0 && $this->dontIgnorePid('pages', $origPid)) {
1992  // If the page had a new id (because it was created) use that instead!
1993  if (substr($this->import_newId_pids[$origPid], 0, 3) === 'NEW') {
1994  if ($this->import_mapId['pages'][$origPid]) {
1995  $mappedPid = $this->import_mapId['pages'][$origPid];
1996  $cmd_data['pages'][$mappedPid]['move'] = $newPid;
1997  }
1998  } else {
1999  $cmd_data['pages'][$origPid]['move'] = $newPid;
2000  }
2001  }
2002  }
2003  // Execute the move commands if any:
2004  if (!empty($cmd_data)) {
2005  $tce = $this->getNewTCE();
2006  $this->callHook('before_writeRecordsPagesOrder', array(
2007  'tce' => &$tce,
2008  'data' => &$cmd_data
2009  ));
2010  $tce->start(array(), $cmd_data);
2011  $tce->process_cmdmap();
2012  $this->callHook('after_writeRecordsPagesOrder', array(
2013  'tce' => &$tce
2014  ));
2015  }
2016  }
2017 
2025  public function writeRecords_records($pid)
2026  {
2027  // Write the rest of the records
2028  $this->import_data = array();
2029  if (is_array($this->dat['header']['records'])) {
2030  foreach ($this->dat['header']['records'] as $table => $recs) {
2031  $this->addGeneralErrorsByTable($table);
2032  if ($table != 'pages') {
2033  foreach ($recs as $uid => $thisRec) {
2034  // PID: Set the main $pid, unless a NEW-id is found
2035  $setPid = isset($this->import_mapId['pages'][$thisRec['pid']])
2036  ? (int)$this->import_mapId['pages'][$thisRec['pid']]
2037  : (int)$pid;
2038  if (is_array($GLOBALS['TCA'][$table]) && isset($GLOBALS['TCA'][$table]['ctrl']['rootLevel'])) {
2039  $rootLevelSetting = (int)$GLOBALS['TCA'][$table]['ctrl']['rootLevel'];
2040  if ($rootLevelSetting === 1) {
2041  $setPid = 0;
2042  } elseif ($rootLevelSetting === 0 && $setPid === 0) {
2043  $this->error('Error: Record type ' . $table . ' is not allowed on pid 0');
2044  continue;
2045  }
2046  }
2047  // Add record:
2048  $this->addSingle($table, $uid, $setPid);
2049  }
2050  }
2051  }
2052  } else {
2053  $this->error('Error: No records defined in internal data array.');
2054  }
2055  // Now write to database:
2056  $tce = $this->getNewTCE();
2057  $this->callHook('before_writeRecordsRecords', array(
2058  'tce' => &$tce,
2059  'data' => &$this->import_data
2060  ));
2061  $tce->suggestedInsertUids = $this->suggestedInsertUids;
2062  // Because all records are being submitted in their correct order with positive pid numbers - and so we should reverse submission order internally.
2063  $tce->reverseOrder = 1;
2064  $tce->isImporting = true;
2065  $tce->start($this->import_data, array());
2066  $tce->process_datamap();
2067  $this->callHook('after_writeRecordsRecords', array(
2068  'tce' => &$tce
2069  ));
2070  // post-processing: Removing files and registering new ids (end all tcemain sessions with this)
2071  $this->addToMapId($tce->substNEWwithIDs);
2072  // In case of an update, order pages from the page tree correctly:
2073  if ($this->update) {
2074  $this->writeRecords_records_order($pid);
2075  }
2076  }
2077 
2087  public function writeRecords_records_order($mainPid)
2088  {
2089  $cmd_data = array();
2090  if (is_array($this->dat['header']['pagetree'])) {
2091  $pagesFromTree = $this->flatInversePageTree($this->dat['header']['pagetree']);
2092  } else {
2093  $pagesFromTree = array();
2094  }
2095  if (is_array($this->dat['header']['pid_lookup'])) {
2096  foreach ($this->dat['header']['pid_lookup'] as $pid => $recList) {
2097  $newPid = isset($this->import_mapId['pages'][$pid]) ? $this->import_mapId['pages'][$pid] : $mainPid;
2098  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($newPid)) {
2099  foreach ($recList as $tableName => $uidList) {
2100  // If $mainPid===$newPid then we are on root level and we can consider to move pages as well!
2101  // (they will not be in the page tree!)
2102  if (($tableName != 'pages' || !$pagesFromTree[$pid]) && is_array($uidList)) {
2103  $uidList = array_reverse(array_keys($uidList));
2104  foreach ($uidList as $uid) {
2105  if ($this->dontIgnorePid($tableName, $uid)) {
2106  $cmd_data[$tableName][$uid]['move'] = $newPid;
2107  } else {
2108  }
2109  }
2110  }
2111  }
2112  }
2113  }
2114  }
2115  // Execute the move commands if any:
2116  if (!empty($cmd_data)) {
2117  $tce = $this->getNewTCE();
2118  $this->callHook('before_writeRecordsRecordsOrder', array(
2119  'tce' => &$tce,
2120  'data' => &$cmd_data
2121  ));
2122  $tce->start(array(), $cmd_data);
2123  $tce->process_cmdmap();
2124  $this->callHook('after_writeRecordsRecordsOrder', array(
2125  'tce' => &$tce
2126  ));
2127  }
2128  }
2129 
2141  public function addSingle($table, $uid, $pid)
2142  {
2143  if ($this->import_mode[$table . ':' . $uid] === 'exclude') {
2144  return;
2145  }
2146  $record = $this->dat['records'][$table . ':' . $uid]['data'];
2147  if (is_array($record)) {
2148  if ($this->update && $this->doesRecordExist($table, $uid) && $this->import_mode[$table . ':' . $uid] !== 'as_new') {
2149  $ID = $uid;
2150  } elseif ($table === 'sys_file_metadata' && $record['sys_language_uid'] == '0' && $this->import_mapId['sys_file'][$record['file']]) {
2151  // on adding sys_file records the belonging sys_file_metadata record was also created
2152  // if there is one the record need to be overwritten instead of creating a new one.
2153  $recordInDatabase = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
2154  'uid',
2155  'sys_file_metadata',
2156  'file = ' . $this->import_mapId['sys_file'][$record['file']] . ' AND sys_language_uid = 0 AND pid = 0'
2157  );
2158  // if no record could be found, $this->import_mapId['sys_file'][$record['file']] is pointing
2159  // to a file, that was already there, thus a new metadata record should be created
2160  if (is_array($recordInDatabase)) {
2161  $this->import_mapId['sys_file_metadata'][$record['uid']] = $recordInDatabase['uid'];
2162  $ID = $recordInDatabase['uid'];
2163  } else {
2164  $ID = StringUtility::getUniqueId('NEW');
2165  }
2166  } else {
2167  $ID = StringUtility::getUniqueId('NEW');
2168  }
2169  $this->import_newId[$table . ':' . $ID] = array('table' => $table, 'uid' => $uid);
2170  if ($table == 'pages') {
2171  $this->import_newId_pids[$uid] = $ID;
2172  }
2173  // Set main record data:
2174  $this->import_data[$table][$ID] = $record;
2175  $this->import_data[$table][$ID]['tx_impexp_origuid'] = $this->import_data[$table][$ID]['uid'];
2176  // Reset permission data:
2177  if ($table === 'pages') {
2178  // Have to reset the user/group IDs so pages are owned by importing user. Otherwise strange things may happen for non-admins!
2179  unset($this->import_data[$table][$ID]['perms_userid']);
2180  unset($this->import_data[$table][$ID]['perms_groupid']);
2181  }
2182  // PID and UID:
2183  unset($this->import_data[$table][$ID]['uid']);
2184  // Updates:
2185  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($ID)) {
2186  unset($this->import_data[$table][$ID]['pid']);
2187  } else {
2188  // Inserts:
2189  $this->import_data[$table][$ID]['pid'] = $pid;
2190  if (($this->import_mode[$table . ':' . $uid] === 'force_uid' && $this->update || $this->force_all_UIDS) && $GLOBALS['BE_USER']->isAdmin()) {
2191  $this->import_data[$table][$ID]['uid'] = $uid;
2192  $this->suggestedInsertUids[$table . ':' . $uid] = 'DELETE';
2193  }
2194  }
2195  // Setting db/file blank:
2196  foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) {
2197  switch ((string)$config['type']) {
2198  case 'db':
2199 
2200  case 'file':
2201  // Fixed later in ->setRelations() [because we need to know ALL newly created IDs before we can map relations!]
2202  // In the meantime we set NO values for relations.
2203  //
2204  // BUT for field uid_local of table sys_file_reference the relation MUST not be cleared here,
2205  // because the value is already the uid of the right imported sys_file record.
2206  // @see fixUidLocalInSysFileReferenceRecords()
2207  // If it's empty or a uid to another record the FileExtensionFilter will throw an exception or
2208  // delete the reference record if the file extension of the related record doesn't match.
2209  if ($table !== 'sys_file_reference' && $field !== 'uid_local') {
2210  $this->import_data[$table][$ID][$field] = '';
2211  }
2212  break;
2213  case 'flex':
2214  // Fixed later in setFlexFormRelations()
2215  // In the meantime we set NO value for flexforms - this is mainly because file references
2216  // inside will not be processed properly; In fact references will point to no file
2217  // or existing files (in which case there will be double-references which is a big problem of course!)
2218  $this->import_data[$table][$ID][$field] = '';
2219  break;
2220  }
2221  }
2222  } elseif ($table . ':' . $uid != 'pages:0') {
2223  // On root level we don't want this error message.
2224  $this->error('Error: no record was found in data array!', 1);
2225  }
2226  }
2227 
2235  public function addToMapId($substNEWwithIDs)
2236  {
2237  foreach ($this->import_data as $table => $recs) {
2238  foreach ($recs as $id => $value) {
2239  $old_uid = $this->import_newId[$table . ':' . $id]['uid'];
2240  if (isset($substNEWwithIDs[$id])) {
2241  $this->import_mapId[$table][$old_uid] = $substNEWwithIDs[$id];
2242  } elseif ($this->update) {
2243  // Map same ID to same ID....
2244  $this->import_mapId[$table][$old_uid] = $id;
2245  } else {
2246  // if $this->import_mapId contains already the right mapping, skip the error msg.
2247  // See special handling of sys_file_metadata in addSingle() => nothing to do
2248  if (!($table === 'sys_file_metadata' && isset($this->import_mapId[$table][$old_uid]) && $this->import_mapId[$table][$old_uid] == $id)) {
2249  $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!', 1);
2250  }
2251  }
2252  }
2253  }
2254  }
2255 
2261  public function getNewTCE()
2262  {
2263  $tce = GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class);
2264  $tce->stripslashes_values = 0;
2265  $tce->dontProcessTransformations = 1;
2266  $tce->enableLogging = $this->enableLogging;
2267  $tce->alternativeFileName = $this->alternativeFileName;
2268  $tce->alternativeFilePath = $this->alternativeFilePath;
2269  return $tce;
2270  }
2271 
2277  public function unlinkTempFiles()
2278  {
2279  foreach ($this->unlinkFiles as $fileName) {
2280  if (GeneralUtility::isFirstPartOfStr($fileName, PATH_site . 'typo3temp/')) {
2282  clearstatcache();
2283  if (is_file($fileName)) {
2284  $this->error('Error: ' . $fileName . ' was NOT unlinked as it should have been!', 1);
2285  }
2286  } else {
2287  $this->error('Error: ' . $fileName . ' was not in temp-path. Not removed!', 1);
2288  }
2289  }
2290  $this->unlinkFiles = array();
2291  }
2292 
2293  /***************************
2294  * Import / Relations setting
2295  ***************************/
2296 
2305  public function setRelations()
2306  {
2307  $updateData = array();
2308  // import_newId contains a register of all records that was in the import memorys "records" key
2309  foreach ($this->import_newId as $nId => $dat) {
2310  $table = $dat['table'];
2311  $uid = $dat['uid'];
2312  // original UID - NOT the new one!
2313  // If the record has been written and received a new id, then proceed:
2314  if (is_array($this->import_mapId[$table]) && isset($this->import_mapId[$table][$uid])) {
2315  $thisNewUid = BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]);
2316  if (is_array($this->dat['records'][$table . ':' . $uid]['rels'])) {
2317  $thisNewPageUid = 0;
2318  if ($this->legacyImport) {
2319  if ($table != 'pages') {
2320  $oldPid = $this->dat['records'][$table . ':' . $uid]['data']['pid'];
2321  $thisNewPageUid = BackendUtility::wsMapId($table, $this->import_mapId['pages'][$oldPid]);
2322  } else {
2323  $thisNewPageUid = $thisNewUid;
2324  }
2325  }
2326  // Traverse relation fields of each record
2327  foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) {
2328  // uid_local of sys_file_reference needs no update because the correct reference uid was already written
2329  // @see ImportExport::fixUidLocalInSysFileReferenceRecords()
2330  if ($table === 'sys_file_reference' && $field === 'uid_local') {
2331  continue;
2332  }
2333  switch ((string)$config['type']) {
2334  case 'db':
2335  if (is_array($config['itemArray']) && !empty($config['itemArray'])) {
2336  $itemConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
2337  $valArray = $this->setRelations_db($config['itemArray'], $itemConfig);
2338  $updateData[$table][$thisNewUid][$field] = implode(',', $valArray);
2339  }
2340  break;
2341  case 'file':
2342  if (is_array($config['newValueFiles']) && !empty($config['newValueFiles'])) {
2343  $valArr = array();
2344  foreach ($config['newValueFiles'] as $fI) {
2345  $valArr[] = $this->import_addFileNameToBeCopied($fI);
2346  }
2347  if ($this->legacyImport && $this->legacyImportFolder === null && isset($this->legacyImportMigrationTables[$table][$field])) {
2348  // Do nothing - the legacy import folder is missing
2349  } elseif ($this->legacyImport && $this->legacyImportFolder !== null && isset($this->legacyImportMigrationTables[$table][$field])) {
2350  $refIds = array();
2351  foreach ($valArr as $tempFile) {
2352  $fileName = $this->alternativeFileName[$tempFile];
2353  $fileObject = null;
2354 
2355  try {
2356  // check, if there is alreay the same file in the folder
2357  if ($this->legacyImportFolder->hasFile($fileName)) {
2358  $fileStorage = $this->legacyImportFolder->getStorage();
2359  $file = $fileStorage->getFile($this->legacyImportFolder->getIdentifier() . $fileName);
2360  if ($file->getSha1() === sha1_file($tempFile)) {
2361  $fileObject = $file;
2362  }
2363  }
2364  } catch (Exception $e) {
2365  }
2366 
2367  if ($fileObject === null) {
2368  try {
2369  $fileObject = $this->legacyImportFolder->addFile($tempFile, $fileName, DuplicationBehavior::RENAME);
2370  } catch (\TYPO3\CMS\Core\Exception $e) {
2371  $this->error('Error: no file could be added to the storage for file name' . $this->alternativeFileName[$tempFile]);
2372  }
2373  }
2374  if ($fileObject !== null) {
2375  $refId = StringUtility::getUniqueId('NEW');
2376  $refIds[] = $refId;
2377  $updateData['sys_file_reference'][$refId] = array(
2378  'uid_local' => $fileObject->getUid(),
2379  'uid_foreign' => $thisNewUid, // uid of your content record
2380  'tablenames' => $table,
2381  'fieldname' => $field,
2382  'pid' => $thisNewPageUid, // parent id of the parent page
2383  'table_local' => 'sys_file',
2384  );
2385  }
2386  }
2387  $updateData[$table][$thisNewUid][$field] = implode(',', $refIds);
2388  if (!empty($this->legacyImportMigrationTables[$table][$field])) {
2389  $this->legacyImportMigrationRecords[$table][$thisNewUid][$field] = $refIds;
2390  }
2391  } else {
2392  $updateData[$table][$thisNewUid][$field] = implode(',', $valArr);
2393  }
2394  }
2395  break;
2396  }
2397  }
2398  } else {
2399  $this->error('Error: no record was found in data array!', 1);
2400  }
2401  } else {
2402  $this->error('Error: this records is NOT created it seems! (' . $table . ':' . $uid . ')', 1);
2403  }
2404  }
2405  if (!empty($updateData)) {
2406  $tce = $this->getNewTCE();
2407  $tce->isImporting = true;
2408  $this->callHook('before_setRelation', array(
2409  'tce' => &$tce,
2410  'data' => &$updateData
2411  ));
2412  $tce->start($updateData, array());
2413  $tce->process_datamap();
2414  // Replace the temporary "NEW" ids with the final ones.
2415  foreach ($this->legacyImportMigrationRecords as $table => $records) {
2416  foreach ($records as $uid => $fields) {
2417  foreach ($fields as $field => $referenceIds) {
2418  foreach ($referenceIds as $key => $referenceId) {
2419  $this->legacyImportMigrationRecords[$table][$uid][$field][$key] = $tce->substNEWwithIDs[$referenceId];
2420  }
2421  }
2422  }
2423  }
2424  $this->callHook('after_setRelations', array(
2425  'tce' => &$tce
2426  ));
2427  }
2428  }
2429 
2437  public function setRelations_db($itemArray, $itemConfig)
2438  {
2439  $valArray = array();
2440  foreach ($itemArray as $relDat) {
2441  if (is_array($this->import_mapId[$relDat['table']]) && isset($this->import_mapId[$relDat['table']][$relDat['id']])) {
2442  // Since non FAL file relation type group internal_type file_reference are handled as reference to
2443  // sys_file records Datahandler requires the value as uid of the the related sys_file record only
2444  if ($itemConfig['type'] === 'group' && $itemConfig['internal_type'] === 'file_reference') {
2445  $value = $this->import_mapId[$relDat['table']][$relDat['id']];
2446  } elseif ($itemConfig['type'] === 'input' && isset($itemConfig['wizards']['link'])) {
2447  // If an input field has a relation to a sys_file record this need to be converted back to
2448  // the public path. But use getPublicUrl here, because could normally only be a local file path.
2449  $fileUid = $this->import_mapId[$relDat['table']][$relDat['id']];
2450  // Fallback value
2451  $value = 'file:' . $fileUid;
2452  try {
2453  $file = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->retrieveFileOrFolderObject($fileUid);
2454  } catch (\Exception $e) {
2455  }
2456  if ($file instanceof \TYPO3\CMS\Core\Resource\FileInterface) {
2457  $value = $file->getPublicUrl();
2458  }
2459  } else {
2460  $value = $relDat['table'] . '_' . $this->import_mapId[$relDat['table']][$relDat['id']];
2461  }
2462  $valArray[] = $value;
2463  } elseif ($this->isTableStatic($relDat['table']) || $this->isExcluded($relDat['table'], $relDat['id']) || $relDat['id'] < 0) {
2464  // Checking for less than zero because some select types could contain negative values,
2465  // eg. fe_groups (-1, -2) and sys_language (-1 = ALL languages). This must be handled on both export and import.
2466  $valArray[] = $relDat['table'] . '_' . $relDat['id'];
2467  } else {
2468  $this->error('Lost relation: ' . $relDat['table'] . ':' . $relDat['id'], 1);
2469  }
2470  }
2471  return $valArray;
2472  }
2473 
2480  public function import_addFileNameToBeCopied($fI)
2481  {
2482  if (is_array($this->dat['files'][$fI['ID']])) {
2483  $tmpFile = null;
2484  // check if there is the right file already in the local folder
2485  if ($this->filesPathForImport !== null) {
2486  if (is_file($this->filesPathForImport . '/' . $this->dat['files'][$fI['ID']]['content_md5']) &&
2487  md5_file($this->filesPathForImport . '/' . $this->dat['files'][$fI['ID']]['content_md5']) === $this->dat['files'][$fI['ID']]['content_md5']) {
2488  $tmpFile = $this->filesPathForImport . '/' . $this->dat['files'][$fI['ID']]['content_md5'];
2489  }
2490  }
2491  if ($tmpFile === null) {
2492  $tmpFile = GeneralUtility::tempnam('import_temp_');
2493  GeneralUtility::writeFile($tmpFile, $this->dat['files'][$fI['ID']]['content']);
2494  }
2495  clearstatcache();
2496  if (@is_file($tmpFile)) {
2497  $this->unlinkFiles[] = $tmpFile;
2498  if (filesize($tmpFile) == $this->dat['files'][$fI['ID']]['filesize']) {
2499  $this->alternativeFileName[$tmpFile] = $fI['filename'];
2500  $this->alternativeFilePath[$tmpFile] = $this->dat['files'][$fI['ID']]['relFileRef'];
2501  return $tmpFile;
2502  } else {
2503  $this->error('Error: temporary file ' . $tmpFile . ' had a size (' . filesize($tmpFile) . ') different from the original (' . $this->dat['files'][$fI['ID']]['filesize'] . ')', 1);
2504  }
2505  } else {
2506  $this->error('Error: temporary file ' . $tmpFile . ' was not written as it should have been!', 1);
2507  }
2508  } else {
2509  $this->error('Error: No file found for ID ' . $fI['ID'], 1);
2510  }
2511  return null;
2512  }
2513 
2521  public function setFlexFormRelations()
2522  {
2523  $updateData = array();
2524  // import_newId contains a register of all records that was in the import memorys "records" key
2525  foreach ($this->import_newId as $nId => $dat) {
2526  $table = $dat['table'];
2527  $uid = $dat['uid'];
2528  // original UID - NOT the new one!
2529  // If the record has been written and received a new id, then proceed:
2530  if (!isset($this->import_mapId[$table][$uid])) {
2531  $this->error('Error: this records is NOT created it seems! (' . $table . ':' . $uid . ')', 1);
2532  continue;
2533  }
2534 
2535  if (!is_array($this->dat['records'][$table . ':' . $uid]['rels'])) {
2536  $this->error('Error: no record was found in data array!', 1);
2537  continue;
2538  }
2539  $thisNewUid = BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]);
2540  // Traverse relation fields of each record
2541  foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) {
2542  switch ((string)$config['type']) {
2543  case 'flex':
2544  // Get XML content and set as default value (string, non-processed):
2545  $updateData[$table][$thisNewUid][$field] = $this->dat['records'][$table . ':' . $uid]['data'][$field];
2546  // If there has been registered relations inside the flex form field, run processing on the content:
2547  if (!empty($config['flexFormRels']['db']) || !empty($config['flexFormRels']['file'])) {
2548  $origRecordRow = BackendUtility::getRecord($table, $thisNewUid, '*');
2549  // This will fetch the new row for the element (which should be updated with any references to data structures etc.)
2550  $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
2551  if (is_array($origRecordRow) && is_array($conf) && $conf['type'] === 'flex') {
2552  // Get current data structure and value array:
2553  $dataStructArray = BackendUtility::getFlexFormDS($conf, $origRecordRow, $table, $field);
2554  $currentValueArray = GeneralUtility::xml2array($updateData[$table][$thisNewUid][$field]);
2555  // Do recursive processing of the XML data:
2556  $iteratorObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class);
2557  $iteratorObj->callBackObj = $this;
2558  $currentValueArray['data'] = $iteratorObj->checkValue_flex_procInData(
2559  $currentValueArray['data'],
2560  array(),
2561  array(),
2562  $dataStructArray,
2563  array($table, $thisNewUid, $field, $config),
2564  'remapListedDBRecords_flexFormCallBack'
2565  );
2566  // The return value is set as an array which means it will be processed by tcemain for file and DB references!
2567  if (is_array($currentValueArray['data'])) {
2568  $updateData[$table][$thisNewUid][$field] = $currentValueArray;
2569  }
2570  }
2571  }
2572  break;
2573  }
2574  }
2575  }
2576  if (!empty($updateData)) {
2577  $tce = $this->getNewTCE();
2578  $tce->isImporting = true;
2579  $this->callHook('before_setFlexFormRelations', array(
2580  'tce' => &$tce,
2581  'data' => &$updateData
2582  ));
2583  $tce->start($updateData, array());
2584  $tce->process_datamap();
2585  $this->callHook('after_setFlexFormRelations', array(
2586  'tce' => &$tce
2587  ));
2588  }
2589  }
2590 
2603  public function remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)
2604  {
2605  // Extract parameters:
2606  list($table, $uid, $field, $config) = $pParams;
2607  // In case the $path is used as index without a trailing slash we will remove that
2608  if (!is_array($config['flexFormRels']['db'][$path]) && is_array($config['flexFormRels']['db'][rtrim($path, '/')])) {
2609  $path = rtrim($path, '/');
2610  }
2611  if (is_array($config['flexFormRels']['db'][$path])) {
2612  $valArray = $this->setRelations_db($config['flexFormRels']['db'][$path], $dsConf);
2613  $dataValue = implode(',', $valArray);
2614  }
2615  if (is_array($config['flexFormRels']['file'][$path])) {
2616  $valArr = array();
2617  foreach ($config['flexFormRels']['file'][$path] as $fI) {
2618  $valArr[] = $this->import_addFileNameToBeCopied($fI);
2619  }
2620  $dataValue = implode(',', $valArr);
2621  }
2622  return array('value' => $dataValue);
2623  }
2624 
2625  /**************************
2626  * Import / Soft References
2627  *************************/
2628 
2634  public function processSoftReferences()
2635  {
2636  // Initialize:
2637  $inData = array();
2638  // Traverse records:
2639  if (is_array($this->dat['header']['records'])) {
2640  foreach ($this->dat['header']['records'] as $table => $recs) {
2641  foreach ($recs as $uid => $thisRec) {
2642  // If there are soft references defined, traverse those:
2643  if (isset($GLOBALS['TCA'][$table]) && is_array($thisRec['softrefs'])) {
2644  // First traversal is to collect softref configuration and split them up based on fields.
2645  // This could probably also have been done with the "records" key instead of the header.
2646  $fieldsIndex = array();
2647  foreach ($thisRec['softrefs'] as $softrefDef) {
2648  // If a substitution token is set:
2649  if ($softrefDef['field'] && is_array($softrefDef['subst']) && $softrefDef['subst']['tokenID']) {
2650  $fieldsIndex[$softrefDef['field']][$softrefDef['subst']['tokenID']] = $softrefDef;
2651  }
2652  }
2653  // The new id:
2654  $thisNewUid = BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]);
2655  // Now, if there are any fields that require substitution to be done, lets go for that:
2656  foreach ($fieldsIndex as $field => $softRefCfgs) {
2657  if (is_array($GLOBALS['TCA'][$table]['columns'][$field])) {
2658  $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
2659  if ($conf['type'] === 'flex') {
2660  // This will fetch the new row for the element (which should be updated with any references to data structures etc.)
2661  $origRecordRow = BackendUtility::getRecord($table, $thisNewUid, '*');
2662  if (is_array($origRecordRow)) {
2663  // Get current data structure and value array:
2664  $dataStructArray = BackendUtility::getFlexFormDS($conf, $origRecordRow, $table, $field);
2665  $currentValueArray = GeneralUtility::xml2array($origRecordRow[$field]);
2666  // Do recursive processing of the XML data:
2668  $iteratorObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class);
2669  $iteratorObj->callBackObj = $this;
2670  $currentValueArray['data'] = $iteratorObj->checkValue_flex_procInData($currentValueArray['data'], array(), array(), $dataStructArray, array($table, $uid, $field, $softRefCfgs), 'processSoftReferences_flexFormCallBack');
2671  // The return value is set as an array which means it will be processed by tcemain for file and DB references!
2672  if (is_array($currentValueArray['data'])) {
2673  $inData[$table][$thisNewUid][$field] = $currentValueArray;
2674  }
2675  }
2676  } else {
2677  // Get tokenizedContent string and proceed only if that is not blank:
2678  $tokenizedContent = $this->dat['records'][$table . ':' . $uid]['rels'][$field]['softrefs']['tokenizedContent'];
2679  if (strlen($tokenizedContent) && is_array($softRefCfgs)) {
2680  $inData[$table][$thisNewUid][$field] = $this->processSoftReferences_substTokens($tokenizedContent, $softRefCfgs, $table, $uid);
2681  }
2682  }
2683  }
2684  }
2685  }
2686  }
2687  }
2688  }
2689  // Now write to database:
2690  $tce = $this->getNewTCE();
2691  $tce->isImporting = true;
2692  $this->callHook('before_processSoftReferences', array(
2693  'tce' => $tce,
2694  'data' => &$inData
2695  ));
2696  $tce->enableLogging = true;
2697  $tce->start($inData, array());
2698  $tce->process_datamap();
2699  $this->callHook('after_processSoftReferences', array(
2700  'tce' => $tce
2701  ));
2702  }
2703 
2716  public function processSoftReferences_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)
2717  {
2718  // Extract parameters:
2719  list($table, $origUid, $field, $softRefCfgs) = $pParams;
2720  if (is_array($softRefCfgs)) {
2721  // First, find all soft reference configurations for this structure path (they are listed flat in the header):
2722  $thisSoftRefCfgList = array();
2723  foreach ($softRefCfgs as $sK => $sV) {
2724  if ($sV['structurePath'] === $path) {
2725  $thisSoftRefCfgList[$sK] = $sV;
2726  }
2727  }
2728  // If any was found, do processing:
2729  if (!empty($thisSoftRefCfgList)) {
2730  // Get tokenizedContent string and proceed only if that is not blank:
2731  $tokenizedContent = $this->dat['records'][$table . ':' . $origUid]['rels'][$field]['flexFormRels']['softrefs'][$path]['tokenizedContent'];
2732  if (strlen($tokenizedContent)) {
2733  $dataValue = $this->processSoftReferences_substTokens($tokenizedContent, $thisSoftRefCfgList, $table, $origUid);
2734  }
2735  }
2736  }
2737  // Return
2738  return array('value' => $dataValue);
2739  }
2740 
2750  public function processSoftReferences_substTokens($tokenizedContent, $softRefCfgs, $table, $uid)
2751  {
2752  // traverse each softref type for this field:
2753  foreach ($softRefCfgs as $cfg) {
2754  // Get token ID:
2755  $tokenID = $cfg['subst']['tokenID'];
2756  // Default is current token value:
2757  $insertValue = $cfg['subst']['tokenValue'];
2758  // Based on mode:
2759  switch ((string)$this->softrefCfg[$tokenID]['mode']) {
2760  case 'exclude':
2761  // Exclude is a simple passthrough of the value
2762  break;
2763  case 'editable':
2764  // Editable always picks up the value from this input array:
2765  $insertValue = $this->softrefInputValues[$tokenID];
2766  break;
2767  default:
2768  // Mapping IDs/creating files: Based on type, look up new value:
2769  switch ((string)$cfg['subst']['type']) {
2770  case 'file':
2771  // Create / Overwrite file:
2772  $insertValue = $this->processSoftReferences_saveFile($cfg['subst']['relFileName'], $cfg, $table, $uid);
2773  break;
2774  case 'db':
2775  default:
2776  // Trying to map database element if found in the mapID array:
2777  list($tempTable, $tempUid) = explode(':', $cfg['subst']['recordRef']);
2778  if (isset($this->import_mapId[$tempTable][$tempUid])) {
2779  $insertValue = BackendUtility::wsMapId($tempTable, $this->import_mapId[$tempTable][$tempUid]);
2780  // 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!
2781  if ($tempTable === 'pages' && !\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($cfg['subst']['tokenValue'])) {
2782  $recWithUniqueValue = BackendUtility::getRecord($tempTable, $insertValue, 'alias');
2783  if ($recWithUniqueValue['alias']) {
2784  $insertValue = $recWithUniqueValue['alias'];
2785  }
2786  } elseif (strpos($cfg['subst']['tokenValue'], ':') !== false) {
2787  list($tokenKey, $tokenId) = explode(':', $cfg['subst']['tokenValue']);
2788  $insertValue = $tokenKey . ':' . $insertValue;
2789  }
2790  }
2791  }
2792  }
2793  // Finally, swap the soft reference token in tokenized content with the insert value:
2794  $tokenizedContent = str_replace('{softref:' . $tokenID . '}', $insertValue, $tokenizedContent);
2795  }
2796  return $tokenizedContent;
2797  }
2798 
2808  public function processSoftReferences_saveFile($relFileName, $cfg, $table, $uid)
2809  {
2810  if ($fileHeaderInfo = $this->dat['header']['files'][$cfg['file_ID']]) {
2811  // Initialize; Get directory prefix for file and find possible RTE filename
2812  $dirPrefix = PathUtility::dirname($relFileName) . '/';
2813  $rteOrigName = $this->getRTEoriginalFilename(PathUtility::basename($relFileName));
2814  // If filename looks like an RTE file, and the directory is in "uploads/", then process as a RTE file!
2815  if ($rteOrigName && GeneralUtility::isFirstPartOfStr($dirPrefix, 'uploads/')) {
2816  // RTE:
2817  // First, find unique RTE file name:
2818  if (@is_dir((PATH_site . $dirPrefix))) {
2819  // From the "original" RTE filename, produce a new "original" destination filename which is unused.
2820  // Even if updated, the image should be unique. Currently the problem with this is that it leaves a lot of unused RTE images...
2821  $fileProcObj = $this->getFileProcObj();
2822  $origDestName = $fileProcObj->getUniqueName($rteOrigName, PATH_site . $dirPrefix);
2823  // Create copy file name:
2824  $pI = pathinfo($relFileName);
2825  $copyDestName = PathUtility::dirname($origDestName) . '/RTEmagicC_' . substr(PathUtility::basename($origDestName), 10) . '.' . $pI['extension'];
2826  if (
2827  !@is_file($copyDestName) && !@is_file($origDestName)
2828  && $origDestName === GeneralUtility::getFileAbsFileName($origDestName)
2829  && $copyDestName === GeneralUtility::getFileAbsFileName($copyDestName)
2830  ) {
2831  if ($this->dat['header']['files'][$fileHeaderInfo['RTE_ORIG_ID']]) {
2832  if ($this->legacyImport) {
2833  $fileName = PathUtility::basename($copyDestName);
2834  $this->writeSysFileResourceForLegacyImport($fileName, $cfg['file_ID']);
2835  $relFileName = $this->filePathMap[$cfg['file_ID']] . '" data-htmlarea-file-uid="' . $fileName . '" data-htmlarea-file-table="sys_file';
2836  // Also save the original file
2837  $originalFileName = PathUtility::basename($origDestName);
2838  $this->writeSysFileResourceForLegacyImport($originalFileName, $fileHeaderInfo['RTE_ORIG_ID']);
2839  } else {
2840  // Write the copy and original RTE file to the respective filenames:
2841  $this->writeFileVerify($copyDestName, $cfg['file_ID'], true);
2842  $this->writeFileVerify($origDestName, $fileHeaderInfo['RTE_ORIG_ID'], true);
2843  // Return the relative path of the copy file name:
2844  return PathUtility::stripPathSitePrefix($copyDestName);
2845  }
2846  } else {
2847  $this->error('ERROR: Could not find original file ID');
2848  }
2849  } else {
2850  $this->error('ERROR: The destination filenames "' . $copyDestName . '" and "' . $origDestName . '" either existed or have non-valid names');
2851  }
2852  } else {
2853  $this->error('ERROR: "' . PATH_site . $dirPrefix . '" was not a directory, so could not process file "' . $relFileName . '"');
2854  }
2855  } elseif (GeneralUtility::isFirstPartOfStr($dirPrefix, $this->fileadminFolderName . '/')) {
2856  // File in fileadmin/ folder:
2857  // Create file (and possible resources)
2858  $newFileName = $this->processSoftReferences_saveFile_createRelFile($dirPrefix, PathUtility::basename($relFileName), $cfg['file_ID'], $table, $uid);
2859  if (strlen($newFileName)) {
2860  $relFileName = $newFileName;
2861  } else {
2862  $this->error('ERROR: No new file created for "' . $relFileName . '"');
2863  }
2864  } else {
2865  $this->error('ERROR: Sorry, cannot operate on non-RTE files which are outside the fileadmin folder.');
2866  }
2867  } else {
2868  $this->error('ERROR: Could not find file ID in header.');
2869  }
2870  // Return (new) filename relative to PATH_site:
2871  return $relFileName;
2872  }
2873 
2884  public function processSoftReferences_saveFile_createRelFile($origDirPrefix, $fileName, $fileID, $table, $uid)
2885  {
2886  // If the fileID map contains an entry for this fileID then just return the relative filename of that entry;
2887  // we don't want to write another unique filename for this one!
2888  if (isset($this->fileIDMap[$fileID])) {
2889  return PathUtility::stripPathSitePrefix($this->fileIDMap[$fileID]);
2890  }
2891  if ($this->legacyImport) {
2892  // set dirPrefix to fileadmin because the right target folder is set and checked for permissions later
2893  $dirPrefix = $this->fileadminFolderName . '/';
2894  } else {
2895  // Verify FileMount access to dir-prefix. Returns the best alternative relative path if any
2896  $dirPrefix = $this->verifyFolderAccess($origDirPrefix);
2897  }
2898  if ($dirPrefix && (!$this->update || $origDirPrefix === $dirPrefix) && $this->checkOrCreateDir($dirPrefix)) {
2899  $fileHeaderInfo = $this->dat['header']['files'][$fileID];
2900  $updMode = $this->update && $this->import_mapId[$table][$uid] === $uid && $this->import_mode[$table . ':' . $uid] !== 'as_new';
2901  // Create new name for file:
2902  // Must have same ID in map array (just for security, is not really needed) and NOT be set "as_new".
2903 
2904  // Write main file:
2905  if ($this->legacyImport) {
2906  $fileWritten = $this->writeSysFileResourceForLegacyImport($fileName, $fileID);
2907  if ($fileWritten) {
2908  $newName = 'file:' . $fileName;
2909  return $newName;
2910  // no support for HTML/CSS file resources attached ATM - see below
2911  }
2912  } else {
2913  if ($updMode) {
2914  $newName = PATH_site . $dirPrefix . $fileName;
2915  } else {
2916  // Create unique filename:
2917  $fileProcObj = $this->getFileProcObj();
2918  $newName = $fileProcObj->getUniqueName($fileName, PATH_site . $dirPrefix);
2919  }
2920  if ($this->writeFileVerify($newName, $fileID)) {
2921  // If the resource was an HTML/CSS file with resources attached, we will write those as well!
2922  if (is_array($fileHeaderInfo['EXT_RES_ID'])) {
2923  $tokenizedContent = $this->dat['files'][$fileID]['tokenizedContent'];
2924  $tokenSubstituted = false;
2925  $fileProcObj = $this->getFileProcObj();
2926  if ($updMode) {
2927  foreach ($fileHeaderInfo['EXT_RES_ID'] as $res_fileID) {
2928  if ($this->dat['files'][$res_fileID]['filename']) {
2929  // Resolve original filename:
2930  $relResourceFileName = $this->dat['files'][$res_fileID]['parentRelFileName'];
2931  $absResourceFileName = GeneralUtility::resolveBackPath(PATH_site . $origDirPrefix . $relResourceFileName);
2932  $absResourceFileName = GeneralUtility::getFileAbsFileName($absResourceFileName);
2933  if ($absResourceFileName && GeneralUtility::isFirstPartOfStr($absResourceFileName, PATH_site . $this->fileadminFolderName . '/')) {
2934  $destDir = PathUtility::stripPathSitePrefix(PathUtility::dirname($absResourceFileName) . '/');
2935  if ($this->verifyFolderAccess($destDir, true) && $this->checkOrCreateDir($destDir)) {
2936  $this->writeFileVerify($absResourceFileName, $res_fileID);
2937  } else {
2938  $this->error('ERROR: Could not create file in directory "' . $destDir . '"');
2939  }
2940  } else {
2941  $this->error('ERROR: Could not resolve path for "' . $relResourceFileName . '"');
2942  }
2943  $tokenizedContent = str_replace('{EXT_RES_ID:' . $res_fileID . '}', $relResourceFileName, $tokenizedContent);
2944  $tokenSubstituted = true;
2945  }
2946  }
2947  } else {
2948  // Create the resouces directory name (filename without extension, suffixed "_FILES")
2949  $resourceDir = PathUtility::dirname($newName) . '/' . preg_replace('/\\.[^.]*$/', '', PathUtility::basename($newName)) . '_FILES';
2950  if (GeneralUtility::mkdir($resourceDir)) {
2951  foreach ($fileHeaderInfo['EXT_RES_ID'] as $res_fileID) {
2952  if ($this->dat['files'][$res_fileID]['filename']) {
2953  $absResourceFileName = $fileProcObj->getUniqueName($this->dat['files'][$res_fileID]['filename'], $resourceDir);
2954  $relResourceFileName = substr($absResourceFileName, strlen(PathUtility::dirname($resourceDir)) + 1);
2955  $this->writeFileVerify($absResourceFileName, $res_fileID);
2956  $tokenizedContent = str_replace('{EXT_RES_ID:' . $res_fileID . '}', $relResourceFileName, $tokenizedContent);
2957  $tokenSubstituted = true;
2958  }
2959  }
2960  }
2961  }
2962  // If substitutions has been made, write the content to the file again:
2963  if ($tokenSubstituted) {
2964  GeneralUtility::writeFile($newName, $tokenizedContent);
2965  }
2966  }
2967  return PathUtility::stripPathSitePrefix($newName);
2968  }
2969  }
2970  }
2971  return null;
2972  }
2973 
2982  public function writeFileVerify($fileName, $fileID, $bypassMountCheck = false)
2983  {
2984  $fileProcObj = $this->getFileProcObj();
2985  if (!$fileProcObj->actionPerms['addFile']) {
2986  $this->error('ERROR: You did not have sufficient permissions to write the file "' . $fileName . '"');
2987  return false;
2988  }
2989  // Just for security, check again. Should actually not be necessary.
2990  if (!$fileProcObj->checkPathAgainstMounts($fileName) && !$bypassMountCheck) {
2991  $this->error('ERROR: Filename "' . $fileName . '" was not allowed in destination path!');
2992  return false;
2993  }
2994  $fI = GeneralUtility::split_fileref($fileName);
2995  if (!$fileProcObj->checkIfAllowed($fI['fileext'], $fI['path'], $fI['file']) && (!$this->allowPHPScripts || !$GLOBALS['BE_USER']->isAdmin())) {
2996  $this->error('ERROR: Filename "' . $fileName . '" failed against extension check or deny-pattern!');
2997  return false;
2998  }
2999  if (!GeneralUtility::getFileAbsFileName($fileName)) {
3000  $this->error('ERROR: Filename "' . $fileName . '" was not a valid relative file path!');
3001  return false;
3002  }
3003  if (!$this->dat['files'][$fileID]) {
3004  $this->error('ERROR: File ID "' . $fileID . '" could not be found');
3005  return false;
3006  }
3007  GeneralUtility::writeFile($fileName, $this->dat['files'][$fileID]['content']);
3008  $this->fileIDMap[$fileID] = $fileName;
3009  if (md5(GeneralUtility::getUrl($fileName)) == $this->dat['files'][$fileID]['content_md5']) {
3010  return true;
3011  } else {
3012  $this->error('ERROR: File content "' . $fileName . '" was corrupted');
3013  return false;
3014  }
3015  }
3016 
3026  protected function writeSysFileResourceForLegacyImport(&$fileName, $fileId)
3027  {
3028  if ($this->legacyImportFolder === null) {
3029  return false;
3030  }
3031 
3032  if (!isset($this->dat['files'][$fileId])) {
3033  $this->error('ERROR: File ID "' . $fileId . '" could not be found');
3034  return false;
3035  }
3036 
3037  $temporaryFile = $this->writeTemporaryFileFromData($fileId, 'files');
3038  if ($temporaryFile === null) {
3039  // error on writing the file. Error message was already added
3040  return false;
3041  }
3042 
3043  $importFolder = $this->legacyImportFolder;
3044 
3045  if (isset($this->dat['files'][$fileId]['relFileName'])) {
3046  $relativeFilePath = PathUtility::dirname($this->dat['files'][$fileId]['relFileName']);
3047 
3048  if (!$this->legacyImportFolder->hasFolder($relativeFilePath)) {
3049  $this->legacyImportFolder->createFolder($relativeFilePath);
3050  }
3051  $importFolder = $this->legacyImportFolder->getSubfolder($relativeFilePath);
3052  }
3053 
3054  $fileObject = null;
3055 
3056  try {
3057  // check, if there is alreay the same file in the folder
3058  if ($importFolder->hasFile($fileName)) {
3059  $fileStorage = $importFolder->getStorage();
3060  $file = $fileStorage->getFile($importFolder->getIdentifier() . $fileName);
3061  if ($file->getSha1() === sha1_file($temporaryFile)) {
3062  $fileObject = $file;
3063  }
3064  }
3065  } catch (Exception $e) {
3066  }
3067 
3068  if ($fileObject === null) {
3069  try {
3070  $fileObject = $importFolder->addFile($temporaryFile, $fileName, DuplicationBehavior::RENAME);
3071  } catch (\TYPO3\CMS\Core\Exception $e) {
3072  $this->error('Error: no file could be added to the storage for file name ' . $this->alternativeFileName[$temporaryFile]);
3073  }
3074  }
3075 
3076  if (md5_file(PATH_site . $fileObject->getPublicUrl()) == $this->dat['files'][$fileId]['content_md5']) {
3077  $fileName = $fileObject->getUid();
3078  $this->fileIDMap[$fileId] = $fileName;
3079  $this->filePathMap[$fileId] = $fileObject->getPublicUrl();
3080  return true;
3081  } else {
3082  $this->error('ERROR: File content "' . $this->dat['files'][$fileId]['relFileName'] . '" was corrupted');
3083  }
3084 
3085  return false;
3086  }
3087 
3093  protected function migrateLegacyImportRecords()
3094  {
3095  $updateData= array();
3096 
3097  foreach ($this->legacyImportMigrationRecords as $table => $records) {
3098  foreach ($records as $uid => $fields) {
3099  $row = BackendUtility::getRecord($table, $uid);
3100  if (empty($row)) {
3101  continue;
3102  }
3103 
3104  foreach ($fields as $field => $referenceIds) {
3105  $fieldConfiguration = $this->legacyImportMigrationTables[$table][$field];
3106 
3107  if (isset($fieldConfiguration['titleTexts'])) {
3108  $titleTextField = $fieldConfiguration['titleTexts'];
3109  if (isset($row[$titleTextField]) && $row[$titleTextField] !== '') {
3110  $titleTextContents = explode(LF, $row[$titleTextField]);
3111  $updateData[$table][$uid][$titleTextField] = '';
3112  }
3113  }
3114 
3115  if (isset($fieldConfiguration['alternativeTexts'])) {
3116  $alternativeTextField = $fieldConfiguration['alternativeTexts'];
3117  if (isset($row[$alternativeTextField]) && $row[$alternativeTextField] !== '') {
3118  $alternativeTextContents = explode(LF, $row[$alternativeTextField]);
3119  $updateData[$table][$uid][$alternativeTextField] = '';
3120  }
3121  }
3122  if (isset($fieldConfiguration['description'])) {
3123  $descriptionField = $fieldConfiguration['description'];
3124  if ($row[$descriptionField] !== '') {
3125  $descriptionContents = explode(LF, $row[$descriptionField]);
3126  $updateData[$table][$uid][$descriptionField] = '';
3127  }
3128  }
3129  if (isset($fieldConfiguration['links'])) {
3130  $linkField = $fieldConfiguration['links'];
3131  if ($row[$linkField] !== '') {
3132  $linkContents = explode(LF, $row[$linkField]);
3133  $updateData[$table][$uid][$linkField] = '';
3134  }
3135  }
3136 
3137  foreach ($referenceIds as $key => $referenceId) {
3138  if (isset($titleTextContents[$key])) {
3139  $updateData['sys_file_reference'][$referenceId]['title'] = trim($titleTextContents[$key]);
3140  }
3141  if (isset($alternativeTextContents[$key])) {
3142  $updateData['sys_file_reference'][$referenceId]['alternative'] = trim($alternativeTextContents[$key]);
3143  }
3144  if (isset($descriptionContents[$key])) {
3145  $updateData['sys_file_reference'][$referenceId]['description'] = trim($descriptionContents[$key]);
3146  }
3147  if (isset($linkContents[$key])) {
3148  $updateData['sys_file_reference'][$referenceId]['link'] = trim($linkContents[$key]);
3149  }
3150  }
3151  }
3152  }
3153  }
3154 
3155  // update
3156  $tce = $this->getNewTCE();
3157  $tce->isImporting = true;
3158  $tce->start($updateData, array());
3159  $tce->process_datamap();
3160  }
3161 
3168  public function checkOrCreateDir($dirPrefix)
3169  {
3170  // Split dir path and remove first directory (which should be "fileadmin")
3171  $filePathParts = explode('/', $dirPrefix);
3172  $firstDir = array_shift($filePathParts);
3173  if ($firstDir === $this->fileadminFolderName && GeneralUtility::getFileAbsFileName($dirPrefix)) {
3174  $pathAcc = '';
3175  foreach ($filePathParts as $dirname) {
3176  $pathAcc .= '/' . $dirname;
3177  if (strlen($dirname)) {
3178  if (!@is_dir((PATH_site . $this->fileadminFolderName . $pathAcc))) {
3179  if (!GeneralUtility::mkdir((PATH_site . $this->fileadminFolderName . $pathAcc))) {
3180  $this->error('ERROR: Directory could not be created....B');
3181  return false;
3182  }
3183  }
3184  } elseif ($dirPrefix === $this->fileadminFolderName . $pathAcc) {
3185  return true;
3186  } else {
3187  $this->error('ERROR: Directory could not be created....A');
3188  }
3189  }
3190  }
3191  return false;
3192  }
3193 
3202  public function verifyFolderAccess($dirPrefix, $noAlternative = false)
3203  {
3204  $fileProcObj = $this->getFileProcObj();
3205  // Check, if dirPrefix is inside a valid Filemount for user:
3206  $result = $fileProcObj->checkPathAgainstMounts(PATH_site . $dirPrefix);
3207  // If not, try to find another relative filemount and use that instead:
3208  if (!$result) {
3209  if ($noAlternative) {
3210  return false;
3211  }
3212  // Find first web folder:
3213  $result = $fileProcObj->findFirstWebFolder();
3214  // If that succeeded, return the path to it:
3215  if ($result) {
3216  // Remove the "fileadmin/" prefix of input path - and append the rest to the return value:
3217  if (GeneralUtility::isFirstPartOfStr($dirPrefix, $this->fileadminFolderName . '/')) {
3218  $dirPrefix = substr($dirPrefix, strlen($this->fileadminFolderName . '/'));
3219  }
3220  return PathUtility::stripPathSitePrefix($fileProcObj->mounts[$result]['path'] . $dirPrefix);
3221  }
3222  } else {
3223  return $dirPrefix;
3224  }
3225  return false;
3226  }
3227 
3228  /**************************
3229  * File Input
3230  *************************/
3231 
3239  public function loadFile($filename, $all = false)
3240  {
3241  if (!@is_file($filename)) {
3242  $this->error('Filename not found: ' . $filename);
3243  return false;
3244  }
3245  $fI = pathinfo($filename);
3246  if (@is_dir($filename . '.files')) {
3247  if (GeneralUtility::isAllowedAbsPath($filename . '.files')) {
3248  // copy the folder lowlevel to typo3temp, because the files would be deleted after import
3249  $temporaryFolderName = $this->getTemporaryFolderName();
3250  GeneralUtility::copyDirectory($filename . '.files', $temporaryFolderName);
3251  $this->filesPathForImport = $temporaryFolderName;
3252  } else {
3253  $this->error('External import files for the given import source is currently not supported.');
3254  }
3255  }
3256  if (strtolower($fI['extension']) == 'xml') {
3257  // XML:
3258  $xmlContent = GeneralUtility::getUrl($filename);
3259  if (strlen($xmlContent)) {
3260  $this->dat = GeneralUtility::xml2array($xmlContent, '', true);
3261  if (is_array($this->dat)) {
3262  if ($this->dat['_DOCUMENT_TAG'] === 'T3RecordDocument' && is_array($this->dat['header']) && is_array($this->dat['records'])) {
3263  $this->loadInit();
3264  return true;
3265  } else {
3266  $this->error('XML file did not contain proper XML for TYPO3 Import');
3267  }
3268  } else {
3269  $this->error('XML could not be parsed: ' . $this->dat);
3270  }
3271  } else {
3272  $this->error('Error opening file: ' . $filename);
3273  }
3274  } else {
3275  // T3D
3276  if ($fd = fopen($filename, 'rb')) {
3277  $this->dat['header'] = $this->getNextFilePart($fd, 1, 'header');
3278  if ($all) {
3279  $this->dat['records'] = $this->getNextFilePart($fd, 1, 'records');
3280  $this->dat['files'] = $this->getNextFilePart($fd, 1, 'files');
3281  $this->dat['files_fal'] = $this->getNextFilePart($fd, 1, 'files_fal');
3282  }
3283  $this->loadInit();
3284  return true;
3285  } else {
3286  $this->error('Error opening file: ' . $filename);
3287  }
3288  fclose($fd);
3289  }
3290  return false;
3291  }
3292 
3303  public function getNextFilePart($fd, $unserialize = false, $name = '')
3304  {
3305  $initStrLen = 32 + 1 + 1 + 1 + 10 + 1;
3306  // Getting header data
3307  $initStr = fread($fd, $initStrLen);
3308  if (empty($initStr)) {
3309  $this->error('File does not contain data for "' . $name . '"');
3310  return null;
3311  }
3312  $initStrDat = explode(':', $initStr);
3313  if (strstr($initStrDat[0], 'Warning')) {
3314  $this->error('File read error: Warning message in file. (' . $initStr . fgets($fd) . ')');
3315  return null;
3316  }
3317  if ((string)$initStrDat[3] !== '') {
3318  $this->error('File read error: InitString had a wrong length. (' . $name . ')');
3319  return null;
3320  }
3321  $datString = fread($fd, (int)$initStrDat[2]);
3322  fread($fd, 1);
3323  if (md5($datString) === $initStrDat[0]) {
3324  if ($initStrDat[1]) {
3325  if ($this->compress) {
3326  $datString = gzuncompress($datString);
3327  return $unserialize ? unserialize($datString) : $datString;
3328  } else {
3329  $this->error('Content read error: This file requires decompression, but this server does not offer gzcompress()/gzuncompress() functions.', 1);
3330  }
3331  }
3332  } else {
3333  $this->error('MD5 check failed (' . $name . ')');
3334  }
3335  return null;
3336  }
3337 
3345  public function loadContent($filecontent)
3346  {
3347  $pointer = 0;
3348  $this->dat['header'] = $this->getNextContentPart($filecontent, $pointer, 1, 'header');
3349  $this->dat['records'] = $this->getNextContentPart($filecontent, $pointer, 1, 'records');
3350  $this->dat['files'] = $this->getNextContentPart($filecontent, $pointer, 1, 'files');
3351  $this->loadInit();
3352  }
3353 
3363  public function getNextContentPart($filecontent, &$pointer, $unserialize = false, $name = '')
3364  {
3365  $initStrLen = 32 + 1 + 1 + 1 + 10 + 1;
3366  // getting header data
3367  $initStr = substr($filecontent, $pointer, $initStrLen);
3368  $pointer += $initStrLen;
3369  $initStrDat = explode(':', $initStr);
3370  if ((string)$initStrDat[3] !== '') {
3371  $this->error('Content read error: InitString had a wrong length. (' . $name . ')');
3372  return null;
3373  }
3374  $datString = substr($filecontent, $pointer, (int)$initStrDat[2]);
3375  $pointer += (int)$initStrDat[2] + 1;
3376  if (md5($datString) === $initStrDat[0]) {
3377  if ($initStrDat[1]) {
3378  if ($this->compress) {
3379  $datString = gzuncompress($datString);
3380  return $unserialize ? unserialize($datString) : $datString;
3381  } else {
3382  $this->error('Content read error: This file requires decompression, but this server does not offer gzcompress()/gzuncompress() functions.', 1);
3383  }
3384  }
3385  } else {
3386  $this->error('MD5 check failed (' . $name . ')');
3387  }
3388  return null;
3389  }
3390 
3396  public function loadInit()
3397  {
3398  $this->relStaticTables = (array)$this->dat['header']['relStaticTables'];
3399  $this->excludeMap = (array)$this->dat['header']['excludeMap'];
3400  $this->softrefCfg = (array)$this->dat['header']['softrefCfg'];
3401  $this->extensionDependencies = (array)$this->dat['header']['extensionDependencies'];
3402  $this->fixCharsets();
3403  if (
3404  isset($this->dat['header']['meta']['TYPO3_version'])
3405  && \TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionNumberToInteger($this->dat['header']['meta']['TYPO3_version']) < 6000000
3406  ) {
3407  $this->legacyImport = true;
3408  $this->initializeLegacyImportFolder();
3409  }
3410  }
3411 
3418  public function fixCharsets()
3419  {
3420  $importCharset = $this->dat['header']['charset'];
3421  if ($importCharset) {
3422  if ($importCharset !== $GLOBALS['LANG']->charSet) {
3423  $this->error('CHARSET: Converting charset of input file (' . $importCharset . ') to the system charset (' . $GLOBALS['LANG']->charSet . ')');
3424  // Convert meta data:
3425  if (is_array($this->dat['header']['meta'])) {
3426  $GLOBALS['LANG']->csConvObj->convArray($this->dat['header']['meta'], $importCharset, $GLOBALS['LANG']->charSet);
3427  }
3428  // Convert record headers:
3429  if (is_array($this->dat['header']['records'])) {
3430  $GLOBALS['LANG']->csConvObj->convArray($this->dat['header']['records'], $importCharset, $GLOBALS['LANG']->charSet);
3431  }
3432  // Convert records themselves:
3433  if (is_array($this->dat['records'])) {
3434  $GLOBALS['LANG']->csConvObj->convArray($this->dat['records'], $importCharset, $GLOBALS['LANG']->charSet);
3435  }
3436  }
3437  } else {
3438  $this->error('CHARSET: No charset found in import file!');
3439  }
3440  }
3441 
3442  /********************************************************
3443  * Visual rendering of import/export memory, $this->dat
3444  ********************************************************/
3445 
3451  public function displayContentOverview()
3452  {
3453  // Check extension dependencies:
3454  if (is_array($this->dat['header']['extensionDependencies'])) {
3455  foreach ($this->dat['header']['extensionDependencies'] as $extKey) {
3456  if (!\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded($extKey)) {
3457  $this->error('DEPENDENCY: The extension with key "' . $extKey . '" must be installed!');
3458  }
3459  }
3460  }
3461  // Probably this is done to save memory space?
3462  unset($this->dat['files']);
3463  $out = '';
3464  // Traverse header:
3465  if (is_array($this->dat['header'])) {
3466  $this->remainHeader = $this->dat['header'];
3467  // If there is a page tree set, show that:
3468  if (is_array($this->dat['header']['pagetree'])) {
3469  reset($this->dat['header']['pagetree']);
3470  $lines = array();
3471  $this->traversePageTree($this->dat['header']['pagetree'], $lines);
3472  $rows = array();
3473  $rows[] = '
3474  <tr class="bgColor5 tableheader">
3475  <td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_controls', true) . '</td>
3476  <td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_title', true) . '</td>
3477  <td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_size', true) . '</td>
3478  <td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_message', true) . '</td>
3479  ' . ($this->update ? '<td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_updateMode', true) . '</td>' : '') . '
3480  ' . ($this->update ? '<td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_currentPath', true) . '</td>' : '') . '
3481  ' . ($this->showDiff ? '<td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_result', true) . '</td>' : '') . '
3482  </tr>';
3483  foreach ($lines as $r) {
3484  $rows[] = '
3485  <tr class="' . $r['class'] . '">
3486  <td>' . $this->renderControls($r) . '</td>
3487  <td nowrap="nowrap">' . $r['preCode'] . $r['title'] . '</td>
3488  <td nowrap="nowrap">' . GeneralUtility::formatSize($r['size']) . '</td>
3489  <td nowrap="nowrap">' . ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '') . '</td>
3490  ' . ($this->update ? '<td nowrap="nowrap">' . $r['updateMode'] . '</td>' : '') . '
3491  ' . ($this->update ? '<td nowrap="nowrap">' . $r['updatePath'] . '</td>' : '') . '
3492  ' . ($this->showDiff ? '<td>' . $r['showDiffContent'] . '</td>' : '') . '
3493  </tr>';
3494  }
3495  $out = '
3496  <strong>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_insidePagetree', true) . '</strong>
3497  <br /><br />
3498  <table border="0" cellpadding="0" cellspacing="1">' . implode('', $rows) . '</table>
3499  <br /><br />';
3500  }
3501  // Print remaining records that were not contained inside the page tree:
3502  $lines = array();
3503  if (is_array($this->remainHeader['records'])) {
3504  if (is_array($this->remainHeader['records']['pages'])) {
3505  $this->traversePageRecords($this->remainHeader['records']['pages'], $lines);
3506  }
3507  $this->traverseAllRecords($this->remainHeader['records'], $lines);
3508  if (!empty($lines)) {
3509  $rows = array();
3510  $rows[] = '
3511  <tr class="bgColor5 tableheader">
3512  <td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_controls', true) . '</td>
3513  <td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_title', true) . '</td>
3514  <td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_size', true) . '</td>
3515  <td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_message', true) . '</td>
3516  ' . ($this->update ? '<td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_updateMode', true) . '</td>' : '') . '
3517  ' . ($this->update ? '<td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_currentPath', true) . '</td>' : '') . '
3518  ' . ($this->showDiff ? '<td>' . $GLOBALS['LANG']->getLL('impexpcore_displaycon_result', true) . '</td>' : '') . '
3519  </tr>';
3520  foreach ($lines as $r) {
3521  $rows[] = '<tr class="' . $r['class'] . '">
3522  <td>' . $this->renderControls($r) . '</td>
3523  <td nowrap="nowrap">' . $r['preCode'] . $r['title'] . '</td>
3524  <td nowrap="nowrap">' . GeneralUtility::formatSize($r['size']) . '</td>
3525  <td nowrap="nowrap">' . ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '') . '</td>
3526  ' . ($this->update ? '<td nowrap="nowrap">' . $r['updateMode'] . '</td>' : '') . '
3527  ' . ($this->update ? '<td nowrap="nowrap">' . $r['updatePath'] . '</td>' : '') . '
3528  ' . ($this->showDiff ? '<td>' . $r['showDiffContent'] . '</td>' : '') . '
3529  </tr>';
3530  }
3531  $out .= '
3532  <strong>' . $GLOBALS['LANG']->getLL('impexpcore_singlereco_outsidePagetree', true) . '</strong>
3533  <br /><br />
3534  <table border="0" cellpadding="0" cellspacing="1">' . implode('', $rows) . '</table>';
3535  }
3536  }
3537  }
3538  return $out;
3539  }
3540 
3549  public function traversePageTree($pT, &$lines, $preCode = '')
3550  {
3551  foreach ($pT as $k => $v) {
3552  // Add this page:
3553  $this->singleRecordLines('pages', $k, $lines, $preCode);
3554  // Subrecords:
3555  if (is_array($this->dat['header']['pid_lookup'][$k])) {
3556  foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) {
3557  if ($t != 'pages') {
3558  foreach ($recUidArr as $ruid => $value) {
3559  $this->singleRecordLines($t, $ruid, $lines, $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;');
3560  }
3561  }
3562  }
3563  unset($this->remainHeader['pid_lookup'][$k]);
3564  }
3565  // Subpages, called recursively:
3566  if (is_array($v['subrow'])) {
3567  $this->traversePageTree($v['subrow'], $lines, $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;');
3568  }
3569  }
3570  }
3571 
3579  public function traversePageRecords($pT, &$lines)
3580  {
3581  foreach ($pT as $k => $rHeader) {
3582  $this->singleRecordLines('pages', $k, $lines, '', 1);
3583  // Subrecords:
3584  if (is_array($this->dat['header']['pid_lookup'][$k])) {
3585  foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) {
3586  if ($t != 'pages') {
3587  foreach ($recUidArr as $ruid => $value) {
3588  $this->singleRecordLines($t, $ruid, $lines, '&nbsp;&nbsp;&nbsp;&nbsp;');
3589  }
3590  }
3591  }
3592  unset($this->remainHeader['pid_lookup'][$k]);
3593  }
3594  }
3595  }
3596 
3604  public function traverseAllRecords($pT, &$lines)
3605  {
3606  foreach ($pT as $t => $recUidArr) {
3607  $this->addGeneralErrorsByTable($t);
3608  if ($t != 'pages') {
3609  $preCode = '';
3610  foreach ($recUidArr as $ruid => $value) {
3611  $this->singleRecordLines($t, $ruid, $lines, $preCode, 1);
3612  }
3613  }
3614  }
3615  }
3616 
3623  protected function addGeneralErrorsByTable($table)
3624  {
3625  if ($this->update && $table === 'sys_file') {
3626  $this->error('Updating sys_file records is not supported! They will be imported as new records!');
3627  }
3628  if ($this->force_all_UIDS && $table === 'sys_file') {
3629  $this->error('Forcing uids of sys_file records is not supported! They will be imported as new records!');
3630  }
3631  }
3632 
3643  public function singleRecordLines($table, $uid, &$lines, $preCode, $checkImportInPidRecord = false)
3644  {
3645  // Get record:
3646  $record = $this->dat['header']['records'][$table][$uid];
3647  unset($this->remainHeader['records'][$table][$uid]);
3648  if (!is_array($record) && !($table === 'pages' && !$uid)) {
3649  $this->error('MISSING RECORD: ' . $table . ':' . $uid, 1);
3650  }
3651  // Begin to create the line arrays information record, pInfo:
3652  $pInfo = array();
3653  $pInfo['ref'] = $table . ':' . $uid;
3654  // Unknown table name:
3655  if ($table === '_SOFTREF_') {
3656  $pInfo['preCode'] = $preCode;
3657  $pInfo['title'] = '<em>' . $GLOBALS['LANG']->getLL('impexpcore_singlereco_softReferencesFiles', true) . '</em>';
3658  } elseif (!isset($GLOBALS['TCA'][$table])) {
3659  // Unknown table name:
3660  $pInfo['preCode'] = $preCode;
3661  $pInfo['msg'] = 'UNKNOWN TABLE \'' . $pInfo['ref'] . '\'';
3662  $pInfo['title'] = '<em>' . htmlspecialchars($record['title']) . '</em>';
3663  } else {
3664  // Otherwise, set table icon and title.
3665  // Import Validation (triggered by $this->display_import_pid_record) will show messages if import is not possible of various items.
3666  if (is_array($this->display_import_pid_record) && !empty($this->display_import_pid_record)) {
3667  if ($checkImportInPidRecord) {
3668  if (!$GLOBALS['BE_USER']->doesUserHaveAccess($this->display_import_pid_record, ($table === 'pages' ? 8 : 16))) {
3669  $pInfo['msg'] .= '\'' . $pInfo['ref'] . '\' cannot be INSERTED on this page! ';
3670  }
3671  if (!$this->checkDokType($table, $this->display_import_pid_record['doktype']) && !$GLOBALS['TCA'][$table]['ctrl']['rootLevel']) {
3672  $pInfo['msg'] .= '\'' . $table . '\' cannot be INSERTED on this page type (change page type to \'Folder\'.) ';
3673  }
3674  }
3675  if (!$GLOBALS['BE_USER']->check('tables_modify', $table)) {
3676  $pInfo['msg'] .= 'You are not allowed to CREATE \'' . $table . '\' tables! ';
3677  }
3678  if ($GLOBALS['TCA'][$table]['ctrl']['readOnly']) {
3679  $pInfo['msg'] .= 'TABLE \'' . $table . '\' is READ ONLY! ';
3680  }
3681  if ($GLOBALS['TCA'][$table]['ctrl']['adminOnly'] && !$GLOBALS['BE_USER']->isAdmin()) {
3682  $pInfo['msg'] .= 'TABLE \'' . $table . '\' is ADMIN ONLY! ';
3683  }
3684  if ($GLOBALS['TCA'][$table]['ctrl']['is_static']) {
3685  $pInfo['msg'] .= 'TABLE \'' . $table . '\' is a STATIC TABLE! ';
3686  }
3687  if ((int)$GLOBALS['TCA'][$table]['ctrl']['rootLevel'] === 1) {
3688  $pInfo['msg'] .= 'TABLE \'' . $table . '\' will be inserted on ROOT LEVEL! ';
3689  }
3690  $diffInverse = false;
3691  $recInf = null;
3692  if ($this->update) {
3693  // In case of update-PREVIEW we swap the diff-sources.
3694  $diffInverse = true;
3695  $recInf = $this->doesRecordExist($table, $uid, $this->showDiff ? '*' : '');
3696  $pInfo['updatePath'] = $recInf ? htmlspecialchars($this->getRecordPath($recInf['pid'])) : '<strong>NEW!</strong>';
3697  // Mode selector:
3698  $optValues = array();
3699  $optValues[] = $recInf ? $GLOBALS['LANG']->getLL('impexpcore_singlereco_update') : $GLOBALS['LANG']->getLL('impexpcore_singlereco_insert');
3700  if ($recInf) {
3701  $optValues['as_new'] = $GLOBALS['LANG']->getLL('impexpcore_singlereco_importAsNew');
3702  }
3703  if ($recInf) {
3704  if (!$this->global_ignore_pid) {
3705  $optValues['ignore_pid'] = $GLOBALS['LANG']->getLL('impexpcore_singlereco_ignorePid');
3706  } else {
3707  $optValues['respect_pid'] = $GLOBALS['LANG']->getLL('impexpcore_singlereco_respectPid');
3708  }
3709  }
3710  if (!$recInf && $GLOBALS['BE_USER']->isAdmin()) {
3711  $optValues['force_uid'] = sprintf($GLOBALS['LANG']->getLL('impexpcore_singlereco_forceUidSAdmin'), $uid);
3712  }
3713  $optValues['exclude'] = $GLOBALS['LANG']->getLL('impexpcore_singlereco_exclude');
3714  if ($table === 'sys_file') {
3715  $pInfo['updateMode'] = '';
3716  } else {
3717  $pInfo['updateMode'] = $this->renderSelectBox('tx_impexp[import_mode][' . $table . ':' . $uid . ']', $this->import_mode[$table . ':' . $uid], $optValues);
3718  }
3719  }
3720  // Diff view:
3721  if ($this->showDiff) {
3722  // For IMPORTS, get new id:
3723  if ($newUid = $this->import_mapId[$table][$uid]) {
3724  $diffInverse = false;
3725  $recInf = $this->doesRecordExist($table, $newUid, '*');
3726  BackendUtility::workspaceOL($table, $recInf);
3727  }
3728  if (is_array($recInf)) {
3729  $pInfo['showDiffContent'] = $this->compareRecords($recInf, $this->dat['records'][$table . ':' . $uid]['data'], $table, $diffInverse);
3730  }
3731  }
3732  }
3733  $pInfo['preCode'] = $preCode . '<span title="' . htmlspecialchars($table . ':' . $uid) . '">'
3734  . $this->iconFactory->getIconForRecord($table, (array)$this->dat['records'][($table . ':' . $uid)]['data'], Icon::SIZE_SMALL)->render()
3735  . '</span>';
3736  $pInfo['title'] = htmlspecialchars($record['title']);
3737  // View page:
3738  if ($table === 'pages') {
3739  $viewID = $this->mode === 'export' ? $uid : ($this->doesImport ? $this->import_mapId['pages'][$uid] : 0);
3740  if ($viewID) {
3741  $pInfo['title'] = '<a href="#" onclick="' . htmlspecialchars(BackendUtility::viewOnClick($viewID)) . 'return false;">' . $pInfo['title'] . '</a>';
3742  }
3743  }
3744  }
3745  $pInfo['class'] = $table == 'pages' ? 'bgColor4-20' : 'bgColor4';
3746  $pInfo['type'] = 'record';
3747  $pInfo['size'] = $record['size'];
3748  $lines[] = $pInfo;
3749  // File relations:
3750  if (is_array($record['filerefs'])) {
3751  $this->addFiles($record['filerefs'], $lines, $preCode);
3752  }
3753  // DB relations
3754  if (is_array($record['rels'])) {
3755  $this->addRelations($record['rels'], $lines, $preCode);
3756  }
3757  // Soft ref
3758  if (!empty($record['softrefs'])) {
3759  $preCode_A = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;';
3760  $preCode_B = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
3761  foreach ($record['softrefs'] as $info) {
3762  $pInfo = array();
3763  $pInfo['preCode'] = $preCode_A . $this->iconFactory->getIcon('status-status-reference-soft', Icon::SIZE_SMALL)->render();
3764  $pInfo['title'] = '<em>' . $info['field'] . ', "' . $info['spKey'] . '" </em>: <span title="' . htmlspecialchars($info['matchString']) . '">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['matchString'], 60)) . '</span>';
3765  if ($info['subst']['type']) {
3766  if (strlen($info['subst']['title'])) {
3767  $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . $GLOBALS['LANG']->getLL('impexpcore_singlereco_title', true) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['title'], 60));
3768  }
3769  if (strlen($info['subst']['description'])) {
3770  $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . $GLOBALS['LANG']->getLL('impexpcore_singlereco_descr', true) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['description'], 60));
3771  }
3772  $pInfo['title'] .= '<br/>' . $preCode_B . ($info['subst']['type'] == 'file' ? $GLOBALS['LANG']->getLL('impexpcore_singlereco_filename', true) . ' <strong>' . $info['subst']['relFileName'] . '</strong>' : '') . ($info['subst']['type'] == 'string' ? $GLOBALS['LANG']->getLL('impexpcore_singlereco_value', true) . ' <strong>' . $info['subst']['tokenValue'] . '</strong>' : '') . ($info['subst']['type'] == 'db' ? $GLOBALS['LANG']->getLL('impexpcore_softrefsel_record', true) . ' <strong>' . $info['subst']['recordRef'] . '</strong>' : '');
3773  }
3774  $pInfo['ref'] = 'SOFTREF';
3775  $pInfo['size'] = '';
3776  $pInfo['class'] = 'bgColor3';
3777  $pInfo['type'] = 'softref';
3778  $pInfo['_softRefInfo'] = $info;
3779  $pInfo['type'] = 'softref';
3780  if ($info['error'] && !GeneralUtility::inList('editable,exclude', $this->softrefCfg[$info['subst']['tokenID']]['mode'])) {
3781  $pInfo['msg'] .= $info['error'];
3782  }
3783  $lines[] = $pInfo;
3784  // Add relations:
3785  if ($info['subst']['type'] == 'db') {
3786  list($tempTable, $tempUid) = explode(':', $info['subst']['recordRef']);
3787  $this->addRelations(array(array('table' => $tempTable, 'id' => $tempUid, 'tokenID' => $info['subst']['tokenID'])), $lines, $preCode_B, array(), '');
3788  }
3789  // Add files:
3790  if ($info['subst']['type'] == 'file') {
3791  $this->addFiles(array($info['file_ID']), $lines, $preCode_B, '', $info['subst']['tokenID']);
3792  }
3793  }
3794  }
3795  }
3796 
3809  public function addRelations($rels, &$lines, $preCode, $recurCheck = array(), $htmlColorClass = '')
3810  {
3811  foreach ($rels as $dat) {
3812  $table = $dat['table'];
3813  $uid = $dat['id'];
3814  $pInfo = array();
3815  $pInfo['ref'] = $table . ':' . $uid;
3816  if (in_array($pInfo['ref'], $recurCheck)) {
3817  $this->error($pInfo['ref'] . ' was recursive...');
3818  continue;
3819  }
3820  $iconName = 'status-status-checked';
3821  $iconClass = '';
3822  $staticFixed = false;
3823  $record = null;
3824  if ($uid > 0) {
3825  $record = $this->dat['header']['records'][$table][$uid];
3826  if (!is_array($record)) {
3827  if ($this->isTableStatic($table) || $this->isExcluded($table, $uid) || $dat['tokenID'] && !$this->includeSoftref($dat['tokenID'])) {
3828  $pInfo['title'] = htmlspecialchars('STATIC: ' . $pInfo['ref']);
3829  $iconClass = 'text-info';
3830  $staticFixed = true;
3831  } else {
3832  $doesRE = $this->doesRecordExist($table, $uid);
3833  $lostPath = $this->getRecordPath($table === 'pages' ? $doesRE['uid'] : $doesRE['pid']);
3834  $pInfo['title'] = htmlspecialchars($pInfo['ref']);
3835  $pInfo['title'] = '<span title="' . htmlspecialchars($lostPath) . '">' . $pInfo['title'] . '</span>';
3836  $pInfo['msg'] = 'LOST RELATION' . (!$doesRE ? ' (Record not found!)' : ' (Path: ' . $lostPath . ')');
3837  $iconClass = 'text-danger';
3838  $iconName = 'status-dialog-warning';
3839  }
3840  } else {
3841  $pInfo['title'] = htmlspecialchars($record['title']);
3842  $pInfo['title'] = '<span title="' . htmlspecialchars($this->getRecordPath(($table === 'pages' ? $record['uid'] : $record['pid']))) . '">' . $pInfo['title'] . '</span>';
3843  }
3844  } else {
3845  // Negative values in relation fields. This is typically sys_language fields, fe_users fields etc. They are static values. They CAN theoretically be negative pointers to uids in other tables but this is so rarely used that it is not supported
3846  $pInfo['title'] = htmlspecialchars('FIXED: ' . $pInfo['ref']);
3847  $staticFixed = true;
3848  }
3849 
3850  $icon = '<span class="' . $iconClass . '" title="' . htmlspecialchars($pInfo['ref']) . '">' . $this->iconFactory->getIcon($iconName, Icon::SIZE_SMALL)->render() . '</span>';
3851 
3852  $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $icon;
3853  $pInfo['class'] = $htmlColorClass ?: 'bgColor3';
3854  $pInfo['type'] = 'rel';
3855  if (!$staticFixed || $this->showStaticRelations) {
3856  $lines[] = $pInfo;
3857  if (is_array($record) && is_array($record['rels'])) {
3858  $this->addRelations($record['rels'], $lines, $preCode . '&nbsp;&nbsp;', array_merge($recurCheck, array($pInfo['ref'])), $htmlColorClass);
3859  }
3860  }
3861  }
3862  }
3863 
3876  public function addFiles($rels, &$lines, $preCode, $htmlColorClass = '', $tokenID = '')
3877  {
3878  foreach ($rels as $ID) {
3879  // Process file:
3880  $pInfo = array();
3881  $fI = $this->dat['header']['files'][$ID];
3882  if (!is_array($fI)) {
3883  if (!$tokenID || $this->includeSoftref($tokenID)) {
3884  $pInfo['msg'] = 'MISSING FILE: ' . $ID;
3885  $this->error('MISSING FILE: ' . $ID, 1);
3886  } else {
3887  return;
3888  }
3889  }
3890  $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('status-status-reference-hard', Icon::SIZE_SMALL)->render();
3891  $pInfo['title'] = htmlspecialchars($fI['filename']);
3892  $pInfo['ref'] = 'FILE';
3893  $pInfo['size'] = $fI['filesize'];
3894  $pInfo['class'] = $htmlColorClass ?: 'bgColor3';
3895  $pInfo['type'] = 'file';
3896  // If import mode and there is a non-RTE softreference, check the destination directory:
3897  if ($this->mode === 'import' && $tokenID && !$fI['RTE_ORIG_ID']) {
3898  if (isset($fI['parentRelFileName'])) {
3899  $pInfo['msg'] = 'Seems like this file is already referenced from within an HTML/CSS file. That takes precedence. ';
3900  } else {
3901  $testDirPrefix = PathUtility::dirname($fI['relFileName']) . '/';
3902  $testDirPrefix2 = $this->verifyFolderAccess($testDirPrefix);
3903  if (!$testDirPrefix2) {
3904  $pInfo['msg'] = 'ERROR: There are no available filemounts to write file in! ';
3905  } elseif ($testDirPrefix !== $testDirPrefix2) {
3906  $pInfo['msg'] = 'File will be attempted written to "' . $testDirPrefix2 . '". ';
3907  }
3908  }
3909  // Check if file exists:
3910  if (file_exists(PATH_site . $fI['relFileName'])) {
3911  if ($this->update) {
3912  $pInfo['updatePath'] .= 'File exists.';
3913  } else {
3914  $pInfo['msg'] .= 'File already exists! ';
3915  }
3916  }
3917  // Check extension:
3918  $fileProcObj = $this->getFileProcObj();
3919  if ($fileProcObj->actionPerms['addFile']) {
3920  $testFI = GeneralUtility::split_fileref(PATH_site . $fI['relFileName']);
3921  if (!$this->allowPHPScripts && !$fileProcObj->checkIfAllowed($testFI['fileext'], $testFI['path'], $testFI['file'])) {
3922  $pInfo['msg'] .= 'File extension was not allowed!';
3923  }
3924  } else {
3925  $pInfo['msg'] = 'You user profile does not allow you to create files on the server!';
3926  }
3927  }
3928  $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$ID]);
3929  $lines[] = $pInfo;
3930  unset($this->remainHeader['files'][$ID]);
3931  // RTE originals:
3932  if ($fI['RTE_ORIG_ID']) {
3933  $ID = $fI['RTE_ORIG_ID'];
3934  $pInfo = array();
3935  $fI = $this->dat['header']['files'][$ID];
3936  if (!is_array($fI)) {
3937  $pInfo['msg'] = 'MISSING RTE original FILE: ' . $ID;
3938  $this->error('MISSING RTE original FILE: ' . $ID, 1);
3939  }
3940  $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$ID]);
3941  $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('status-status-reference-hard', Icon::SIZE_SMALL)->render();
3942  $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Original)</em>';
3943  $pInfo['ref'] = 'FILE';
3944  $pInfo['size'] = $fI['filesize'];
3945  $pInfo['class'] = $htmlColorClass ?: 'bgColor3';
3946  $pInfo['type'] = 'file';
3947  $lines[] = $pInfo;
3948  unset($this->remainHeader['files'][$ID]);
3949  }
3950  // External resources:
3951  if (is_array($fI['EXT_RES_ID'])) {
3952  foreach ($fI['EXT_RES_ID'] as $extID) {
3953  $pInfo = array();
3954  $fI = $this->dat['header']['files'][$extID];
3955  if (!is_array($fI)) {
3956  $pInfo['msg'] = 'MISSING External Resource FILE: ' . $extID;
3957  $this->error('MISSING External Resource FILE: ' . $extID, 1);
3958  } else {
3959  $pInfo['updatePath'] = $fI['parentRelFileName'];
3960  }
3961  $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$extID]);
3962  $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('actions-insert-reference', Icon::SIZE_SMALL)->render();
3963  $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Resource)</em>';
3964  $pInfo['ref'] = 'FILE';
3965  $pInfo['size'] = $fI['filesize'];
3966  $pInfo['class'] = $htmlColorClass ?: 'bgColor3';
3967  $pInfo['type'] = 'file';
3968  $lines[] = $pInfo;
3969  unset($this->remainHeader['files'][$extID]);
3970  }
3971  }
3972  }
3973  }
3974 
3982  public function checkDokType($checkTable, $doktype)
3983  {
3984  $allowedTableList = isset($GLOBALS['PAGES_TYPES'][$doktype]['allowedTables']) ? $GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'] : $GLOBALS['PAGES_TYPES']['default']['allowedTables'];
3985  $allowedArray = GeneralUtility::trimExplode(',', $allowedTableList, true);
3986  // If all tables or the table is listed as an allowed type, return TRUE
3987  if (strstr($allowedTableList, '*') || in_array($checkTable, $allowedArray)) {
3988  return true;
3989  }
3990  return false;
3991  }
3992 
3999  public function renderControls($r)
4000  {
4001  if ($this->mode === 'export') {
4002  if ($r['type'] === 'record') {
4003  return '<input type="checkbox" name="tx_impexp[exclude][' . $r['ref'] . ']" id="checkExclude' . $r['ref'] . '" value="1" /> <label for="checkExclude' . $r['ref'] . '">' . $GLOBALS['LANG']->getLL('impexpcore_singlereco_exclude', true) . '</label>';
4004  } else {
4005  return $r['type'] == 'softref' ? $this->softrefSelector($r['_softRefInfo']) : '';
4006  }
4007  } else {
4008  // During import
4009  // For softreferences with editable fields:
4010  if ($r['type'] == 'softref' && is_array($r['_softRefInfo']['subst']) && $r['_softRefInfo']['subst']['tokenID']) {
4011  $tokenID = $r['_softRefInfo']['subst']['tokenID'];
4012  $cfg = $this->softrefCfg[$tokenID];
4013  if ($cfg['mode'] === 'editable') {
4014  return (strlen($cfg['title']) ? '<strong>' . htmlspecialchars($cfg['title']) . '</strong><br/>' : '') . htmlspecialchars($cfg['description']) . '<br/>
4015  <input type="text" name="tx_impexp[softrefInputValues][' . $tokenID . ']" value="' . htmlspecialchars((isset($this->softrefInputValues[$tokenID]) ? $this->softrefInputValues[$tokenID] : $cfg['defValue'])) . '" />';
4016  }
4017  }
4018  }
4019  return '';
4020  }
4021 
4028  public function softrefSelector($cfg)
4029  {
4030  // Looking for file ID if any:
4031  $fI = $cfg['file_ID'] ? $this->dat['header']['files'][$cfg['file_ID']] : array();
4032  // Substitution scheme has to be around and RTE images MUST be exported.
4033  if (is_array($cfg['subst']) && $cfg['subst']['tokenID'] && !$fI['RTE_ORIG_ID']) {
4034  // Create options:
4035  $optValues = array();
4036  $optValues[''] = '';
4037  $optValues['editable'] = $GLOBALS['LANG']->getLL('impexpcore_softrefsel_editable');
4038  $optValues['exclude'] = $GLOBALS['LANG']->getLL('impexpcore_softrefsel_exclude');
4039  // Get current value:
4040  $value = $this->softrefCfg[$cfg['subst']['tokenID']]['mode'];
4041  // Render options selector:
4042  $selectorbox = $this->renderSelectBox(('tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][mode]'), $value, $optValues) . '<br/>';
4043  if ($value === 'editable') {
4044  $descriptionField = '';
4045  // Title:
4046  if (strlen($cfg['subst']['title'])) {
4047  $descriptionField .= '
4048  <input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][title]" value="' . htmlspecialchars($cfg['subst']['title']) . '" />
4049  <strong>' . htmlspecialchars($cfg['subst']['title']) . '</strong><br/>';
4050  }
4051  // Description:
4052  if (!strlen($cfg['subst']['description'])) {
4053  $descriptionField .= '
4054  ' . $GLOBALS['LANG']->getLL('impexpcore_printerror_description', true) . '<br/>
4055  <input type="text" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($this->softrefCfg[$cfg['subst']['tokenID']]['description']) . '" />';
4056  } else {
4057  $descriptionField .= '
4058 
4059  <input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($cfg['subst']['description']) . '" />' . htmlspecialchars($cfg['subst']['description']);
4060  }
4061  // Default Value:
4062  $descriptionField .= '<input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][defValue]" value="' . htmlspecialchars($cfg['subst']['tokenValue']) . '" />';
4063  } else {
4064  $descriptionField = '';
4065  }
4066  return $selectorbox . $descriptionField;
4067  }
4068  return '';
4069  }
4070 
4071  /*****************************
4072  * Helper functions of kinds
4073  *****************************/
4074 
4081  public function isTableStatic($table)
4082  {
4083  if (is_array($GLOBALS['TCA'][$table])) {
4084  return $GLOBALS['TCA'][$table]['ctrl']['is_static'] || in_array($table, $this->relStaticTables) || in_array('_ALL', $this->relStaticTables);
4085  }
4086  return false;
4087  }
4088 
4095  public function inclRelation($table)
4096  {
4097  return is_array($GLOBALS['TCA'][$table])
4098  && (in_array($table, $this->relOnlyTables) || in_array('_ALL', $this->relOnlyTables))
4099  && $GLOBALS['BE_USER']->check('tables_select', $table);
4100  }
4101 
4109  public function isExcluded($table, $uid)
4110  {
4111  return (bool)$this->excludeMap[$table . ':' . $uid];
4112  }
4113 
4120  public function includeSoftref($tokenID)
4121  {
4122  return $tokenID && !GeneralUtility::inList('exclude,editable', $this->softrefCfg[$tokenID]['mode']);
4123  }
4124 
4131  public function checkPID($pid)
4132  {
4133  if (!isset($this->checkPID_cache[$pid])) {
4134  $this->checkPID_cache[$pid] = (bool)$GLOBALS['BE_USER']->isInWebMount($pid);
4135  }
4136  return $this->checkPID_cache[$pid];
4137  }
4138 
4146  public function dontIgnorePid($table, $uid)
4147  {
4148  return $this->import_mode[$table . ':' . $uid] !== 'ignore_pid' && (!$this->global_ignore_pid || $this->import_mode[$table . ':' . $uid] === 'respect_pid');
4149  }
4150 
4159  public function doesRecordExist($table, $uid, $fields = '')
4160  {
4161  return BackendUtility::getRecord($table, $uid, $fields ? $fields : 'uid,pid');
4162  }
4163 
4170  public function getRecordPath($pid)
4171  {
4172  if (!isset($this->cache_getRecordPath[$pid])) {
4173  $clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
4174  $this->cache_getRecordPath[$pid] = (string)BackendUtility::getRecordPath($pid, $clause, 20);
4175  }
4176  return $this->cache_getRecordPath[$pid];
4177  }
4178 
4187  public function renderSelectBox($prefix, $value, $optValues)
4188  {
4189  $opt = array();
4190  $isSelFlag = 0;
4191  foreach ($optValues as $k => $v) {
4192  $sel = (string)$k === (string)$value ? ' selected="selected"' : '';
4193  if ($sel) {
4194  $isSelFlag++;
4195  }
4196  $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>';
4197  }
4198  if (!$isSelFlag && (string)$value !== '') {
4199  $opt[] = '<option value="' . htmlspecialchars($value) . '" selected="selected">' . htmlspecialchars(('[\'' . $value . '\']')) . '</option>';
4200  }
4201  return '<select name="' . $prefix . '">' . implode('', $opt) . '</select>';
4202  }
4203 
4214  public function compareRecords($databaseRecord, $importRecord, $table, $inverseDiff = false)
4215  {
4216  // Initialize:
4217  $output = array();
4218  $diffUtility = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Utility\DiffUtility::class);
4219  // Check if both inputs are records:
4220  if (is_array($databaseRecord) && is_array($importRecord)) {
4221  // Traverse based on database record
4222  foreach ($databaseRecord as $fN => $value) {
4223  if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] != 'passthrough') {
4224  if (isset($importRecord[$fN])) {
4225  if (trim($databaseRecord[$fN]) !== trim($importRecord[$fN])) {
4226  // Create diff-result:
4227  $output[$fN] = $diffUtility->makeDiffDisplay(BackendUtility::getProcessedValue($table, $fN, !$inverseDiff ? $importRecord[$fN] : $databaseRecord[$fN], 0, 1, 1), BackendUtility::getProcessedValue($table, $fN, !$inverseDiff ? $databaseRecord[$fN] : $importRecord[$fN], 0, 1, 1));
4228  }
4229  unset($importRecord[$fN]);
4230  }
4231  }
4232  }
4233  // Traverse remaining in import record:
4234  foreach ($importRecord as $fN => $value) {
4235  if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') {
4236  $output[$fN] = '<strong>Field missing</strong> in database';
4237  }
4238  }
4239  // Create output:
4240  if (!empty($output)) {
4241  $tRows = array();
4242  foreach ($output as $fN => $state) {
4243  $tRows[] = '
4244  <tr>
4245  <td class="bgColor5">' . $GLOBALS['LANG']->sL($GLOBALS['TCA'][$table]['columns'][$fN]['label'], true) . ' (' . htmlspecialchars($fN) . ')</td>
4246  <td class="bgColor4">' . $state . '</td>
4247  </tr>
4248  ';
4249  }
4250  $output = '<table border="0" cellpadding="0" cellspacing="1">' . implode('', $tRows) . '</table>';
4251  } else {
4252  $output = 'Match';
4253  }
4254  return '<strong class="text-nowrap">[' . htmlspecialchars(($table . ':' . $importRecord['uid'] . ' => ' . $databaseRecord['uid'])) . ']:</strong> ' . $output;
4255  }
4256  return 'ERROR: One of the inputs were not an array!';
4257  }
4258 
4265  public function getRTEoriginalFilename($string)
4266  {
4267  // If "magic image":
4268  if (GeneralUtility::isFirstPartOfStr($string, 'RTEmagicC_')) {
4269  // Find original file:
4270  $pI = pathinfo(substr($string, strlen('RTEmagicC_')));
4271  $filename = substr($pI['basename'], 0, -strlen(('.' . $pI['extension'])));
4272  $origFilePath = 'RTEmagicP_' . $filename;
4273  return $origFilePath;
4274  }
4275  return null;
4276  }
4277 
4283  public function getFileProcObj()
4284  {
4285  if ($this->fileProcObj === null) {
4286  $this->fileProcObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::class);
4287  $this->fileProcObj->init(array(), $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']);
4288  $this->fileProcObj->setActionPermissions();
4289  }
4290  return $this->fileProcObj;
4291  }
4292 
4300  public function callHook($name, $params)
4301  {
4302  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/impexp/class.tx_impexp.php'][$name])) {
4303  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/impexp/class.tx_impexp.php'][$name] as $hook) {
4304  GeneralUtility::callUserFunction($hook, $params, $this);
4305  }
4306  }
4307  }
4308 
4309  /*****************************
4310  * Error handling
4311  *****************************/
4312 
4319  public function error($msg)
4320  {
4321  $this->errorLog[] = $msg;
4322  }
4323 
4329  public function printErrorLog()
4330  {
4331  return !empty($this->errorLog) ? \TYPO3\CMS\Core\Utility\DebugUtility::viewArray($this->errorLog) : '';
4332  }
4333 }