Test Failed
Branch master (7b1793)
by Tymoteusz
15:35
created

ImportExport::addRelations()   D

Complexity

Conditions 16
Paths 17

Size

Total Lines 48
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 39
nc 17
nop 5
dl 0
loc 48
rs 4.9765
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Utility\DebugUtility;
25
use TYPO3\CMS\Core\Utility\DiffUtility;
26
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
27
use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
use TYPO3\CMS\Core\Utility\PathUtility;
30
31
/**
32
 * EXAMPLE for using the impexp-class for exporting stuff:
33
 *
34
 * Create and initialize:
35
 * $this->export = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Impexp\ImportExport::class);
36
 * $this->export->init();
37
 * Set which tables relations we will allow:
38
 * $this->export->relOnlyTables[]="tt_news";	// exclusively includes. See comment in the class
39
 *
40
 * Adding records:
41
 * $this->export->export_addRecord("pages", $this->pageinfo);
42
 * $this->export->export_addRecord("pages", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("pages", 38));
43
 * $this->export->export_addRecord("pages", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("pages", 39));
44
 * $this->export->export_addRecord("tt_content", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("tt_content", 12));
45
 * $this->export->export_addRecord("tt_content", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("tt_content", 74));
46
 * $this->export->export_addRecord("sys_template", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("sys_template", 20));
47
 *
48
 * Adding all the relations (recursively in 5 levels so relations has THEIR relations registered as well)
49
 * for($a=0;$a<5;$a++) {
50
 * $addR = $this->export->export_addDBRelations($a);
51
 * if (empty($addR)) break;
52
 * }
53
 *
54
 * Finally load all the files.
55
 * $this->export->export_addFilesFromRelations();	// MUST be after the DBrelations are set so that file from ALL added records are included!
56
 *
57
 * Write export
58
 * $out = $this->export->compileMemoryToFileContent();
59
 */
60
61
/**
62
 * T3D file Import/Export library (TYPO3 Record Document)
63
 */
