Passed
Branch master (6c65a4)
by Christian
16:31
created

ImportExport::getLanguageService()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
namespace TYPO3\CMS\Impexp;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use TYPO3\CMS\Backend\Utility\BackendUtility;
18
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
19
use TYPO3\CMS\Core\Imaging\Icon;
20
use TYPO3\CMS\Core\Imaging\IconFactory;
21
use TYPO3\CMS\Core\Localization\LanguageService;
22
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
23
use TYPO3\CMS\Core\Resource\ResourceFactory;
24
use TYPO3\CMS\Core\Type\Bitmask\Permission;
25
use TYPO3\CMS\Core\Utility\DebugUtility;
26
use TYPO3\CMS\Core\Utility\DiffUtility;
27
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
28
use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
use TYPO3\CMS\Core\Utility\PathUtility;
31
32
/**
33
 * EXAMPLE for using the impexp-class for exporting stuff:
34
 *
35
 * Create and initialize:
36
 * $this->export = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Impexp\ImportExport::class);
37
 * $this->export->init();
38
 * Set which tables relations we will allow:
39
 * $this->export->relOnlyTables[]="tt_news";	// exclusively includes. See comment in the class
40
 *
41
 * Adding records:
42
 * $this->export->export_addRecord("pages", $this->pageinfo);
43
 * $this->export->export_addRecord("pages", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("pages", 38));
44
 * $this->export->export_addRecord("pages", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("pages", 39));
45
 * $this->export->export_addRecord("tt_content", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("tt_content", 12));
46
 * $this->export->export_addRecord("tt_content", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("tt_content", 74));
47
 * $this->export->export_addRecord("sys_template", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("sys_template", 20));
48
 *
49
 * Adding all the relations (recursively in 5 levels so relations has THEIR relations registered as well)
50
 * for($a=0;$a<5;$a++) {
51
 * $addR = $this->export->export_addDBRelations($a);
52
 * if (empty($addR)) break;
53
 * }
54
 *
55
 * Finally load all the files.
56
 * $this->export->export_addFilesFromRelations();	// MUST be after the DBrelations are set so that file from ALL added records are included!
57
 *
58
 * Write export
59
 * $out = $this->export->compileMemoryToFileContent();
60
 */
61
62
/**
63
 * T3D file Import/Export library (TYPO3 Record Document)
64
 */
