2 namespace TYPO3\CMS\Impexp
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Backend\Utility\BackendUtility
;
18 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication
;
19 use TYPO3\CMS\Core\Database\DatabaseConnection
;
20 use TYPO3\CMS\Core\Imaging\Icon
;
21 use TYPO3\CMS\Core\Imaging\IconFactory
;
22 use TYPO3\CMS\Core\Utility\DebugUtility
;
23 use TYPO3\CMS\Core\Utility\DiffUtility
;
24 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility
;
25 use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility
;
26 use TYPO3\CMS\Core\Utility\GeneralUtility
;
27 use TYPO3\CMS\Core\Utility\PathUtility
;
28 use TYPO3\CMS\Lang\LanguageService
;
31 * EXAMPLE for using the impexp-class for exporting stuff:
33 * Create and initialize:
34 * $this->export = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Impexp\ImportExport::class);
35 * $this->export->init();
36 * Set which tables relations we will allow:
37 * $this->export->relOnlyTables[]="tt_news"; // exclusively includes. See comment in the class
40 * $this->export->export_addRecord("pages", $this->pageinfo);
41 * $this->export->export_addRecord("pages", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("pages", 38));
42 * $this->export->export_addRecord("pages", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("pages", 39));
43 * $this->export->export_addRecord("tt_content", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("tt_content", 12));
44 * $this->export->export_addRecord("tt_content", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("tt_content", 74));
45 * $this->export->export_addRecord("sys_template", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("sys_template", 20));
47 * Adding all the relations (recursively in 5 levels so relations has THEIR relations registered as well)
48 * for($a=0;$a<5;$a++) {
49 * $addR = $this->export->export_addDBRelations($a);
50 * if (empty($addR)) break;
53 * Finally load all the files.
54 * $this->export->export_addFilesFromRelations(); // MUST be after the DBrelations are set so that file from ALL added records are included!
57 * $out = $this->export->compileMemoryToFileContent();
61 * T3D file Import/Export library (TYPO3 Record Document)
63 abstract class ImportExport
66 * If set, static relations (not exported) will be shown in overview as well
70 public $showStaticRelations = false;
73 * Name of the "fileadmin" folder where files for export/import should be located
77 public $fileadminFolderName = '';
80 * Whether "import" or "export" mode of object. Set through init() function
87 * Updates all records that has same UID instead of creating new!
91 public $update = false;
94 * Is set by importData() when an import has been done.
98 public $doesImport = false;
101 * If set to a page-record, then the preview display of the content will expect this page-record to be the target
102 * for the import and accordingly display validation information. This triggers the visual view of the
103 * import/export memory to validate if import is possible
107 public $display_import_pid_record = array();
110 * Setting import modes during update state: as_new, exclude, force_uid
114 public $import_mode = array();
117 * If set, PID correct is ignored globally
121 public $global_ignore_pid = false;
124 * If set, all UID values are forced! (update or import)
128 public $force_all_UIDS = false;
131 * If set, a diff-view column is added to the overview.
135 public $showDiff = false;
138 * If set, and if the user is admin, allow the writing of PHP scripts to fileadmin/ area.
142 public $allowPHPScripts = false;
145 * Array of values to substitute in editable softreferences.
149 public $softrefInputValues = array();
152 * Mapping between the fileID from import memory and the final filenames they are written to.
156 public $fileIDMap = array();
159 * Add table names here which are THE ONLY ones which will be included
160 * into export if found as relations. '_ALL' will allow all tables.
164 public $relOnlyTables = array();
167 * Add tables names here which should not be exported with the file.
168 * (Where relations should be mapped to same UIDs in target system).
172 public $relStaticTables = array();
175 * Exclude map. Keys are table:uid pairs and if set, records are not added to the export.
179 public $excludeMap = array();
182 * Soft Reference Token ID modes.
186 public $softrefCfg = array();
189 * Listing extension dependencies.
193 public $extensionDependencies = array();
196 * After records are written this array is filled with [table][original_uid] = [new_uid]
200 public $import_mapId = array();
207 public $errorLog = array();
210 * Cache for record paths
214 public $cache_getRecordPath = array();
217 * Cache of checkPID values.
221 public $checkPID_cache = array();
224 * Set internally if the gzcompress function exists
225 * Used by ImportExportController
229 public $compress = false;
232 * Internal import/export memory
236 public $dat = array();
239 * File processing object
241 * @var ExtendedFileUtility
243 protected $fileProcObj = null;
248 protected $remainHeader = array();
253 protected $iconFactory;
258 public function __construct()
260 $this->iconFactory
= GeneralUtility
::makeInstance(IconFactory
::class);
263 /**************************
265 *************************/
268 * Init the object, both import and export
272 public function init()
274 $this->compress
= function_exists('gzcompress');
275 $this->fileadminFolderName
= !empty($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir']) ?
rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') : 'fileadmin';
278 /********************************************************
279 * Visual rendering of import/export memory, $this->dat
280 ********************************************************/
283 * Displays an overview of the header-content.
285 * @return array The view data
287 public function displayContentOverview()
289 // Check extension dependencies:
290 if (is_array($this->dat
['header']['extensionDependencies'])) {
291 foreach ($this->dat
['header']['extensionDependencies'] as $extKey) {
292 if (!ExtensionManagementUtility
::isLoaded($extKey)) {
293 $this->error('DEPENDENCY: The extension with key "' . $extKey . '" must be installed!');
297 // Probably this is done to save memory space?
298 unset($this->dat
['files']);
302 if (is_array($this->dat
['header'])) {
303 $this->remainHeader
= $this->dat
['header'];
304 // If there is a page tree set, show that:
305 if (is_array($this->dat
['header']['pagetree'])) {
306 reset($this->dat
['header']['pagetree']);
308 $this->traversePageTree($this->dat
['header']['pagetree'], $lines);
310 $viewData['dat'] = $this->dat
;
311 $viewData['update'] = $this->update
;
312 $viewData['showDiff'] = $this->showDiff
;
313 if (!empty($lines)) {
314 foreach ($lines as &$r) {
315 $r['controls'] = $this->renderControls($r);
316 $r['fileSize'] = GeneralUtility
::formatSize($r['size']);
317 $r['message'] = ($r['msg'] && !$this->doesImport ?
'<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
319 $viewData['pagetreeLines'] = $lines;
321 $viewData['pagetreeLines'] = array();
324 // Print remaining records that were not contained inside the page tree:
325 if (is_array($this->remainHeader
['records'])) {
327 if (is_array($this->remainHeader
['records']['pages'])) {
328 $this->traversePageRecords($this->remainHeader
['records']['pages'], $lines);
330 $this->traverseAllRecords($this->remainHeader
['records'], $lines);
331 if (!empty($lines)) {
332 foreach ($lines as &$r) {
333 $r['controls'] = $this->renderControls($r);
334 $r['fileSize'] = GeneralUtility
::formatSize($r['size']);
335 $r['message'] = ($r['msg'] && !$this->doesImport ?
'<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
337 $viewData['remainingRecords'] = $lines;
346 * Go through page tree for display
348 * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
349 * @param array $lines Output lines array (is passed by reference and modified)
350 * @param string $preCode Pre-HTML code
353 public function traversePageTree($pT, &$lines, $preCode = '')
355 foreach ($pT as $k => $v) {
357 $this->singleRecordLines('pages', $k, $lines, $preCode);
359 if (is_array($this->dat
['header']['pid_lookup'][$k])) {
360 foreach ($this->dat
['header']['pid_lookup'][$k] as $t => $recUidArr) {
362 foreach ($recUidArr as $ruid => $value) {
363 $this->singleRecordLines($t, $ruid, $lines, $preCode . ' ');
367 unset($this->remainHeader
['pid_lookup'][$k]);
369 // Subpages, called recursively:
370 if (is_array($v['subrow'])) {
371 $this->traversePageTree($v['subrow'], $lines, $preCode . ' ');
377 * Go through remaining pages (not in tree)
379 * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
380 * @param array $lines Output lines array (is passed by reference and modified)
383 public function traversePageRecords($pT, &$lines)
385 foreach ($pT as $k => $rHeader) {
386 $this->singleRecordLines('pages', $k, $lines, '', 1);
388 if (is_array($this->dat
['header']['pid_lookup'][$k])) {
389 foreach ($this->dat
['header']['pid_lookup'][$k] as $t => $recUidArr) {
391 foreach ($recUidArr as $ruid => $value) {
392 $this->singleRecordLines($t, $ruid, $lines, ' ');
396 unset($this->remainHeader
['pid_lookup'][$k]);
402 * Go through ALL records (if the pages are displayed first, those will not be amoung these!)
404 * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
405 * @param array $lines Output lines array (is passed by reference and modified)
408 public function traverseAllRecords($pT, &$lines)
410 foreach ($pT as $t => $recUidArr) {
411 $this->addGeneralErrorsByTable($t);
414 foreach ($recUidArr as $ruid => $value) {
415 $this->singleRecordLines($t, $ruid, $lines, $preCode, 1);
422 * Log general error message for a given table
424 * @param string $table database table name
427 protected function addGeneralErrorsByTable($table)
429 if ($this->update
&& $table === 'sys_file') {
430 $this->error('Updating sys_file records is not supported! They will be imported as new records!');
432 if ($this->force_all_UIDS
&& $table === 'sys_file') {
433 $this->error('Forcing uids of sys_file records is not supported! They will be imported as new records!');
438 * Add entries for a single record
440 * @param string $table Table name
441 * @param int $uid Record uid
442 * @param array $lines Output lines array (is passed by reference and modified)
443 * @param string $preCode Pre-HTML code
444 * @param bool $checkImportInPidRecord If you want import validation, you can set this so it checks if the import can take place on the specified page.
447 public function singleRecordLines($table, $uid, &$lines, $preCode, $checkImportInPidRecord = false)
450 $record = $this->dat
['header']['records'][$table][$uid];
451 unset($this->remainHeader
['records'][$table][$uid]);
452 if (!is_array($record) && !($table === 'pages' && !$uid)) {
453 $this->error('MISSING RECORD: ' . $table . ':' . $uid);
455 // Begin to create the line arrays information record, pInfo:
457 $pInfo['ref'] = $table . ':' . $uid;
458 // Unknown table name:
459 $lang = $this->getLanguageService();
460 if ($table === '_SOFTREF_') {
461 $pInfo['preCode'] = $preCode;
462 $pInfo['title'] = '<em>' . $lang->getLL('impexpcore_singlereco_softReferencesFiles', true) . '</em>';
463 } elseif (!isset($GLOBALS['TCA'][$table])) {
464 // Unknown table name:
465 $pInfo['preCode'] = $preCode;
466 $pInfo['msg'] = 'UNKNOWN TABLE \'' . $pInfo['ref'] . '\'';
467 $pInfo['title'] = '<em>' . htmlspecialchars($record['title']) . '</em>';
469 // Otherwise, set table icon and title.
470 // Import Validation (triggered by $this->display_import_pid_record) will show messages if import is not possible of various items.
471 if (is_array($this->display_import_pid_record
) && !empty($this->display_import_pid_record
)) {
472 if ($checkImportInPidRecord) {
473 if (!$this->getBackendUser()->doesUserHaveAccess($this->display_import_pid_record
, ($table === 'pages' ?
8 : 16))) {
474 $pInfo['msg'] .= '\'' . $pInfo['ref'] . '\' cannot be INSERTED on this page! ';
476 if (!$this->checkDokType($table, $this->display_import_pid_record
['doktype']) && !$GLOBALS['TCA'][$table]['ctrl']['rootLevel']) {
477 $pInfo['msg'] .= '\'' . $table . '\' cannot be INSERTED on this page type (change page type to \'Folder\'.) ';
480 if (!$this->getBackendUser()->check('tables_modify', $table)) {
481 $pInfo['msg'] .= 'You are not allowed to CREATE \'' . $table . '\' tables! ';
483 if ($GLOBALS['TCA'][$table]['ctrl']['readOnly']) {
484 $pInfo['msg'] .= 'TABLE \'' . $table . '\' is READ ONLY! ';
486 if ($GLOBALS['TCA'][$table]['ctrl']['adminOnly'] && !$this->getBackendUser()->isAdmin()) {
487 $pInfo['msg'] .= 'TABLE \'' . $table . '\' is ADMIN ONLY! ';
489 if ($GLOBALS['TCA'][$table]['ctrl']['is_static']) {
490 $pInfo['msg'] .= 'TABLE \'' . $table . '\' is a STATIC TABLE! ';
492 if ((int)$GLOBALS['TCA'][$table]['ctrl']['rootLevel'] === 1) {
493 $pInfo['msg'] .= 'TABLE \'' . $table . '\' will be inserted on ROOT LEVEL! ';
495 $diffInverse = false;
498 // In case of update-PREVIEW we swap the diff-sources.
500 $recInf = $this->doesRecordExist($table, $uid, $this->showDiff ?
'*' : '');
501 $pInfo['updatePath'] = $recInf ?
htmlspecialchars($this->getRecordPath($recInf['pid'])) : '<strong>NEW!</strong>';
503 $optValues = array();
504 $optValues[] = $recInf ?
$lang->getLL('impexpcore_singlereco_update') : $lang->getLL('impexpcore_singlereco_insert');
506 $optValues['as_new'] = $lang->getLL('impexpcore_singlereco_importAsNew');
509 if (!$this->global_ignore_pid
) {
510 $optValues['ignore_pid'] = $lang->getLL('impexpcore_singlereco_ignorePid');
512 $optValues['respect_pid'] = $lang->getLL('impexpcore_singlereco_respectPid');
515 if (!$recInf && $this->getBackendUser()->isAdmin()) {
516 $optValues['force_uid'] = sprintf($lang->getLL('impexpcore_singlereco_forceUidSAdmin'), $uid);
518 $optValues['exclude'] = $lang->getLL('impexpcore_singlereco_exclude');
519 if ($table === 'sys_file') {
520 $pInfo['updateMode'] = '';
522 $pInfo['updateMode'] = $this->renderSelectBox('tx_impexp[import_mode][' . $table . ':' . $uid . ']', $this->import_mode
[$table . ':' . $uid], $optValues);
526 if ($this->showDiff
) {
527 // For IMPORTS, get new id:
528 if ($newUid = $this->import_mapId
[$table][$uid]) {
529 $diffInverse = false;
530 $recInf = $this->doesRecordExist($table, $newUid, '*');
531 BackendUtility
::workspaceOL($table, $recInf);
533 if (is_array($recInf)) {
534 $pInfo['showDiffContent'] = $this->compareRecords($recInf, $this->dat
['records'][$table . ':' . $uid]['data'], $table, $diffInverse);
538 $pInfo['preCode'] = $preCode . '<span title="' . htmlspecialchars($table . ':' . $uid) . '">'
539 . $this->iconFactory
->getIconForRecord($table, (array)$this->dat
['records'][$table . ':' . $uid]['data'], Icon
::SIZE_SMALL
)->render()
541 $pInfo['title'] = htmlspecialchars($record['title']);
543 if ($table === 'pages') {
544 $viewID = $this->mode
=== 'export' ?
$uid : ($this->doesImport ?
$this->import_mapId
['pages'][$uid] : 0);
546 $pInfo['title'] = '<a href="#" onclick="' . htmlspecialchars(BackendUtility
::viewOnClick($viewID)) . 'return false;">' . $pInfo['title'] . '</a>';
550 $pInfo['class'] = $table == 'pages' ?
'bgColor4-20' : 'bgColor4';
551 $pInfo['type'] = 'record';
552 $pInfo['size'] = $record['size'];
555 if (is_array($record['filerefs'])) {
556 $this->addFiles($record['filerefs'], $lines, $preCode);
559 if (is_array($record['rels'])) {
560 $this->addRelations($record['rels'], $lines, $preCode);
563 if (!empty($record['softrefs'])) {
564 $preCode_A = $preCode . ' ';
565 $preCode_B = $preCode . ' ';
566 foreach ($record['softrefs'] as $info) {
568 $pInfo['preCode'] = $preCode_A . $this->iconFactory
->getIcon('status-status-reference-soft', Icon
::SIZE_SMALL
)->render();
569 $pInfo['title'] = '<em>' . $info['field'] . ', "' . $info['spKey'] . '" </em>: <span title="' . htmlspecialchars($info['matchString']) . '">' . htmlspecialchars(GeneralUtility
::fixed_lgd_cs($info['matchString'], 60)) . '</span>';
570 if ($info['subst']['type']) {
571 if (strlen($info['subst']['title'])) {
572 $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . $lang->getLL('impexpcore_singlereco_title', true) . '</strong> ' . htmlspecialchars(GeneralUtility
::fixed_lgd_cs($info['subst']['title'], 60));
574 if (strlen($info['subst']['description'])) {
575 $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . $lang->getLL('impexpcore_singlereco_descr', true) . '</strong> ' . htmlspecialchars(GeneralUtility
::fixed_lgd_cs($info['subst']['description'], 60));
577 $pInfo['title'] .= '<br/>' . $preCode_B . ($info['subst']['type'] == 'file' ?
$lang->getLL('impexpcore_singlereco_filename', true) . ' <strong>' . $info['subst']['relFileName'] . '</strong>' : '') . ($info['subst']['type'] == 'string' ?
$lang->getLL('impexpcore_singlereco_value', true) . ' <strong>' . $info['subst']['tokenValue'] . '</strong>' : '') . ($info['subst']['type'] == 'db' ?
$lang->getLL('impexpcore_softrefsel_record', true) . ' <strong>' . $info['subst']['recordRef'] . '</strong>' : '');
579 $pInfo['ref'] = 'SOFTREF';
581 $pInfo['class'] = 'bgColor3';
582 $pInfo['type'] = 'softref';
583 $pInfo['_softRefInfo'] = $info;
584 $pInfo['type'] = 'softref';
585 $mode = $this->softrefCfg
[$info['subst']['tokenID']]['mode'];
586 if ($info['error'] && $mode !== 'editable' && $mode !== 'exclude') {
587 $pInfo['msg'] .= $info['error'];
591 if ($info['subst']['type'] == 'db') {
592 list($tempTable, $tempUid) = explode(':', $info['subst']['recordRef']);
593 $this->addRelations(array(array('table' => $tempTable, 'id' => $tempUid, 'tokenID' => $info['subst']['tokenID'])), $lines, $preCode_B, array(), '');
596 if ($info['subst']['type'] == 'file') {
597 $this->addFiles(array($info['file_ID']), $lines, $preCode_B, '', $info['subst']['tokenID']);
604 * Add DB relations entries for a record's rels-array
606 * @param array $rels Array of relations
607 * @param array $lines Output lines array (is passed by reference and modified)
608 * @param string $preCode Pre-HTML code
609 * @param array $recurCheck Recursivity check stack
610 * @param string $htmlColorClass Alternative HTML color class to use.
613 * @see singleRecordLines()
615 public function addRelations($rels, &$lines, $preCode, $recurCheck = array(), $htmlColorClass = '')
617 foreach ($rels as $dat) {
618 $table = $dat['table'];
621 $pInfo['ref'] = $table . ':' . $uid;
622 if (in_array($pInfo['ref'], $recurCheck)) {
625 $iconName = 'status-status-checked';
627 $staticFixed = false;
630 $record = $this->dat
['header']['records'][$table][$uid];
631 if (!is_array($record)) {
632 if ($this->isTableStatic($table) ||
$this->isExcluded($table, $uid) ||
$dat['tokenID'] && !$this->includeSoftref($dat['tokenID'])) {
633 $pInfo['title'] = htmlspecialchars('STATIC: ' . $pInfo['ref']);
634 $iconClass = 'text-info';
637 $doesRE = $this->doesRecordExist($table, $uid);
638 $lostPath = $this->getRecordPath($table === 'pages' ?
$doesRE['uid'] : $doesRE['pid']);
639 $pInfo['title'] = htmlspecialchars($pInfo['ref']);
640 $pInfo['title'] = '<span title="' . htmlspecialchars($lostPath) . '">' . $pInfo['title'] . '</span>';
641 $pInfo['msg'] = 'LOST RELATION' . (!$doesRE ?
' (Record not found!)' : ' (Path: ' . $lostPath . ')');
642 $iconClass = 'text-danger';
643 $iconName = 'status-dialog-warning';
646 $pInfo['title'] = htmlspecialchars($record['title']);
647 $pInfo['title'] = '<span title="' . htmlspecialchars($this->getRecordPath(($table === 'pages' ?
$record['uid'] : $record['pid']))) . '">' . $pInfo['title'] . '</span>';
650 // 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
651 $pInfo['title'] = htmlspecialchars('FIXED: ' . $pInfo['ref']);
655 $icon = '<span class="' . $iconClass . '" title="' . htmlspecialchars($pInfo['ref']) . '">' . $this->iconFactory
->getIcon($iconName, Icon
::SIZE_SMALL
)->render() . '</span>';
657 $pInfo['preCode'] = $preCode . ' ' . $icon;
658 $pInfo['class'] = $htmlColorClass ?
: 'bgColor3';
659 $pInfo['type'] = 'rel';
660 if (!$staticFixed ||
$this->showStaticRelations
) {
662 if (is_array($record) && is_array($record['rels'])) {
663 $this->addRelations($record['rels'], $lines, $preCode . ' ', array_merge($recurCheck, array($pInfo['ref'])), $htmlColorClass);
670 * Add file relation entries for a record's rels-array
672 * @param array $rels Array of file IDs
673 * @param array $lines Output lines array (is passed by reference and modified)
674 * @param string $preCode Pre-HTML code
675 * @param string $htmlColorClass Alternative HTML color class to use.
676 * @param string $tokenID Token ID if this is a softreference (in which case it only makes sense with a single element in the $rels array!)
679 * @see singleRecordLines()
681 public function addFiles($rels, &$lines, $preCode, $htmlColorClass = '', $tokenID = '')
683 foreach ($rels as $ID) {
686 $fI = $this->dat
['header']['files'][$ID];
687 if (!is_array($fI)) {
688 if (!$tokenID ||
$this->includeSoftref($tokenID)) {
689 $pInfo['msg'] = 'MISSING FILE: ' . $ID;
690 $this->error('MISSING FILE: ' . $ID);
695 $pInfo['preCode'] = $preCode . ' ' . $this->iconFactory
->getIcon('status-status-reference-hard', Icon
::SIZE_SMALL
)->render();
696 $pInfo['title'] = htmlspecialchars($fI['filename']);
697 $pInfo['ref'] = 'FILE';
698 $pInfo['size'] = $fI['filesize'];
699 $pInfo['class'] = $htmlColorClass ?
: 'bgColor3';
700 $pInfo['type'] = 'file';
701 // If import mode and there is a non-RTE softreference, check the destination directory:
702 if ($this->mode
=== 'import' && $tokenID && !$fI['RTE_ORIG_ID']) {
703 if (isset($fI['parentRelFileName'])) {
704 $pInfo['msg'] = 'Seems like this file is already referenced from within an HTML/CSS file. That takes precedence. ';
706 $testDirPrefix = PathUtility
::dirname($fI['relFileName']) . '/';
707 $testDirPrefix2 = $this->verifyFolderAccess($testDirPrefix);
708 if (!$testDirPrefix2) {
709 $pInfo['msg'] = 'ERROR: There are no available filemounts to write file in! ';
710 } elseif ($testDirPrefix !== $testDirPrefix2) {
711 $pInfo['msg'] = 'File will be attempted written to "' . $testDirPrefix2 . '". ';
714 // Check if file exists:
715 if (file_exists(PATH_site
. $fI['relFileName'])) {
717 $pInfo['updatePath'] .= 'File exists.';
719 $pInfo['msg'] .= 'File already exists! ';
723 $fileProcObj = $this->getFileProcObj();
724 if ($fileProcObj->actionPerms
['addFile']) {
725 $testFI = GeneralUtility
::split_fileref(PATH_site
. $fI['relFileName']);
726 if (!$this->allowPHPScripts
&& !$fileProcObj->checkIfAllowed($testFI['fileext'], $testFI['path'], $testFI['file'])) {
727 $pInfo['msg'] .= 'File extension was not allowed!';
730 $pInfo['msg'] = 'You user profile does not allow you to create files on the server!';
733 $pInfo['showDiffContent'] = PathUtility
::stripPathSitePrefix($this->fileIDMap
[$ID]);
735 unset($this->remainHeader
['files'][$ID]);
737 if ($fI['RTE_ORIG_ID']) {
738 $ID = $fI['RTE_ORIG_ID'];
740 $fI = $this->dat
['header']['files'][$ID];
741 if (!is_array($fI)) {
742 $pInfo['msg'] = 'MISSING RTE original FILE: ' . $ID;
743 $this->error('MISSING RTE original FILE: ' . $ID);
745 $pInfo['showDiffContent'] = PathUtility
::stripPathSitePrefix($this->fileIDMap
[$ID]);
746 $pInfo['preCode'] = $preCode . ' ' . $this->iconFactory
->getIcon('status-status-reference-hard', Icon
::SIZE_SMALL
)->render();
747 $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Original)</em>';
748 $pInfo['ref'] = 'FILE';
749 $pInfo['size'] = $fI['filesize'];
750 $pInfo['class'] = $htmlColorClass ?
: 'bgColor3';
751 $pInfo['type'] = 'file';
753 unset($this->remainHeader
['files'][$ID]);
755 // External resources:
756 if (is_array($fI['EXT_RES_ID'])) {
757 foreach ($fI['EXT_RES_ID'] as $extID) {
759 $fI = $this->dat
['header']['files'][$extID];
760 if (!is_array($fI)) {
761 $pInfo['msg'] = 'MISSING External Resource FILE: ' . $extID;
762 $this->error('MISSING External Resource FILE: ' . $extID);
764 $pInfo['updatePath'] = $fI['parentRelFileName'];
766 $pInfo['showDiffContent'] = PathUtility
::stripPathSitePrefix($this->fileIDMap
[$extID]);
767 $pInfo['preCode'] = $preCode . ' ' . $this->iconFactory
->getIcon('actions-insert-reference', Icon
::SIZE_SMALL
)->render();
768 $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Resource)</em>';
769 $pInfo['ref'] = 'FILE';
770 $pInfo['size'] = $fI['filesize'];
771 $pInfo['class'] = $htmlColorClass ?
: 'bgColor3';
772 $pInfo['type'] = 'file';
774 unset($this->remainHeader
['files'][$extID]);
781 * Verifies that a table is allowed on a certain doktype of a page
783 * @param string $checkTable Table name to check
784 * @param int $doktype doktype value.
785 * @return bool TRUE if OK
787 public function checkDokType($checkTable, $doktype)
789 $allowedTableList = isset($GLOBALS['PAGES_TYPES'][$doktype]['allowedTables']) ?
$GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'] : $GLOBALS['PAGES_TYPES']['default']['allowedTables'];
790 $allowedArray = GeneralUtility
::trimExplode(',', $allowedTableList, true);
791 // If all tables or the table is listed as an allowed type, return TRUE
792 if (strstr($allowedTableList, '*') ||
in_array($checkTable, $allowedArray)) {
799 * Render input controls for import or export
801 * @param array $r Configuration for element
802 * @return string HTML
804 public function renderControls($r)
806 if ($this->mode
=== 'export') {
807 if ($r['type'] === 'record') {
808 return '<input type="checkbox" name="tx_impexp[exclude][' . $r['ref'] . ']" id="checkExclude' . $r['ref'] . '" value="1" /> <label for="checkExclude' . $r['ref'] . '">' . $this->getLanguageService()->getLL('impexpcore_singlereco_exclude', true) . '</label>';
810 return $r['type'] == 'softref' ?
$this->softrefSelector($r['_softRefInfo']) : '';
814 // For softreferences with editable fields:
815 if ($r['type'] == 'softref' && is_array($r['_softRefInfo']['subst']) && $r['_softRefInfo']['subst']['tokenID']) {
816 $tokenID = $r['_softRefInfo']['subst']['tokenID'];
817 $cfg = $this->softrefCfg
[$tokenID];
818 if ($cfg['mode'] === 'editable') {
819 return (strlen($cfg['title']) ?
'<strong>' . htmlspecialchars($cfg['title']) . '</strong><br/>' : '') . htmlspecialchars($cfg['description']) . '<br/>
820 <input type="text" name="tx_impexp[softrefInputValues][' . $tokenID . ']" value="' . htmlspecialchars((isset($this->softrefInputValues
[$tokenID]) ?
$this->softrefInputValues
[$tokenID] : $cfg['defValue'])) . '" />';
828 * Selectorbox with export options for soft references
830 * @param array $cfg Softref configuration array. An export box is shown only if a substitution scheme is found for the soft reference.
831 * @return string Selector box HTML
833 public function softrefSelector($cfg)
835 // Looking for file ID if any:
836 $fI = $cfg['file_ID'] ?
$this->dat
['header']['files'][$cfg['file_ID']] : array();
837 // Substitution scheme has to be around and RTE images MUST be exported.
838 if (is_array($cfg['subst']) && $cfg['subst']['tokenID'] && !$fI['RTE_ORIG_ID']) {
840 $optValues = array();
842 $optValues['editable'] = $this->getLanguageService()->getLL('impexpcore_softrefsel_editable');
843 $optValues['exclude'] = $this->getLanguageService()->getLL('impexpcore_softrefsel_exclude');
844 // Get current value:
845 $value = $this->softrefCfg
[$cfg['subst']['tokenID']]['mode'];
846 // Render options selector:
847 $selectorbox = $this->renderSelectBox(('tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][mode]'), $value, $optValues) . '<br/>';
848 if ($value === 'editable') {
849 $descriptionField = '';
851 if (strlen($cfg['subst']['title'])) {
852 $descriptionField .= '
853 <input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][title]" value="' . htmlspecialchars($cfg['subst']['title']) . '" />
854 <strong>' . htmlspecialchars($cfg['subst']['title']) . '</strong><br/>';
857 if (!strlen($cfg['subst']['description'])) {
858 $descriptionField .= '
859 ' . $this->getLanguageService()->getLL('impexpcore_printerror_description', true) . '<br/>
860 <input type="text" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($this->softrefCfg
[$cfg['subst']['tokenID']]['description']) . '" />';
862 $descriptionField .= '
864 <input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($cfg['subst']['description']) . '" />' . htmlspecialchars($cfg['subst']['description']);
867 $descriptionField .= '<input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][defValue]" value="' . htmlspecialchars($cfg['subst']['tokenValue']) . '" />';
869 $descriptionField = '';
871 return $selectorbox . $descriptionField;
877 * Verifies that the input path (relative to PATH_site) is found in the backend users filemounts.
878 * If it doesn't it will try to find another relative filemount for the user and return an alternative path prefix for the file.
880 * @param string $dirPrefix Path relative to PATH_site
881 * @param bool $noAlternative If set, Do not look for alternative path! Just return FALSE
882 * @return string|bool If a path is available that will be returned, otherwise FALSE.
884 public function verifyFolderAccess($dirPrefix, $noAlternative = false)
886 $fileProcObj = $this->getFileProcObj();
887 // Check, if dirPrefix is inside a valid Filemount for user:
888 $result = $fileProcObj->checkPathAgainstMounts(PATH_site
. $dirPrefix);
889 // If not, try to find another relative filemount and use that instead:
891 if ($noAlternative) {
894 // Find first web folder:
895 $result = $fileProcObj->findFirstWebFolder();
896 // If that succeeded, return the path to it:
898 // Remove the "fileadmin/" prefix of input path - and append the rest to the return value:
899 if (GeneralUtility
::isFirstPartOfStr($dirPrefix, $this->fileadminFolderName
. '/')) {
900 $dirPrefix = substr($dirPrefix, strlen($this->fileadminFolderName
. '/'));
902 return PathUtility
::stripPathSitePrefix($fileProcObj->mounts
[$result]['path'] . $dirPrefix);
910 /*****************************
911 * Helper functions of kinds
912 *****************************/
918 protected function getTemporaryFolderName()
920 $temporaryPath = PATH_site
. 'typo3temp/';
922 $temporaryFolderName = $temporaryPath . 'export_temp_files_' . mt_rand(1, PHP_INT_MAX
);
923 } while (is_dir($temporaryFolderName));
924 GeneralUtility
::mkdir($temporaryFolderName);
925 return $temporaryFolderName;
929 * Recursively flattening the idH array
931 * @param array $idH Page uid hierarchy
932 * @param array $a Accumulation array of pages (internal, don't set from outside)
933 * @return array Array with uid-uid pairs for all pages in the page tree.
934 * @see Import::flatInversePageTree_pid()
936 public function flatInversePageTree($idH, $a = array())
938 if (is_array($idH)) {
939 $idH = array_reverse($idH);
940 foreach ($idH as $k => $v) {
941 $a[$v['uid']] = $v['uid'];
942 if (is_array($v['subrow'])) {
943 $a = $this->flatInversePageTree($v['subrow'], $a);
951 * Returns TRUE if the input table name is to be regarded as a static relation (that is, not exported etc).
953 * @param string $table Table name
954 * @return bool TRUE, if table is marked static
956 public function isTableStatic($table)
958 if (is_array($GLOBALS['TCA'][$table])) {
959 return $GLOBALS['TCA'][$table]['ctrl']['is_static'] ||
in_array($table, $this->relStaticTables
) ||
in_array('_ALL', $this->relStaticTables
);
965 * Returns TRUE if the input table name is to be included as relation
967 * @param string $table Table name
968 * @return bool TRUE, if table is marked static
970 public function inclRelation($table)
972 return is_array($GLOBALS['TCA'][$table])
973 && (in_array($table, $this->relOnlyTables
) ||
in_array('_ALL', $this->relOnlyTables
))
974 && $this->getBackendUser()->check('tables_select', $table);
978 * Returns TRUE if the element should be excluded as static record.
980 * @param string $table Table name
981 * @param int $uid UID value
982 * @return bool TRUE, if table is marked static
984 public function isExcluded($table, $uid)
986 return (bool)$this->excludeMap
[$table . ':' . $uid];
990 * Returns TRUE if soft reference should be included in exported file.
992 * @param string $tokenID Token ID for soft reference
993 * @return bool TRUE if softreference media should be included
995 public function includeSoftref($tokenID)
997 $mode = $this->softrefCfg
[$tokenID]['mode'];
998 return $tokenID && $mode !== 'exclude' && $mode !== 'editable';
1002 * Checking if a PID is in the webmounts of the user
1004 * @param int $pid Page ID to check
1005 * @return bool TRUE if OK
1007 public function checkPID($pid)
1009 if (!isset($this->checkPID_cache
[$pid])) {
1010 $this->checkPID_cache
[$pid] = (bool)$this->getBackendUser()->isInWebMount($pid);
1012 return $this->checkPID_cache
[$pid];
1016 * Checks if the position of an updated record is configured to be corrected. This can be disabled globally and changed for elements individually.
1018 * @param string $table Table name
1019 * @param int $uid Uid or record
1020 * @return bool TRUE if the position of the record should be updated to match the one in the import structure
1022 public function dontIgnorePid($table, $uid)
1024 return $this->import_mode
[$table . ':' . $uid] !== 'ignore_pid' && (!$this->global_ignore_pid ||
$this->import_mode
[$table . ':' . $uid] === 'respect_pid');
1028 * Checks if the record exists
1030 * @param string $table Table name
1031 * @param int $uid UID of record
1032 * @param string $fields Field list to select. Default is "uid,pid
1033 * @return array Result of \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord() which means the record if found, otherwise FALSE
1035 public function doesRecordExist($table, $uid, $fields = '')
1037 return BackendUtility
::getRecord($table, $uid, $fields ?
$fields : 'uid,pid');
1041 * Returns the page title path of a PID value. Results are cached internally
1043 * @param int $pid Record PID to check
1044 * @return string The path for the input PID
1046 public function getRecordPath($pid)
1048 if (!isset($this->cache_getRecordPath
[$pid])) {
1049 $clause = $this->getBackendUser()->getPagePermsClause(1);
1050 $this->cache_getRecordPath
[$pid] = (string)BackendUtility
::getRecordPath($pid, $clause, 20);
1052 return $this->cache_getRecordPath
[$pid];
1056 * Makes a selector-box from optValues
1058 * @param string $prefix Form element name
1059 * @param string $value Current value
1060 * @param array $optValues Options to display (key/value pairs)
1061 * @return string HTML select element
1063 public function renderSelectBox($prefix, $value, $optValues)
1067 foreach ($optValues as $k => $v) {
1068 $sel = (string)$k === (string)$value ?
' selected="selected"' : '';
1072 $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>';
1074 if (!$isSelFlag && (string)$value !== '') {
1075 $opt[] = '<option value="' . htmlspecialchars($value) . '" selected="selected">' . htmlspecialchars(('[\'' . $value . '\']')) . '</option>';
1077 return '<select name="' . $prefix . '">' . implode('', $opt) . '</select>';
1081 * Compares two records, the current database record and the one from the import memory.
1082 * Will return HTML code to show any differences between them!
1084 * @param array $databaseRecord Database record, all fields (new values)
1085 * @param array $importRecord Import memorys record for the same table/uid, all fields (old values)
1086 * @param string $table The table name of the record
1087 * @param bool $inverseDiff Inverse the diff view (switch red/green, needed for pre-update difference view)
1088 * @return string HTML
1090 public function compareRecords($databaseRecord, $importRecord, $table, $inverseDiff = false)
1094 $diffUtility = GeneralUtility
::makeInstance(DiffUtility
::class);
1095 // Check if both inputs are records:
1096 if (is_array($databaseRecord) && is_array($importRecord)) {
1097 // Traverse based on database record
1098 foreach ($databaseRecord as $fN => $value) {
1099 if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] != 'passthrough') {
1100 if (isset($importRecord[$fN])) {
1101 if (trim($databaseRecord[$fN]) !== trim($importRecord[$fN])) {
1102 // Create diff-result:
1103 $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));
1105 unset($importRecord[$fN]);
1109 // Traverse remaining in import record:
1110 foreach ($importRecord as $fN => $value) {
1111 if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') {
1112 $output[$fN] = '<strong>Field missing</strong> in database';
1116 if (!empty($output)) {
1118 foreach ($output as $fN => $state) {
1121 <td class="bgColor5">' . $this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fN]['label'], true) . ' (' . htmlspecialchars($fN) . ')</td>
1122 <td class="bgColor4">' . $state . '</td>
1126 $output = '<table border="0" cellpadding="0" cellspacing="1">' . implode('', $tRows) . '</table>';
1130 return '<strong class="text-nowrap">[' . htmlspecialchars(($table . ':' . $importRecord['uid'] . ' => ' . $databaseRecord['uid'])) . ']:</strong> ' . $output;
1132 return 'ERROR: One of the inputs were not an array!';
1136 * Creates the original file name for a copy-RTE image (magic type)
1138 * @param string $string RTE copy filename, eg. "RTEmagicC_user_pm_icon_01.gif.gif
1139 * @return string|NULL RTE original filename, eg. "RTEmagicP_user_pm_icon_01.gif". If the input filename was NOT prefixed RTEmagicC_ as RTE images would be, NULL is returned!
1141 public function getRTEoriginalFilename($string)
1143 // If "magic image":
1144 if (GeneralUtility
::isFirstPartOfStr($string, 'RTEmagicC_')) {
1145 // Find original file:
1146 $pI = pathinfo(substr($string, strlen('RTEmagicC_')));
1147 $filename = substr($pI['basename'], 0, -strlen(('.' . $pI['extension'])));
1148 $origFilePath = 'RTEmagicP_' . $filename;
1149 return $origFilePath;
1155 * Returns file processing object, initialized only once.
1157 * @return ExtendedFileUtility File processor object
1159 public function getFileProcObj()
1161 if ($this->fileProcObj
=== null) {
1162 $this->fileProcObj
= GeneralUtility
::makeInstance(ExtendedFileUtility
::class);
1163 $this->fileProcObj
->init(array(), $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']);
1164 $this->fileProcObj
->setActionPermissions();
1166 return $this->fileProcObj
;
1172 * @param string $name Name of the hook
1173 * @param array $params Array with params
1176 public function callHook($name, $params)
1178 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/impexp/class.tx_impexp.php'][$name])) {
1179 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/impexp/class.tx_impexp.php'][$name] as $hook) {
1180 GeneralUtility
::callUserFunction($hook, $params, $this);
1185 /*****************************
1187 *****************************/
1190 * Sets error message in the internal error log
1192 * @param string $msg Error message
1195 public function error($msg)
1197 $this->errorLog
[] = $msg;
1201 * Returns a table with the error-messages.
1203 * @return string HTML print of error log
1205 public function printErrorLog()
1207 return !empty($this->errorLog
) ? DebugUtility
::viewArray($this->errorLog
) : '';
1211 * @return BackendUserAuthentication
1213 protected function getBackendUser()
1215 return $GLOBALS['BE_USER'];
1219 * @return DatabaseConnection
1221 protected function getDatabaseConnection()
1223 return $GLOBALS['TYPO3_DB'];
1227 * @return LanguageService
1229 protected function getLanguageService()
1231 return $GLOBALS['LANG'];