64
abstract class ImportExport
65
{
66
    /**
67
     * If set, static relations (not exported) will be shown in overview as well
68
     *
69
     * @var bool
70
     */
71
    public $showStaticRelations = false;
72
73
    /**
74
     * Name of the "fileadmin" folder where files for export/import should be located
75
     *
76
     * @var string
77
     */
78
    public $fileadminFolderName = '';
79
80
    /**
81
     * Whether "import" or "export" mode of object. Set through init() function
82
     *
83
     * @var string
84
     */
85
    public $mode = '';
86
87
    /**
88
     * Updates all records that has same UID instead of creating new!
89
     *
90
     * @var bool
91
     */
92
    public $update = false;
93
94
    /**
95
     * Is set by importData() when an import has been done.
96
     *
97
     * @var bool
98
     */
99
    public $doesImport = false;
100
101
    /**
102
     * If set to a page-record, then the preview display of the content will expect this page-record to be the target
103
     * for the import and accordingly display validation information. This triggers the visual view of the
104
     * import/export memory to validate if import is possible
105
     *
106
     * @var array
107
     */
108
    public $display_import_pid_record = [];
109
110
    /**
111
     * Setting import modes during update state: as_new, exclude, force_uid
112
     *
113
     * @var array
114
     */
115
    public $import_mode = [];
116
117
    /**
118
     * If set, PID correct is ignored globally
119
     *
120
     * @var bool
121
     */
122
    public $global_ignore_pid = false;
123
124
    /**
125
     * If set, all UID values are forced! (update or import)
126
     *
127
     * @var bool
128
     */
129
    public $force_all_UIDS = false;
130
131
    /**
132
     * If set, a diff-view column is added to the overview.
133
     *
134
     * @var bool
135
     */
136
    public $showDiff = false;
137
138
    /**
139
     * If set, and if the user is admin, allow the writing of PHP scripts to fileadmin/ area.
140
     *
141
     * @var bool
142
     */
143
    public $allowPHPScripts = false;
144
145
    /**
146
     * Array of values to substitute in editable softreferences.
147
     *
148
     * @var array
149
     */
150
    public $softrefInputValues = [];
151
152
    /**
153
     * Mapping between the fileID from import memory and the final filenames they are written to.
154
     *
155
     * @var array
156
     */
157
    public $fileIDMap = [];
158
159
    /**
160
     * Add table names here which are THE ONLY ones which will be included
161
     * into export if found as relations. '_ALL' will allow all tables.
162
     *
163
     * @var array
164
     */
165
    public $relOnlyTables = [];
166
167
    /**
168
     * Add tables names here which should not be exported with the file.
169
     * (Where relations should be mapped to same UIDs in target system).
170
     *
171
     * @var array
172
     */
173
    public $relStaticTables = [];
174
175
    /**
176
     * Exclude map. Keys are table:uid  pairs and if set, records are not added to the export.
177
     *
178
     * @var array
179
     */
180
    public $excludeMap = [];
181
182
    /**
183
     * Soft Reference Token ID modes.
184
     *
185
     * @var array
186
     */
187
    public $softrefCfg = [];
188
189
    /**
190
     * Listing extension dependencies.
191
     *
192
     * @var array
193
     */
194
    public $extensionDependencies = [];
195
196
    /**
197
     * After records are written this array is filled with [table][original_uid] = [new_uid]
198
     *
199
     * @var array
200
     */
201
    public $import_mapId = [];
202
203
    /**
204
     * Error log.
205
     *
206
     * @var array
207
     */
208
    public $errorLog = [];
209
210
    /**
211
     * Cache for record paths
212
     *
213
     * @var array
214
     */
215
    public $cache_getRecordPath = [];
216
217
    /**
218
     * Cache of checkPID values.
219
     *
220
     * @var array
221
     */
222
    public $checkPID_cache = [];
223
224
    /**
225
     * Set internally if the gzcompress function exists
226
     * Used by ImportExportController
227
     *
228
     * @var bool
229
     */
230
    public $compress = false;
231
232
    /**
233
     * Internal import/export memory
234
     *
235
     * @var array
236
     */
237
    public $dat = [];
238
239
    /**
240
     * File processing object
241
     *
242
     * @var ExtendedFileUtility
243
     */
244
    protected $fileProcObj = null;
245
246
    /**
247
     * @var array
248
     */
249
    protected $remainHeader = [];
250
251
    /**
252
     * @var IconFactory
253
     */
254
    protected $iconFactory;
255
256
    /**
257
     * Flag to control whether all disabled records and their children are excluded (true) or included (false). Defaults
258
     * to the old behaviour of including everything.
259
     *
260
     * @var bool
261
     */
262
    protected $excludeDisabledRecords = false;
263
264
    /**
265
     * The constructor
266
     */
267
    public function __construct()
268
    {
269
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
270
    }
271
272
    /**************************
273
     * Initialize
274
     *************************/
275
276
    /**
277
     * Init the object, both import and export
278
     */
279
    public function init()
280
    {
281
        $this->compress = function_exists('gzcompress');
282
        $this->fileadminFolderName = !empty($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir']) ? rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') : 'fileadmin';
283
    }
284
285
    /********************************************************
286
     * Visual rendering of import/export memory, $this->dat
287
     ********************************************************/
288
289
    /**
290
     * Displays an overview of the header-content.
291
     *
292
     * @return array The view data
293
     */
294
    public function displayContentOverview()
295
    {
296
        if (!isset($this->dat['header'])) {
297
            return [];
298
        }
299
        // Check extension dependencies:
300
        foreach ($this->dat['header']['extensionDependencies'] as $extKey) {
301
            if (!empty($extKey) && !ExtensionManagementUtility::isLoaded($extKey)) {
302
                $this->error('DEPENDENCY: The extension with key "' . $extKey . '" must be installed!');
303
            }
304
        }
305
306
        // Probably this is done to save memory space?
307
        unset($this->dat['files']);
308
309
        $viewData = [];
310
        // Traverse header:
311
        $this->remainHeader = $this->dat['header'];
312
        // If there is a page tree set, show that:
313
        if (is_array($this->dat['header']['pagetree'])) {
314
            reset($this->dat['header']['pagetree']);
315
            $lines = [];
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 View Code Duplication
            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'] = [];
330
            }
331
        }
332
        // Print remaining records that were not contained inside the page tree:
333
        if (is_array($this->remainHeader['records'])) {
334
            $lines = [];
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 View Code Duplication
            if (!empty($lines)) {
340
                foreach ($lines as &$r) {
341
                    $r['controls'] = $this->renderControls($r);
342
                    $r['fileSize'] = GeneralUtility::formatSize($r['size']);
343
                    $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
344
                }
345
                $viewData['remainingRecords'] = $lines;
346
            }
347
        }
348
349
        return $viewData;
350
    }