65
abstract class ImportExport
66
{
67
    /**
68
     * If set, static relations (not exported) will be shown in overview as well
69
     *
70
     * @var bool
71
     */
72
    public $showStaticRelations = false;
73
74
    /**
75
     * Name of the "fileadmin" folder where files for export/import should be located
76
     *
77
     * @var string
78
     */
79
    public $fileadminFolderName = '';
80
81
    /**
82
     * Whether "import" or "export" mode of object. Set through init() function
83
     *
84
     * @var string
85
     */
86
    public $mode = '';
87
88
    /**
89
     * Updates all records that has same UID instead of creating new!
90
     *
91
     * @var bool
92
     */
93
    public $update = false;
94
95
    /**
96
     * Is set by importData() when an import has been done.
97
     *
98
     * @var bool
99
     */
100
    public $doesImport = false;
101
102
    /**
103
     * If set to a page-record, then the preview display of the content will expect this page-record to be the target
104
     * for the import and accordingly display validation information. This triggers the visual view of the
105
     * import/export memory to validate if import is possible
106
     *
107
     * @var array
108
     */
109
    public $display_import_pid_record = [];
110
111
    /**
112
     * Setting import modes during update state: as_new, exclude, force_uid
113
     *
114
     * @var array
115
     */
116
    public $import_mode = [];
117
118
    /**
119
     * If set, PID correct is ignored globally
120
     *
121
     * @var bool
122
     */
123
    public $global_ignore_pid = false;
124
125
    /**
126
     * If set, all UID values are forced! (update or import)
127
     *
128
     * @var bool
129
     */
130
    public $force_all_UIDS = false;
131
132
    /**
133
     * If set, a diff-view column is added to the overview.
134
     *
135
     * @var bool
136
     */
137
    public $showDiff = false;
138
139
    /**
140
     * If set, and if the user is admin, allow the writing of PHP scripts to fileadmin/ area.
141
     *
142
     * @var bool
143
     */
144
    public $allowPHPScripts = false;
145
146
    /**
147
     * Array of values to substitute in editable softreferences.
148
     *
149
     * @var array
150
     */
151
    public $softrefInputValues = [];
152
153
    /**
154
     * Mapping between the fileID from import memory and the final filenames they are written to.
155
     *
156
     * @var array
157
     */
158
    public $fileIDMap = [];
159
160
    /**
161
     * Add table names here which are THE ONLY ones which will be included
162
     * into export if found as relations. '_ALL' will allow all tables.
163
     *
164
     * @var array
165
     */
166
    public $relOnlyTables = [];
167
168
    /**
169
     * Add tables names here which should not be exported with the file.
170
     * (Where relations should be mapped to same UIDs in target system).
171
     *
172
     * @var array
173
     */
174
    public $relStaticTables = [];
175
176
    /**
177
     * Exclude map. Keys are table:uid  pairs and if set, records are not added to the export.
178
     *
179
     * @var array
180
     */
181
    public $excludeMap = [];
182
183
    /**
184
     * Soft Reference Token ID modes.
185
     *
186
     * @var array
187
     */
188
    public $softrefCfg = [];
189
190
    /**
191
     * Listing extension dependencies.
192
     *
193
     * @var array
194
     */
195
    public $extensionDependencies = [];
196
197
    /**
198
     * After records are written this array is filled with [table][original_uid] = [new_uid]
199
     *
200
     * @var array
201
     */
202
    public $import_mapId = [];
203
204
    /**
205
     * Error log.
206
     *
207
     * @var array
208
     */
209
    public $errorLog = [];
210
211
    /**
212
     * Cache for record paths
213
     *
214
     * @var array
215
     */
216
    public $cache_getRecordPath = [];
217
218
    /**
219
     * Cache of checkPID values.
220
     *
221
     * @var array
222
     */
223
    public $checkPID_cache = [];
224
225
    /**
226
     * Set internally if the gzcompress function exists
227
     * Used by ImportExportController
228
     *
229
     * @var bool
230
     */
231
    public $compress = false;
232
233
    /**
234
     * Internal import/export memory
235
     *
236
     * @var array
237
     */
238
    public $dat = [];
239
240
    /**
241
     * File processing object
242
     *
243
     * @var ExtendedFileUtility
244
     */
245
    protected $fileProcObj = null;
246
247
    /**
248
     * @var array
249
     */
250
    protected $remainHeader = [];
251
252
    /**
253
     * @var IconFactory
254
     */
255
    protected $iconFactory;
256
257
    /**
258
     * Flag to control whether all disabled records and their children are excluded (true) or included (false). Defaults
259
     * to the old behaviour of including everything.
260
     *
261
     * @var bool
262
     */
263
    protected $excludeDisabledRecords = false;
264
265
    /**
266
     * The constructor
267
     */
268
    public function __construct()
269
    {
270
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
271
    }
272
273
    /**************************
274
     * Initialize
275
     *************************/
276
277
    /**
278
     * Init the object, both import and export
279
     */
280
    public function init()
281
    {
282
        $this->compress = function_exists('gzcompress');
283
        $this->fileadminFolderName = !empty($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir']) ? rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') : 'fileadmin';
284
    }
285
286
    /********************************************************
287
     * Visual rendering of import/export memory, $this->dat
288
     ********************************************************/
289
290
    /**
291
     * Displays an overview of the header-content.
292
     *
293
     * @return array The view data
294
     */
295
    public function displayContentOverview()
296
    {
297
        if (!isset($this->dat['header'])) {
298
            return [];
299
        }
300
        // Check extension dependencies:
301
        foreach ($this->dat['header']['extensionDependencies'] as $extKey) {
302
            if (!empty($extKey) && !ExtensionManagementUtility::isLoaded($extKey)) {
303
                $this->error('DEPENDENCY: The extension with key "' . $extKey . '" must be installed!');
304
            }
305
        }
306
307
        // Probably this is done to save memory space?
308
        unset($this->dat['files']);
309
310
        $viewData = [];
311
        // Traverse header:
312
        $this->remainHeader = $this->dat['header'];
313
        // If there is a page tree set, show that:
314
        if (is_array($this->dat['header']['pagetree'])) {
315
            reset($this->dat['header']['pagetree']);
316
            $lines = [];
317
            $this->traversePageTree($this->dat['header']['pagetree'], $lines);
318
319
            $viewData['dat'] = $this->dat;
320
            $viewData['update'] = $this->update;
321
            $viewData['showDiff'] = $this->showDiff;
322
            if (!empty($lines)) {
323
                foreach ($lines as &$r) {
324
                    $r['controls'] = $this->renderControls($r);
325
                    $r['fileSize'] = GeneralUtility::formatSize($r['size']);
326
                    $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
327
                }
328
                $viewData['pagetreeLines'] = $lines;
329
            } else {
330
                $viewData['pagetreeLines'] = [];
331
            }
332
        }
333
        // Print remaining records that were not contained inside the page tree:
334
        if (is_array($this->remainHeader['records'])) {
335
            $lines = [];
336
            if (is_array($this->remainHeader['records']['pages'])) {
337
                $this->traversePageRecords($this->remainHeader['records']['pages'], $lines);
338
            }
339
            $this->traverseAllRecords($this->remainHeader['records'], $lines);
340
            if (!empty($lines)) {
341
                foreach ($lines as &$r) {
342
                    $r['controls'] = $this->renderControls($r);
343
                    $r['fileSize'] = GeneralUtility::formatSize($r['size']);
344
                    $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
345
                }
346
                $viewData['remainingRecords'] = $lines;
347
            }
348
        }
349
350
        return $viewData;
351
    }
352
353
    /**
354
     * Go through page tree for display
355
     *
356
     * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
357
     * @param array $lines Output lines array (is passed by reference and modified)
358
     * @param string $preCode Pre-HTML code
359
     */
360
    public function traversePageTree($pT, &$lines, $preCode = '')
361
    {
362
        foreach ($pT as $k => $v) {
363
            if ($this->excludeDisabledRecords === true && !$this->isActive('pages', $k)) {
364
                $this->excludePageAndRecords($k, $v);
365
                continue;
366
            }
367
368
            // Add this page:
369
            $this->singleRecordLines('pages', $k, $lines, $preCode);
370
            // Subrecords:
371
            if (is_array($this->dat['header']['pid_lookup'][$k])) {
372
                foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) {
373
                    if ($t !== 'pages') {
374
                        foreach ($recUidArr as $ruid => $value) {
375
                            $this->singleRecordLines($t, $ruid, $lines, $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;');
376
                        }
377
                    }
378
                }
379
                unset($this->remainHeader['pid_lookup'][$k]);
380
            }
381
            // Subpages, called recursively:
382
            if (is_array($v['subrow'])) {
383
                $this->traversePageTree($v['subrow'], $lines, $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;');
384
            }
385
        }
386
    }
387
388
    /**
389
     * Test whether a record is active (i.e. not hidden)
390
     *
391
     * @param string $table Name of the records' database table
392
     * @param int $uid Database uid of the record
393
     * @return bool true if the record is active, false otherwise
394
     */
395
    protected function isActive($table, $uid)
396
    {
397
        return
398
            !isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])
399
            || !(bool)$this->dat['records'][$table . ':' . $uid]['data'][
400
                $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']
401
            ];
402
    }
403
404
    /**
405
     * Exclude a page, its sub pages (recursively) and records placed in them from this import/export
406
     *
407
     * @param int $pageUid Uid of the page to exclude
408
     * @param array $pageTree Page tree array with uid/subrow (from ->dat[header][pagetree]
409
     */
410
    protected function excludePageAndRecords($pageUid, $pageTree)
411
    {
412
        // Prevent having this page appear in "remaining records" table
413
        unset($this->remainHeader['records']['pages'][$pageUid]);
414
415
        // Subrecords
416
        if (is_array($this->dat['header']['pid_lookup'][$pageUid])) {
417
            foreach ($this->dat['header']['pid_lookup'][$pageUid] as $table => $recordData) {
418
                if ($table !== 'pages') {
419
                    foreach (array_keys($recordData) as $uid) {
420
                        unset($this->remainHeader['records'][$table][$uid]);
421
                    }
422
                }
423
            }
424
            unset($this->remainHeader['pid_lookup'][$pageUid]);
425
        }
426
        // Subpages excluded recursively
427
        if (is_array($pageTree['subrow'])) {
428
            foreach ($pageTree['subrow'] as $subPageUid => $subPageTree) {
429
                $this->excludePageAndRecords($subPageUid, $subPageTree);
430
            }
431
        }
432
    }
433
434
    /**
435
     * Go through remaining pages (not in tree)
436
     *
437
     * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
438
     * @param array $lines Output lines array (is passed by reference and modified)
439
     */
440
    public function traversePageRecords($pT, &$lines)
441
    {
442
        foreach ($pT as $k => $rHeader) {
443
            $this->singleRecordLines('pages', $k, $lines, '', 1);
444
            // Subrecords:
445
            if (is_array($this->dat['header']['pid_lookup'][$k])) {
446
                foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) {
447
                    if ($t !== 'pages') {
448
                        foreach ($recUidArr as $ruid => $value) {
449
                            $this->singleRecordLines($t, $ruid, $lines, '&nbsp;&nbsp;&nbsp;&nbsp;');
450
                        }
451
                    }
452
                }
453
                unset($this->remainHeader['pid_lookup'][$k]);
454
            }
455
        }
