‪TYPO3CMS  10.4
ImportExport.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
16 namespace ‪TYPO3\CMS\Impexp;
17 
34 
69 abstract class ‪ImportExport
70 {
76  public ‪$showStaticRelations = false;
77 
83  public ‪$fileadminFolderName = '';
84 
90  public ‪$mode = '';
91 
97  public ‪$update = false;
98 
104  public ‪$doesImport = false;
105 
114 
120  public ‪$import_mode = [];
121 
127  public ‪$global_ignore_pid = false;
128 
134  public ‪$force_all_UIDS = false;
135 
141  public ‪$showDiff = false;
142 
148  public ‪$softrefInputValues = [];
149 
155  public ‪$fileIDMap = [];
156 
163  public ‪$relOnlyTables = [];
164 
171  public ‪$relStaticTables = [];
172 
178  public ‪$excludeMap = [];
179 
185  public ‪$softrefCfg = [];
186 
192  public ‪$extensionDependencies = [];
193 
199  public ‪$import_mapId = [];
200 
206  public ‪$errorLog = [];
207 
213  public ‪$cache_getRecordPath = [];
214 
220  public ‪$checkPID_cache = [];
221 
228  public ‪$compress = false;
229 
235  public ‪$dat = [];
236 
242  protected ‪$fileProcObj;
243 
247  protected ‪$remainHeader = [];
248 
252  protected ‪$iconFactory;
253 
260  protected ‪$excludeDisabledRecords = false;
261 
265  public function ‪__construct()
266  {
267  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
268  }
269 
270  /**************************
271  * Initialize
272  *************************/
273 
277  public function ‪init()
278  {
279  $this->compress = function_exists('gzcompress');
280  $this->fileadminFolderName = !empty(‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir']) ? rtrim(‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') : 'fileadmin';
281  }
282 
283  /********************************************************
284  * Visual rendering of import/export memory, $this->dat
285  ********************************************************/
286 
292  public function ‪displayContentOverview()
293  {
294  if (!isset($this->dat['header'])) {
295  return [];
296  }
297  // Check extension dependencies:
298  foreach ($this->dat['header']['extensionDependencies'] as $extKey) {
299  if (!empty($extKey) && !‪ExtensionManagementUtility::isLoaded($extKey)) {
300  $this->‪error('DEPENDENCY: The extension with key "' . $extKey . '" must be installed!');
301  }
302  }
303 
304  // Probably this is done to save memory space?
305  unset($this->dat['files']);
306 
307  $viewData = [];
308  // Traverse header:
309  $this->remainHeader = $this->dat['header'];
310  // If there is a page tree set, show that:
311  if (is_array($this->dat['header']['pagetree'])) {
312  reset($this->dat['header']['pagetree']);
313  $lines = [];
314  $this->‪traversePageTree($this->dat['header']['pagetree'], $lines);
315 
316  $viewData['dat'] = ‪$this->dat;
317  $viewData['update'] = ‪$this->update;
318  $viewData['showDiff'] = ‪$this->showDiff;
319  if (!empty($lines)) {
320  foreach ($lines as &$r) {
321  $r['controls'] = $this->‪renderControls($r);
322  $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
323  }
324  $viewData['pagetreeLines'] = $lines;
325  } else {
326  $viewData['pagetreeLines'] = [];
327  }
328  }
329  // Print remaining records that were not contained inside the page tree:
330  if (is_array($this->remainHeader['records'])) {
331  $lines = [];
332  if (is_array($this->remainHeader['records']['pages'])) {
333  $this->‪traversePageRecords($this->remainHeader['records']['pages'], $lines);
334  }
335  $this->‪traverseAllRecords($this->remainHeader['records'], $lines);
336  if (!empty($lines)) {
337  foreach ($lines as &$r) {
338  $r['controls'] = $this->‪renderControls($r);
339  $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
340  }
341  $viewData['remainingRecords'] = $lines;
342  }
343  }
344 
345  return $viewData;
346  }
347 
355  public function ‪traversePageTree($pT, &$lines, $preCode = '')
356  {
357  foreach ($pT as $k => $v) {
358  if ($this->excludeDisabledRecords === true && !$this->‪isActive('pages', $k)) {
359  $this->‪excludePageAndRecords($k, $v);
360  continue;
361  }
362 
363  // Add this page:
364  $this->‪singleRecordLines('pages', $k, $lines, $preCode);
365  // Subrecords:
366  if (is_array($this->dat['header']['pid_lookup'][$k])) {
367  foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) {
368  $t = (string)$t;
369  if ($t !== 'pages') {
370  foreach ($recUidArr as $ruid => $value) {
371  $this->‪singleRecordLines($t, $ruid, $lines, $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;');
372  }
373  }
374  }
375  unset($this->remainHeader['pid_lookup'][$k]);
376  }
377  // Subpages, called recursively:
378  if (is_array($v['subrow'])) {
379  $this->‪traversePageTree($v['subrow'], $lines, $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;');
380  }
381  }
382  }
383 
391  protected function ‪isActive($table, $uid)
392  {
393  return
394  !isset(‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])
395  || !(bool)$this->dat['records'][$table . ':' . $uid]['data'][
396  ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']
397  ];
398  }
399 
406  protected function ‪excludePageAndRecords($pageUid, $pageTree)
407  {
408  // Prevent having this page appear in "remaining records" table
409  unset($this->remainHeader['records']['pages'][$pageUid]);
410 
411  // Subrecords
412  if (is_array($this->dat['header']['pid_lookup'][$pageUid])) {
413  foreach ($this->dat['header']['pid_lookup'][$pageUid] as $table => $recordData) {
414  if ($table !== 'pages') {
415  foreach (array_keys($recordData) as $uid) {
416  unset($this->remainHeader['records'][$table][$uid]);
417  }
418  }
419  }
420  unset($this->remainHeader['pid_lookup'][$pageUid]);
421  }
422  // Subpages excluded recursively
423  if (is_array($pageTree['subrow'])) {
424  foreach ($pageTree['subrow'] as $subPageUid => $subPageTree) {
425  $this->‪excludePageAndRecords($subPageUid, $subPageTree);
426  }
427  }
428  }
429 
436  public function ‪traversePageRecords($pT, &$lines)
437  {
438  foreach ($pT as $k => $rHeader) {
439  $this->‪singleRecordLines('pages', (int)$k, $lines, '', true);
440  // Subrecords:
441  if (is_array($this->dat['header']['pid_lookup'][$k])) {
442  foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) {
443  if ($t !== 'pages') {
444  foreach ($recUidArr as $ruid => $value) {
445  $this->‪singleRecordLines((string)$t, (int)$ruid, $lines, '&nbsp;&nbsp;&nbsp;&nbsp;');
446  }
447  }
448  }
449  unset($this->remainHeader['pid_lookup'][$k]);
450  }
451  }
452  }
453 
460  public function ‪traverseAllRecords($pT, &$lines)
461  {
462  foreach ($pT as $t => $recUidArr) {
463  $this->‪addGeneralErrorsByTable($t);
464  if ($t !== 'pages') {
465  $preCode = '';
466  foreach ($recUidArr as $ruid => $value) {
467  $this->‪singleRecordLines((string)$t, (int)$ruid, $lines, $preCode, true);
468  }
469  }
470  }
471  }
472 
478  protected function ‪addGeneralErrorsByTable($table)
479  {
480  if ($this->update && $table === 'sys_file') {
481  $this->‪error('Updating sys_file records is not supported! They will be imported as new records!');
482  }
483  if ($this->force_all_UIDS && $table === 'sys_file') {
484  $this->‪error('Forcing uids of sys_file records is not supported! They will be imported as new records!');
485  }
486  }
487 
497  public function ‪singleRecordLines($table, $uid, &$lines, $preCode, $checkImportInPidRecord = false)
498  {
499  // Get record:
500  $record = $this->dat['header']['records'][$table][$uid];
501  unset($this->remainHeader['records'][$table][$uid]);
502  if (!is_array($record) && !($table === 'pages' && !$uid)) {
503  $this->‪error('MISSING RECORD: ' . $table . ':' . $uid);
504  }
505  // Begin to create the line arrays information record, pInfo:
506  $pInfo = [];
507  $pInfo['ref'] = $table . ':' . $uid;
508  // Unknown table name:
509  $lang = $this->‪getLanguageService();
510  if ($table === '_SOFTREF_') {
511  $pInfo['preCode'] = $preCode;
512  $pInfo['title'] = '<em>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_softReferencesFiles')) . '</em>';
513  } elseif (!isset(‪$GLOBALS['TCA'][$table])) {
514  // Unknown table name:
515  $pInfo['preCode'] = $preCode;
516  $pInfo['msg'] = 'UNKNOWN TABLE \'' . $pInfo['ref'] . '\'';
517  $pInfo['title'] = '<em>' . htmlspecialchars($record['title']) . '</em>';
518  } else {
519  // prepare data attribute telling whether the record is active or hidden, allowing frontend bulk selection
520  $pInfo['active'] = $this->‪isActive($table, $uid) ? 'active' : 'hidden';
521 
522  // Otherwise, set table icon and title.
523  // Import Validation (triggered by $this->display_import_pid_record) will show messages if import is not possible of various items.
524  if (is_array($this->display_import_pid_record) && !empty($this->display_import_pid_record)) {
525  if ($checkImportInPidRecord) {
526  if (!$this->‪getBackendUser()->doesUserHaveAccess($this->display_import_pid_record, ($table === 'pages' ? 8 : 16))) {
527  $pInfo['msg'] .= '\'' . $pInfo['ref'] . '\' cannot be INSERTED on this page! ';
528  }
529  if (!$this->checkDokType($table, $this->display_import_pid_record['doktype']) && !$GLOBALS['TCA'][$table]['ctrl']['rootLevel']) {
530  $pInfo['msg'] .= '\'' . $table . '\' cannot be INSERTED on this page type (change page type to \'Folder\'.) ';
531  }
532  }
533  if (!$this->‪getBackendUser()->check('tables_modify', $table)) {
534  $pInfo['msg'] .= 'You are not allowed to CREATE \'' . $table . '\' tables! ';
535  }
536  if ($GLOBALS['TCA'][$table]['ctrl']['readOnly']) {
537  $pInfo['msg'] .= 'TABLE \'' . $table . '\' is READ ONLY! ';
538  }
539  if ($GLOBALS['TCA'][$table]['ctrl']['adminOnly'] && !$this->getBackendUser()->isAdmin()) {
540  $pInfo['msg'] .= 'TABLE \'' . $table . '\' is ADMIN ONLY! ';
541  }
542  if ($GLOBALS['TCA'][$table]['ctrl']['is_static']) {
543  $pInfo['msg'] .= 'TABLE \'' . $table . '\' is a STATIC TABLE! ';
544  }
545  if ((int)$GLOBALS['TCA'][$table]['ctrl']['rootLevel'] === 1) {
546  $pInfo['msg'] .= 'TABLE \'' . $table . '\' will be inserted on ROOT LEVEL! ';
547  }
548  $diffInverse = false;
549  $recInf = null;
550  if ($this->update) {
551  // In case of update-PREVIEW we swap the diff-sources.
552  $diffInverse = true;
553  $recInf = $this->doesRecordExist($table, $uid, $this->showDiff ? '*' : '');
554  $pInfo['updatePath'] = $recInf ? htmlspecialchars($this->getRecordPath($recInf['pid'])) : '<strong>NEW!</strong>';
555  // Mode selector:
556  $optValues = [];
557  $optValues[] = $recInf ? $lang->getLL('impexpcore_singlereco_update') : $lang->getLL('impexpcore_singlereco_insert');
558  if ($recInf) {
559  $optValues['as_new'] = $lang->getLL('impexpcore_singlereco_importAsNew');
560  }
561  if ($recInf) {
562  if (!$this->global_ignore_pid) {
563  $optValues['ignore_pid'] = $lang->getLL('impexpcore_singlereco_ignorePid');
564  } else {
565  $optValues['respect_pid'] = $lang->getLL('impexpcore_singlereco_respectPid');
566  }
567  }
568  if (!$recInf && $this->getBackendUser()->isAdmin()) {
569  $optValues['force_uid'] = sprintf($lang->getLL('impexpcore_singlereco_forceUidSAdmin'), $uid);
570  }
571  $optValues['exclude'] = $lang->getLL('impexpcore_singlereco_exclude');
572  if ($table === 'sys_file') {
573  $pInfo['updateMode'] = '';
574  } else {
575  $pInfo['updateMode'] = $this->renderSelectBox('tx_impexp[import_mode][' . $table . ':' . $uid . ']', $this->import_mode[$table . ':' . $uid], $optValues);
576  }
577  }
578  // Diff view:
579  if ($this->showDiff) {
580  // For IMPORTS, get new id:
581  if ($newUid = $this->import_mapId[$table][$uid]) {
582  $diffInverse = false;
583  $recInf = $this->doesRecordExist($table, $newUid, '*');
584  BackendUtility::workspaceOL($table, $recInf);
585  }
586  if (is_array($recInf)) {
587  $pInfo['showDiffContent'] = $this->compareRecords($recInf, $this->dat['records'][$table . ':' . $uid]['data'], $table, $diffInverse);
588  }
589  }
590  }
591  $pInfo['preCode'] = $preCode . '<span title="' . htmlspecialchars($table . ':' . $uid) . '">'
592  . $this->iconFactory->getIconForRecord($table, (array)$this->dat['records'][$table . ':' . $uid]['data'], Icon::SIZE_SMALL)->render()
593  . '</span>';
594  $pInfo['title'] = htmlspecialchars($record['title']);
595  // View page:
596  if ($table === 'pages') {
597  $viewID = $this->mode === 'export' ? $uid : ($this->doesImport ? $this->import_mapId['pages'][$uid] : 0);
598  if ($viewID) {
599  $pInfo['title'] = '<a href="#" onclick="' . htmlspecialchars(BackendUtility::viewOnClick($viewID)) . 'return false;">' . $pInfo['title'] . '</a>';
600  }
601  }
602  }
603  $pInfo['type'] = 'record';
604  $lines[] = $pInfo;
605  // File relations:
606  if (is_array($record['filerefs'])) {
607  $this->addFiles($record['filerefs'], $lines, $preCode);
608  }
609  // DB relations
610  if (is_array($record['rels'])) {
611  $this->addRelations($record['rels'], $lines, $preCode);
612  }
613  // Soft ref
614  if (!empty($record['softrefs'])) {
615  $preCode_A = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;';
616  $preCode_B = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
617  foreach ($record['softrefs'] as $info) {
618  $pInfo = [];
619  $pInfo['preCode'] = $preCode_A . $this->iconFactory->getIcon('status-reference-soft', Icon::SIZE_SMALL)->render();
620  $pInfo['title'] = '<em>' . $info['field'] . ', "' . $info['spKey'] . '" </em>: <span title="' . htmlspecialchars($info['matchString']) . '">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['matchString'], 60)) . '</span>';
621  if ($info['subst']['type']) {
622  if (strlen($info['subst']['title'])) {
623  $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_title')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['title'], 60));
624  }
625  if (strlen($info['subst']['description'])) {
626  $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_descr')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['description'], 60));
627  }
628  $pInfo['title'] .= '<br/>' . $preCode_B . ($info['subst']['type'] === 'file' ? htmlspecialchars($lang->getLL('impexpcore_singlereco_filename')) . ' <strong>' . $info['subst']['relFileName'] . '</strong>' : '') . ($info['subst']['type'] === 'string' ? htmlspecialchars($lang->getLL('impexpcore_singlereco_value')) . ' <strong>' . $info['subst']['tokenValue'] . '</strong>' : '') . ($info['subst']['type'] === 'db' ? htmlspecialchars($lang->getLL('impexpcore_softrefsel_record')) . ' <strong>' . $info['subst']['recordRef'] . '</strong>' : '');
629  }
630  $pInfo['ref'] = 'SOFTREF';
631  $pInfo['type'] = 'softref';
632  $pInfo['_softRefInfo'] = $info;
633  $pInfo['type'] = 'softref';
634  $mode = $this->softrefCfg[$info['subst']['tokenID']]['mode'];
635  if ($info['‪error'] && $mode !== 'editable' && $mode !== 'exclude') {
636  $pInfo['msg'] .= $info['‪error'];
637  }
638  $lines[] = $pInfo;
639  // Add relations:
640  if ($info['subst']['type'] === 'db') {
641  [$tempTable, $tempUid] = explode(':', $info['subst']['recordRef']);
642  $this->addRelations([['table' => $tempTable, 'id' => $tempUid, 'tokenID' => $info['subst']['tokenID']]], $lines, $preCode_B, [], '');
643  }
644  // Add files:
645  if ($info['subst']['type'] === 'file') {
646  $this->addFiles([$info['file_ID']], $lines, $preCode_B, '', $info['subst']['tokenID']);
647  }
648  }
649  }
650  }
651 
663  public function addRelations($rels, &$lines, $preCode, $recurCheck = [], $htmlColorClass = '')
664  {
665  foreach ($rels as $dat) {
666  $table = $dat['table'];
667  $uid = $dat['id'];
668  $pInfo = [];
669  $pInfo['ref'] = $table . ':' . $uid;
670  if (in_array($pInfo['ref'], $recurCheck)) {
671  continue;
672  }
673  $iconName = 'status-status-checked';
674  $iconClass = '';
675  $staticFixed = false;
676  $record = null;
677  if ($uid > 0) {
678  $record = $this->dat['header']['records'][$table][$uid];
679  if (!is_array($record)) {
680  if ($this->isTableStatic($table) || $this->isExcluded($table, $uid) || $dat['tokenID'] && !$this->includeSoftref($dat['tokenID'])) {
681  $pInfo['title'] = htmlspecialchars('STATIC: ' . $pInfo['ref']);
682  $iconClass = 'text-info';
683  $staticFixed = true;
684  } else {
685  $doesRE = $this->doesRecordExist($table, $uid);
686  $lostPath = $this->getRecordPath($table === 'pages' ? $doesRE['uid'] : $doesRE['pid']);
687  $pInfo['title'] = htmlspecialchars($pInfo['ref']);
688  $pInfo['title'] = '<span title="' . htmlspecialchars($lostPath) . '">' . $pInfo['title'] . '</span>';
689  $pInfo['msg'] = 'LOST RELATION' . (!$doesRE ? ' (Record not found!)' : ' (Path: ' . $lostPath . ')');
690  $iconClass = 'text-danger';
691  $iconName = 'status-dialog-warning';
692  }
693  } else {
694  $pInfo['title'] = htmlspecialchars($record['title']);
695  $pInfo['title'] = '<span title="' . htmlspecialchars($this->getRecordPath(($table === 'pages' ? $record['uid'] : $record['pid']))) . '">' . $pInfo['title'] . '</span>';
696  }
697  } else {
698  // 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
699  $pInfo['title'] = htmlspecialchars('FIXED: ' . $pInfo['ref']);
700  $staticFixed = true;
701  }
702 
703  $icon = '<span class="' . $iconClass . '" title="' . htmlspecialchars($pInfo['ref']) . '">' . $this->iconFactory->getIcon($iconName, Icon::SIZE_SMALL)->render() . '</span>';
704 
705  $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $icon;
706  $pInfo['type'] = 'rel';
707  if (!$staticFixed || $this->showStaticRelations) {
708  $lines[] = $pInfo;
709  if (is_array($record) && is_array($record['rels'])) {
710  $this->addRelations($record['rels'], $lines, $preCode . '&nbsp;&nbsp;', array_merge($recurCheck, [$pInfo['ref']]));
711  }
712  }
713  }
714  }
715 
727  public function addFiles($rels, &$lines, $preCode, $htmlColorClass = '', $tokenID = '')
728  {
729  foreach ($rels as $ID) {
730  // Process file:
731  $pInfo = [];
732  $fI = $this->dat['header']['files'][$ID];
733  if (!is_array($fI)) {
734  if (!$tokenID || $this->includeSoftref($tokenID)) {
735  $pInfo['msg'] = 'MISSING FILE: ' . $ID;
736  $this->error('MISSING FILE: ' . $ID);
737  } else {
738  return;
739  }
740  }
741  $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('status-reference-hard', Icon::SIZE_SMALL)->render();
742  $pInfo['title'] = htmlspecialchars($fI['filename']);
743  $pInfo['ref'] = 'FILE';
744  $pInfo['type'] = 'file';
745  // If import mode and there is a non-RTE softreference, check the destination directory:
746  if ($this->mode === 'import' && $tokenID && !$fI['RTE_ORIG_ID']) {
747  if (isset($fI['parentRelFileName'])) {
748  $pInfo['msg'] = 'Seems like this file is already referenced from within an HTML/CSS file. That takes precedence. ';
749  } else {
750  $testDirPrefix = PathUtility::dirname($fI['relFileName']) . '/';
751  $testDirPrefix2 = $this->verifyFolderAccess($testDirPrefix);
752  if (!$testDirPrefix2) {
753  $pInfo['msg'] = 'ERROR: There are no available filemounts to write file in! ';
754  } elseif ($testDirPrefix !== $testDirPrefix2) {
755  $pInfo['msg'] = '‪File will be attempted written to "' . $testDirPrefix2 . '". ';
756  }
757  }
758  // Check if file exists:
759  if (file_exists(Environment::getPublicPath() . '/' . $fI['relFileName'])) {
760  if ($this->update) {
761  $pInfo['updatePath'] .= '‪File exists.';
762  } else {
763  $pInfo['msg'] .= '‪File already exists! ';
764  }
765  }
766  // Check extension:
767  $fileProcObj = $this->getFileProcObj();
768  if ($fileProcObj->actionPerms['addFile']) {
769  $testFI = GeneralUtility::split_fileref(Environment::getPublicPath() . '/' . $fI['relFileName']);
770  if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($testFI['file'])) {
771  $pInfo['msg'] .= '‪File extension was not allowed!';
772  }
773  } else {
774  $pInfo['msg'] = 'Your user profile does not allow you to create files on the server!';
775  }
776  }
777  $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$ID]);
778  $lines[] = $pInfo;
779  unset($this->remainHeader['files'][$ID]);
780  // RTE originals:
781  if ($fI['RTE_ORIG_ID']) {
782  $ID = $fI['RTE_ORIG_ID'];
783  $pInfo = [];
784  $fI = $this->dat['header']['files'][$ID];
785  if (!is_array($fI)) {
786  $pInfo['msg'] = 'MISSING RTE original FILE: ' . $ID;
787  $this->error('MISSING RTE original FILE: ' . $ID);
788  }
789  $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$ID]);
790  $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('status-reference-hard', Icon::SIZE_SMALL)->render();
791  $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Original)</em>';
792  $pInfo['ref'] = 'FILE';
793  $pInfo['type'] = 'file';
794  $lines[] = $pInfo;
795  unset($this->remainHeader['files'][$ID]);
796  }
797  // External resources:
798  if (is_array($fI['EXT_RES_ID'])) {
799  foreach ($fI['EXT_RES_ID'] as $extID) {
800  $pInfo = [];
801  $fI = $this->dat['header']['files'][$extID];
802  if (!is_array($fI)) {
803  $pInfo['msg'] = 'MISSING External Resource FILE: ' . $extID;
804  $this->error('MISSING External Resource FILE: ' . $extID);
805  } else {
806  $pInfo['updatePath'] = $fI['parentRelFileName'];
807  }
808  $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$extID]);
809  $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('actions-insert-reference', Icon::SIZE_SMALL)->render();
810  $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Resource)</em>';
811  $pInfo['ref'] = 'FILE';
812  $pInfo['type'] = 'file';
813  $lines[] = $pInfo;
814  unset($this->remainHeader['files'][$extID]);
815  }
816  }
817  }
818  }
819 
827  public function checkDokType($checkTable, $doktype)
828  {
829  $allowedTableList = $GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'] ?? $GLOBALS['PAGES_TYPES']['default']['allowedTables'];
830  $allowedArray = GeneralUtility::trimExplode(',', $allowedTableList, true);
831  // If all tables or the table is listed as an allowed type, return TRUE
832  if (strpos($allowedTableList, '*') !== false || in_array($checkTable, $allowedArray)) {
833  return true;
834  }
835  return false;
836  }
837 
844  public function renderControls($r)
845  {
846  if ($this->mode === 'export') {
847  if ($r['type'] === 'record') {
848  return '<input type="checkbox" class="t3js-exclude-checkbox" name="tx_impexp[exclude][' . $r['ref'] . ']" id="checkExclude' . $r['ref'] . '" value="1" /> <label for="checkExclude' . $r['ref'] . '">' . htmlspecialchars($this->getLanguageService()->getLL('impexpcore_singlereco_exclude')) . '</label>';
849  }
850  return $r['type'] === 'softref' ? $this->softrefSelector($r['_softRefInfo']) : '';
851  }
852  // During import
853  // For softreferences with editable fields:
854  if ($r['type'] === 'softref' && is_array($r['_softRefInfo']['subst']) && $r['_softRefInfo']['subst']['tokenID']) {
855  $tokenID = $r['_softRefInfo']['subst']['tokenID'];
856  $cfg = $this->softrefCfg[$tokenID];
857  if ($cfg['mode'] === 'editable') {
858  return (strlen($cfg['title']) ? '<strong>' . htmlspecialchars($cfg['title']) . '</strong><br/>' : '') . htmlspecialchars($cfg['description']) . '<br/>
859  <input type="text" name="tx_impexp[softrefInputValues][' . $tokenID . ']" value="' . htmlspecialchars($this->softrefInputValues[$tokenID] ?? $cfg['defValue']) . '" />';
860  }
861  }
862 
863  return '';
864  }
865 
872  public function softrefSelector($cfg)
873  {
874  // Looking for file ID if any:
875  $fI = $cfg['file_ID'] ? $this->dat['header']['files'][$cfg['file_ID']] : [];
876  // Substitution scheme has to be around and RTE images MUST be exported.
877  if (is_array($cfg['subst']) && $cfg['subst']['tokenID'] && !$fI['RTE_ORIG_ID']) {
878  // Create options:
879  $optValues = [];
880  $optValues[''] = '';
881  $optValues['editable'] = $this->getLanguageService()->getLL('impexpcore_softrefsel_editable');
882  $optValues['exclude'] = $this->getLanguageService()->getLL('impexpcore_softrefsel_exclude');
883  // Get current value:
884  $value = $this->softrefCfg[$cfg['subst']['tokenID']]['mode'];
885  // Render options selector:
886  $selectorbox = $this->renderSelectBox('tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][mode]', $value, $optValues) . '<br/>';
887  if ($value === 'editable') {
888  $descriptionField = '';
889  // Title:
890  if (strlen($cfg['subst']['title'])) {
891  $descriptionField .= '
892  <input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][title]" value="' . htmlspecialchars($cfg['subst']['title']) . '" />
893  <strong>' . htmlspecialchars($cfg['subst']['title']) . '</strong><br/>';
894  }
895  // Description:
896  if (!strlen($cfg['subst']['description'])) {
897  $descriptionField .= '
898  ' . htmlspecialchars($this->getLanguageService()->getLL('impexpcore_printerror_description')) . '<br/>
899  <input type="text" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($this->softrefCfg[$cfg['subst']['tokenID']]['description']) . '" />';
900  } else {
901  $descriptionField .= '
902 
903  <input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($cfg['subst']['description']) . '" />' . htmlspecialchars($cfg['subst']['description']);
904  }
905  // Default Value:
906  $descriptionField .= '<input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][defValue]" value="' . htmlspecialchars($cfg['subst']['tokenValue']) . '" />';
907  } else {
908  $descriptionField = '';
909  }
910  return $selectorbox . $descriptionField;
911  }
912  return '';
913  }
914 
923  public function verifyFolderAccess($dirPrefix, $noAlternative = false)
924  {
925  // Check the absolute path for public web path, if the user has access - no problem
926  try {
927  GeneralUtility::makeInstance(ResourceFactory::class)->getFolderObjectFromCombinedIdentifier($dirPrefix);
928  return $dirPrefix;
929  } catch (InsufficientFolderAccessPermissionsException $e) {
930  // Check all storages available for the user as alternative
931  if (!$noAlternative) {
932  $fileStorages = $this->getBackendUser()->getFileStorages();
933  foreach ($fileStorages as $fileStorage) {
934  try {
935  $folder = $fileStorage->getFolder(rtrim($dirPrefix, '/'));
936  return $folder->getPublicUrl();
937  } catch (InsufficientFolderAccessPermissionsException $e) {
938  }
939  }
940  }
941  }
942  return false;
943  }
944 
945  /*****************************
946  * Helper functions of kinds
947  *****************************/
948 
952  protected function getTemporaryFolderName()
953  {
954  $temporaryPath = Environment::getVarPath() . '/transient/';
955  do {
956  $temporaryFolderName = $temporaryPath . 'export_temp_files_' . random_int(1, PHP_INT_MAX);
957  } while (is_dir($temporaryFolderName));
958  GeneralUtility::mkdir($temporaryFolderName);
959  return $temporaryFolderName;
960  }
961 
970  public function flatInversePageTree($idH, $a = [])
971  {
972  if (is_array($idH)) {
973  $idH = array_reverse($idH);
974  foreach ($idH as $k => $v) {
975  $a[$v['uid']] = $v['uid'];
976  if (is_array($v['subrow'])) {
977  $a = $this->flatInversePageTree($v['subrow'], $a);
978  }
979  }
980  }
981  return $a;
982  }
983 
990  public function isTableStatic($table)
991  {
992  if (is_array($GLOBALS['TCA'][$table])) {
993  return $GLOBALS['TCA'][$table]['ctrl']['is_static'] || in_array($table, $this->relStaticTables) || in_array('_ALL', $this->relStaticTables);
994  }
995  return false;
996  }
997 
1004  public function inclRelation($table)
1005  {
1006  return is_array($GLOBALS['TCA'][$table])
1007  && (in_array($table, $this->relOnlyTables) || in_array('_ALL', $this->relOnlyTables))
1008  && $this->getBackendUser()->check('tables_select', $table);
1009  }
1010 
1018  public function isExcluded($table, $uid)
1019  {
1020  return (bool)$this->excludeMap[$table . ':' . $uid];
1021  }
1022 
1029  public function includeSoftref($tokenID)
1030  {
1031  $mode = $this->softrefCfg[$tokenID]['mode'];
1032  return $tokenID && $mode !== 'exclude' && $mode !== 'editable';
1033  }
1034 
1041  public function checkPID($pid)
1042  {
1043  if (!isset($this->checkPID_cache[$pid])) {
1044  $this->checkPID_cache[$pid] = (bool)$this->getBackendUser()->isInWebMount($pid);
1045  }
1046  return $this->checkPID_cache[$pid];
1047  }
1048 
1056  public function dontIgnorePid($table, $uid)
1057  {
1058  return $this->import_mode[$table . ':' . $uid] !== 'ignore_pid' && (!$this->global_ignore_pid || $this->import_mode[$table . ':' . $uid] === 'respect_pid');
1059  }
1060 
1069  public function doesRecordExist($table, $uid, $fields = '')
1070  {
1071  return BackendUtility::getRecord($table, $uid, $fields ?: 'uid,pid');
1072  }
1073 
1080  public function getRecordPath($pid)
1081  {
1082  if (!isset($this->cache_getRecordPath[$pid])) {
1083  $clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
1084  $this->cache_getRecordPath[$pid] = (string)BackendUtility::getRecordPath($pid, $clause, 20);
1085  }
1086  return $this->cache_getRecordPath[$pid];
1087  }
1088 
1097  public function renderSelectBox($prefix, $value, $optValues)
1098  {
1099  $opt = [];
1100  $isSelFlag = 0;
1101  foreach ($optValues as $k => $v) {
1102  $sel = (string)$k === (string)$value ? ' selected="selected"' : '';
1103  if ($sel) {
1104  $isSelFlag++;
1105  }
1106  $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>';
1107  }
1108  if (!$isSelFlag && (string)$value !== '') {
1109  $opt[] = '<option value="' . htmlspecialchars($value) . '" selected="selected">' . htmlspecialchars('[\'' . $value . '\']') . '</option>';
1110  }
1111  return '<select name="' . $prefix . '">' . implode('', $opt) . '</select>';
1112  }
1113 
1124  public function compareRecords($databaseRecord, $importRecord, $table, $inverseDiff = false)
1125  {
1126  // Initialize:
1127  $output = [];
1128  $diffUtility = GeneralUtility::makeInstance(DiffUtility::class);
1129  // Check if both inputs are records:
1130  if (is_array($databaseRecord) && is_array($importRecord)) {
1131  // Traverse based on database record
1132  foreach ($databaseRecord as $fN => $value) {
1133  if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') {
1134  if (isset($importRecord[$fN])) {
1135  if (trim($databaseRecord[$fN]) !== trim($importRecord[$fN])) {
1136  // Create diff-result:
1137  $output[$fN] = $diffUtility->makeDiffDisplay(BackendUtility::getProcessedValue($table, $fN, !$inverseDiff ? $importRecord[$fN] : $databaseRecord[$fN], 0, true, true), BackendUtility::getProcessedValue($table, $fN, !$inverseDiff ? $databaseRecord[$fN] : $importRecord[$fN], 0, true, true));
1138  }
1139  unset($importRecord[$fN]);
1140  }
1141  }
1142  }
1143  // Traverse remaining in import record:
1144  foreach ($importRecord as $fN => $value) {
1145  if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') {
1146  $output[$fN] = '<strong>Field missing</strong> in database';
1147  }
1148  }
1149  // Create output:
1150  if (!empty($output)) {
1151  $tRows = [];
1152  foreach ($output as $fN => $state) {
1153  $tRows[] = '
1154  <tr>
1155  <td>' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fN]['label'])) . ' (' . htmlspecialchars((string)$fN) . ')</td>
1156  <td>' . $state . '</td>
1157  </tr>
1158  ';
1159  }
1160  $output = '<table class="table table-striped table-hover">' . implode('', $tRows) . '</table>';
1161  } else {
1162  $output = 'Match';
1163  }
1164  return '<strong class="text-nowrap">[' . htmlspecialchars($table . ':' . $importRecord['uid'] . ' => ' . $databaseRecord['uid']) . ']:</strong> ' . $output;
1165  }
1166  return 'ERROR: One of the inputs were not an array!';
1167  }
1168 
1174  public function getFileProcObj()
1175  {
1176  if ($this->fileProcObj === null) {
1177  $this->fileProcObj = GeneralUtility::makeInstance(ExtendedFileUtility::class);
1178  $this->fileProcObj->setActionPermissions();
1179  }
1180  return $this->fileProcObj;
1181  }
1182 
1189  public function callHook($name, $params)
1190  {
1191  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/impexp/class.tx_impexp.php'][$name] ?? [] as $hook) {
1192  GeneralUtility::callUserFunction($hook, $params, $this);
1193  }
1194  }
1195 
1203  public function setExcludeDisabledRecords($excludeDisabledRecords = false)
1204  {
1205  $this->excludeDisabledRecords = $excludeDisabledRecords;
1206  return $this;
1207  }
1208 
1209  /*****************************
1210  * Error handling
1211  *****************************/
1212 
1218  public function error($msg)
1219  {
1220  $this->errorLog[] = $msg;
1221  }
1222 
1228  public function printErrorLog()
1229  {
1230  return !empty($this->errorLog) ? DebugUtility::viewArray($this->errorLog) : '';
1231  }
1232 
1236  protected function getBackendUser()
1237  {
1238  return $GLOBALS['BE_USER'];
1239  }
1240 
1244  protected function getLanguageService()
1245  {
1246  return $GLOBALS['LANG'];
1247  }
1248 }
‪TYPO3\CMS\Core\Utility\DiffUtility
Definition: DiffUtility.php:25
‪TYPO3\CMS\Impexp\ImportExport\renderControls
‪string renderControls($r)
Definition: ImportExport.php:817
‪TYPO3\CMS\Impexp\ImportExport\isActive
‪bool isActive($table, $uid)
Definition: ImportExport.php:364
‪TYPO3\CMS\Impexp\ImportExport\$extensionDependencies
‪array $extensionDependencies
Definition: ImportExport.php:175
‪TYPO3\CMS\Impexp\ImportExport\$iconFactory
‪IconFactory $iconFactory
Definition: ImportExport.php:226
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:24
‪TYPO3\CMS\Impexp\ImportExport\$showStaticRelations
‪bool $showStaticRelations
Definition: ImportExport.php:75
‪TYPO3\CMS\Impexp\ImportExport\$mode
‪string $mode
Definition: ImportExport.php:87
‪TYPO3\CMS\Impexp\ImportExport\$errorLog
‪array $errorLog
Definition: ImportExport.php:187
‪TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException
Definition: InsufficientFolderAccessPermissionsException.php:24
‪TYPO3\CMS\Core\Resource\Security\FileNameValidator
Definition: FileNameValidator.php:25
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:26
‪TYPO3\CMS\Impexp\ImportExport\traversePageTree
‪traversePageTree($pT, &$lines, $preCode='')
Definition: ImportExport.php:328
‪TYPO3\CMS\Impexp\ImportExport\error
‪error($msg)
Definition: ImportExport.php:1191
‪TYPO3\CMS\Impexp\ImportExport\$import_mode
‪array $import_mode
Definition: ImportExport.php:113
‪TYPO3\CMS\Impexp\ImportExport\$checkPID_cache
‪array $checkPID_cache
Definition: ImportExport.php:199
‪TYPO3\CMS\Impexp\ImportExport\$showDiff
‪bool $showDiff
Definition: ImportExport.php:131
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:33
‪TYPO3\CMS\Impexp\ImportExport\$cache_getRecordPath
‪array $cache_getRecordPath
Definition: ImportExport.php:193
‪TYPO3\CMS\Impexp\ImportExport\$softrefCfg
‪array $softrefCfg
Definition: ImportExport.php:169
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:24
‪TYPO3\CMS\Impexp\ImportExport\displayContentOverview
‪array displayContentOverview()
Definition: ImportExport.php:265
‪TYPO3\CMS\Impexp\ImportExport\excludePageAndRecords
‪excludePageAndRecords($pageUid, $pageTree)
Definition: ImportExport.php:379
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:43
‪TYPO3\CMS\Impexp\ImportExport\traversePageRecords
‪traversePageRecords($pT, &$lines)
Definition: ImportExport.php:409
‪TYPO3\CMS\Core\Utility\File\ExtendedFileUtility
Definition: ExtendedFileUtility.php:70
‪TYPO3\CMS\Impexp\ImportExport\$relOnlyTables
‪array $relOnlyTables
Definition: ImportExport.php:150
‪TYPO3\CMS\Impexp\ImportExport\$fileadminFolderName
‪string $fileadminFolderName
Definition: ImportExport.php:81
‪TYPO3\CMS\Impexp\ImportExport\$doesImport
‪bool $doesImport
Definition: ImportExport.php:99
‪TYPO3\CMS\Impexp\ImportExport\singleRecordLines
‪singleRecordLines($table, $uid, &$lines, $preCode, $checkImportInPidRecord=false)
Definition: ImportExport.php:470
‪TYPO3\CMS\Impexp\ImportExport\traverseAllRecords
‪traverseAllRecords($pT, &$lines)
Definition: ImportExport.php:433
‪TYPO3\CMS\Impexp\ImportExport\$fileProcObj
‪ExtendedFileUtility $fileProcObj
Definition: ImportExport.php:218
‪TYPO3\CMS\Core\Resource\ResourceFactory
Definition: ResourceFactory.php:41
‪TYPO3\CMS\Core\Resource\File
Definition: File.php:24
‪TYPO3\CMS\Impexp\ImportExport\getLanguageService
‪LanguageService getLanguageService()
Definition: ImportExport.php:1217
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Impexp\ImportExport\__construct
‪__construct()
Definition: ImportExport.php:238
‪TYPO3\CMS\Impexp\ImportExport
Definition: ImportExport.php:70
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:75
‪TYPO3\CMS\Core\Utility\DebugUtility
Definition: DebugUtility.php:25
‪TYPO3\CMS\Impexp\ImportExport\$update
‪bool $update
Definition: ImportExport.php:93
‪TYPO3\CMS\Impexp\ImportExport\$relStaticTables
‪array $relStaticTables
Definition: ImportExport.php:157
‪TYPO3\CMS\Impexp\ImportExport\$display_import_pid_record
‪array $display_import_pid_record
Definition: ImportExport.php:107
‪TYPO3\CMS\Impexp\ImportExport\$excludeDisabledRecords
‪bool $excludeDisabledRecords
Definition: ImportExport.php:233
‪TYPO3\CMS\Impexp\ImportExport\$import_mapId
‪array $import_mapId
Definition: ImportExport.php:181
‪TYPO3\CMS\Impexp\ImportExport\$remainHeader
‪array $remainHeader
Definition: ImportExport.php:222
‪TYPO3\CMS\Impexp\ImportExport\init
‪init()
Definition: ImportExport.php:250
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Impexp\ImportExport\getBackendUser
‪BackendUserAuthentication getBackendUser()
Definition: ImportExport.php:1209
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:40
‪TYPO3\CMS\Impexp\ImportExport\$excludeMap
‪array $excludeMap
Definition: ImportExport.php:163
‪TYPO3\CMS\Impexp\ImportExport\$compress
‪bool $compress
Definition: ImportExport.php:206
‪TYPO3\CMS\Impexp\ImportExport\addGeneralErrorsByTable
‪addGeneralErrorsByTable($table)
Definition: ImportExport.php:451
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Impexp\ImportExport\$dat
‪array $dat
Definition: ImportExport.php:212
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Impexp\ImportExport\$global_ignore_pid
‪bool $global_ignore_pid
Definition: ImportExport.php:119
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\isLoaded
‪static bool isLoaded($key)
Definition: ExtensionManagementUtility.php:114
‪TYPO3\CMS\Impexp\ImportExport\$fileIDMap
‪array $fileIDMap
Definition: ImportExport.php:143
‪TYPO3\CMS\Impexp
‪TYPO3\CMS\Impexp\ImportExport\$softrefInputValues
‪array $softrefInputValues
Definition: ImportExport.php:137
‪TYPO3\CMS\Impexp\ImportExport\$force_all_UIDS
‪bool $force_all_UIDS
Definition: ImportExport.php:125