351
352
    /**
353
     * Go through page tree for display
354
     *
355
     * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
356
     * @param array $lines Output lines array (is passed by reference and modified)
357
     * @param string $preCode Pre-HTML code
358
     */
359
    public function traversePageTree($pT, &$lines, $preCode = '')
360
    {
361
        foreach ($pT as $k => $v) {
362
            if ($this->excludeDisabledRecords === true && !$this->isActive('pages', $k)) {
363
                $this->excludePageAndRecords($k, $v);
364
                continue;
365
            }
366
367
            // Add this page:
368
            $this->singleRecordLines('pages', $k, $lines, $preCode);
369
            // Subrecords:
370 View Code Duplication
            if (is_array($this->dat['header']['pid_lookup'][$k])) {
371
                foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) {
372
                    if ($t !== 'pages') {
373
                        foreach ($recUidArr as $ruid => $value) {
374
                            $this->singleRecordLines($t, $ruid, $lines, $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;');
375
                        }
376
                    }
377
                }
378
                unset($this->remainHeader['pid_lookup'][$k]);
379
            }
380
            // Subpages, called recursively:
381
            if (is_array($v['subrow'])) {
382
                $this->traversePageTree($v['subrow'], $lines, $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;');
383
            }
384
        }
385
    }
386
387
    /**
388
     * Test whether a record is active (i.e. not hidden)
389
     *
390
     * @param string $table Name of the records' database table
391
     * @param int $uid Database uid of the record
392
     * @return bool true if the record is active, false otherwise
393
     */
394
    protected function isActive($table, $uid)
395
    {
396
        return
397
            !isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])
398
            || !(bool)$this->dat['records'][$table . ':' . $uid]['data'][
399
                $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']
400
            ];
401
    }
402
403
    /**
404
     * Exclude a page, its sub pages (recursively) and records placed in them from this import/export
405
     *
406
     * @param int $pageUid Uid of the page to exclude
407
     * @param array $pageTree Page tree array with uid/subrow (from ->dat[header][pagetree]
408
     */
409
    protected function excludePageAndRecords($pageUid, $pageTree)
410
    {
411
        // Prevent having this page appear in "remaining records" table
412
        unset($this->remainHeader['records']['pages'][$pageUid]);
413
414
        // Subrecords
415
        if (is_array($this->dat['header']['pid_lookup'][$pageUid])) {
416
            foreach ($this->dat['header']['pid_lookup'][$pageUid] as $table => $recordData) {
417
                if ($table !== 'pages') {
418
                    foreach (array_keys($recordData) as $uid) {
419
                        unset($this->remainHeader['records'][$table][$uid]);
420
                    }
421
                }
422
            }
423
            unset($this->remainHeader['pid_lookup'][$pageUid]);
424
        }
425
        // Subpages excluded recursively
426
        if (is_array($pageTree['subrow'])) {
427
            foreach ($pageTree['subrow'] as $subPageUid => $subPageTree) {
428
                $this->excludePageAndRecords($subPageUid, $subPageTree);
429
            }
430
        }
431
    }
432
433
    /**
434
     * Go through remaining pages (not in tree)
435
     *
436
     * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
437
     * @param array $lines Output lines array (is passed by reference and modified)
438
     */
439
    public function traversePageRecords($pT, &$lines)