456
    }
457
458
    /**
459
     * Go through ALL records (if the pages are displayed first, those will not be among these!)
460
     *
461
     * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
462
     * @param array $lines Output lines array (is passed by reference and modified)
463
     */
464
    public function traverseAllRecords($pT, &$lines)
465
    {
466
        foreach ($pT as $t => $recUidArr) {
467
            $this->addGeneralErrorsByTable($t);
468
            if ($t !== 'pages') {
469
                $preCode = '';
470
                foreach ($recUidArr as $ruid => $value) {
471
                    $this->singleRecordLines($t, $ruid, $lines, $preCode, 1);
472
                }
473
            }
474
        }
475
    }
476
477
    /**
478
     * Log general error message for a given table
479
     *
480
     * @param string $table database table name
481
     */
482
    protected function addGeneralErrorsByTable($table)
483
    {
484
        if ($this->update && $table === 'sys_file') {
485
            $this->error('Updating sys_file records is not supported! They will be imported as new records!');
486
        }
487
        if ($this->force_all_UIDS && $table === 'sys_file') {
488
            $this->error('Forcing uids of sys_file records is not supported! They will be imported as new records!');
489
        }
490
    }
491
492
    /**
493
     * Add entries for a single record
494
     *
495
     * @param string $table Table name
496
     * @param int $uid Record uid
497
     * @param array $lines Output lines array (is passed by reference and modified)
498
     * @param string $preCode Pre-HTML code
499
     * @param bool $checkImportInPidRecord If you want import validation, you can set this so it checks if the import can take place on the specified page.
500
     */
501
    public function singleRecordLines($table, $uid, &$lines, $preCode, $checkImportInPidRecord = false)
502
    {
503
        // Get record:
504
        $record = $this->dat['header']['records'][$table][$uid];
505
        unset($this->remainHeader['records'][$table][$uid]);
506
        if (!is_array($record) && !($table === 'pages' && !$uid)) {
507
            $this->error('MISSING RECORD: ' . $table . ':' . $uid);
508
        }
509
        // Begin to create the line arrays information record, pInfo:
510
        $pInfo = [];
511
        $pInfo['ref'] = $table . ':' . $uid;
512
        // Unknown table name:
513
        $lang = $this->getLanguageService();
514
        if ($table === '_SOFTREF_') {
515
            $pInfo['preCode'] = $preCode;
516
            $pInfo['title'] = '<em>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_softReferencesFiles')) . '</em>';
517
        } elseif (!isset($GLOBALS['TCA'][$table])) {
518
            // Unknown table name:
519
            $pInfo['preCode'] = $preCode;
520
            $pInfo['msg'] = 'UNKNOWN TABLE \'' . $pInfo['ref'] . '\'';
521
            $pInfo['title'] = '<em>' . htmlspecialchars($record['title']) . '</em>';
522
        } else {
523
            // prepare data attribute telling whether the record is active or hidden, allowing frontend bulk selection
524
            $pInfo['active'] = $this->isActive($table, $uid) ? 'active' : 'hidden';
525
526
            // Otherwise, set table icon and title.
527
            // Import Validation (triggered by $this->display_import_pid_record) will show messages if import is not possible of various items.
528
            if (is_array($this->display_import_pid_record) && !empty($this->display_import_pid_record)) {
529
                if ($checkImportInPidRecord) {
530
                    if (!$this->getBackendUser()->doesUserHaveAccess($this->display_import_pid_record, ($table === 'pages' ? 8 : 16))) {
531
                        $pInfo['msg'] .= '\'' . $pInfo['ref'] . '\' cannot be INSERTED on this page! ';
532
                    }
533
                    if (!$this->checkDokType($table, $this->display_import_pid_record['doktype']) && !$GLOBALS['TCA'][$table]['ctrl']['rootLevel']) {
534
                        $pInfo['msg'] .= '\'' . $table . '\' cannot be INSERTED on this page type (change page type to \'Folder\'.) ';
535
                    }
536
                }
537
                if (!$this->getBackendUser()->check('tables_modify', $table)) {
538
                    $pInfo['msg'] .= 'You are not allowed to CREATE \'' . $table . '\' tables! ';
539
                }
540
                if ($GLOBALS['TCA'][$table]['ctrl']['readOnly']) {
541
                    $pInfo['msg'] .= 'TABLE \'' . $table . '\' is READ ONLY! ';
542
                }
543
                if ($GLOBALS['TCA'][$table]['ctrl']['adminOnly'] && !$this->getBackendUser()->isAdmin()) {
544
                    $pInfo['msg'] .= 'TABLE \'' . $table . '\' is ADMIN ONLY! ';
545
                }
546
                if ($GLOBALS['TCA'][$table]['ctrl']['is_static']) {
547
                    $pInfo['msg'] .= 'TABLE \'' . $table . '\' is a STATIC TABLE! ';
548
                }
549
                if ((int)$GLOBALS['TCA'][$table]['ctrl']['rootLevel'] === 1) {
550
                    $pInfo['msg'] .= 'TABLE \'' . $table . '\' will be inserted on ROOT LEVEL! ';
551
                }
552
                $diffInverse = false;
553
                $recInf = null;
554
                if ($this->update) {
555
                    // In case of update-PREVIEW we swap the diff-sources.
556
                    $diffInverse = true;
557
                    $recInf = $this->doesRecordExist($table, $uid, $this->showDiff ? '*' : '');
558
                    $pInfo['updatePath'] = $recInf ? htmlspecialchars($this->getRecordPath($recInf['pid'])) : '<strong>NEW!</strong>';
559
                    // Mode selector:
560
                    $optValues = [];
561
                    $optValues[] = $recInf ? $lang->getLL('impexpcore_singlereco_update') : $lang->getLL('impexpcore_singlereco_insert');
562
                    if ($recInf) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $recInf of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
563
                        $optValues['as_new'] = $lang->getLL('impexpcore_singlereco_importAsNew');
564
                    }
565
                    if ($recInf) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $recInf of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
566
                        if (!$this->global_ignore_pid) {
567
                            $optValues['ignore_pid'] = $lang->getLL('impexpcore_singlereco_ignorePid');
568
                        } else {
569
                            $optValues['respect_pid'] = $lang->getLL('impexpcore_singlereco_respectPid');
570
                        }
571
                    }
572
                    if (!$recInf && $this->getBackendUser()->isAdmin()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $recInf of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
573
                        $optValues['force_uid'] = sprintf($lang->getLL('impexpcore_singlereco_forceUidSAdmin'), $uid);
574
                    }
575
                    $optValues['exclude'] = $lang->getLL('impexpcore_singlereco_exclude');
576
                    if ($table === 'sys_file') {
577
                        $pInfo['updateMode'] = '';
578
                    } else {
579
                        $pInfo['updateMode'] = $this->renderSelectBox('tx_impexp[import_mode][' . $table . ':' . $uid . ']', $this->import_mode[$table . ':' . $uid], $optValues);
580
                    }
581
                }
