[FEATURE] Use dynamic path for typo3temp/var/
[Packages/TYPO3.CMS.git] / typo3 / sysext / impexp / Classes / ImportExport.php
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
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
19 use TYPO3\CMS\Core\Core\Environment;
20 use TYPO3\CMS\Core\Imaging\Icon;
21 use TYPO3\CMS\Core\Imaging\IconFactory;
22 use TYPO3\CMS\Core\Localization\LanguageService;
23 use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
24 use TYPO3\CMS\Core\Resource\ResourceFactory;
25 use TYPO3\CMS\Core\Type\Bitmask\Permission;
26 use TYPO3\CMS\Core\Utility\DebugUtility;
27 use TYPO3\CMS\Core\Utility\DiffUtility;
28 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
29 use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31 use TYPO3\CMS\Core\Utility\PathUtility;
32
33 /**
34 * EXAMPLE for using the impexp-class for exporting stuff:
35 *
36 * Create and initialize:
37 * $this->export = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Impexp\ImportExport::class);
38 * $this->export->init();
39 * Set which tables relations we will allow:
40 * $this->export->relOnlyTables[]="tt_news"; // exclusively includes. See comment in the class
41 *
42 * Adding records:
43 * $this->export->export_addRecord("pages", $this->pageinfo);
44 * $this->export->export_addRecord("pages", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("pages", 38));
45 * $this->export->export_addRecord("pages", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("pages", 39));
46 * $this->export->export_addRecord("tt_content", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("tt_content", 12));
47 * $this->export->export_addRecord("tt_content", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("tt_content", 74));
48 * $this->export->export_addRecord("sys_template", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("sys_template", 20));
49 *
50 * Adding all the relations (recursively in 5 levels so relations has THEIR relations registered as well)
51 * for($a=0;$a<5;$a++) {
52 * $addR = $this->export->export_addDBRelations($a);
53 * if (empty($addR)) break;
54 * }
55 *
56 * Finally load all the files.
57 * $this->export->export_addFilesFromRelations(); // MUST be after the DBrelations are set so that file from ALL added records are included!
58 *
59 * Write export
60 * $out = $this->export->compileMemoryToFileContent();
61 */
62
63 /**
64 * T3D file Import/Export library (TYPO3 Record Document)
65 */
66 abstract class ImportExport
67 {
68 /**
69 * If set, static relations (not exported) will be shown in overview as well
70 *
71 * @var bool
72 */
73 public $showStaticRelations = false;
74
75 /**
76 * Name of the "fileadmin" folder where files for export/import should be located
77 *
78 * @var string
79 */
80 public $fileadminFolderName = '';
81
82 /**
83 * Whether "import" or "export" mode of object. Set through init() function
84 *
85 * @var string
86 */
87 public $mode = '';
88
89 /**
90 * Updates all records that has same UID instead of creating new!
91 *
92 * @var bool
93 */
94 public $update = false;
95
96 /**
97 * Is set by importData() when an import has been done.
98 *
99 * @var bool
100 */
101 public $doesImport = false;
102
103 /**
104 * If set to a page-record, then the preview display of the content will expect this page-record to be the target
105 * for the import and accordingly display validation information. This triggers the visual view of the
106 * import/export memory to validate if import is possible
107 *
108 * @var array
109 */
110 public $display_import_pid_record = [];
111
112 /**
113 * Setting import modes during update state: as_new, exclude, force_uid
114 *
115 * @var array
116 */
117 public $import_mode = [];
118
119 /**
120 * If set, PID correct is ignored globally
121 *
122 * @var bool
123 */
124 public $global_ignore_pid = false;
125
126 /**
127 * If set, all UID values are forced! (update or import)
128 *
129 * @var bool
130 */
131 public $force_all_UIDS = false;
132
133 /**
134 * If set, a diff-view column is added to the overview.
135 *
136 * @var bool
137 */
138 public $showDiff = false;
139
140 /**
141 * If set, and if the user is admin, allow the writing of PHP scripts to fileadmin/ area.
142 *
143 * @var bool
144 */
145 public $allowPHPScripts = false;
146
147 /**
148 * Array of values to substitute in editable softreferences.
149 *
150 * @var array
151 */
152 public $softrefInputValues = [];
153
154 /**
155 * Mapping between the fileID from import memory and the final filenames they are written to.
156 *
157 * @var array
158 */
159 public $fileIDMap = [];
160
161 /**
162 * Add table names here which are THE ONLY ones which will be included
163 * into export if found as relations. '_ALL' will allow all tables.
164 *
165 * @var array
166 */
167 public $relOnlyTables = [];
168
169 /**
170 * Add tables names here which should not be exported with the file.
171 * (Where relations should be mapped to same UIDs in target system).
172 *
173 * @var array
174 */
175 public $relStaticTables = [];
176
177 /**
178 * Exclude map. Keys are table:uid pairs and if set, records are not added to the export.
179 *
180 * @var array
181 */
182 public $excludeMap = [];
183
184 /**
185 * Soft Reference Token ID modes.
186 *
187 * @var array
188 */
189 public $softrefCfg = [];
190
191 /**
192 * Listing extension dependencies.
193 *
194 * @var array
195 */
196 public $extensionDependencies = [];
197
198 /**
199 * After records are written this array is filled with [table][original_uid] = [new_uid]
200 *
201 * @var array
202 */
203 public $import_mapId = [];
204
205 /**
206 * Error log.
207 *
208 * @var array
209 */
210 public $errorLog = [];
211
212 /**
213 * Cache for record paths
214 *
215 * @var array
216 */
217 public $cache_getRecordPath = [];
218
219 /**
220 * Cache of checkPID values.
221 *
222 * @var array
223 */
224 public $checkPID_cache = [];
225
226 /**
227 * Set internally if the gzcompress function exists
228 * Used by ImportExportController
229 *
230 * @var bool
231 */
232 public $compress = false;
233
234 /**
235 * Internal import/export memory
236 *
237 * @var array
238 */
239 public $dat = [];
240
241 /**
242 * File processing object
243 *
244 * @var ExtendedFileUtility
245 */
246 protected $fileProcObj = null;
247
248 /**
249 * @var array
250 */
251 protected $remainHeader = [];
252
253 /**
254 * @var IconFactory
255 */
256 protected $iconFactory;
257
258 /**
259 * Flag to control whether all disabled records and their children are excluded (true) or included (false). Defaults
260 * to the old behaviour of including everything.
261 *
262 * @var bool
263 */
264 protected $excludeDisabledRecords = false;
265
266 /**
267 * The constructor
268 */
269 public function __construct()
270 {
271 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
272 }
273
274 /**************************
275 * Initialize
276 *************************/
277
278 /**
279 * Init the object, both import and export
280 */
281 public function init()
282 {
283 $this->compress = function_exists('gzcompress');
284 $this->fileadminFolderName = !empty($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir']) ? rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') : 'fileadmin';
285 }
286
287 /********************************************************
288 * Visual rendering of import/export memory, $this->dat
289 ********************************************************/
290
291 /**
292 * Displays an overview of the header-content.
293 *
294 * @return array The view data
295 */
296 public function displayContentOverview()
297 {
298 if (!isset($this->dat['header'])) {
299 return [];
300 }
301 // Check extension dependencies:
302 foreach ($this->dat['header']['extensionDependencies'] as $extKey) {
303 if (!empty($extKey) && !ExtensionManagementUtility::isLoaded($extKey)) {
304 $this->error('DEPENDENCY: The extension with key "' . $extKey . '" must be installed!');
305 }
306 }
307
308 // Probably this is done to save memory space?
309 unset($this->dat['files']);
310
311 $viewData = [];
312 // Traverse header:
313 $this->remainHeader = $this->dat['header'];
314 // If there is a page tree set, show that:
315 if (is_array($this->dat['header']['pagetree'])) {
316 reset($this->dat['header']['pagetree']);
317 $lines = [];
318 $this->traversePageTree($this->dat['header']['pagetree'], $lines);
319
320 $viewData['dat'] = $this->dat;
321 $viewData['update'] = $this->update;
322 $viewData['showDiff'] = $this->showDiff;
323 if (!empty($lines)) {
324 foreach ($lines as &$r) {
325 $r['controls'] = $this->renderControls($r);
326 $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
327 }
328 $viewData['pagetreeLines'] = $lines;
329 } else {
330 $viewData['pagetreeLines'] = [];
331 }
332 }
333 // Print remaining records that were not contained inside the page tree:
334 if (is_array($this->remainHeader['records'])) {
335 $lines = [];
336 if (is_array($this->remainHeader['records']['pages'])) {
337 $this->traversePageRecords($this->remainHeader['records']['pages'], $lines);
338 }
339 $this->traverseAllRecords($this->remainHeader['records'], $lines);
340 if (!empty($lines)) {
341 foreach ($lines as &$r) {
342 $r['controls'] = $this->renderControls($r);
343 $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
344 }
345 $viewData['remainingRecords'] = $lines;
346 }
347 }
348
349 return $viewData;
350 }
351
352 /**
353 * Go through page tree for display
354 *
355 * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
356 * @param array $lines Output lines array (is passed by reference and modified)
357 * @param string $preCode Pre-HTML code
358 */
359 public function traversePageTree($pT, &$lines, $preCode = '')
360 {
361 foreach ($pT as $k => $v) {
362 if ($this->excludeDisabledRecords === true && !$this->isActive('pages', $k)) {
363 $this->excludePageAndRecords($k, $v);
364 continue;
365 }
366
367 // Add this page:
368 $this->singleRecordLines('pages', $k, $lines, $preCode);
369 // Subrecords:
370 if (is_array($this->dat['header']['pid_lookup'][$k])) {
371 foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) {
372 if ($t !== 'pages') {
373 foreach ($recUidArr as $ruid => $value) {
374 $this->singleRecordLines($t, $ruid, $lines, $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;');
375 }
376 }
377 }
378 unset($this->remainHeader['pid_lookup'][$k]);
379 }
380 // Subpages, called recursively:
381 if (is_array($v['subrow'])) {
382 $this->traversePageTree($v['subrow'], $lines, $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;');
383 }
384 }
385 }
386
387 /**
388 * Test whether a record is active (i.e. not hidden)
389 *
390 * @param string $table Name of the records' database table
391 * @param int $uid Database uid of the record
392 * @return bool true if the record is active, false otherwise
393 */
394 protected function isActive($table, $uid)
395 {
396 return
397 !isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])
398 || !(bool)$this->dat['records'][$table . ':' . $uid]['data'][
399 $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']
400 ];
401 }
402
403 /**
404 * Exclude a page, its sub pages (recursively) and records placed in them from this import/export
405 *
406 * @param int $pageUid Uid of the page to exclude
407 * @param array $pageTree Page tree array with uid/subrow (from ->dat[header][pagetree]
408 */
409 protected function excludePageAndRecords($pageUid, $pageTree)
410 {
411 // Prevent having this page appear in "remaining records" table
412 unset($this->remainHeader['records']['pages'][$pageUid]);
413
414 // Subrecords
415 if (is_array($this->dat['header']['pid_lookup'][$pageUid])) {
416 foreach ($this->dat['header']['pid_lookup'][$pageUid] as $table => $recordData) {
417 if ($table !== 'pages') {
418 foreach (array_keys($recordData) as $uid) {
419 unset($this->remainHeader['records'][$table][$uid]);
420 }
421 }
422 }
423 unset($this->remainHeader['pid_lookup'][$pageUid]);
424 }
425 // Subpages excluded recursively
426 if (is_array($pageTree['subrow'])) {
427 foreach ($pageTree['subrow'] as $subPageUid => $subPageTree) {
428 $this->excludePageAndRecords($subPageUid, $subPageTree);
429 }
430 }
431 }
432
433 /**
434 * Go through remaining pages (not in tree)
435 *
436 * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
437 * @param array $lines Output lines array (is passed by reference and modified)
438 */
439 public function traversePageRecords($pT, &$lines)
440 {
441 foreach ($pT as $k => $rHeader) {
442 $this->singleRecordLines('pages', $k, $lines, '', 1);
443 // Subrecords:
444 if (is_array($this->dat['header']['pid_lookup'][$k])) {
445 foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) {
446 if ($t !== 'pages') {
447 foreach ($recUidArr as $ruid => $value) {
448 $this->singleRecordLines($t, $ruid, $lines, '&nbsp;&nbsp;&nbsp;&nbsp;');
449 }
450 }
451 }
452 unset($this->remainHeader['pid_lookup'][$k]);
453 }
454 }
455 }
456
457 /**
458 * Go through ALL records (if the pages are displayed first, those will not be among these!)
459 *
460 * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
461 * @param array $lines Output lines array (is passed by reference and modified)
462 */
463 public function traverseAllRecords($pT, &$lines)
464 {
465 foreach ($pT as $t => $recUidArr) {
466 $this->addGeneralErrorsByTable($t);
467 if ($t !== 'pages') {
468 $preCode = '';
469 foreach ($recUidArr as $ruid => $value) {
470 $this->singleRecordLines($t, $ruid, $lines, $preCode, 1);
471 }
472 }
473 }
474 }
475
476 /**
477 * Log general error message for a given table
478 *
479 * @param string $table database table name
480 */
481 protected function addGeneralErrorsByTable($table)
482 {
483 if ($this->update && $table === 'sys_file') {
484 $this->error('Updating sys_file records is not supported! They will be imported as new records!');
485 }
486 if ($this->force_all_UIDS && $table === 'sys_file') {
487 $this->error('Forcing uids of sys_file records is not supported! They will be imported as new records!');
488 }
489 }
490
491 /**
492 * Add entries for a single record
493 *
494 * @param string $table Table name
495 * @param int $uid Record uid
496 * @param array $lines Output lines array (is passed by reference and modified)
497 * @param string $preCode Pre-HTML code
498 * @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.
499 */
500 public function singleRecordLines($table, $uid, &$lines, $preCode, $checkImportInPidRecord = false)
501 {
502 // Get record:
503 $record = $this->dat['header']['records'][$table][$uid];
504 unset($this->remainHeader['records'][$table][$uid]);
505 if (!is_array($record) && !($table === 'pages' && !$uid)) {
506 $this->error('MISSING RECORD: ' . $table . ':' . $uid);
507 }
508 // Begin to create the line arrays information record, pInfo:
509 $pInfo = [];
510 $pInfo['ref'] = $table . ':' . $uid;
511 // Unknown table name:
512 $lang = $this->getLanguageService();
513 if ($table === '_SOFTREF_') {
514 $pInfo['preCode'] = $preCode;
515 $pInfo['title'] = '<em>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_softReferencesFiles')) . '</em>';
516 } elseif (!isset($GLOBALS['TCA'][$table])) {
517 // Unknown table name:
518 $pInfo['preCode'] = $preCode;
519 $pInfo['msg'] = 'UNKNOWN TABLE \'' . $pInfo['ref'] . '\'';
520 $pInfo['title'] = '<em>' . htmlspecialchars($record['title']) . '</em>';
521 } else {
522 // prepare data attribute telling whether the record is active or hidden, allowing frontend bulk selection
523 $pInfo['active'] = $this->isActive($table, $uid) ? 'active' : 'hidden';
524
525 // Otherwise, set table icon and title.
526 // Import Validation (triggered by $this->display_import_pid_record) will show messages if import is not possible of various items.
527 if (is_array($this->display_import_pid_record) && !empty($this->display_import_pid_record)) {
528 if ($checkImportInPidRecord) {
529 if (!$this->getBackendUser()->doesUserHaveAccess($this->display_import_pid_record, ($table === 'pages' ? 8 : 16))) {
530 $pInfo['msg'] .= '\'' . $pInfo['ref'] . '\' cannot be INSERTED on this page! ';
531 }
532 if (!$this->checkDokType($table, $this->display_import_pid_record['doktype']) && !$GLOBALS['TCA'][$table]['ctrl']['rootLevel']) {
533 $pInfo['msg'] .= '\'' . $table . '\' cannot be INSERTED on this page type (change page type to \'Folder\'.) ';
534 }
535 }
536 if (!$this->getBackendUser()->check('tables_modify', $table)) {
537 $pInfo['msg'] .= 'You are not allowed to CREATE \'' . $table . '\' tables! ';
538 }
539 if ($GLOBALS['TCA'][$table]['ctrl']['readOnly']) {
540 $pInfo['msg'] .= 'TABLE \'' . $table . '\' is READ ONLY! ';
541 }
542 if ($GLOBALS['TCA'][$table]['ctrl']['adminOnly'] && !$this->getBackendUser()->isAdmin()) {
543 $pInfo['msg'] .= 'TABLE \'' . $table . '\' is ADMIN ONLY! ';
544 }
545 if ($GLOBALS['TCA'][$table]['ctrl']['is_static']) {
546 $pInfo['msg'] .= 'TABLE \'' . $table . '\' is a STATIC TABLE! ';
547 }
548 if ((int)$GLOBALS['TCA'][$table]['ctrl']['rootLevel'] === 1) {
549 $pInfo['msg'] .= 'TABLE \'' . $table . '\' will be inserted on ROOT LEVEL! ';
550 }
551 $diffInverse = false;
552 $recInf = null;
553 if ($this->update) {
554 // In case of update-PREVIEW we swap the diff-sources.
555 $diffInverse = true;
556 $recInf = $this->doesRecordExist($table, $uid, $this->showDiff ? '*' : '');
557 $pInfo['updatePath'] = $recInf ? htmlspecialchars($this->getRecordPath($recInf['pid'])) : '<strong>NEW!</strong>';
558 // Mode selector:
559 $optValues = [];
560 $optValues[] = $recInf ? $lang->getLL('impexpcore_singlereco_update') : $lang->getLL('impexpcore_singlereco_insert');
561 if ($recInf) {
562 $optValues['as_new'] = $lang->getLL('impexpcore_singlereco_importAsNew');
563 }
564 if ($recInf) {
565 if (!$this->global_ignore_pid) {
566 $optValues['ignore_pid'] = $lang->getLL('impexpcore_singlereco_ignorePid');
567 } else {
568 $optValues['respect_pid'] = $lang->getLL('impexpcore_singlereco_respectPid');
569 }
570 }
571 if (!$recInf && $this->getBackendUser()->isAdmin()) {
572 $optValues['force_uid'] = sprintf($lang->getLL('impexpcore_singlereco_forceUidSAdmin'), $uid);
573 }
574 $optValues['exclude'] = $lang->getLL('impexpcore_singlereco_exclude');
575 if ($table === 'sys_file') {
576 $pInfo['updateMode'] = '';
577 } else {
578 $pInfo['updateMode'] = $this->renderSelectBox('tx_impexp[import_mode][' . $table . ':' . $uid . ']', $this->import_mode[$table . ':' . $uid], $optValues);
579 }
580 }
581 // Diff view:
582 if ($this->showDiff) {
583 // For IMPORTS, get new id:
584 if ($newUid = $this->import_mapId[$table][$uid]) {
585 $diffInverse = false;
586 $recInf = $this->doesRecordExist($table, $newUid, '*');
587 BackendUtility::workspaceOL($table, $recInf);
588 }
589 if (is_array($recInf)) {
590 $pInfo['showDiffContent'] = $this->compareRecords($recInf, $this->dat['records'][$table . ':' . $uid]['data'], $table, $diffInverse);
591 }
592 }
593 }
594 $pInfo['preCode'] = $preCode . '<span title="' . htmlspecialchars($table . ':' . $uid) . '">'
595 . $this->iconFactory->getIconForRecord($table, (array)$this->dat['records'][$table . ':' . $uid]['data'], Icon::SIZE_SMALL)->render()
596 . '</span>';
597 $pInfo['title'] = htmlspecialchars($record['title']);
598 // View page:
599 if ($table === 'pages') {
600 $viewID = $this->mode === 'export' ? $uid : ($this->doesImport ? $this->import_mapId['pages'][$uid] : 0);
601 if ($viewID) {
602 $pInfo['title'] = '<a href="#" onclick="' . htmlspecialchars(BackendUtility::viewOnClick($viewID)) . 'return false;">' . $pInfo['title'] . '</a>';
603 }
604 }
605 }
606 $pInfo['type'] = 'record';
607 $lines[] = $pInfo;
608 // File relations:
609 if (is_array($record['filerefs'])) {
610 $this->addFiles($record['filerefs'], $lines, $preCode);
611 }
612 // DB relations
613 if (is_array($record['rels'])) {
614 $this->addRelations($record['rels'], $lines, $preCode);
615 }
616 // Soft ref
617 if (!empty($record['softrefs'])) {
618 $preCode_A = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;';
619 $preCode_B = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
620 foreach ($record['softrefs'] as $info) {
621 $pInfo = [];
622 $pInfo['preCode'] = $preCode_A . $this->iconFactory->getIcon('status-reference-soft', Icon::SIZE_SMALL)->render();
623 $pInfo['title'] = '<em>' . $info['field'] . ', "' . $info['spKey'] . '" </em>: <span title="' . htmlspecialchars($info['matchString']) . '">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['matchString'], 60)) . '</span>';
624 if ($info['subst']['type']) {
625 if (strlen($info['subst']['title'])) {
626 $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_title')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['title'], 60));
627 }
628 if (strlen($info['subst']['description'])) {
629 $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_descr')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['description'], 60));
630 }
631 $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>' : '');
632 }
633 $pInfo['ref'] = 'SOFTREF';
634 $pInfo['type'] = 'softref';
635 $pInfo['_softRefInfo'] = $info;
636 $pInfo['type'] = 'softref';
637 $mode = $this->softrefCfg[$info['subst']['tokenID']]['mode'];
638 if ($info['error'] && $mode !== 'editable' && $mode !== 'exclude') {
639 $pInfo['msg'] .= $info['error'];
640 }
641 $lines[] = $pInfo;
642 // Add relations:
643 if ($info['subst']['type'] === 'db') {
644 list($tempTable, $tempUid) = explode(':', $info['subst']['recordRef']);
645 $this->addRelations([['table' => $tempTable, 'id' => $tempUid, 'tokenID' => $info['subst']['tokenID']]], $lines, $preCode_B, [], '');
646 }
647 // Add files:
648 if ($info['subst']['type'] === 'file') {
649 $this->addFiles([$info['file_ID']], $lines, $preCode_B, '', $info['subst']['tokenID']);
650 }
651 }
652 }
653 }
654
655 /**
656 * Add DB relations entries for a record's rels-array
657 *
658 * @param array $rels Array of relations
659 * @param array $lines Output lines array (is passed by reference and modified)
660 * @param string $preCode Pre-HTML code
661 * @param array $recurCheck Recursivity check stack
662 * @param string $htmlColorClass Alternative HTML color class to use.
663 * @access private
664 * @see singleRecordLines()
665 */
666 public function addRelations($rels, &$lines, $preCode, $recurCheck = [], $htmlColorClass = '')
667 {
668 foreach ($rels as $dat) {
669 $table = $dat['table'];
670 $uid = $dat['id'];
671 $pInfo = [];
672 $pInfo['ref'] = $table . ':' . $uid;
673 if (in_array($pInfo['ref'], $recurCheck)) {
674 continue;
675 }
676 $iconName = 'status-status-checked';
677 $iconClass = '';
678 $staticFixed = false;
679 $record = null;
680 if ($uid > 0) {
681 $record = $this->dat['header']['records'][$table][$uid];
682 if (!is_array($record)) {
683 if ($this->isTableStatic($table) || $this->isExcluded($table, $uid) || $dat['tokenID'] && !$this->includeSoftref($dat['tokenID'])) {
684 $pInfo['title'] = htmlspecialchars('STATIC: ' . $pInfo['ref']);
685 $iconClass = 'text-info';
686 $staticFixed = true;
687 } else {
688 $doesRE = $this->doesRecordExist($table, $uid);
689 $lostPath = $this->getRecordPath($table === 'pages' ? $doesRE['uid'] : $doesRE['pid']);
690 $pInfo['title'] = htmlspecialchars($pInfo['ref']);
691 $pInfo['title'] = '<span title="' . htmlspecialchars($lostPath) . '">' . $pInfo['title'] . '</span>';
692 $pInfo['msg'] = 'LOST RELATION' . (!$doesRE ? ' (Record not found!)' : ' (Path: ' . $lostPath . ')');
693 $iconClass = 'text-danger';
694 $iconName = 'status-dialog-warning';
695 }
696 } else {
697 $pInfo['title'] = htmlspecialchars($record['title']);
698 $pInfo['title'] = '<span title="' . htmlspecialchars($this->getRecordPath(($table === 'pages' ? $record['uid'] : $record['pid']))) . '">' . $pInfo['title'] . '</span>';
699 }
700 } else {
701 // 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
702 $pInfo['title'] = htmlspecialchars('FIXED: ' . $pInfo['ref']);
703 $staticFixed = true;
704 }
705
706 $icon = '<span class="' . $iconClass . '" title="' . htmlspecialchars($pInfo['ref']) . '">' . $this->iconFactory->getIcon($iconName, Icon::SIZE_SMALL)->render() . '</span>';
707
708 $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $icon;
709 $pInfo['type'] = 'rel';
710 if (!$staticFixed || $this->showStaticRelations) {
711 $lines[] = $pInfo;
712 if (is_array($record) && is_array($record['rels'])) {
713 $this->addRelations($record['rels'], $lines, $preCode . '&nbsp;&nbsp;', array_merge($recurCheck, [$pInfo['ref']]));
714 }
715 }
716 }
717 }
718
719 /**
720 * Add file relation entries for a record's rels-array
721 *
722 * @param array $rels Array of file IDs
723 * @param array $lines Output lines array (is passed by reference and modified)
724 * @param string $preCode Pre-HTML code
725 * @param string $htmlColorClass Alternative HTML color class to use.
726 * @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!)
727 * @access private
728 * @see singleRecordLines()
729 */
730 public function addFiles($rels, &$lines, $preCode, $htmlColorClass = '', $tokenID = '')
731 {
732 foreach ($rels as $ID) {
733 // Process file:
734 $pInfo = [];
735 $fI = $this->dat['header']['files'][$ID];
736 if (!is_array($fI)) {
737 if (!$tokenID || $this->includeSoftref($tokenID)) {
738 $pInfo['msg'] = 'MISSING FILE: ' . $ID;
739 $this->error('MISSING FILE: ' . $ID);
740 } else {
741 return;
742 }
743 }
744 $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('status-reference-hard', Icon::SIZE_SMALL)->render();
745 $pInfo['title'] = htmlspecialchars($fI['filename']);
746 $pInfo['ref'] = 'FILE';
747 $pInfo['type'] = 'file';
748 // If import mode and there is a non-RTE softreference, check the destination directory:
749 if ($this->mode === 'import' && $tokenID && !$fI['RTE_ORIG_ID']) {
750 if (isset($fI['parentRelFileName'])) {
751 $pInfo['msg'] = 'Seems like this file is already referenced from within an HTML/CSS file. That takes precedence. ';
752 } else {
753 $testDirPrefix = PathUtility::dirname($fI['relFileName']) . '/';
754 $testDirPrefix2 = $this->verifyFolderAccess($testDirPrefix);
755 if (!$testDirPrefix2) {
756 $pInfo['msg'] = 'ERROR: There are no available filemounts to write file in! ';
757 } elseif ($testDirPrefix !== $testDirPrefix2) {
758 $pInfo['msg'] = 'File will be attempted written to "' . $testDirPrefix2 . '". ';
759 }
760 }
761 // Check if file exists:
762 if (file_exists(PATH_site . $fI['relFileName'])) {
763 if ($this->update) {
764 $pInfo['updatePath'] .= 'File exists.';
765 } else {
766 $pInfo['msg'] .= 'File already exists! ';
767 }
768 }
769 // Check extension:
770 $fileProcObj = $this->getFileProcObj();
771 if ($fileProcObj->actionPerms['addFile']) {
772 $testFI = GeneralUtility::split_fileref(PATH_site . $fI['relFileName']);
773 if (!$this->allowPHPScripts && !$fileProcObj->checkIfAllowed($testFI['fileext'], $testFI['path'], $testFI['file'])) {
774 $pInfo['msg'] .= 'File extension was not allowed!';
775 }
776 } else {
777 $pInfo['msg'] = 'You user profile does not allow you to create files on the server!';
778 }
779 }
780 $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$ID]);
781 $lines[] = $pInfo;
782 unset($this->remainHeader['files'][$ID]);
783 // RTE originals:
784 if ($fI['RTE_ORIG_ID']) {
785 $ID = $fI['RTE_ORIG_ID'];
786 $pInfo = [];
787 $fI = $this->dat['header']['files'][$ID];
788 if (!is_array($fI)) {
789 $pInfo['msg'] = 'MISSING RTE original FILE: ' . $ID;
790 $this->error('MISSING RTE original FILE: ' . $ID);
791 }
792 $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$ID]);
793 $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('status-reference-hard', Icon::SIZE_SMALL)->render();
794 $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Original)</em>';
795 $pInfo['ref'] = 'FILE';
796 $pInfo['type'] = 'file';
797 $lines[] = $pInfo;
798 unset($this->remainHeader['files'][$ID]);
799 }
800 // External resources:
801 if (is_array($fI['EXT_RES_ID'])) {
802 foreach ($fI['EXT_RES_ID'] as $extID) {
803 $pInfo = [];
804 $fI = $this->dat['header']['files'][$extID];
805 if (!is_array($fI)) {
806 $pInfo['msg'] = 'MISSING External Resource FILE: ' . $extID;
807 $this->error('MISSING External Resource FILE: ' . $extID);
808 } else {
809 $pInfo['updatePath'] = $fI['parentRelFileName'];
810 }
811 $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$extID]);
812 $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('actions-insert-reference', Icon::SIZE_SMALL)->render();
813 $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Resource)</em>';
814 $pInfo['ref'] = 'FILE';
815 $pInfo['type'] = 'file';
816 $lines[] = $pInfo;
817 unset($this->remainHeader['files'][$extID]);
818 }
819 }
820 }
821 }
822
823 /**
824 * Verifies that a table is allowed on a certain doktype of a page
825 *
826 * @param string $checkTable Table name to check
827 * @param int $doktype doktype value.
828 * @return bool TRUE if OK
829 */
830 public function checkDokType($checkTable, $doktype)
831 {
832 $allowedTableList = $GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'] ?? $GLOBALS['PAGES_TYPES']['default']['allowedTables'];
833 $allowedArray = GeneralUtility::trimExplode(',', $allowedTableList, true);
834 // If all tables or the table is listed as an allowed type, return TRUE
835 if (strstr($allowedTableList, '*') || in_array($checkTable, $allowedArray)) {
836 return true;
837 }
838 return false;
839 }
840
841 /**
842 * Render input controls for import or export
843 *
844 * @param array $r Configuration for element
845 * @return string HTML
846 */
847 public function renderControls($r)
848 {
849 if ($this->mode === 'export') {
850 if ($r['type'] === 'record') {
851 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>';
852 }
853 return $r['type'] === 'softref' ? $this->softrefSelector($r['_softRefInfo']) : '';
854 }
855 // During import
856 // For softreferences with editable fields:
857 if ($r['type'] === 'softref' && is_array($r['_softRefInfo']['subst']) && $r['_softRefInfo']['subst']['tokenID']) {
858 $tokenID = $r['_softRefInfo']['subst']['tokenID'];
859 $cfg = $this->softrefCfg[$tokenID];
860 if ($cfg['mode'] === 'editable') {
861 return (strlen($cfg['title']) ? '<strong>' . htmlspecialchars($cfg['title']) . '</strong><br/>' : '') . htmlspecialchars($cfg['description']) . '<br/>
862 <input type="text" name="tx_impexp[softrefInputValues][' . $tokenID . ']" value="' . htmlspecialchars($this->softrefInputValues[$tokenID] ?? $cfg['defValue']) . '" />';
863 }
864 }
865
866 return '';
867 }
868
869 /**
870 * Selectorbox with export options for soft references
871 *
872 * @param array $cfg Softref configuration array. An export box is shown only if a substitution scheme is found for the soft reference.
873 * @return string Selector box HTML
874 */
875 public function softrefSelector($cfg)
876 {
877 // Looking for file ID if any:
878 $fI = $cfg['file_ID'] ? $this->dat['header']['files'][$cfg['file_ID']] : [];
879 // Substitution scheme has to be around and RTE images MUST be exported.
880 if (is_array($cfg['subst']) && $cfg['subst']['tokenID'] && !$fI['RTE_ORIG_ID']) {
881 // Create options:
882 $optValues = [];
883 $optValues[''] = '';
884 $optValues['editable'] = $this->getLanguageService()->getLL('impexpcore_softrefsel_editable');
885 $optValues['exclude'] = $this->getLanguageService()->getLL('impexpcore_softrefsel_exclude');
886 // Get current value:
887 $value = $this->softrefCfg[$cfg['subst']['tokenID']]['mode'];
888 // Render options selector:
889 $selectorbox = $this->renderSelectBox('tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][mode]', $value, $optValues) . '<br/>';
890 if ($value === 'editable') {
891 $descriptionField = '';
892 // Title:
893 if (strlen($cfg['subst']['title'])) {
894 $descriptionField .= '
895 <input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][title]" value="' . htmlspecialchars($cfg['subst']['title']) . '" />
896 <strong>' . htmlspecialchars($cfg['subst']['title']) . '</strong><br/>';
897 }
898 // Description:
899 if (!strlen($cfg['subst']['description'])) {
900 $descriptionField .= '
901 ' . htmlspecialchars($this->getLanguageService()->getLL('impexpcore_printerror_description')) . '<br/>
902 <input type="text" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($this->softrefCfg[$cfg['subst']['tokenID']]['description']) . '" />';
903 } else {
904 $descriptionField .= '
905
906 <input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($cfg['subst']['description']) . '" />' . htmlspecialchars($cfg['subst']['description']);
907 }
908 // Default Value:
909 $descriptionField .= '<input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][defValue]" value="' . htmlspecialchars($cfg['subst']['tokenValue']) . '" />';
910 } else {
911 $descriptionField = '';
912 }
913 return $selectorbox . $descriptionField;
914 }
915 return '';
916 }
917
918 /**
919 * Verifies that the input path (relative to PATH_site) is found in the backend users filemounts.
920 * If it doesn't it will try to find another relative filemount for the user and return an alternative path prefix for the file.
921 *
922 * @param string $dirPrefix Path relative to PATH_site
923 * @param bool $noAlternative If set, Do not look for alternative path! Just return FALSE
924 * @return string|bool If a path is available that will be returned, otherwise FALSE.
925 */
926 public function verifyFolderAccess($dirPrefix, $noAlternative = false)
927 {
928 // Check the absolute path for PATH_site, if the user has access - no problem
929 try {
930 ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($dirPrefix);
931 return $dirPrefix;
932 } catch (InsufficientFolderAccessPermissionsException $e) {
933 // Check all storages available for the user as alternative
934 if (!$noAlternative) {
935 $fileStorages = $this->getBackendUser()->getFileStorages();
936 foreach ($fileStorages as $fileStorage) {
937 try {
938 $folder = $fileStorage->getFolder(rtrim($dirPrefix, '/'));
939 return $folder->getPublicUrl();
940 } catch (InsufficientFolderAccessPermissionsException $e) {
941 }
942 }
943 }
944 }
945 return false;
946 }
947
948 /*****************************
949 * Helper functions of kinds
950 *****************************/
951
952 /**
953 * @return string
954 */
955 protected function getTemporaryFolderName()
956 {
957 $temporaryPath = Environment::getVarPath() . '/transient/';
958 do {
959 $temporaryFolderName = $temporaryPath . 'export_temp_files_' . mt_rand(1, PHP_INT_MAX);
960 } while (is_dir($temporaryFolderName));
961 GeneralUtility::mkdir($temporaryFolderName);
962 return $temporaryFolderName;
963 }
964
965 /**
966 * Recursively flattening the idH array
967 *
968 * @param array $idH Page uid hierarchy
969 * @param array $a Accumulation array of pages (internal, don't set from outside)
970 * @return array Array with uid-uid pairs for all pages in the page tree.
971 * @see Import::flatInversePageTree_pid()
972 */
973 public function flatInversePageTree($idH, $a = [])
974 {
975 if (is_array($idH)) {
976 $idH = array_reverse($idH);
977 foreach ($idH as $k => $v) {
978 $a[$v['uid']] = $v['uid'];
979 if (is_array($v['subrow'])) {
980 $a = $this->flatInversePageTree($v['subrow'], $a);
981 }
982 }
983 }
984 return $a;
985 }
986
987 /**
988 * Returns TRUE if the input table name is to be regarded as a static relation (that is, not exported etc).
989 *
990 * @param string $table Table name
991 * @return bool TRUE, if table is marked static
992 */
993 public function isTableStatic($table)
994 {
995 if (is_array($GLOBALS['TCA'][$table])) {
996 return $GLOBALS['TCA'][$table]['ctrl']['is_static'] || in_array($table, $this->relStaticTables) || in_array('_ALL', $this->relStaticTables);
997 }
998 return false;
999 }
1000
1001 /**
1002 * Returns TRUE if the input table name is to be included as relation
1003 *
1004 * @param string $table Table name
1005 * @return bool TRUE, if table is marked static
1006 */
1007 public function inclRelation($table)
1008 {
1009 return is_array($GLOBALS['TCA'][$table])
1010 && (in_array($table, $this->relOnlyTables) || in_array('_ALL', $this->relOnlyTables))
1011 && $this->getBackendUser()->check('tables_select', $table);
1012 }
1013
1014 /**
1015 * Returns TRUE if the element should be excluded as static record.
1016 *
1017 * @param string $table Table name
1018 * @param int $uid UID value
1019 * @return bool TRUE, if table is marked static
1020 */
1021 public function isExcluded($table, $uid)
1022 {
1023 return (bool)$this->excludeMap[$table . ':' . $uid];
1024 }
1025
1026 /**
1027 * Returns TRUE if soft reference should be included in exported file.
1028 *
1029 * @param string $tokenID Token ID for soft reference
1030 * @return bool TRUE if softreference media should be included
1031 */
1032 public function includeSoftref($tokenID)
1033 {
1034 $mode = $this->softrefCfg[$tokenID]['mode'];
1035 return $tokenID && $mode !== 'exclude' && $mode !== 'editable';
1036 }
1037
1038 /**
1039 * Checking if a PID is in the webmounts of the user
1040 *
1041 * @param int $pid Page ID to check
1042 * @return bool TRUE if OK
1043 */
1044 public function checkPID($pid)
1045 {
1046 if (!isset($this->checkPID_cache[$pid])) {
1047 $this->checkPID_cache[$pid] = (bool)$this->getBackendUser()->isInWebMount($pid);
1048 }
1049 return $this->checkPID_cache[$pid];
1050 }
1051
1052 /**
1053 * Checks if the position of an updated record is configured to be corrected. This can be disabled globally and changed for elements individually.
1054 *
1055 * @param string $table Table name
1056 * @param int $uid Uid or record
1057 * @return bool TRUE if the position of the record should be updated to match the one in the import structure
1058 */
1059 public function dontIgnorePid($table, $uid)
1060 {
1061 return $this->import_mode[$table . ':' . $uid] !== 'ignore_pid' && (!$this->global_ignore_pid || $this->import_mode[$table . ':' . $uid] === 'respect_pid');
1062 }
1063
1064 /**
1065 * Checks if the record exists
1066 *
1067 * @param string $table Table name
1068 * @param int $uid UID of record
1069 * @param string $fields Field list to select. Default is "uid,pid
1070 * @return array Result of \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord() which means the record if found, otherwise FALSE
1071 */
1072 public function doesRecordExist($table, $uid, $fields = '')
1073 {
1074 return BackendUtility::getRecord($table, $uid, $fields ? $fields : 'uid,pid');
1075 }
1076
1077 /**
1078 * Returns the page title path of a PID value. Results are cached internally
1079 *
1080 * @param int $pid Record PID to check
1081 * @return string The path for the input PID
1082 */
1083 public function getRecordPath($pid)
1084 {
1085 if (!isset($this->cache_getRecordPath[$pid])) {
1086 $clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
1087 $this->cache_getRecordPath[$pid] = (string)BackendUtility::getRecordPath($pid, $clause, 20);
1088 }
1089 return $this->cache_getRecordPath[$pid];
1090 }
1091
1092 /**
1093 * Makes a selector-box from optValues
1094 *
1095 * @param string $prefix Form element name
1096 * @param string $value Current value
1097 * @param array $optValues Options to display (key/value pairs)
1098 * @return string HTML select element
1099 */
1100 public function renderSelectBox($prefix, $value, $optValues)
1101 {
1102 $opt = [];
1103 $isSelFlag = 0;
1104 foreach ($optValues as $k => $v) {
1105 $sel = (string)$k === (string)$value ? ' selected="selected"' : '';
1106 if ($sel) {
1107 $isSelFlag++;
1108 }
1109 $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>';
1110 }
1111 if (!$isSelFlag && (string)$value !== '') {
1112 $opt[] = '<option value="' . htmlspecialchars($value) . '" selected="selected">' . htmlspecialchars('[\'' . $value . '\']') . '</option>';
1113 }
1114 return '<select name="' . $prefix . '">' . implode('', $opt) . '</select>';
1115 }
1116
1117 /**
1118 * Compares two records, the current database record and the one from the import memory.
1119 * Will return HTML code to show any differences between them!
1120 *
1121 * @param array $databaseRecord Database record, all fields (new values)
1122 * @param array $importRecord Import memorys record for the same table/uid, all fields (old values)
1123 * @param string $table The table name of the record
1124 * @param bool $inverseDiff Inverse the diff view (switch red/green, needed for pre-update difference view)
1125 * @return string HTML
1126 */
1127 public function compareRecords($databaseRecord, $importRecord, $table, $inverseDiff = false)
1128 {
1129 // Initialize:
1130 $output = [];
1131 $diffUtility = GeneralUtility::makeInstance(DiffUtility::class);
1132 // Check if both inputs are records:
1133 if (is_array($databaseRecord) && is_array($importRecord)) {
1134 // Traverse based on database record
1135 foreach ($databaseRecord as $fN => $value) {
1136 if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') {
1137 if (isset($importRecord[$fN])) {
1138 if (trim($databaseRecord[$fN]) !== trim($importRecord[$fN])) {
1139 // Create diff-result:
1140 $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));
1141 }
1142 unset($importRecord[$fN]);
1143 }
1144 }
1145 }
1146 // Traverse remaining in import record:
1147 foreach ($importRecord as $fN => $value) {
1148 if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') {
1149 $output[$fN] = '<strong>Field missing</strong> in database';
1150 }
1151 }
1152 // Create output:
1153 if (!empty($output)) {
1154 $tRows = [];
1155 foreach ($output as $fN => $state) {
1156 $tRows[] = '
1157 <tr>
1158 <td>' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fN]['label'])) . ' (' . htmlspecialchars($fN) . ')</td>
1159 <td>' . $state . '</td>
1160 </tr>
1161 ';
1162 }
1163 $output = '<table class="table table-striped table-hover">' . implode('', $tRows) . '</table>';
1164 } else {
1165 $output = 'Match';
1166 }
1167 return '<strong class="text-nowrap">[' . htmlspecialchars($table . ':' . $importRecord['uid'] . ' => ' . $databaseRecord['uid']) . ']:</strong> ' . $output;
1168 }
1169 return 'ERROR: One of the inputs were not an array!';
1170 }
1171
1172 /**
1173 * Creates the original file name for a copy-RTE image (magic type)
1174 *
1175 * @param string $string RTE copy filename, eg. "RTEmagicC_user_pm_icon_01.gif.gif
1176 * @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!
1177 */
1178 public function getRTEoriginalFilename($string)
1179 {
1180 // If "magic image":
1181 if (GeneralUtility::isFirstPartOfStr($string, 'RTEmagicC_')) {
1182 // Find original file:
1183 $pI = pathinfo(substr($string, strlen('RTEmagicC_')));
1184 $filename = substr($pI['basename'], 0, -strlen('.' . $pI['extension']));
1185 $origFilePath = 'RTEmagicP_' . $filename;
1186 return $origFilePath;
1187 }
1188 return null;
1189 }
1190
1191 /**
1192 * Returns file processing object, initialized only once.
1193 *
1194 * @return ExtendedFileUtility File processor object
1195 */
1196 public function getFileProcObj()
1197 {
1198 if ($this->fileProcObj === null) {
1199 $this->fileProcObj = GeneralUtility::makeInstance(ExtendedFileUtility::class);
1200 $this->fileProcObj->setActionPermissions();
1201 }
1202 return $this->fileProcObj;
1203 }
1204
1205 /**
1206 * Call Hook
1207 *
1208 * @param string $name Name of the hook
1209 * @param array $params Array with params
1210 */
1211 public function callHook($name, $params)
1212 {
1213 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/impexp/class.tx_impexp.php'][$name] ?? [] as $hook) {
1214 GeneralUtility::callUserFunction($hook, $params, $this);
1215 }
1216 }
1217
1218 /**
1219 * Set flag to control whether disabled records and their children are excluded (true) or included (false). Defaults
1220 * to the old behaviour of including everything.
1221 *
1222 * @param bool $excludeDisabledRecords Set to true if if all disabled records should be excluded, false otherwise
1223 * @return \TYPO3\CMS\Impexp\ImportExport $this for fluent calls
1224 */
1225 public function setExcludeDisabledRecords($excludeDisabledRecords = false)
1226 {
1227 $this->excludeDisabledRecords = $excludeDisabledRecords;
1228 return $this;
1229 }
1230
1231 /*****************************
1232 * Error handling
1233 *****************************/
1234
1235 /**
1236 * Sets error message in the internal error log
1237 *
1238 * @param string $msg Error message
1239 */
1240 public function error($msg)
1241 {
1242 $this->errorLog[] = $msg;
1243 }
1244
1245 /**
1246 * Returns a table with the error-messages.
1247 *
1248 * @return string HTML print of error log
1249 */
1250 public function printErrorLog()
1251 {
1252 return !empty($this->errorLog) ? DebugUtility::viewArray($this->errorLog) : '';
1253 }
1254
1255 /**
1256 * @return BackendUserAuthentication
1257 */
1258 protected function getBackendUser()
1259 {
1260 return $GLOBALS['BE_USER'];
1261 }
1262
1263 /**
1264 * @return LanguageService
1265 */
1266 protected function getLanguageService()
1267 {
1268 return $GLOBALS['LANG'];
1269 }
1270 }