440
    {
441
        foreach ($pT as $k => $rHeader) {
442
            $this->singleRecordLines('pages', $k, $lines, '', 1);
443
            // Subrecords:
444 View Code Duplication
            if (is_array($this->dat['header']['pid_lookup'][$k])) {
445
                foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) {
446
                    if ($t !== 'pages') {
447
                        foreach ($recUidArr as $ruid => $value) {
448
                            $this->singleRecordLines($t, $ruid, $lines, '&nbsp;&nbsp;&nbsp;&nbsp;');
449
                        }
450
                    }
451
                }
452
                unset($this->remainHeader['pid_lookup'][$k]);
453
            }
454
        }
455
    }
456
457
    /**
458
     * Go through ALL records (if the pages are displayed first, those will not be among these!)
459
     *
460
     * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
461
     * @param array $lines Output lines array (is passed by reference and modified)
462
     */
463
    public function traverseAllRecords($pT, &$lines)
464
    {
465
        foreach ($pT as $t => $recUidArr) {
466
            $this->addGeneralErrorsByTable($t);
467
            if ($t !== 'pages') {
468
                $preCode = '';
469
                foreach ($recUidArr as $ruid => $value) {
470
                    $this->singleRecordLines($t, $ruid, $lines, $preCode, 1);
471
                }
472
            }
473
        }
474
    }
475
476
    /**
477
     * Log general error message for a given table
478
     *
479
     * @param string $table database table name
480
     */
481
    protected function addGeneralErrorsByTable($table)
482
    {
483
        if ($this->update && $table === 'sys_file') {
484
            $this->error('Updating sys_file records is not supported! They will be imported as new records!');
485
        }
486
        if ($this->force_all_UIDS && $table === 'sys_file') {
487
            $this->error('Forcing uids of sys_file records is not supported! They will be imported as new records!');
488
        }
489
    }
490
491
    /**
492
     * Add entries for a single record
493
     *
494
     * @param string $table Table name
495
     * @param int $uid Record uid
496
     * @param array $lines Output lines array (is passed by reference and modified)
497
     * @param string $preCode Pre-HTML code
498
     * @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.
499
     */
500
    public function singleRecordLines($table, $uid, &$lines, $preCode, $checkImportInPidRecord = false)
501
    {
502
        // Get record:
503
        $record = $this->dat['header']['records'][$table][$uid];
504
        unset($this->remainHeader['records'][$table][$uid]);
505
        if (!is_array($record) && !($table === 'pages' && !$uid)) {
506
            $this->error('MISSING RECORD: ' . $table . ':' . $uid);
507
        }
508
        // Begin to create the line arrays information record, pInfo:
509
        $pInfo = [];
510
        $pInfo['ref'] = $table . ':' . $uid;
511
        // Unknown table name:
512
        $lang = $this->getLanguageService();
513
        if ($table === '_SOFTREF_') {
514
            $pInfo['preCode'] = $preCode;
515
            $pInfo['title'] = '<em>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_softReferencesFiles')) . '</em>';