582
                // Diff view:
583
                if ($this->showDiff) {
584
                    // For IMPORTS, get new id:
585
                    if ($newUid = $this->import_mapId[$table][$uid]) {
586
                        $diffInverse = false;
587
                        $recInf = $this->doesRecordExist($table, $newUid, '*');
588
                        BackendUtility::workspaceOL($table, $recInf);
589
                    }
590
                    if (is_array($recInf)) {
591
                        $pInfo['showDiffContent'] = $this->compareRecords($recInf, $this->dat['records'][$table . ':' . $uid]['data'], $table, $diffInverse);
592
                    }
593
                }
594
            }
595
            $pInfo['preCode'] = $preCode . '<span title="' . htmlspecialchars($table . ':' . $uid) . '">'
596
                . $this->iconFactory->getIconForRecord($table, (array)$this->dat['records'][$table . ':' . $uid]['data'], Icon::SIZE_SMALL)->render()
597
                . '</span>';
598
            $pInfo['title'] = htmlspecialchars($record['title']);
599
            // View page:
600
            if ($table === 'pages') {
601
                $viewID = $this->mode === 'export' ? $uid : ($this->doesImport ? $this->import_mapId['pages'][$uid] : 0);
602
                if ($viewID) {
603
                    $pInfo['title'] = '<a href="#" onclick="' . htmlspecialchars(BackendUtility::viewOnClick($viewID)) . 'return false;">' . $pInfo['title'] . '</a>';
604
                }
605
            }
606
        }
607
        $pInfo['type'] = 'record';
608
        $pInfo['size'] = (int)$record['size'];
609
        $lines[] = $pInfo;
610
        // File relations:
611
        if (is_array($record['filerefs'])) {
612
            $this->addFiles($record['filerefs'], $lines, $preCode);
613
        }
614
        // DB relations
615
        if (is_array($record['rels'])) {
616
            $this->addRelations($record['rels'], $lines, $preCode);
617
        }
618
        // Soft ref
619
        if (!empty($record['softrefs'])) {
620
            $preCode_A = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;';
621
            $preCode_B = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
622
            foreach ($record['softrefs'] as $info) {
623
                $pInfo = [];
624
                $pInfo['preCode'] = $preCode_A . $this->iconFactory->getIcon('status-reference-soft', Icon::SIZE_SMALL)->render();
625
                $pInfo['title'] = '<em>' . $info['field'] . ', "' . $info['spKey'] . '" </em>: <span title="' . htmlspecialchars($info['matchString']) . '">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['matchString'], 60)) . '</span>';
626
                if ($info['subst']['type']) {
627
                    if (strlen($info['subst']['title'])) {
628
                        $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_title')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['title'], 60));
629
                    }
630
                    if (strlen($info['subst']['description'])) {
631
                        $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_descr')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['description'], 60));
632
                    }
633
                    $pInfo['title'] .= '<br/>' . $preCode_B . ($info['subst']['type'] === 'file' ? htmlspecialchars($lang->getLL('impexpcore_singlereco_filename')) . ' <strong>' . $info['subst']['relFileName'] . '</strong>' : '') . ($info['subst']['type'] === 'string' ? htmlspecialchars($lang->getLL('impexpcore_singlereco_value')) . ' <strong>' . $info['subst']['tokenValue'] . '</strong>' : '') . ($info['subst']['type'] === 'db' ? htmlspecialchars($lang->getLL('impexpcore_softrefsel_record')) . ' <strong>' . $info['subst']['recordRef'] . '</strong>' : '');
634
                }
635
                $pInfo['ref'] = 'SOFTREF';
636
                $pInfo['size'] = 0;
637
                $pInfo['type'] = 'softref';
638
                $pInfo['_softRefInfo'] = $info;
639
                $pInfo['type'] = 'softref';
640
                $mode = $this->softrefCfg[$info['subst']['tokenID']]['mode'];
641
                if ($info['error'] && $mode !== 'editable' && $mode !== 'exclude') {
642
                    $pInfo['msg'] .= $info['error'];
643
                }
644
                $lines[] = $pInfo;
645
                // Add relations:
646
                if ($info['subst']['type'] === 'db') {
647
                    list($tempTable, $tempUid) = explode(':', $info['subst']['recordRef']);
648
                    $this->addRelations([['table' => $tempTable, 'id' => $tempUid, 'tokenID' => $info['subst']['tokenID']]], $lines, $preCode_B, [], '');
649
                }
650
                // Add files:
651
                if ($info['subst']['type'] === 'file') {
652
                    $this->addFiles([$info['file_ID']], $lines, $preCode_B, '', $info['subst']['tokenID']);
653
                }
654
            }
655
        }
656
    }
657
658
    /**
659
     * Add DB relations entries for a record's rels-array
660
     *
661
     * @param array $rels Array of relations
662
     * @param array $lines Output lines array (is passed by reference and modified)
663
     * @param string $preCode Pre-HTML code
664
     * @param array $recurCheck Recursivity check stack
665
     * @param string $htmlColorClass Alternative HTML color class to use.
666
     * @access private
667
     * @see singleRecordLines()
668
     */
669
    public function addRelations($rels, &$lines, $preCode, $recurCheck = [], $htmlColorClass = '')
0 ignored issues
show
Unused Code introduced by
The parameter $htmlColorClass is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

