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