516
        } elseif (!isset($GLOBALS['TCA'][$table])) {
517
            // Unknown table name:
518
            $pInfo['preCode'] = $preCode;
519
            $pInfo['msg'] = 'UNKNOWN TABLE \'' . $pInfo['ref'] . '\'';
520
            $pInfo['title'] = '<em>' . htmlspecialchars($record['title']) . '</em>';
521
        } else {
522
            // prepare data attribute telling whether the record is active or hidden, allowing frontend bulk selection
523
            $pInfo['active'] = $this->isActive($table, $uid) ? 'active' : 'hidden';
524
525
            // Otherwise, set table icon and title.
526
            // Import Validation (triggered by $this->display_import_pid_record) will show messages if import is not possible of various items.
527
            if (is_array($this->display_import_pid_record) && !empty($this->display_import_pid_record)) {
528
                if ($checkImportInPidRecord) {
529
                    if (!$this->getBackendUser()->doesUserHaveAccess($this->display_import_pid_record, ($table === 'pages' ? 8 : 16))) {
530
                        $pInfo['msg'] .= '\'' . $pInfo['ref'] . '\' cannot be INSERTED on this page! ';
531
                    }
532
                    if (!$this->checkDokType($table, $this->display_import_pid_record['doktype']) && !$GLOBALS['TCA'][$table]['ctrl']['rootLevel']) {
533
                        $pInfo['msg'] .= '\'' . $table . '\' cannot be INSERTED on this page type (change page type to \'Folder\'.) ';
534
                    }
535
                }
536
                if (!$this->getBackendUser()->check('tables_modify', $table)) {
537
                    $pInfo['msg'] .= 'You are not allowed to CREATE \'' . $table . '\' tables! ';
538
                }
539
                if ($GLOBALS['TCA'][$table]['ctrl']['readOnly']) {
540
                    $pInfo['msg'] .= 'TABLE \'' . $table . '\' is READ ONLY! ';
541
                }
542
                if ($GLOBALS['TCA'][$table]['ctrl']['adminOnly'] && !$this->getBackendUser()->isAdmin()) {
543
                    $pInfo['msg'] .= 'TABLE \'' . $table . '\' is ADMIN ONLY! ';
544
                }
545
                if ($GLOBALS['TCA'][$table]['ctrl']['is_static']) {
546
                    $pInfo['msg'] .= 'TABLE \'' . $table . '\' is a STATIC TABLE! ';
547
                }
548
                if ((int)$GLOBALS['TCA'][$table]['ctrl']['rootLevel'] === 1) {
549
                    $pInfo['msg'] .= 'TABLE \'' . $table . '\' will be inserted on ROOT LEVEL! ';
550
                }
551
                $diffInverse = false;
552
                $recInf = null;
553
                if ($this->update) {
554
                    // In case of update-PREVIEW we swap the diff-sources.
555
                    $diffInverse = true;
556
                    $recInf = $this->doesRecordExist($table, $uid, $this->showDiff ? '*' : '');
557
                    $pInfo['updatePath'] = $recInf ? htmlspecialchars($this->getRecordPath($recInf['pid'])) : '<strong>NEW!</strong>';
558
                    // Mode selector:
559
                    $optValues = [];
560
                    $optValues[] = $recInf ? $lang->getLL('impexpcore_singlereco_update') : $lang->getLL('impexpcore_singlereco_insert');
561
                    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...
562
                        $optValues['as_new'] = $lang->getLL('impexpcore_singlereco_importAsNew');
563
                    }
564
                    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...
565
                        if (!$this->global_ignore_pid) {
566
                            $optValues['ignore_pid'] = $lang->getLL('impexpcore_singlereco_ignorePid');
567
                        } else {
568
                            $optValues['respect_pid'] = $lang->getLL('impexpcore_singlereco_respectPid');
569
                        }
570
                    }
571
                    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...
572
                        $optValues['force_uid'] = sprintf($lang->getLL('impexpcore_singlereco_forceUidSAdmin'), $uid);
573
                    }
574
                    $optValues['exclude'] = $lang->getLL('impexpcore_singlereco_exclude');
575
                    if ($table === 'sys_file') {
576
                        $pInfo['updateMode'] = '';
577
                    } else {
578
                        $pInfo['updateMode'] = $this->renderSelectBox('tx_impexp[import_mode][' . $table . ':' . $uid . ']', $this->import_mode[$table . ':' . $uid], $optValues);
579
                    }
580
                }
581
                // Diff view:
582
                if ($this->showDiff) {
583
                    // For IMPORTS, get new id:
584
                    if ($newUid = $this->import_mapId[$table][$uid]) {
585
                        $diffInverse = false;
586
                        $recInf = $this->doesRecordExist($table, $newUid, '*');
587
                        BackendUtility::workspaceOL($table, $recInf);
588
                    }
589
                    if (is_array($recInf)) {
590
                        $pInfo['showDiffContent'] = $this->compareRecords($recInf, $this->dat['records'][$table . ':' . $uid]['data'], $table, $diffInverse);
591
                    }
592
                }
593
            }
594
            $pInfo['preCode'] = $preCode . '<span title="' . htmlspecialchars($table . ':' . $uid) . '">'
595
                . $this->iconFactory->getIconForRecord($table, (array)$this->dat['records'][$table . ':' . $uid]['data'], Icon::SIZE_SMALL)->render()
596
                . '</span>';
597
            $pInfo['title'] = htmlspecialchars($record['title']);