669
    public function addRelations($rels, &$lines, $preCode, $recurCheck = [], /** @scrutinizer ignore-unused */ $htmlColorClass = '')

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
670
    {
671
        foreach ($rels as $dat) {
672
            $table = $dat['table'];
673
            $uid = $dat['id'];
674
            $pInfo = [];
675
            $pInfo['ref'] = $table . ':' . $uid;
676
            if (in_array($pInfo['ref'], $recurCheck)) {
677
                continue;
678
            }
679
            $iconName = 'status-status-checked';
680
            $iconClass = '';
681
            $staticFixed = false;
682
            $record = null;
683
            if ($uid > 0) {
684
                $record = $this->dat['header']['records'][$table][$uid];
685
                if (!is_array($record)) {
686
                    if ($this->isTableStatic($table) || $this->isExcluded($table, $uid) || $dat['tokenID'] && !$this->includeSoftref($dat['tokenID'])) {
687
                        $pInfo['title'] = htmlspecialchars('STATIC: ' . $pInfo['ref']);
688
                        $iconClass = 'text-info';
689
                        $staticFixed = true;
690
                    } else {
691
                        $doesRE = $this->doesRecordExist($table, $uid);
692
                        $lostPath = $this->getRecordPath($table === 'pages' ? $doesRE['uid'] : $doesRE['pid']);
693
                        $pInfo['title'] = htmlspecialchars($pInfo['ref']);
694
                        $pInfo['title'] = '<span title="' . htmlspecialchars($lostPath) . '">' . $pInfo['title'] . '</span>';
695
                        $pInfo['msg'] = 'LOST RELATION' . (!$doesRE ? ' (Record not found!)' : ' (Path: ' . $lostPath . ')');
696
                        $iconClass = 'text-danger';
697
                        $iconName = 'status-dialog-warning';
698
                    }
699
                } else {
700
                    $pInfo['title'] = htmlspecialchars($record['title']);
701
                    $pInfo['title'] = '<span title="' . htmlspecialchars($this->getRecordPath(($table === 'pages' ? $record['uid'] : $record['pid']))) . '">' . $pInfo['title'] . '</span>';
702
                }
703
            } else {
704
                // Negative values in relation fields. This is typically sys_language fields, fe_users fields etc. They are static values. They CAN theoretically be negative pointers to uids in other tables but this is so rarely used that it is not supported
705
                $pInfo['title'] = htmlspecialchars('FIXED: ' . $pInfo['ref']);
706
                $staticFixed = true;
707
            }
708
709
            $icon = '<span class="' . $iconClass . '" title="' . htmlspecialchars($pInfo['ref']) . '">' . $this->iconFactory->getIcon($iconName, Icon::SIZE_SMALL)->render() . '</span>';
710
711
            $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $icon;
712
            $pInfo['type'] = 'rel';
713
            if (!$staticFixed || $this->showStaticRelations) {
714
                $lines[] = $pInfo;
715
                if (is_array($record) && is_array($record['rels'])) {
716
                    $this->addRelations($record['rels'], $lines, $preCode . '&nbsp;&nbsp;', array_merge($recurCheck, [$pInfo['ref']]));
717
                }
718
            }
719
        }
720
    }
721
722
    /**
723
     * Add file relation entries for a record's rels-array
724
     *
725
     * @param array $rels Array of file IDs
726
     * @param array $lines Output lines array (is passed by reference and modified)
727
     * @param string $preCode Pre-HTML code
728
     * @param string $htmlColorClass Alternative HTML color class to use.
729
     * @param string $tokenID Token ID if this is a softreference (in which case it only makes sense with a single element in the $rels array!)
730
     * @access private
731
     * @see singleRecordLines()
732
     */
733
    public function addFiles($rels, &$lines, $preCode, $htmlColorClass = '', $tokenID = '')
0 ignored issues
show
Unused Code introduced by
The parameter $htmlColorClass is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

733
    public function addFiles($rels, &$lines, $preCode, /** @scrutinizer ignore-unused */ $htmlColorClass = '', $tokenID = '')

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
734
    {
735
        foreach ($rels as $ID) {
736
            // Process file:
737
            $pInfo = [];
738
            $fI = $this->dat['header']['files'][$ID];
739
            if (!is_array($fI)) {
740
                if (!$tokenID || $this->includeSoftref($tokenID)) {
741
                    $pInfo['msg'] = 'MISSING FILE: ' . $ID;
742
                    $this->error('MISSING FILE: ' . $ID);
743
                } else {
744
                    return;
745
                }
746
            }
747
            $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('status-reference-hard', Icon::SIZE_SMALL)->render();
748
            $pInfo['title'] = htmlspecialchars($fI['filename']);
749
            $pInfo['ref'] = 'FILE';
750
            $pInfo['size'] = $fI['filesize'];
751
            $pInfo['type'] = 'file';
752
            // If import mode and there is a non-RTE softreference, check the destination directory:
753
            if ($this->mode === 'import' && $tokenID && !$fI['RTE_ORIG_ID']) {
754
                if (isset($fI['parentRelFileName'])) {
755
                    $pInfo['msg'] = 'Seems like this file is already referenced from within an HTML/CSS file. That takes precedence. ';
756
                } else {
757
                    $testDirPrefix = PathUtility::dirname($fI['relFileName']) . '/';
758
                    $testDirPrefix2 = $this->verifyFolderAccess($testDirPrefix);
759
                    if (!$testDirPrefix2) {
760
                        $pInfo['msg'] = 'ERROR: There are no available filemounts to write file in! ';
761
                    } elseif ($testDirPrefix !== $testDirPrefix2) {
762
                        $pInfo['msg'] = 'File will be attempted written to "' . $testDirPrefix2 . '". ';
763
                    }
764
                }
765
                // Check if file exists:
766
                if (file_exists(PATH_site . $fI['relFileName'])) {
767
                    if ($this->update) {
768
                        $pInfo['updatePath'] .= 'File exists.';
769
                    } else {
770
                        $pInfo['msg'] .= 'File already exists! ';
771
                    }
772
                }
773
                // Check extension:
774
                $fileProcObj = $this->getFileProcObj();
775
                if ($fileProcObj->actionPerms['addFile']) {
776
                    $testFI = GeneralUtility::split_fileref(PATH_site . $fI['relFileName']);
777
                    if (!$this->allowPHPScripts && !$fileProcObj->checkIfAllowed($testFI['fileext'], $testFI['path'], $testFI['file'])) {
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Core\Utility\F...ility::checkIfAllowed() has been deprecated: but still in use in the Core. Don't use in your extensions! ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

777
                    if (!$this->allowPHPScripts && !/** @scrutinizer ignore-deprecated */ $fileProcObj->checkIfAllowed($testFI['fileext'], $testFI['path'], $testFI['file'])) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
778
                        $pInfo['msg'] .= 'File extension was not allowed!';
779
                    }
780
                } else {
781
                    $pInfo['msg'] = 'You user profile does not allow you to create files on the server!';
782
                }
783
            }
784
            $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$ID]);
785
            $lines[] = $pInfo;
786
            unset($this->remainHeader['files'][$ID]);
787
            // RTE originals:
788
            if ($fI['RTE_ORIG_ID']) {
789
                $ID = $fI['RTE_ORIG_ID'];
790
                $pInfo = [];
791
                $fI = $this->dat['header']['files'][$ID];
792
                if (!is_array($fI)) {
793
                    $pInfo['msg'] = 'MISSING RTE original FILE: ' . $ID;
794
                    $this->error('MISSING RTE original FILE: ' . $ID);
795
                }
796
                $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$ID]);
797
                $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('status-reference-hard', Icon::SIZE_SMALL)->render();
798
                $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Original)</em>';
799
                $pInfo['ref'] = 'FILE';
