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