598
            // View page:
599
            if ($table === 'pages') {
600
                $viewID = $this->mode === 'export' ? $uid : ($this->doesImport ? $this->import_mapId['pages'][$uid] : 0);
601
                if ($viewID) {
602
                    $pInfo['title'] = '<a href="#" onclick="' . htmlspecialchars(BackendUtility::viewOnClick($viewID)) . 'return false;">' . $pInfo['title'] . '</a>';
603
                }
604
            }
605
        }
606
        $pInfo['type'] = 'record';
607
        $pInfo['size'] = (int)$record['size'];
608
        $lines[] = $pInfo;
609
        // File relations:
610
        if (is_array($record['filerefs'])) {
611
            $this->addFiles($record['filerefs'], $lines, $preCode);
612
        }
613
        // DB relations
614
        if (is_array($record['rels'])) {
615
            $this->addRelations($record['rels'], $lines, $preCode);
616
        }
617
        // Soft ref
618
        if (!empty($record['softrefs'])) {
619
            $preCode_A = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;';
620
            $preCode_B = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
621
            foreach ($record['softrefs'] as $info) {
622
                $pInfo = [];
623
                $pInfo['preCode'] = $preCode_A . $this->iconFactory->getIcon('status-reference-soft', Icon::SIZE_SMALL)->render();
624
                $pInfo['title'] = '<em>' . $info['field'] . ', "' . $info['spKey'] . '" </em>: <span title="' . htmlspecialchars($info['matchString']) . '">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['matchString'], 60)) . '</span>';
625
                if ($info['subst']['type']) {
626 View Code Duplication
                    if (strlen($info['subst']['title'])) {
627
                        $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_title')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['title'], 60));
628
                    }
629 View Code Duplication
                    if (strlen($info['subst']['description'])) {
630
                        $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_descr')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['description'], 60));
631
                    }
632
                    $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>' : '');
633
                }
634
                $pInfo['ref'] = 'SOFTREF';
635
                $pInfo['size'] = 0;
636
                $pInfo['type'] = 'softref';
637
                $pInfo['_softRefInfo'] = $info;
638
                $pInfo['type'] = 'softref';
639
                $mode = $this->softrefCfg[$info['subst']['tokenID']]['mode'];
640
                if ($info['error'] && $mode !== 'editable' && $mode !== 'exclude') {
641
                    $pInfo['msg'] .= $info['error'];
642
                }
643
                $lines[] = $pInfo;
644
                // Add relations:
645
                if ($info['subst']['type'] === 'db') {
646
                    list($tempTable, $tempUid) = explode(':', $info['subst']['recordRef']);
647
                    $this->addRelations([['table' => $tempTable, 'id' => $tempUid, 'tokenID' => $info['subst']['tokenID']]], $lines, $preCode_B, [], '');
648
                }
649
                // Add files:
650
                if ($info['subst']['type'] === 'file') {
651
                    $this->addFiles([$info['file_ID']], $lines, $preCode_B, '', $info['subst']['tokenID']);
652
                }
653
            }
654
        }
655
    }
656
657
    /**
658
     * Add DB relations entries for a record's rels-array
659
     *
660
     * @param array $rels Array of relations
661
     * @param array $lines Output lines array (is passed by reference and modified)
662
     * @param string $preCode Pre-HTML code
663
     * @param array $recurCheck Recursivity check stack
664
     * @param string $htmlColorClass Alternative HTML color class to use.
665
     * @access private
666
     * @see singleRecordLines()
667
     */
668
    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

668
    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...