800
                $pInfo['size'] = $fI['filesize'];
801
                $pInfo['type'] = 'file';
802
                $lines[] = $pInfo;
803
                unset($this->remainHeader['files'][$ID]);
804
            }
805
            // External resources:
806
            if (is_array($fI['EXT_RES_ID'])) {
807
                foreach ($fI['EXT_RES_ID'] as $extID) {
808
                    $pInfo = [];
809
                    $fI = $this->dat['header']['files'][$extID];
810
                    if (!is_array($fI)) {
811
                        $pInfo['msg'] = 'MISSING External Resource FILE: ' . $extID;
812
                        $this->error('MISSING External Resource FILE: ' . $extID);
813
                    } else {
814
                        $pInfo['updatePath'] = $fI['parentRelFileName'];
815
                    }
816
                    $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$extID]);
817
                    $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('actions-insert-reference', Icon::SIZE_SMALL)->render();
818
                    $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Resource)</em>';
819
                    $pInfo['ref'] = 'FILE';
820
                    $pInfo['size'] = $fI['filesize'];
821
                    $pInfo['type'] = 'file';
822
                    $lines[] = $pInfo;
823
                    unset($this->remainHeader['files'][$extID]);
824
                }
825
            }
826
        }
827
    }
828
829
    /**
830
     * Verifies that a table is allowed on a certain doktype of a page
831
     *
832
     * @param string $checkTable Table name to check
833
     * @param int $doktype doktype value.
834
     * @return bool TRUE if OK
835
     */
836
    public function checkDokType($checkTable, $doktype)
837
    {
838
        $allowedTableList = $GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'] ?? $GLOBALS['PAGES_TYPES']['default']['allowedTables'];
839
        $allowedArray = GeneralUtility::trimExplode(',', $allowedTableList, true);
840
        // If all tables or the table is listed as an allowed type, return TRUE
841
        if (strstr($allowedTableList, '*') || in_array($checkTable, $allowedArray)) {
842
            return true;
843
        }
844
        return false;
845
    }
846
847
    /**
848
     * Render input controls for import or export
849
     *
850
     * @param array $r Configuration for element
851
     * @return string HTML
852
     */
853
    public function renderControls($r)
854
    {
855
        if ($this->mode === 'export') {
856
            if ($r['type'] === 'record') {
857
                return '<input type="checkbox" class="t3js-exclude-checkbox" name="tx_impexp[exclude][' . $r['ref'] . ']" id="checkExclude' . $r['ref'] . '" value="1" /> <label for="checkExclude' . $r['ref'] . '">' . htmlspecialchars($this->getLanguageService()->getLL('impexpcore_singlereco_exclude')) . '</label>';
858
            }
859
            return  $r['type'] === 'softref' ? $this->softrefSelector($r['_softRefInfo']) : '';
860
        }
861
        // During import
862
        // For softreferences with editable fields:
863
        if ($r['type'] === 'softref' && is_array($r['_softRefInfo']['subst']) && $r['_softRefInfo']['subst']['tokenID']) {
864
            $tokenID = $r['_softRefInfo']['subst']['tokenID'];
865
            $cfg = $this->softrefCfg[$tokenID];
866
            if ($cfg['mode'] === 'editable') {
867
                return (strlen($cfg['title']) ? '<strong>' . htmlspecialchars($cfg['title']) . '</strong><br/>' : '') . htmlspecialchars($cfg['description']) . '<br/>
868
						<input type="text" name="tx_impexp[softrefInputValues][' . $tokenID . ']" value="' . htmlspecialchars(($this->softrefInputValues[$tokenID] ?? $cfg['defValue'])) . '" />';
869
            }
870
        }
871
872
        return '';
873
    }
874
875
    /**
876
     * Selectorbox with export options for soft references
877
     *
878
     * @param array $cfg Softref configuration array. An export box is shown only if a substitution scheme is found for the soft reference.
879
     * @return string Selector box HTML
880
     */
881
    public function softrefSelector($cfg)
882
    {
883
        // Looking for file ID if any:
884
        $fI = $cfg['file_ID'] ? $this->dat['header']['files'][$cfg['file_ID']] : [];
885
        // Substitution scheme has to be around and RTE images MUST be exported.
886
        if (is_array($cfg['subst']) && $cfg['subst']['tokenID'] && !$fI['RTE_ORIG_ID']) {
887
            // Create options:
888
            $optValues = [];
889
            $optValues[''] = '';
890
            $optValues['editable'] = $this->getLanguageService()->getLL('impexpcore_softrefsel_editable');
891
            $optValues['exclude'] = $this->getLanguageService()->getLL('impexpcore_softrefsel_exclude');
892
            // Get current value:
893
            $value = $this->softrefCfg[$cfg['subst']['tokenID']]['mode'];
894
            // Render options selector:
895
            $selectorbox = $this->renderSelectBox(('tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][mode]'), $value, $optValues) . '<br/>';
896
            if ($value === 'editable') {
897
                $descriptionField = '';
898
                // Title:
899
                if (strlen($cfg['subst']['title'])) {
900
                    $descriptionField .= '
901
					<input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][title]" value="' . htmlspecialchars($cfg['subst']['title']) . '" />
902
					<strong>' . htmlspecialchars($cfg['subst']['title']) . '</strong><br/>';
903
                }
904
                // Description:
905
                if (!strlen($cfg['subst']['description'])) {
906
                    $descriptionField .= '
907
					' . htmlspecialchars($this->getLanguageService()->getLL('impexpcore_printerror_description')) . '<br/>
908
					<input type="text" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($this->softrefCfg[$cfg['subst']['tokenID']]['description']) . '" />';
909
                } else {
910
                    $descriptionField .= '
911
912
					<input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($cfg['subst']['description']) . '" />' . htmlspecialchars($cfg['subst']['description']);
913
                }
914
                // Default Value:
915
                $descriptionField .= '<input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][defValue]" value="' . htmlspecialchars($cfg['subst']['tokenValue']) . '" />';
916
            } else {
917
                $descriptionField = '';
918
            }
919
            return $selectorbox . $descriptionField;
920
        }
921
        return '';
922
    }
923
924
    /**
925
     * Verifies that the input path (relative to PATH_site) is found in the backend users filemounts.
926
     * If it doesn't it will try to find another relative filemount for the user and return an alternative path prefix for the file.
927
     *
928
     * @param string $dirPrefix Path relative to PATH_site
929
     * @param bool $noAlternative If set, Do not look for alternative path! Just return FALSE
930
     * @return string|bool If a path is available that will be returned, otherwise FALSE.
931
     */
932
    public function verifyFolderAccess($dirPrefix, $noAlternative = false)
