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