669
    {
670
        foreach ($rels as $dat) {
671
            $table = $dat['table'];
672
            $uid = $dat['id'];
673
            $pInfo = [];
674
            $pInfo['ref'] = $table . ':' . $uid;
675
            if (in_array($pInfo['ref'], $recurCheck)) {
676
                continue;
677
            }
678
            $iconName = 'status-status-checked';
679
            $iconClass = '';
680
            $staticFixed = false;
681
            $record = null;
682
            if ($uid > 0) {
683
                $record = $this->dat['header']['records'][$table][$uid];
684
                if (!is_array($record)) {
685
                    if ($this->isTableStatic($table) || $this->isExcluded($table, $uid) || $dat['tokenID'] && !$this->includeSoftref($dat['tokenID'])) {
686
                        $pInfo['title'] = htmlspecialchars('STATIC: ' . $pInfo['ref']);
687
                        $iconClass = 'text-info';
688
                        $staticFixed = true;
689
                    } else {
690
                        $doesRE = $this->doesRecordExist($table, $uid);
691
                        $lostPath = $this->getRecordPath($table === 'pages' ? $doesRE['uid'] : $doesRE['pid']);
692
                        $pInfo['title'] = htmlspecialchars($pInfo['ref']);
693
                        $pInfo['title'] = '<span title="' . htmlspecialchars($lostPath) . '">' . $pInfo['title'] . '</span>';
694
                        $pInfo['msg'] = 'LOST RELATION' . (!$doesRE ? ' (Record not found!)' : ' (Path: ' . $lostPath . ')');
695
                        $iconClass = 'text-danger';
696
                        $iconName = 'status-dialog-warning';
697
                    }
698
                } else {
699
                    $pInfo['title'] = htmlspecialchars($record['title']);
700
                    $pInfo['title'] = '<span title="' . htmlspecialchars($this->getRecordPath(($table === 'pages' ? $record['uid'] : $record['pid']))) . '">' . $pInfo['title'] . '</span>';
701
                }
702
            } else {
703
                // 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
704
                $pInfo['title'] = htmlspecialchars('FIXED: ' . $pInfo['ref']);
705
                $staticFixed = true;
706
            }
707
708
            $icon = '<span class="' . $iconClass . '" title="' . htmlspecialchars($pInfo['ref']) . '">' . $this->iconFactory->getIcon($iconName, Icon::SIZE_SMALL)->render() . '</span>';
709
710
            $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $icon;
711
            $pInfo['type'] = 'rel';
712
            if (!$staticFixed || $this->showStaticRelations) {
713
                $lines[] = $pInfo;
714
                if (is_array($record) && is_array($record['rels'])) {
715
                    $this->addRelations($record['rels'], $lines, $preCode . '&nbsp;&nbsp;', array_merge($recurCheck, [$pInfo['ref']]));
716
                }
717
            }
718
        }
719
    }
720
721
    /**
722
     * Add file relation entries for a record's rels-array
723
     *
724
     * @param array $rels Array of file IDs
725
     * @param array $lines Output lines array (is passed by reference and modified)
726
     * @param string $preCode Pre-HTML code
727
     * @param string $htmlColorClass Alternative HTML color class to use.
728
     * @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!)
729
     * @access private
730
     * @see singleRecordLines()
731
     */
732
    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

732
    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...