933
    {
934
        // Check the absolute path for PATH_site, if the user has access - no problem
935
        try {
936
            ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($dirPrefix);
937
            return $dirPrefix;
938
        } catch (InsufficientFolderAccessPermissionsException $e) {
939
            // Check all storages available for the user as alternative
940
            if (!$noAlternative) {
941
                $fileStorages = $this->getBackendUser()->getFileStorages();
942
                foreach ($fileStorages as $fileStorage) {
943
                    try {
944
                        $folder = $fileStorage->getFolder(rtrim($dirPrefix, '/'));
945
                        return $folder->getPublicUrl();
946
                    } catch (InsufficientFolderAccessPermissionsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
947
                    }
948
                }
949
            }
950
        }
951
        return false;
952
    }
953
954
    /*****************************
955
     * Helper functions of kinds
956
     *****************************/
957
958
    /**
959
     * @return string
960
     */
961
    protected function getTemporaryFolderName()
962
    {
963
        $temporaryPath = PATH_site . 'typo3temp/var/transient/';
964
        do {
965
            $temporaryFolderName = $temporaryPath . 'export_temp_files_' . mt_rand(1, PHP_INT_MAX);
966
        } while (is_dir($temporaryFolderName));
967
        GeneralUtility::mkdir($temporaryFolderName);
968
        return $temporaryFolderName;
969
    }
970
971
    /**
972
     * Recursively flattening the idH array
973
     *
974
     * @param array $idH Page uid hierarchy
975
     * @param array $a Accumulation array of pages (internal, don't set from outside)
976
     * @return array Array with uid-uid pairs for all pages in the page tree.
977
     * @see Import::flatInversePageTree_pid()
978
     */
979
    public function flatInversePageTree($idH, $a = [])
980
    {
981
        if (is_array($idH)) {
982
            $idH = array_reverse($idH);
983
            foreach ($idH as $k => $v) {
984
                $a[$v['uid']] = $v['uid'];
985
                if (is_array($v['subrow'])) {
986
                    $a = $this->flatInversePageTree($v['subrow'], $a);
987
                }
988
            }
989
        }
990
        return $a;
991
    }
992
993
    /**
994
     * Returns TRUE if the input table name is to be regarded as a static relation (that is, not exported etc).
995
     *
996
     * @param string $table Table name
997
     * @return bool TRUE, if table is marked static
998
     */
999
    public function isTableStatic($table)
1000
    {
1001
        if (is_array($GLOBALS['TCA'][$table])) {
1002
            return $GLOBALS['TCA'][$table]['ctrl']['is_static'] || in_array($table, $this->relStaticTables) || in_array('_ALL', $this->relStaticTables);
1003
        }
1004
        return false;
1005
    }
1006
1007
    /**
1008
     * Returns TRUE if the input table name is to be included as relation
1009
     *
1010
     * @param string $table Table name
1011
     * @return bool TRUE, if table is marked static
1012
     */
1013
    public function inclRelation($table)
1014
    {
1015
        return is_array($GLOBALS['TCA'][$table])
1016
            && (in_array($table, $this->relOnlyTables) || in_array('_ALL', $this->relOnlyTables))
1017
            && $this->getBackendUser()->check('tables_select', $table);
1018
    }
1019
1020
    /**
1021
     * Returns TRUE if the element should be excluded as static record.
1022
     *
1023
     * @param string $table Table name
1024
     * @param int $uid UID value
1025
     * @return bool TRUE, if table is marked static
1026
     */
1027
    public function isExcluded($table, $uid)
1028
    {
1029
        return (bool)$this->excludeMap[$table . ':' . $uid];
1030
    }
1031
1032
    /**
1033
     * Returns TRUE if soft reference should be included in exported file.
1034
     *
1035
     * @param string $tokenID Token ID for soft reference
1036
     * @return bool TRUE if softreference media should be included
1037
     */
1038
    public function includeSoftref($tokenID)
1039
    {
1040
        $mode = $this->softrefCfg[$tokenID]['mode'];
1041
        return $tokenID && $mode !== 'exclude' && $mode !== 'editable';
1042
    }
1043
1044
    /**
1045
     * Checking if a PID is in the webmounts of the user
1046
     *
1047
     * @param int $pid Page ID to check
1048
     * @return bool TRUE if OK
1049
     */
1050
    public function checkPID($pid)
1051
    {
1052
        if (!isset($this->checkPID_cache[$pid])) {
1053
            $this->checkPID_cache[$pid] = (bool)$this->getBackendUser()->isInWebMount($pid);
1054
        }
1055
        return $this->checkPID_cache[$pid];
1056
    }
1057
1058
    /**
1059
     * Checks if the position of an updated record is configured to be corrected. This can be disabled globally and changed for elements individually.
1060
     *
1061
     * @param string $table Table name
1062
     * @param int $uid Uid or record
1063
     * @return bool TRUE if the position of the record should be updated to match the one in the import structure
1064
     */
1065
    public function dontIgnorePid($table, $uid)
1066
    {
1067
        return $this->import_mode[$table . ':' . $uid] !== 'ignore_pid' && (!$this->global_ignore_pid || $this->import_mode[$table . ':' . $uid] === 'respect_pid');
1068
    }
1069
1070
    /**
1071
     * Checks if the record exists
1072
     *
1073
     * @param string $table Table name
1074
     * @param int $uid UID of record
1075
     * @param string $fields Field list to select. Default is "uid,pid
1076
     * @return array Result of \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord() which means the record if found, otherwise FALSE
1077
     */
1078
    public function doesRecordExist($table, $uid, $fields = '')
1079
    {
1080
        return BackendUtility::getRecord($table, $uid, $fields ? $fields : 'uid,pid');
1081
    }
1082
1083
    /**
1084
     * Returns the page title path of a PID value. Results are cached internally
1085
     *
1086
     * @param int $pid Record PID to check
1087
     * @return string The path for the input PID
1088
     */
1089
    public function getRecordPath($pid)
1090
    {
1091
        if (!isset($this->cache_getRecordPath[$pid])) {
1092
            $clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
1093
            $this->cache_getRecordPath[$pid] = (string)BackendUtility::getRecordPath($pid, $clause, 20);
1094
        }
1095
        return $this->cache_getRecordPath[$pid];
1096
    }
1097
1098
    /**
1099
     * Makes a selector-box from optValues
1100
     *
1101
     * @param string $prefix Form element name
1102
     * @param string $value Current value
1103
     * @param array $optValues Options to display (key/value pairs)
1104
     * @return string HTML select element
1105
     */
1106
    public function renderSelectBox($prefix, $value, $optValues)
