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