733
    {
734
        foreach ($rels as $ID) {
735
            // Process file:
736
            $pInfo = [];
737
            $fI = $this->dat['header']['files'][$ID];
738
            if (!is_array($fI)) {
739
                if (!$tokenID || $this->includeSoftref($tokenID)) {
740
                    $pInfo['msg'] = 'MISSING FILE: ' . $ID;
741
                    $this->error('MISSING FILE: ' . $ID);
742
                } else {
743
                    return;
744
                }
745
            }
746
            $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $this->iconFactory->getIcon('status-reference-hard', Icon::SIZE_SMALL)->render();
747
            $pInfo['title'] = htmlspecialchars($fI['filename']);
748
            $pInfo['ref'] = 'FILE';
749
            $pInfo['size'] = $fI['filesize'];
750
            $pInfo['type'] = 'file';
751
            // If import mode and there is a non-RTE softreference, check the destination directory:
752
            if ($this->mode === 'import' && $tokenID && !$fI['RTE_ORIG_ID']) {
753
                if (isset($fI['parentRelFileName'])) {
754
                    $pInfo['msg'] = 'Seems like this file is already referenced from within an HTML/CSS file. That takes precedence. ';
755
                } else {
756
                    $testDirPrefix = PathUtility::dirname($fI['relFileName']) . '/';
757
                    $testDirPrefix2 = $this->verifyFolderAccess($testDirPrefix);
758
                    if (!$testDirPrefix2) {
759
                        $pInfo['msg'] = 'ERROR: There are no available filemounts to write file in! ';
760
                    } elseif ($testDirPrefix !== $testDirPrefix2) {
761
                        $pInfo['msg'] = 'File will be attempted written to "' . $testDirPrefix2 . '". ';
762
                    }
763
                }
764
                // Check if file exists:
765
                if (file_exists(PATH_site . $fI['relFileName'])) {
766
                    if ($this->update) {
767
                        $pInfo['updatePath'] .= 'File exists.';
768
                    } else {
769
                        $pInfo['msg'] .= 'File already exists! ';
770
                    }
771
                }
772
                // Check extension:
773
                $fileProcObj = $this->getFileProcObj();
774
                if ($fileProcObj->actionPerms['addFile']) {
775
                    $testFI = GeneralUtility::split_fileref(PATH_site . $fI['relFileName']);
776
                    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

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

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

1188
            $pI = pathinfo(/** @scrutinizer ignore-type */ substr($string, strlen('RTEmagicC_')));
Loading history...
1189
            $filename = substr($pI['basename'], 0, -strlen(('.' . $pI['extension'])));
1190
            $origFilePath = 'RTEmagicP_' . $filename;
0 ignored issues
show
Bug introduced by
Are you sure $filename of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

1190
            $origFilePath = 'RTEmagicP_' . /** @scrutinizer ignore-type */ $filename;
Loading history...
1191
            return $origFilePath;
1192
        }
1193
        return null;
1194
    }
1195
1196
    /**
1197
     * Returns file processing object, initialized only once.
1198
     *
1199
     * @return ExtendedFileUtility File processor object
1200
     */
1201
    public function getFileProcObj()
1202
    {
1203
        if ($this->fileProcObj === null) {
1204
            $this->fileProcObj = GeneralUtility::makeInstance(ExtendedFileUtility::class);
1205
            $this->fileProcObj->setActionPermissions();
1206
        }
1207
        return $this->fileProcObj;
1208
    }
1209
1210
    /**
1211
     * Call Hook
1212
     *
1213
     * @param string $name Name of the hook
1214
     * @param array $params Array with params
1215
     */
1216
    public function callHook($name, $params)
1217
    {
1218
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/impexp/class.tx_impexp.php'][$name] ?? [] as $hook) {
1219
            GeneralUtility::callUserFunction($hook, $params, $this);
1220
        }
1221
    }
1222
1223
    /**
1224
     * Set flag to control whether disabled records and their children are excluded (true) or included (false). Defaults
1225
     * to the old behaviour of including everything.
1226
     *
1227
     * @param bool $excludeDisabledRecords Set to true if if all disabled records should be excluded, false otherwise
1228
     * @return \TYPO3\CMS\Impexp\ImportExport $this for fluent calls
1229
     */
1230
    public function setExcludeDisabledRecords($excludeDisabledRecords = false)
1231
    {
1232
        $this->excludeDisabledRecords = $excludeDisabledRecords;
1233
        return $this;
1234
    }
1235
1236
    /*****************************
1237
     * Error handling
1238
     *****************************/
1239
1240
    /**
1241
     * Sets error message in the internal error log
1242
     *
1243
     * @param string $msg Error message
1244
     */
1245
    public function error($msg)
1246
    {
1247
        $this->errorLog[] = $msg;
1248
    }
1249
1250
    /**
1251
     * Returns a table with the error-messages.
1252
     *
1253
     * @return string HTML print of error log
1254
     */
1255
    public function printErrorLog()
1256
    {
1257
        return !empty($this->errorLog) ? DebugUtility::viewArray($this->errorLog) : '';
1258
    }
1259
1260
    /**
1261
     * @return BackendUserAuthentication
1262
     */
1263
    protected function getBackendUser()
1264
    {
1265
        return $GLOBALS['BE_USER'];
1266
    }
1267
1268
    /**
1269
     * @return LanguageService
1270
     */
1271
    protected function getLanguageService()
1272
    {
1273
        return $GLOBALS['LANG'];
1274
    }
1275
}
1276