1107
    {
1108
        $opt = [];
1109
        $isSelFlag = 0;
1110
        foreach ($optValues as $k => $v) {
1111
            $sel = (string)$k === (string)$value ? ' selected="selected"' : '';
1112
            if ($sel) {
1113
                $isSelFlag++;
1114
            }
1115
            $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>';
1116
        }
1117
        if (!$isSelFlag && (string)$value !== '') {
1118
            $opt[] = '<option value="' . htmlspecialchars($value) . '" selected="selected">' . htmlspecialchars(('[\'' . $value . '\']')) . '</option>';
1119
        }
1120
        return '<select name="' . $prefix . '">' . implode('', $opt) . '</select>';
1121
    }
1122
1123
    /**
1124
     * Compares two records, the current database record and the one from the import memory.
1125
     * Will return HTML code to show any differences between them!
1126
     *
1127
     * @param array $databaseRecord Database record, all fields (new values)
1128
     * @param array $importRecord Import memorys record for the same table/uid, all fields (old values)
1129
     * @param string $table The table name of the record
1130
     * @param bool $inverseDiff Inverse the diff view (switch red/green, needed for pre-update difference view)
1131
     * @return string HTML
1132
     */
1133
    public function compareRecords($databaseRecord, $importRecord, $table, $inverseDiff = false)
1134
    {
1135
        // Initialize:
1136
        $output = [];
1137
        $diffUtility = GeneralUtility::makeInstance(DiffUtility::class);
1138
        // Check if both inputs are records:
1139
        if (is_array($databaseRecord) && is_array($importRecord)) {
1140
            // Traverse based on database record
1141
            foreach ($databaseRecord as $fN => $value) {
1142
                if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') {
1143
                    if (isset($importRecord[$fN])) {
1144
                        if (trim($databaseRecord[$fN]) !== trim($importRecord[$fN])) {
1145
                            // Create diff-result:
1146
                            $output[$fN] = $diffUtility->makeDiffDisplay(BackendUtility::getProcessedValue($table, $fN, !$inverseDiff ? $importRecord[$fN] : $databaseRecord[$fN], 0, 1, 1), BackendUtility::getProcessedValue($table, $fN, !$inverseDiff ? $databaseRecord[$fN] : $importRecord[$fN], 0, 1, 1));
1147
                        }
1148
                        unset($importRecord[$fN]);
1149
                    }
1150
                }
1151
            }
1152
            // Traverse remaining in import record:
1153
            foreach ($importRecord as $fN => $value) {
1154
                if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') {
1155
                    $output[$fN] = '<strong>Field missing</strong> in database';
1156
                }
1157
            }
1158
            // Create output:
1159
            if (!empty($output)) {
1160
                $tRows = [];
1161
                foreach ($output as $fN => $state) {
1162
                    $tRows[] = '
1163
						<tr>
1164
							<td>' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fN]['label'])) . ' (' . htmlspecialchars($fN) . ')</td>
1165
							<td>' . $state . '</td>
1166
						</tr>
1167
					';
1168
                }
1169
                $output = '<table class="table table-striped table-hover">' . implode('', $tRows) . '</table>';
1170
            } else {
1171
                $output = 'Match';
1172
            }
1173
            return '<strong class="text-nowrap">[' . htmlspecialchars(($table . ':' . $importRecord['uid'] . ' => ' . $databaseRecord['uid'])) . ']:</strong> ' . $output;
1174
        }
1175
        return 'ERROR: One of the inputs were not an array!';
1176
    }
1177
1178
    /**
1179
     * Creates the original file name for a copy-RTE image (magic type)
1180
     *
1181
     * @param string $string RTE copy filename, eg. "RTEmagicC_user_pm_icon_01.gif.gif
1182
     * @return string|null RTE original filename, eg. "RTEmagicP_user_pm_icon_01.gif". If the input filename was NOT prefixed RTEmagicC_ as RTE images would be, NULL is returned!
1183
     */
1184
    public function getRTEoriginalFilename($string)
1185
    {
1186
        // If "magic image":
1187
        if (GeneralUtility::isFirstPartOfStr($string, 'RTEmagicC_')) {
1188
            // Find original file:
1189
            $pI = pathinfo(substr($string, strlen('RTEmagicC_')));
1190
            $filename = substr($pI['basename'], 0, -strlen(('.' . $pI['extension'])));
1191
            $origFilePath = 'RTEmagicP_' . $filename;
1192
            return $origFilePath;
1193
        }
1194
        return null;
1195
    }
1196
1197
    /**
1198
     * Returns file processing object, initialized only once.
1199
     *
1200
     * @return ExtendedFileUtility File processor object
1201
     */
1202
    public function getFileProcObj()
1203
    {
1204
        if ($this->fileProcObj === null) {
1205
            $this->fileProcObj = GeneralUtility::makeInstance(ExtendedFileUtility::class);
1206
            $this->fileProcObj->setActionPermissions();
1207
        }
1208
        return $this->fileProcObj;
1209
    }
1210
1211
    /**
1212
     * Call Hook
1213
     *
1214
     * @param string $name Name of the hook
1215
     * @param array $params Array with params
1216
     */
1217
    public function callHook($name, $params)
1218
    {
1219
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/impexp/class.tx_impexp.php'][$name] ?? [] as $hook) {
1220
            GeneralUtility::callUserFunction($hook, $params, $this);
1221
        }
1222
    }
1223
1224
    /**
1225
     * Set flag to control whether disabled records and their children are excluded (true) or included (false). Defaults
1226
     * to the old behaviour of including everything.
1227
     *
1228
     * @param bool $excludeDisabledRecords Set to true if if all disabled records should be excluded, false otherwise
1229
     * @return \TYPO3\CMS\Impexp\ImportExport $this for fluent calls
1230
     */
1231
    public function setExcludeDisabledRecords($excludeDisabledRecords = false)
1232
    {
1233
        $this->excludeDisabledRecords = $excludeDisabledRecords;
1234
        return $this;
1235
    }
1236
1237
    /*****************************
1238
     * Error handling
1239
     *****************************/
1240
1241
    /**
1242
     * Sets error message in the internal error log
1243
     *
1244
     * @param string $msg Error message
1245
     */
1246
    public function error($msg)
1247
    {
1248
        $this->errorLog[] = $msg;
1249
    }
1250
1251
    /**
1252
     * Returns a table with the error-messages.
1253
     *
1254
     * @return string HTML print of error log
1255
     */
1256
    public function printErrorLog()
1257
    {
1258
        return !empty($this->errorLog) ? DebugUtility::viewArray($this->errorLog) : '';
1259
    }
1260
1261
    /**
1262
     * @return BackendUserAuthentication
1263
     */
1264
    protected function getBackendUser()
1265
    {
1266
        return $GLOBALS['BE_USER'];
1267
    }
1268
1269
    /**
1270
     * @return LanguageService
1271
     */
1272
    protected function getLanguageService()
1273
    {
1274
        return $GLOBALS['LANG'];
1275
    }
1276
}
1277