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