f0c1e2b20a157ad9aeca25ea708b71842c7f6471
[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['fileSize'] = GeneralUtility::formatSize($r['size']);
326 $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
327 }
328 $viewData['pagetreeLines'] = $lines;
329 } else {
330 $viewData['pagetreeLines'] = [];
331 }
332 }
333 // Print remaining records that were not contained inside the page tree:
334 if (is_array($this->remainHeader['records'])) {
335 $lines = [];
336 if (is_array($this->remainHeader['records']['pages'])) {
337 $this->traversePageRecords($this->remainHeader['records']['pages'], $lines);
338 }
339 $this->traverseAllRecords($this->remainHeader['records'], $lines);
340 if (!empty($lines)) {
341 foreach ($lines as &$r) {
342 $r['controls'] = $this->renderControls($r);
343 $r['fileSize'] = GeneralUtility::formatSize($r['size']);
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 $pInfo['size'] = (int)$record['size'];
609 $lines[] = $pInfo;
610 // File relations:
611 if (is_array($record['filerefs'])) {
612 $this->addFiles($record['filerefs'], $lines, $preCode);
613 }
614 // DB relations
615 if (is_array($record['rels'])) {
616 $this->addRelations($record['rels'], $lines, $preCode);
617 }
618 // Soft ref
619 if (!empty($record['softrefs'])) {
620 $preCode_A = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;';
621 $preCode_B = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
622 foreach ($record['softrefs'] as $info) {
623 $pInfo = [];
624 $pInfo['preCode'] = $preCode_A . $this->iconFactory->getIcon('status-reference-soft', Icon::SIZE_SMALL)->render();
625 $pInfo['title'] = '<em>' . $info['field'] . ', "' . $info['spKey'] . '" </em>: <span title="' . htmlspecialchars($info['matchString']) . '">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['matchString'], 60)) . '</span>';
626 if ($info['subst']['type']) {
627 if (strlen($info['subst']['title'])) {
628 $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_title')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['title'], 60));
629 }
630 if (strlen($info['subst']['description'])) {
631 $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_descr')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['description'], 60));
632 }
633 $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>' : '');
634 }
635 $pInfo['ref'] = 'SOFTREF';
636 $pInfo['size'] = 0;
637 $pInfo['type'] = 'softref';
638 $pInfo['_softRefInfo'] = $info;
639 $pInfo['type'] = 'softref';
640 $mode = $this->softrefCfg[$info['subst']['tokenID']]['mode'];
641 if ($info['error'] && $mode !== 'editable' && $mode !== 'exclude') {
642 $pInfo['msg'] .= $info['error'];
643 }
644 $lines[] = $pInfo;
645 // Add relations:
646 if ($info['subst']['type'] === 'db') {
647 list($tempTable, $tempUid) = explode(':', $info['subst']['recordRef']);
648 $this->addRelations([['table' => $tempTable, 'id' => $tempUid, 'tokenID' => $info['subst']['tokenID']]], $lines, $preCode_B, [], '');
649 }
650 // Add files:
651 if ($info['subst']['type'] === 'file') {
652 $this->addFiles([$info['file_ID']], $lines, $preCode_B, '', $info['subst']['tokenID']);
653 }
654 }
655 }
656 }
657
658 /**
659 * Add DB relations entries for a record's rels-array
660 *
661 * @param array $rels Array of relations
662 * @param array $lines Output lines array (is passed by reference and modified)
663 * @param string $preCode Pre-HTML code
664 * @param array $recurCheck Recursivity check stack
665 * @param string $htmlColorClass Alternative HTML color class to use.
666 * @access private
667 * @see singleRecordLines()
668 */
669 public function addRelations($rels, &$lines, $preCode, $recurCheck = [], $htmlColorClass = '')
670 {
671 foreach ($rels as $dat) {
672 $table = $dat['table'];
673 $uid = $dat['id'];
674 $pInfo = [];
675 $pInfo['ref'] = $table . ':' . $uid;
676 if (in_array($pInfo['ref'], $recurCheck)) {
677 continue;
678 }
679 $iconName = 'status-status-checked';
680 $iconClass = '';
681 $staticFixed = false;
682 $record = null;
683 if ($uid > 0) {
684 $record = $this->dat['header']['records'][$table][$uid];
685 if (!is_array($record)) {
686 if ($this->isTableStatic($table) || $this->isExcluded($table, $uid) || $dat['tokenID'] && !$this->includeSoftref($dat['tokenID'])) {
687 $pInfo['title'] = htmlspecialchars('STATIC: ' . $pInfo['ref']);
688 $iconClass = 'text-info';
689 $staticFixed = true;
690 } else {
691 $doesRE = $this->doesRecordExist($table, $uid);
692 $lostPath = $this->getRecordPath($table === 'pages' ? $doesRE['uid'] : $doesRE['pid']);
693 $pInfo['title'] = htmlspecialchars($pInfo['ref']);
694 $pInfo['title'] = '<span title="' . htmlspecialchars($lostPath) . '">' . $pInfo['title'] . '</span>';
695 $pInfo['msg'] = 'LOST RELATION' . (!$doesRE ? ' (Record not found!)' : ' (Path: ' . $lostPath . ')');
696 $iconClass = 'text-danger';
697 $iconName = 'status-dialog-warning';
698 }
699 } else {
700 $pInfo['title'] = htmlspecialchars($record['title']);
701 $pInfo['title'] = '<span title="' . htmlspecialchars($this->getRecordPath(($table === 'pages' ? $record['uid'] : $record['pid']))) . '">' . $pInfo['title'] . '</span>';
702 }
703 } else {
704 // 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
705 $pInfo['title'] = htmlspecialchars('FIXED: ' . $pInfo['ref']);
706 $staticFixed = true;
707 }
708
709 $icon = '<span class="' . $iconClass . '" title="' . htmlspecialchars($pInfo['ref']) . '">' . $this->iconFactory->getIcon($iconName, Icon::SIZE_SMALL)->render() . '</span>';
710
711 $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $icon;
712 $pInfo['type'] = 'rel';
713 if (!$staticFixed || $this->showStaticRelations) {
714 $lines[] = $pInfo;
715 if (is_array($record) && is_array($record['rels'])) {
716 $this->addRelations($record['rels'], $lines, $preCode . '&nbsp;&nbsp;', array_merge($recurCheck, [$pInfo['ref']]));
717 }
718 }
719 }
720 }
721
722 /**
723 * Add file relation entries for a record's rels-array
724 *
725 * @param array $rels Array of file IDs
726 * @param array $lines Output lines array (is passed by reference and modified)
727 * @param string $preCode Pre-HTML code
728 * @param string $htmlColorClass Alternative HTML color class to use.
729 * @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!)
730 * @access private
731 * @see singleRecordLines()
732 */
733 public function addFiles($rels, &$lines, $preCode, $htmlColorClass = '', $tokenID = '')
734 {
735 foreach ($rels as $ID) {
736 // Process file:
737 $pInfo = [];
738 $fI = $this->dat['header']['files'][$ID];
739 if (!is_array($fI)) {
740 if (!$tokenID || $this->includeSoftref($tokenID)) {
741 $pInfo['msg'] = 'MISSING FILE: ' . $ID;
742 $this->error('MISSING FILE: ' . $ID);
743 } else {
744 return;
745 }
746 }
747 $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('status-reference-hard', Icon::SIZE_SMALL)->render();
748 $pInfo['title'] = htmlspecialchars($fI['filename']);
749 $pInfo['ref'] = 'FILE';
750 $pInfo['size'] = $fI['filesize'];
751 $pInfo['type'] = 'file';
752 // If import mode and there is a non-RTE softreference, check the destination directory:
753 if ($this->mode === 'import' && $tokenID && !$fI['RTE_ORIG_ID']) {
754 if (isset($fI['parentRelFileName'])) {
755 $pInfo['msg'] = 'Seems like this file is already referenced from within an HTML/CSS file. That takes precedence. ';
756 } else {
757 $testDirPrefix = PathUtility::dirname($fI['relFileName']) . '/';
758 $testDirPrefix2 = $this->verifyFolderAccess($testDirPrefix);
759 if (!$testDirPrefix2) {
760 $pInfo['msg'] = 'ERROR: There are no available filemounts to write file in! ';
761 } elseif ($testDirPrefix !== $testDirPrefix2) {
762 $pInfo['msg'] = 'File will be attempted written to "' . $testDirPrefix2 . '". ';
763 }
764 }
765 // Check if file exists:
766 if (file_exists(PATH_site . $fI['relFileName'])) {
767 if ($this->update) {
768 $pInfo['updatePath'] .= 'File exists.';
769 } else {
770 $pInfo['msg'] .= 'File already exists! ';
771 }
772 }
773 // Check extension:
774 $fileProcObj = $this->getFileProcObj();
775 if ($fileProcObj->actionPerms['addFile']) {
776 $testFI = GeneralUtility::split_fileref(PATH_site . $fI['relFileName']);
777 if (!$this->allowPHPScripts && !$fileProcObj->checkIfAllowed($testFI['fileext'], $testFI['path'], $testFI['file'])) {
778 $pInfo['msg'] .= 'File extension was not allowed!';
779 }
780 } else {
781 $pInfo['msg'] = 'You user profile does not allow you to create files on the server!';
782 }
783 }
784 $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$ID]);
785 $lines[] = $pInfo;
786 unset($this->remainHeader['files'][$ID]);
787 // RTE originals:
788 if ($fI['RTE_ORIG_ID']) {
789 $ID = $fI['RTE_ORIG_ID'];
790 $pInfo = [];
791 $fI = $this->dat['header']['files'][$ID];
792 if (!is_array($fI)) {
793 $pInfo['msg'] = 'MISSING RTE original FILE: ' . $ID;
794 $this->error('MISSING RTE original FILE: ' . $ID);
795 }
796 $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$ID]);
797 $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('status-reference-hard', Icon::SIZE_SMALL)->render();
798 $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Original)</em>';
799 $pInfo['ref'] = 'FILE';
800 $pInfo['size'] = $fI['filesize'];
801 $pInfo['type'] = 'file';
802 $lines[] = $pInfo;
803 unset($this->remainHeader['files'][$ID]);
804 }
805 // External resources:
806 if (is_array($fI['EXT_RES_ID'])) {
807 foreach ($fI['EXT_RES_ID'] as $extID) {
808 $pInfo = [];
809 $fI = $this->dat['header']['files'][$extID];
810 if (!is_array($fI)) {
811 $pInfo['msg'] = 'MISSING External Resource FILE: ' . $extID;
812 $this->error('MISSING External Resource FILE: ' . $extID);
813 } else {
814 $pInfo['updatePath'] = $fI['parentRelFileName'];
815 }
816 $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$extID]);
817 $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('actions-insert-reference', Icon::SIZE_SMALL)->render();
818 $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Resource)</em>';
819 $pInfo['ref'] = 'FILE';
820 $pInfo['size'] = $fI['filesize'];
821 $pInfo['type'] = 'file';
822 $lines[] = $pInfo;
823 unset($this->remainHeader['files'][$extID]);
824 }
825 }
826 }
827 }
828
829 /**
830 * Verifies that a table is allowed on a certain doktype of a page
831 *
832 * @param string $checkTable Table name to check
833 * @param int $doktype doktype value.
834 * @return bool TRUE if OK
835 */
836 public function checkDokType($checkTable, $doktype)
837 {
838 $allowedTableList = isset($GLOBALS['PAGES_TYPES'][$doktype]['allowedTables']) ? $GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'] : $GLOBALS['PAGES_TYPES']['default']['allowedTables'];
839 $allowedArray = GeneralUtility::trimExplode(',', $allowedTableList, true);
840 // If all tables or the table is listed as an allowed type, return TRUE
841 if (strstr($allowedTableList, '*') || in_array($checkTable, $allowedArray)) {
842 return true;
843 }
844 return false;
845 }
846
847 /**
848 * Render input controls for import or export
849 *
850 * @param array $r Configuration for element
851 * @return string HTML
852 */
853 public function renderControls($r)
854 {
855 if ($this->mode === 'export') {
856 if ($r['type'] === 'record') {
857 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>';
858 }
859 return $r['type'] === 'softref' ? $this->softrefSelector($r['_softRefInfo']) : '';
860 }
861 // During import
862 // For softreferences with editable fields:
863 if ($r['type'] === 'softref' && is_array($r['_softRefInfo']['subst']) && $r['_softRefInfo']['subst']['tokenID']) {
864 $tokenID = $r['_softRefInfo']['subst']['tokenID'];
865 $cfg = $this->softrefCfg[$tokenID];
866 if ($cfg['mode'] === 'editable') {
867 return (strlen($cfg['title']) ? '<strong>' . htmlspecialchars($cfg['title']) . '</strong><br/>' : '') . htmlspecialchars($cfg['description']) . '<br/>
868 <input type="text" name="tx_impexp[softrefInputValues][' . $tokenID . ']" value="' . htmlspecialchars((isset($this->softrefInputValues[$tokenID]) ? $this->softrefInputValues[$tokenID] : $cfg['defValue'])) . '" />';
869 }
870 }
871
872 return '';
873 }
874
875 /**
876 * Selectorbox with export options for soft references
877 *
878 * @param array $cfg Softref configuration array. An export box is shown only if a substitution scheme is found for the soft reference.
879 * @return string Selector box HTML
880 */
881 public function softrefSelector($cfg)
882 {
883 // Looking for file ID if any:
884 $fI = $cfg['file_ID'] ? $this->dat['header']['files'][$cfg['file_ID']] : [];
885 // Substitution scheme has to be around and RTE images MUST be exported.
886 if (is_array($cfg['subst']) && $cfg['subst']['tokenID'] && !$fI['RTE_ORIG_ID']) {
887 // Create options:
888 $optValues = [];
889 $optValues[''] = '';
890 $optValues['editable'] = $this->getLanguageService()->getLL('impexpcore_softrefsel_editable');
891 $optValues['exclude'] = $this->getLanguageService()->getLL('impexpcore_softrefsel_exclude');
892 // Get current value:
893 $value = $this->softrefCfg[$cfg['subst']['tokenID']]['mode'];
894 // Render options selector:
895 $selectorbox = $this->renderSelectBox(('tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][mode]'), $value, $optValues) . '<br/>';
896 if ($value === 'editable') {
897 $descriptionField = '';
898 // Title:
899 if (strlen($cfg['subst']['title'])) {
900 $descriptionField .= '
901 <input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][title]" value="' . htmlspecialchars($cfg['subst']['title']) . '" />
902 <strong>' . htmlspecialchars($cfg['subst']['title']) . '</strong><br/>';
903 }
904 // Description:
905 if (!strlen($cfg['subst']['description'])) {
906 $descriptionField .= '
907 ' . htmlspecialchars($this->getLanguageService()->getLL('impexpcore_printerror_description')) . '<br/>
908 <input type="text" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($this->softrefCfg[$cfg['subst']['tokenID']]['description']) . '" />';
909 } else {
910 $descriptionField .= '
911
912 <input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($cfg['subst']['description']) . '" />' . htmlspecialchars($cfg['subst']['description']);
913 }
914 // Default Value:
915 $descriptionField .= '<input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][defValue]" value="' . htmlspecialchars($cfg['subst']['tokenValue']) . '" />';
916 } else {
917 $descriptionField = '';
918 }
919 return $selectorbox . $descriptionField;
920 }
921 return '';
922 }
923
924 /**
925 * Verifies that the input path (relative to PATH_site) is found in the backend users filemounts.
926 * If it doesn't it will try to find another relative filemount for the user and return an alternative path prefix for the file.
927 *
928 * @param string $dirPrefix Path relative to PATH_site
929 * @param bool $noAlternative If set, Do not look for alternative path! Just return FALSE
930 * @return string|bool If a path is available that will be returned, otherwise FALSE.
931 */
932 public function verifyFolderAccess($dirPrefix, $noAlternative = false)
933 {
934 // Check the absolute path for PATH_site, if the user has access - no problem
935 try {
936 ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($dirPrefix);
937 return $dirPrefix;
938 } catch (InsufficientFolderAccessPermissionsException $e) {
939 // Check all storages available for the user as alternative
940 if (!$noAlternative) {
941 $fileStorages = $this->getBackendUser()->getFileStorages();
942 foreach ($fileStorages as $fileStorage) {
943 try {
944 $folder = $fileStorage->getFolder(rtrim($dirPrefix, '/'));
945 return $folder->getPublicUrl();
946 } catch (InsufficientFolderAccessPermissionsException $e) {
947 }
948 }
949 }
950 }
951 return false;
952 }
953
954 /*****************************
955 * Helper functions of kinds
956 *****************************/
957
958 /**
959 * @return string
960 */
961 protected function getTemporaryFolderName()
962 {
963 $temporaryPath = PATH_site . 'typo3temp/var/transient/';
964 do {
965 $temporaryFolderName = $temporaryPath . 'export_temp_files_' . mt_rand(1, PHP_INT_MAX);
966 } while (is_dir($temporaryFolderName));
967 GeneralUtility::mkdir($temporaryFolderName);
968 return $temporaryFolderName;
969 }
970
971 /**
972 * Recursively flattening the idH array
973 *
974 * @param array $idH Page uid hierarchy
975 * @param array $a Accumulation array of pages (internal, don't set from outside)
976 * @return array Array with uid-uid pairs for all pages in the page tree.
977 * @see Import::flatInversePageTree_pid()
978 */
979 public function flatInversePageTree($idH, $a = [])
980 {
981 if (is_array($idH)) {
982 $idH = array_reverse($idH);
983 foreach ($idH as $k => $v) {
984 $a[$v['uid']] = $v['uid'];
985 if (is_array($v['subrow'])) {
986 $a = $this->flatInversePageTree($v['subrow'], $a);
987 }
988 }
989 }
990 return $a;
991 }
992
993 /**
994 * Returns TRUE if the input table name is to be regarded as a static relation (that is, not exported etc).
995 *
996 * @param string $table Table name
997 * @return bool TRUE, if table is marked static
998 */
999 public function isTableStatic($table)
1000 {
1001 if (is_array($GLOBALS['TCA'][$table])) {
1002 return $GLOBALS['TCA'][$table]['ctrl']['is_static'] || in_array($table, $this->relStaticTables) || in_array('_ALL', $this->relStaticTables);
1003 }
1004 return false;
1005 }
1006
1007 /**
1008 * Returns TRUE if the input table name is to be included as relation
1009 *
1010 * @param string $table Table name
1011 * @return bool TRUE, if table is marked static
1012 */
1013 public function inclRelation($table)
1014 {
1015 return is_array($GLOBALS['TCA'][$table])
1016 && (in_array($table, $this->relOnlyTables) || in_array('_ALL', $this->relOnlyTables))
1017 && $this->getBackendUser()->check('tables_select', $table);
1018 }
1019
1020 /**
1021 * Returns TRUE if the element should be excluded as static record.
1022 *
1023 * @param string $table Table name
1024 * @param int $uid UID value
1025 * @return bool TRUE, if table is marked static
1026 */
1027 public function isExcluded($table, $uid)
1028 {
1029 return (bool)$this->excludeMap[$table . ':' . $uid];
1030 }
1031
1032 /**
1033 * Returns TRUE if soft reference should be included in exported file.
1034 *
1035 * @param string $tokenID Token ID for soft reference
1036 * @return bool TRUE if softreference media should be included
1037 */
1038 public function includeSoftref($tokenID)
1039 {
1040 $mode = $this->softrefCfg[$tokenID]['mode'];
1041 return $tokenID && $mode !== 'exclude' && $mode !== 'editable';
1042 }
1043
1044 /**
1045 * Checking if a PID is in the webmounts of the user
1046 *
1047 * @param int $pid Page ID to check
1048 * @return bool TRUE if OK
1049 */
1050 public function checkPID($pid)
1051 {
1052 if (!isset($this->checkPID_cache[$pid])) {
1053 $this->checkPID_cache[$pid] = (bool)$this->getBackendUser()->isInWebMount($pid);
1054 }
1055 return $this->checkPID_cache[$pid];
1056 }
1057
1058 /**
1059 * Checks if the position of an updated record is configured to be corrected. This can be disabled globally and changed for elements individually.
1060 *
1061 * @param string $table Table name
1062 * @param int $uid Uid or record
1063 * @return bool TRUE if the position of the record should be updated to match the one in the import structure
1064 */
1065 public function dontIgnorePid($table, $uid)
1066 {
1067 return $this->import_mode[$table . ':' . $uid] !== 'ignore_pid' && (!$this->global_ignore_pid || $this->import_mode[$table . ':' . $uid] === 'respect_pid');
1068 }
1069
1070 /**
1071 * Checks if the record exists
1072 *
1073 * @param string $table Table name
1074 * @param int $uid UID of record
1075 * @param string $fields Field list to select. Default is "uid,pid
1076 * @return array Result of \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord() which means the record if found, otherwise FALSE
1077 */
1078 public function doesRecordExist($table, $uid, $fields = '')
1079 {
1080 return BackendUtility::getRecord($table, $uid, $fields ? $fields : 'uid,pid');
1081 }
1082
1083 /**
1084 * Returns the page title path of a PID value. Results are cached internally
1085 *
1086 * @param int $pid Record PID to check
1087 * @return string The path for the input PID
1088 */
1089 public function getRecordPath($pid)
1090 {
1091 if (!isset($this->cache_getRecordPath[$pid])) {
1092 $clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
1093 $this->cache_getRecordPath[$pid] = (string)BackendUtility::getRecordPath($pid, $clause, 20);
1094 }
1095 return $this->cache_getRecordPath[$pid];
1096 }
1097
1098 /**
1099 * Makes a selector-box from optValues
1100 *
1101 * @param string $prefix Form element name
1102 * @param string $value Current value
1103 * @param array $optValues Options to display (key/value pairs)
1104 * @return string HTML select element
1105 */
1106 public function renderSelectBox($prefix, $value, $optValues)
1107 {
1108 $opt = [];
1109 $isSelFlag = 0;
1110 foreach ($optValues as $k => $v) {
1111 $sel = (string)$k === (string)$value ? ' selected="selected"' : '';
1112 if ($sel) {
1113 $isSelFlag++;
1114 }
1115 $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>';
1116 }
1117 if (!$isSelFlag && (string)$value !== '') {
1118 $opt[] = '<option value="' . htmlspecialchars($value) . '" selected="selected">' . htmlspecialchars(('[\'' . $value . '\']')) . '</option>';
1119 }
1120 return '<select name="' . $prefix . '">' . implode('', $opt) . '</select>';
1121 }
1122
1123 /**
1124 * Compares two records, the current database record and the one from the import memory.
1125 * Will return HTML code to show any differences between them!
1126 *
1127 * @param array $databaseRecord Database record, all fields (new values)
1128 * @param array $importRecord Import memorys record for the same table/uid, all fields (old values)
1129 * @param string $table The table name of the record
1130 * @param bool $inverseDiff Inverse the diff view (switch red/green, needed for pre-update difference view)
1131 * @return string HTML
1132 */
1133 public function compareRecords($databaseRecord, $importRecord, $table, $inverseDiff = false)
1134 {
1135 // Initialize:
1136 $output = [];
1137 $diffUtility = GeneralUtility::makeInstance(DiffUtility::class);
1138 // Check if both inputs are records:
1139 if (is_array($databaseRecord) && is_array($importRecord)) {
1140 // Traverse based on database record
1141 foreach ($databaseRecord as $fN => $value) {
1142 if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') {
1143 if (isset($importRecord[$fN])) {
1144 if (trim($databaseRecord[$fN]) !== trim($importRecord[$fN])) {
1145 // Create diff-result:
1146 $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));
1147 }
1148 unset($importRecord[$fN]);
1149 }
1150 }
1151 }
1152 // Traverse remaining in import record:
1153 foreach ($importRecord as $fN => $value) {
1154 if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') {
1155 $output[$fN] = '<strong>Field missing</strong> in database';
1156 }
1157 }
1158 // Create output:
1159 if (!empty($output)) {
1160 $tRows = [];
1161 foreach ($output as $fN => $state) {
1162 $tRows[] = '
1163 <tr>
1164 <td>' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fN]['label'])) . ' (' . htmlspecialchars($fN) . ')</td>
1165 <td>' . $state . '</td>
1166 </tr>
1167 ';
1168 }
1169 $output = '<table class="table table-striped table-hover">' . implode('', $tRows) . '</table>';
1170 } else {
1171 $output = 'Match';
1172 }
1173 return '<strong class="text-nowrap">[' . htmlspecialchars(($table . ':' . $importRecord['uid'] . ' => ' . $databaseRecord['uid'])) . ']:</strong> ' . $output;
1174 }
1175 return 'ERROR: One of the inputs were not an array!';
1176 }
1177
1178 /**
1179 * Creates the original file name for a copy-RTE image (magic type)
1180 *
1181 * @param string $string RTE copy filename, eg. "RTEmagicC_user_pm_icon_01.gif.gif
1182 * @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!
1183 */
1184 public function getRTEoriginalFilename($string)
1185 {
1186 // If "magic image":
1187 if (GeneralUtility::isFirstPartOfStr($string, 'RTEmagicC_')) {
1188 // Find original file:
1189 $pI = pathinfo(substr($string, strlen('RTEmagicC_')));
1190 $filename = substr($pI['basename'], 0, -strlen(('.' . $pI['extension'])));
1191 $origFilePath = 'RTEmagicP_' . $filename;
1192 return $origFilePath;
1193 }
1194 return null;
1195 }
1196
1197 /**
1198 * Returns file processing object, initialized only once.
1199 *
1200 * @return ExtendedFileUtility File processor object
1201 */
1202 public function getFileProcObj()
1203 {
1204 if ($this->fileProcObj === null) {
1205 $this->fileProcObj = GeneralUtility::makeInstance(ExtendedFileUtility::class);
1206 $this->fileProcObj->setActionPermissions();
1207 }
1208 return $this->fileProcObj;
1209 }
1210
1211 /**
1212 * Call Hook
1213 *
1214 * @param string $name Name of the hook
1215 * @param array $params Array with params
1216 */
1217 public function callHook($name, $params)
1218 {
1219 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/impexp/class.tx_impexp.php'][$name] ?? [] as $hook) {
1220 GeneralUtility::callUserFunction($hook, $params, $this);
1221 }
1222 }
1223
1224 /**
1225 * Set flag to control whether disabled records and their children are excluded (true) or included (false). Defaults
1226 * to the old behaviour of including everything.
1227 *
1228 * @param bool $excludeDisabledRecords Set to true if if all disabled records should be excluded, false otherwise
1229 * @return \TYPO3\CMS\Impexp\ImportExport $this for fluent calls
1230 */
1231 public function setExcludeDisabledRecords($excludeDisabledRecords = false)
1232 {
1233 $this->excludeDisabledRecords = $excludeDisabledRecords;
1234 return $this;
1235 }
1236
1237 /*****************************
1238 * Error handling
1239 *****************************/
1240
1241 /**
1242 * Sets error message in the internal error log
1243 *
1244 * @param string $msg Error message
1245 */
1246 public function error($msg)
1247 {
1248 $this->errorLog[] = $msg;
1249 }
1250
1251 /**
1252 * Returns a table with the error-messages.
1253 *
1254 * @return string HTML print of error log
1255 */
1256 public function printErrorLog()
1257 {
1258 return !empty($this->errorLog) ? DebugUtility::viewArray($this->errorLog) : '';
1259 }
1260
1261 /**
1262 * @return BackendUserAuthentication
1263 */
1264 protected function getBackendUser()
1265 {
1266 return $GLOBALS['BE_USER'];
1267 }
1268
1269 /**
1270 * @return LanguageService
1271 */
1272 protected function getLanguageService()
1273 {
1274 return $GLOBALS['LANG'];
1275 }
1276 }