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

Import::fixUidLocalInSysFileReferenceRecords()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 4
nop 2
dl 0
loc 11
rs 9.2
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\Configuration\FlexForm\FlexFormTools;
19
use TYPO3\CMS\Core\Database\ConnectionPool;
20
use TYPO3\CMS\Core\DataHandling\DataHandler;
21
use TYPO3\CMS\Core\Exception;
22
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
23
use TYPO3\CMS\Core\Resource\File;
24
use TYPO3\CMS\Core\Resource\FileInterface;
25
use TYPO3\CMS\Core\Resource\ResourceFactory;
26
use TYPO3\CMS\Core\Resource\ResourceStorage;
27
use TYPO3\CMS\Core\Resource\StorageRepository;
28
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
use TYPO3\CMS\Core\Utility\MathUtility;
31
use TYPO3\CMS\Core\Utility\PathUtility;
32
use TYPO3\CMS\Core\Utility\StringUtility;
33
34
/**
35
 * T3D file Import library (TYPO3 Record Document)
36
 */
37
class Import extends ImportExport
38
{
39
    /**
40
     * Used to register the forged UID values for imported records that we want
41
     * to create with the same UIDs as in the import file. Admin-only feature.
42
     *
43
     * @var array
44
     */
45
    public $suggestedInsertUids = [];
46
47
    /**
48
     * Disable logging when importing
49
     *
50
     * @var bool
51
     */
52
    public $enableLogging = false;
53
54
    /**
55
     * Keys are [tablename]:[new NEWxxx ids (or when updating it is uids)]
56
     * while values are arrays with table/uid of the original record it is based on.
57
     * With the array keys the new ids can be looked up inside DataHandler
58
     *
59
     * @var array
60
     */
61
    public $import_newId = [];
62
63
    /**
64
     * Page id map for page tree (import)
65
     *
66
     * @var array
67
     */
68
    public $import_newId_pids = [];
69
70
    /**
71
     * Internal data accumulation for writing records during import
72
     *
73
     * @var array
74
     */
75
    public $import_data = [];
76
77
    /**
78
     * Array of current registered storage objects
79
     *
80
     * @var ResourceStorage[]
81
     */
82
    protected $storageObjects = [];
83
84
    /**
85
     * @var string|null
86
     */
87
    protected $filesPathForImport = null;
88
89
    /**
90
     * @var array
91
     */
92
    protected $unlinkFiles = [];
93
94
    /**
95
     * @var array
96
     */
97
    protected $alternativeFileName = [];
98
99
    /**
100
     * @var array
101
     */
102
    protected $alternativeFilePath = [];
103
104
    /**
105
     * @var array
106
     */
107
    protected $filePathMap = [];
108
109
    /**************************
110
     * Initialize
111
     *************************/
112
113
    /**
114
     * Init the object
115
     */
116
    public function init()
117
    {
118
        parent::init();
119
        $this->mode = 'import';
120
    }
121
122
    /***********************
123
     * Import
124
     ***********************/
125
126
    /**
127
     * Initialize all settings for the import
128
     */
129
    protected function initializeImport()
130
    {
131
        // Set this flag to indicate that an import is being/has been done.
132
        $this->doesImport = 1;
0 ignored issues
show
Documentation Bug introduced by
The property $doesImport was declared of type boolean, but 1 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
133
        // Initialize:
134
        // These vars MUST last for the whole section not being cleared. They are used by the method setRelations() which are called at the end of the import session.
135
        $this->import_mapId = [];
136
        $this->import_newId = [];
137
        $this->import_newId_pids = [];
138
        // Temporary files stack initialized:
139
        $this->unlinkFiles = [];
140
        $this->alternativeFileName = [];
141
        $this->alternativeFilePath = [];
142
143
        $this->initializeStorageObjects();
144
    }
145
146
    /**
147
     * Initialize the all present storage objects
148
     */
149
    protected function initializeStorageObjects()
150
    {
151
        /** @var $storageRepository StorageRepository */
152
        $storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
153
        $this->storageObjects = $storageRepository->findAll();
154
    }
155
156
    /**
157
     * Imports the internal data array to $pid.
158
     *
159
     * @param int $pid Page ID in which to import the content
160
     */
161
    public function importData($pid)
162
    {
163
        $this->initializeImport();
164
165
        // Write sys_file_storages first
166
        $this->writeSysFileStorageRecords();
167
        // Write sys_file records and write the binary file data
168
        $this->writeSysFileRecords();
169
        // Write records, first pages, then the rest
170
        // Fields with "hard" relations to database, files and flexform fields are kept empty during this run
171
        $this->writeRecords_pages($pid);
172
        $this->writeRecords_records($pid);
173
        // Finally all the file and DB record references must be fixed. This is done after all records have supposedly been written to database:
174
        // $this->import_mapId will indicate two things: 1) that a record WAS written to db and 2) that it has got a new id-number.
175
        $this->setRelations();
176
        // And when all DB relations are in place, we can fix file and DB relations in flexform fields (since data structures often depends on relations to a DS record):
177
        $this->setFlexFormRelations();
178
        // Unlink temporary files:
179
        $this->unlinkTempFiles();
180
        // Finally, traverse all records and process softreferences with substitution attributes.
181
        $this->processSoftReferences();
182
    }
183
184
    /**
185
     * Imports the sys_file_storage records from internal data array.
186
     */
187
    protected function writeSysFileStorageRecords()
188
    {
189
        if (!isset($this->dat['header']['records']['sys_file_storage'])) {
190
            return;
191
        }
192
        $sysFileStorageUidsToBeResetToDefaultStorage = [];
193
        foreach ($this->dat['header']['records']['sys_file_storage'] as $sysFileStorageUid => $_) {
194
            $storageRecord = $this->dat['records']['sys_file_storage:' . $sysFileStorageUid]['data'];
195
            // continue with Local, writable and online storage only
196
            if ($storageRecord['driver'] === 'Local' && $storageRecord['is_writable'] && $storageRecord['is_online']) {
197
                foreach ($this->storageObjects as $localStorage) {
198
                    if ($this->isEquivalentObjectStorage($localStorage, $storageRecord)) {
199
                        $this->import_mapId['sys_file_storage'][$sysFileStorageUid] = $localStorage->getUid();
200
                        break;
201
                    }
202
                }
203
204
                if (!isset($this->import_mapId['sys_file_storage'][$sysFileStorageUid])) {
205
                    // Local, writable and online storage. Is allowed to be used to later write files in.
206
                    // Does currently not exist so add the record.
207
                    $this->addSingle('sys_file_storage', $sysFileStorageUid, 0);
208
                }
209
            } else {
210
                // Storage with non Local drivers could be imported but must not be used to saves files in, because you
211
                // could not be sure, that this is supported. The default storage will be used in this case.
212
                // It could happen that non writable and non online storage will be created as dupes because you could not
213
                // check the detailed configuration options at this point
214
                $this->addSingle('sys_file_storage', $sysFileStorageUid, 0);
215
                $sysFileStorageUidsToBeResetToDefaultStorage[] = $sysFileStorageUid;
216
            }
217
        }
218
219
        // Importing the added ones
220
        $tce = $this->getNewTCE();
221
        // Because all records are being submitted in their correct order with positive pid numbers - and so we should reverse submission order internally.
222
        $tce->reverseOrder = 1;
0 ignored issues
show
Documentation Bug introduced by
The property $reverseOrder was declared of type boolean, but 1 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
223
        $tce->isImporting = true;
224
        $tce->start($this->import_data, []);
225
        $tce->process_datamap();
226
        $this->addToMapId($tce->substNEWwithIDs);
227
228
        $defaultStorageUid = null;
229
        // get default storage
230
        $defaultStorage = ResourceFactory::getInstance()->getDefaultStorage();
231
        if ($defaultStorage !== null) {
232
            $defaultStorageUid = $defaultStorage->getUid();
233
        }
234
        foreach ($sysFileStorageUidsToBeResetToDefaultStorage as $sysFileStorageUidToBeResetToDefaultStorage) {
235
            $this->import_mapId['sys_file_storage'][$sysFileStorageUidToBeResetToDefaultStorage] = $defaultStorageUid;
236
        }
237
238
        // unset the sys_file_storage records to prevent an import in writeRecords_records
239
        unset($this->dat['header']['records']['sys_file_storage']);
240
    }
241
242
    /**
243
     * Determines whether the passed storage object and record (sys_file_storage) can be
244
     * seen as equivalent during import.
245
     *
246
     * @param ResourceStorage $storageObject The storage object which should get compared
247
     * @param array $storageRecord The storage record which should get compared
248
     * @return bool Returns TRUE when both object storages can be seen as equivalent
249
     */
250
    protected function isEquivalentObjectStorage(ResourceStorage $storageObject, array $storageRecord)
251
    {
252
        // compare the properties: driver, writable and online
253
        if ($storageObject->getDriverType() === $storageRecord['driver']
254
            && (bool)$storageObject->isWritable() === (bool)$storageRecord['is_writable']
255
            && (bool)$storageObject->isOnline() === (bool)$storageRecord['is_online']
256
        ) {
257
            $storageRecordConfiguration = ResourceFactory::getInstance()->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
258
            $storageObjectConfiguration = $storageObject->getConfiguration();
259
            // compare the properties: pathType and basePath
260
            if ($storageRecordConfiguration['pathType'] === $storageObjectConfiguration['pathType']
261
                && $storageRecordConfiguration['basePath'] === $storageObjectConfiguration['basePath']
262
            ) {
263
                return true;
264
            }
265
        }
266
        return false;
267
    }
268
269
    /**
270
     * Checks any prerequisites necessary to get fulfilled before import
271
     *
272
     * @return array Messages explaining issues which need to get resolved before import
273
     */
274
    public function checkImportPrerequisites()
275
    {
276
        $messages = [];
277
278
        // Check #1: Extension dependencies
279
        $extKeysToInstall = [];
280
        foreach ($this->dat['header']['extensionDependencies'] as $extKey) {
281
            if (!empty($extKey) && !ExtensionManagementUtility::isLoaded($extKey)) {
282
                $extKeysToInstall[] = $extKey;
283
            }
284
        }
285
        if (!empty($extKeysToInstall)) {
286
            $messages['missingExtensions'] = 'Before you can install this T3D file you need to install the extensions "'
287
                . implode('", "', $extKeysToInstall) . '".';
288
        }
289
290
        // Check #2: If the path for every local storage object exists.
291
        // Else files can't get moved into a newly imported storage.
292
        if (!empty($this->dat['header']['records']['sys_file_storage'])) {
293
            foreach ($this->dat['header']['records']['sys_file_storage'] as $sysFileStorageUid => $_) {
294
                $storageRecord = $this->dat['records']['sys_file_storage:' . $sysFileStorageUid]['data'];
295
                // continue with Local, writable and online storage only
296
                if ($storageRecord['driver'] === 'Local'
297
                    && $storageRecord['is_writable']
298
                    && $storageRecord['is_online']
299
                ) {
300
                    foreach ($this->storageObjects as $localStorage) {
301
                        if ($this->isEquivalentObjectStorage($localStorage, $storageRecord)) {
302
                            // There is already an existing storage
303
                            break;
304
                        }
305
306
                        // The storage from the import does not have an equivalent storage
307
                        // in the current instance (same driver, same path, etc.). Before
308
                        // the storage record can get inserted later on take care the path
309
                        // it points to really exists and is accessible.
310
                        $storageRecordUid = $storageRecord['uid'];
311
                        // Unset the storage record UID when trying to create the storage object
312
                        // as the record does not already exist in DB. The constructor of the
313
                        // storage object will check whether the target folder exists and set the
314
                        // isOnline flag depending on the outcome.
315
                        $storageRecord['uid'] = 0;
316
                        $resourceStorage = ResourceFactory::getInstance()->createStorageObject($storageRecord);
317
                        if (!$resourceStorage->isOnline()) {
318
                            $configuration = $resourceStorage->getConfiguration();
319
                            $messages['resourceStorageFolderMissing_' . $storageRecordUid] =
320
                                'The resource storage "'
321
                                . $resourceStorage->getName()
322
                                . $configuration['basePath']
323
                                . '" does not exist. Please create the directory prior to starting the import!';
324
                        }
325
                    }
326
                }
327
            }
328
        }
329
330
        return $messages;
331
    }
332
333
    /**
334
     * Imports the sys_file records and the binary files data from internal data array.
335
     */
336
    protected function writeSysFileRecords()
337
    {
338
        if (!isset($this->dat['header']['records']['sys_file'])) {
339
            return;
340
        }
341
        $this->addGeneralErrorsByTable('sys_file');
342
343
        // fetch fresh storage records from database
344
        $storageRecords = $this->fetchStorageRecords();
345
346
        $defaultStorage = ResourceFactory::getInstance()->getDefaultStorage();
347
348
        $sanitizedFolderMappings = [];
349
350
        foreach ($this->dat['header']['records']['sys_file'] as $sysFileUid => $_) {
351
            $fileRecord = $this->dat['records']['sys_file:' . $sysFileUid]['data'];
352
353
            $temporaryFile = null;
354
            // check if there is the right file already in the local folder
355
            if ($this->filesPathForImport !== null) {
356
                if (is_file($this->filesPathForImport . '/' . $fileRecord['sha1']) && sha1_file($this->filesPathForImport . '/' . $fileRecord['sha1']) === $fileRecord['sha1']) {
357
                    $temporaryFile = $this->filesPathForImport . '/' . $fileRecord['sha1'];
358
                }
359
            }
360
361
            // save file to disk
362
            if ($temporaryFile === null) {
363
                $fileId = md5($fileRecord['storage'] . ':' . $fileRecord['identifier_hash']);
364
                $temporaryFile = $this->writeTemporaryFileFromData($fileId);
365
                if ($temporaryFile === null) {
366
                    // error on writing the file. Error message was already added
367
                    continue;
368
                }
369
            }
370
371
            $originalStorageUid = $fileRecord['storage'];
372
            $useStorageFromStorageRecords = false;
373
374
            // replace storage id, if an alternative one was registered
375
            if (isset($this->import_mapId['sys_file_storage'][$fileRecord['storage']])) {
376
                $fileRecord['storage'] = $this->import_mapId['sys_file_storage'][$fileRecord['storage']];
377
                $useStorageFromStorageRecords = true;
378
            }
379
380
            if (empty($fileRecord['storage']) && !$this->isFallbackStorage($fileRecord['storage'])) {
381
                // no storage for the file is defined, mostly because of a missing default storage.
382
                $this->error('Error: No storage for the file "' . $fileRecord['identifier'] . '" with storage uid "' . $originalStorageUid . '"');
383
                continue;
384
            }
385
386
            // using a storage from the local storage is only allowed, if the uid is present in the
387
            // mapping. Only in this case we could be sure, that it's a local, online and writable storage.
388
            if ($useStorageFromStorageRecords && isset($storageRecords[$fileRecord['storage']])) {
389
                /** @var $storage \TYPO3\CMS\Core\Resource\ResourceStorage */
390
                $storage = ResourceFactory::getInstance()->getStorageObject($fileRecord['storage'], $storageRecords[$fileRecord['storage']]);
391
            } elseif ($this->isFallbackStorage($fileRecord['storage'])) {
392
                $storage = ResourceFactory::getInstance()->getStorageObject(0);
393
            } elseif ($defaultStorage !== null) {
394
                $storage = $defaultStorage;
395
            } else {
396
                $this->error('Error: No storage available for the file "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
397
                continue;
398
            }
399
400
            $newFile = null;
401
402
            // check, if there is an identical file
403
            try {
404
                if ($storage->hasFile($fileRecord['identifier'])) {
405
                    $file = $storage->getFile($fileRecord['identifier']);
406
                    if ($file->getSha1() === $fileRecord['sha1']) {
407
                        $newFile = $file;
408
                    }
409
                }
410
            } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
411
            }
412
413
            if ($newFile === null) {
414
                $folderName = PathUtility::dirname(ltrim($fileRecord['identifier'], '/'));
415
                if (in_array($folderName, $sanitizedFolderMappings)) {
416
                    $folderName = $sanitizedFolderMappings[$folderName];
417
                }
418
                if (!$storage->hasFolder($folderName)) {
419
                    try {
420
                        $importFolder = $storage->createFolder($folderName);
421
                        if ($importFolder->getIdentifier() !== $folderName && !in_array($folderName, $sanitizedFolderMappings)) {
422
                            $sanitizedFolderMappings[$folderName] = $importFolder->getIdentifier();
423
                        }
424
                    } catch (Exception $e) {
425
                        $this->error('Error: Folder "' . $folderName . '" could not be created for file "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
426
                        continue;
427
                    }
428
                } else {
429
                    $importFolder = $storage->getFolder($folderName);
430
                }
431
432
                try {
433
                    /** @var $newFile File */
434
                    $newFile = $storage->addFile($temporaryFile, $importFolder, $fileRecord['name']);
435
                } catch (Exception $e) {
436
                    $this->error('Error: File could not be added to the storage: "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
437
                    continue;
438
                }
439
440
                if ($newFile->getSha1() !== $fileRecord['sha1']) {
441
                    $this->error('Error: The hash of the written file is not identical to the import data! File could be corrupted! File: "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
442
                }
443
            }
444
445
            // save the new uid in the import id map
446
            $this->import_mapId['sys_file'][$fileRecord['uid']] = $newFile->getUid();
447
            $this->fixUidLocalInSysFileReferenceRecords($fileRecord['uid'], $newFile->getUid());
448
        }
449
450
        // unset the sys_file records to prevent an import in writeRecords_records
451
        unset($this->dat['header']['records']['sys_file']);
452
        // remove all sys_file_reference records that point to file records which are unknown
453
        // in the system to prevent exceptions
454
        $this->removeSysFileReferenceRecordsFromImportDataWithRelationToMissingFile();
455
    }
456
457
    /**
458
     * Removes all sys_file_reference records from the import data array that are pointing to sys_file records which
459
     * are missing not in the import data to prevent exceptions on checking the related file started by the Datahandler.
460
     */
461
    protected function removeSysFileReferenceRecordsFromImportDataWithRelationToMissingFile()
462
    {
463
        if (!isset($this->dat['header']['records']['sys_file_reference'])) {
464
            return;
465
        }
466
467
        foreach ($this->dat['header']['records']['sys_file_reference'] as $sysFileReferenceUid => $_) {
468
            $fileReferenceRecord = $this->dat['records']['sys_file_reference:' . $sysFileReferenceUid]['data'];
469
            if (!in_array($fileReferenceRecord['uid_local'], $this->import_mapId['sys_file'])) {
470
                unset($this->dat['header']['records']['sys_file_reference'][$sysFileReferenceUid]);
471
                unset($this->dat['records']['sys_file_reference:' . $sysFileReferenceUid]);
472
                $this->error(
473
                    'Error: sys_file_reference record ' . (int)$sysFileReferenceUid
474
                             . ' with relation to sys_file record ' . (int)$fileReferenceRecord['uid_local']
475
                             . ', which is not part of the import data, was not imported.'
476
                );
477
            }
478
        }
479
    }
480
481
    /**
482
     * Checks if the $storageId is the id of the fallback storage
483
     *
484
     * @param int|string $storageId
485
     * @return bool
486
     */
487
    protected function isFallbackStorage($storageId)
488
    {
489
        return $storageId === 0 || $storageId === '0';
490
    }
491
492
    /**
493
     * Normally the importer works like the following:
494
     * Step 1: import the records with cleared field values of relation fields (see addSingle())
495
     * Step 2: update the records with the right relation ids (see setRelations())
496
     *
497
     * In step 2 the saving fields of type "relation to sys_file_reference" checks the related sys_file_reference
498
     * record (created in step 1) with the FileExtensionFilter for matching file extensions of the related file.
499
     * To make this work correct, the uid_local of sys_file_reference records has to be not empty AND has to
500
     * relate to the correct (imported) sys_file record uid!!!
501
     *
502
     * This is fixed here.
503
     *
504
     * @param int $oldFileUid
505
     * @param int $newFileUid
506
    */
507
    protected function fixUidLocalInSysFileReferenceRecords($oldFileUid, $newFileUid)
508
    {
509
        if (!isset($this->dat['header']['records']['sys_file_reference'])) {
510
            return;
511
        }
512
513
        foreach ($this->dat['header']['records']['sys_file_reference'] as $sysFileReferenceUid => $_) {
514
            $fileReferenceRecord = $this->dat['records']['sys_file_reference:' . $sysFileReferenceUid]['data'];
515
            if ($fileReferenceRecord['uid_local'] == $oldFileUid) {
516
                $fileReferenceRecord['uid_local'] = $newFileUid;
517
                $this->dat['records']['sys_file_reference:' . $sysFileReferenceUid]['data'] = $fileReferenceRecord;
518
            }
519
        }
520
    }
521
522
    /**
523
     * Fetched fresh storage records from database because the new imported
524
     * ones are not in cached data of the StorageRepository
525
     *
526
     * @return bool|array
527
     */
528
    protected function fetchStorageRecords()
529
    {
530
        $result = GeneralUtility::makeInstance(ConnectionPool::class)
531
            ->getQueryBuilderForTable('sys_file_storage')
532
            ->select('*')
533
            ->from('sys_file_storage')
534
            ->orderBy('uid')
535
            ->execute();
536
        $rows = [];
537
        while ($row = $result->fetch()) {
538
            $rows[$row['uid']] = $row;
539
        }
540
        return $rows;
541
    }
542
543
    /**
544
     * Writes the file from import array to temp dir and returns the filename of it.
545
     *
546
     * @param string $fileId
547
     * @param string $dataKey
548
     * @return string Absolute filename of the temporary filename of the file
549
     */
550
    protected function writeTemporaryFileFromData($fileId, $dataKey = 'files_fal')
551
    {
552
        $temporaryFilePath = null;
553
        if (is_array($this->dat[$dataKey][$fileId])) {
554
            $temporaryFilePathInternal = GeneralUtility::tempnam('import_temp_');
555
            GeneralUtility::writeFile($temporaryFilePathInternal, $this->dat[$dataKey][$fileId]['content']);
556
            clearstatcache();
557
            if (@is_file($temporaryFilePathInternal)) {
558
                $this->unlinkFiles[] = $temporaryFilePathInternal;
559
                if (filesize($temporaryFilePathInternal) == $this->dat[$dataKey][$fileId]['filesize']) {
560
                    $temporaryFilePath = $temporaryFilePathInternal;
561
                } else {
562
                    $this->error('Error: temporary file ' . $temporaryFilePathInternal . ' had a size (' . filesize($temporaryFilePathInternal) . ') different from the original (' . $this->dat[$dataKey][$fileId]['filesize'] . ')');
563
                }
564
            } else {
565
                $this->error('Error: temporary file ' . $temporaryFilePathInternal . ' was not written as it should have been!');
566
            }
567
        } else {
568
            $this->error('Error: No file found for ID ' . $fileId);
569
        }
570
        return $temporaryFilePath;
571
    }
572
573
    /**
574
     * Writing pagetree/pages to database:
575
     *
576
     * @param int $pid PID in which to import. If the operation is an update operation, the root of the page tree inside will be moved to this PID unless it is the same as the root page from the import
577
     * @see writeRecords_records()
578
     */
579
    public function writeRecords_pages($pid)
580
    {
581
        // First, write page structure if any:
582
        if (is_array($this->dat['header']['records']['pages'])) {
583
            $this->addGeneralErrorsByTable('pages');
584
            // $pageRecords is a copy of the pages array in the imported file. Records here are unset one by one when the addSingle function is called.
585
            $pageRecords = $this->dat['header']['records']['pages'];
586
            $this->import_data = [];
587
            // First add page tree if any
588
            if (is_array($this->dat['header']['pagetree'])) {
589
                $pagesFromTree = $this->flatInversePageTree($this->dat['header']['pagetree']);
590
                foreach ($pagesFromTree as $uid) {
591
                    $thisRec = $this->dat['header']['records']['pages'][$uid];
592
                    // PID: Set the main $pid, unless a NEW-id is found
593
                    $setPid = isset($this->import_newId_pids[$thisRec['pid']]) ? $this->import_newId_pids[$thisRec['pid']] : $pid;
594
                    $this->addSingle('pages', $uid, $setPid);
595
                    unset($pageRecords[$uid]);
596
                }
597
            }
598
            // Then add all remaining pages not in tree on root level:
599
            if (!empty($pageRecords)) {
600
                $remainingPageUids = array_keys($pageRecords);
601
                foreach ($remainingPageUids as $pUid) {
602
                    $this->addSingle('pages', $pUid, $pid);
603
                }
604
            }
605
            // Now write to database:
606
            $tce = $this->getNewTCE();
607
            $tce->isImporting = true;
608
            $this->callHook('before_writeRecordsPages', [
609
                'tce' => &$tce,
610
                'data' => &$this->import_data
611
            ]);
612
            $tce->suggestedInsertUids = $this->suggestedInsertUids;
613
            $tce->start($this->import_data, []);
614
            $tce->process_datamap();
615
            $this->callHook('after_writeRecordsPages', [
616
                'tce' => &$tce
617
            ]);
618
            // post-processing: Registering new ids (end all DataHandler sessions with this)
619
            $this->addToMapId($tce->substNEWwithIDs);
620
            // In case of an update, order pages from the page tree correctly:
621
            if ($this->update && is_array($this->dat['header']['pagetree'])) {
622
                $this->writeRecords_pages_order();
623
            }
624
        }
625
    }
626
627
    /**
628
     * Organize all updated pages in page tree so they are related like in the import file
629
     * Only used for updates and when $this->dat['header']['pagetree'] is an array.
630
     *
631
     * @access private
632
     * @see writeRecords_pages(), writeRecords_records_order()
633
     */
634
    public function writeRecords_pages_order()
635
    {
636
        $cmd_data = [];
637
        // Get uid-pid relations and traverse them in order to map to possible new IDs
638
        $pidsFromTree = $this->flatInversePageTree_pid($this->dat['header']['pagetree']);
639
        foreach ($pidsFromTree as $origPid => $newPid) {
640
            if ($newPid >= 0 && $this->dontIgnorePid('pages', $origPid)) {
641
                // If the page had a new id (because it was created) use that instead!
642
                if (substr($this->import_newId_pids[$origPid], 0, 3) === 'NEW') {
643
                    if ($this->import_mapId['pages'][$origPid]) {
644
                        $mappedPid = $this->import_mapId['pages'][$origPid];
645
                        $cmd_data['pages'][$mappedPid]['move'] = $newPid;
646
                    }
647
                } else {
648
                    $cmd_data['pages'][$origPid]['move'] = $newPid;
649
                }
650
            }
651
        }
652
        // Execute the move commands if any:
653 View Code Duplication
        if (!empty($cmd_data)) {
654
            $tce = $this->getNewTCE();
655
            $this->callHook('before_writeRecordsPagesOrder', [
656
                'tce' => &$tce,
657
                'data' => &$cmd_data
658
            ]);
659
            $tce->start([], $cmd_data);
660
            $tce->process_cmdmap();
661
            $this->callHook('after_writeRecordsPagesOrder', [
662
                'tce' => &$tce
663
            ]);
664
        }
665
    }
666
667
    /**
668
     * Recursively flattening the idH array, setting PIDs as values
669
     *
670
     * @param array $idH Page uid hierarchy
671
     * @param array $a Accumulation array of pages (internal, don't set from outside)
672
     * @param int $pid PID value (internal)
673
     * @return array Array with uid-pid pairs for all pages in the page tree.
674
     * @see ImportExport::flatInversePageTree()
675
     */
676 View Code Duplication
    public function flatInversePageTree_pid($idH, $a = [], $pid = -1)
677
    {
678
        if (is_array($idH)) {
679
            $idH = array_reverse($idH);
680
            foreach ($idH as $v) {
681
                $a[$v['uid']] = $pid;
682
                if (is_array($v['subrow'])) {
683
                    $a = $this->flatInversePageTree_pid($v['subrow'], $a, $v['uid']);
684
                }
685
            }
686
        }
687
        return $a;
688
    }
689
690
    /**
691
     * Write all database records except pages (writtein in writeRecords_pages())
692
     *
693
     * @param int $pid Page id in which to import
694
     * @see writeRecords_pages()
695
     */
696
    public function writeRecords_records($pid)
697
    {
698
        // Write the rest of the records
699
        $this->import_data = [];
700
        if (is_array($this->dat['header']['records'])) {
701
            foreach ($this->dat['header']['records'] as $table => $recs) {
702
                $this->addGeneralErrorsByTable($table);
703
                if ($table !== 'pages') {
704
                    foreach ($recs as $uid => $thisRec) {
705
                        // PID: Set the main $pid, unless a NEW-id is found
706
                        $setPid = isset($this->import_mapId['pages'][$thisRec['pid']])
707
                            ? (int)$this->import_mapId['pages'][$thisRec['pid']]
708
                            : (int)$pid;
709
                        if (is_array($GLOBALS['TCA'][$table]) && isset($GLOBALS['TCA'][$table]['ctrl']['rootLevel'])) {
710
                            $rootLevelSetting = (int)$GLOBALS['TCA'][$table]['ctrl']['rootLevel'];
711
                            if ($rootLevelSetting === 1) {
712
                                $setPid = 0;
713
                            } elseif ($rootLevelSetting === 0 && $setPid === 0) {
714
                                $this->error('Error: Record type ' . $table . ' is not allowed on pid 0');
715
                                continue;
716
                            }
717
                        }
718
                        // Add record:
719
                        $this->addSingle($table, $uid, $setPid);
720
                    }
721
                }
722
            }
723
        } else {
724
            $this->error('Error: No records defined in internal data array.');
725
        }
726
        // Now write to database:
727
        $tce = $this->getNewTCE();
728
        $this->callHook('before_writeRecordsRecords', [
729
            'tce' => &$tce,
730
            'data' => &$this->import_data
731
        ]);
732
        $tce->suggestedInsertUids = $this->suggestedInsertUids;
733
        // Because all records are being submitted in their correct order with positive pid numbers - and so we should reverse submission order internally.
734
        $tce->reverseOrder = 1;
0 ignored issues
show
Documentation Bug introduced by
The property $reverseOrder was declared of type boolean, but 1 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
735
        $tce->isImporting = true;
736
        $tce->start($this->import_data, []);
737
        $tce->process_datamap();
738
        $this->callHook('after_writeRecordsRecords', [
739
            'tce' => &$tce
740
        ]);
741
        // post-processing: Removing files and registering new ids (end all DataHandler sessions with this)
742
        $this->addToMapId($tce->substNEWwithIDs);
743
        // In case of an update, order pages from the page tree correctly:
744
        if ($this->update) {
745
            $this->writeRecords_records_order($pid);
746
        }
747
    }
748
749
    /**
750
     * Organize all updated record to their new positions.
751
     * Only used for updates
752
     *
753
     * @param int $mainPid Main PID into which we import.
754
     * @access private
755
     * @see writeRecords_records(), writeRecords_pages_order()
756
     */
757
    public function writeRecords_records_order($mainPid)
758
    {
759
        $cmd_data = [];
760
        if (is_array($this->dat['header']['pagetree'])) {
761
            $pagesFromTree = $this->flatInversePageTree($this->dat['header']['pagetree']);
762
        } else {
763
            $pagesFromTree = [];
764
        }
765
        if (is_array($this->dat['header']['pid_lookup'])) {
766
            foreach ($this->dat['header']['pid_lookup'] as $pid => $recList) {
767
                $newPid = isset($this->import_mapId['pages'][$pid]) ? $this->import_mapId['pages'][$pid] : $mainPid;
768
                if (MathUtility::canBeInterpretedAsInteger($newPid)) {
769
                    foreach ($recList as $tableName => $uidList) {
770
                        // If $mainPid===$newPid then we are on root level and we can consider to move pages as well!
771
                        // (they will not be in the page tree!)
772
                        if (($tableName !== 'pages' || !$pagesFromTree[$pid]) && is_array($uidList)) {
773
                            $uidList = array_reverse(array_keys($uidList));
774
                            foreach ($uidList as $uid) {
775
                                if ($this->dontIgnorePid($tableName, $uid)) {
776
                                    $cmd_data[$tableName][$uid]['move'] = $newPid;
777
                                }
778
                            }
779
                        }
780
                    }
781
                }
782
            }
783
        }
784
        // Execute the move commands if any:
785 View Code Duplication
        if (!empty($cmd_data)) {
786
            $tce = $this->getNewTCE();
787
            $this->callHook('before_writeRecordsRecordsOrder', [
788
                'tce' => &$tce,
789
                'data' => &$cmd_data
790
            ]);
791
            $tce->start([], $cmd_data);
792
            $tce->process_cmdmap();
793
            $this->callHook('after_writeRecordsRecordsOrder', [
794
                'tce' => &$tce
795
            ]);
796
        }
797
    }
798
799
    /**
800
     * Adds a single record to the $importData array. Also copies files to tempfolder.
801
     * However all File/DB-references and flexform field contents are set to blank for now!
802
     * That is done with setRelations() later
803
     *
804
     * @param string $table Table name (from import memory)
805
     * @param int $uid Record UID (from import memory)
806
     * @param int $pid Page id
807
     * @see writeRecords()
808
     */
809
    public function addSingle($table, $uid, $pid)
810
    {
811
        if ($this->import_mode[$table . ':' . $uid] === 'exclude') {
812
            return;
813
        }
814
        $record = $this->dat['records'][$table . ':' . $uid]['data'];
815
        if (is_array($record)) {
816
            if ($this->update && $this->doesRecordExist($table, $uid) && $this->import_mode[$table . ':' . $uid] !== 'as_new') {
817
                $ID = $uid;
818
            } elseif ($table === 'sys_file_metadata' && $record['sys_language_uid'] == '0' && $this->import_mapId['sys_file'][$record['file']]) {
819
                // on adding sys_file records the belonging sys_file_metadata record was also created
820
                // if there is one the record need to be overwritten instead of creating a new one.
821
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
822
                    ->getQueryBuilderForTable('sys_file_metadata');
823
                $recordInDatabase = $queryBuilder->select('uid')
824
                    ->from('sys_file_metadata')
825
                    ->where(
826
                        $queryBuilder->expr()->eq(
827
                            'file',
828
                            $queryBuilder->createNamedParameter(
829
                                $this->import_mapId['sys_file'][$record['file']],
830
                                \PDO::PARAM_INT
831
                            )
832
                        ),
833
                        $queryBuilder->expr()->eq(
834
                            'sys_language_uid',
835
                            $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
836
                        ),
837
                        $queryBuilder->expr()->eq(
838
                            'pid',
839
                            $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
840
                        )
841
                    )
842
                    ->execute()
843
                    ->fetch();
844
                // if no record could be found, $this->import_mapId['sys_file'][$record['file']] is pointing
845
                // to a file, that was already there, thus a new metadata record should be created
846
                if (is_array($recordInDatabase)) {
847
                    $this->import_mapId['sys_file_metadata'][$record['uid']] = $recordInDatabase['uid'];
848
                    $ID = $recordInDatabase['uid'];
849
                } else {
850
                    $ID = StringUtility::getUniqueId('NEW');
851
                }
852
            } else {
853
                $ID = StringUtility::getUniqueId('NEW');
854
            }
855
            $this->import_newId[$table . ':' . $ID] = ['table' => $table, 'uid' => $uid];
856
            if ($table === 'pages') {
857
                $this->import_newId_pids[$uid] = $ID;
858
            }
859
            // Set main record data:
860
            $this->import_data[$table][$ID] = $record;
861
            $this->import_data[$table][$ID]['tx_impexp_origuid'] = $this->import_data[$table][$ID]['uid'];
862
            // Reset permission data:
863
            if ($table === 'pages') {
864
                // Have to reset the user/group IDs so pages are owned by importing user. Otherwise strange things may happen for non-admins!
865
                unset($this->import_data[$table][$ID]['perms_userid']);
866
                unset($this->import_data[$table][$ID]['perms_groupid']);
867
            }
868
            // PID and UID:
869
            unset($this->import_data[$table][$ID]['uid']);
870
            // Updates:
871
            if (MathUtility::canBeInterpretedAsInteger($ID)) {
872
                unset($this->import_data[$table][$ID]['pid']);
873
            } else {
874
                // Inserts:
875
                $this->import_data[$table][$ID]['pid'] = $pid;
876
                if (($this->import_mode[$table . ':' . $uid] === 'force_uid' && $this->update || $this->force_all_UIDS) && $this->getBackendUser()->isAdmin()) {
877
                    $this->import_data[$table][$ID]['uid'] = $uid;
878
                    $this->suggestedInsertUids[$table . ':' . $uid] = 'DELETE';
879
                }
880
            }
881
            // Setting db/file blank:
882
            foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) {
883
                switch ((string)$config['type']) {
884
                    case 'db':
885
886
                    case 'file':
887
                        // Fixed later in ->setRelations() [because we need to know ALL newly created IDs before we can map relations!]
888
                        // In the meantime we set NO values for relations.
889
                        //
890
                        // BUT for field uid_local of table sys_file_reference the relation MUST not be cleared here,
891
                        // because the value is already the uid of the right imported sys_file record.
892
                        // @see fixUidLocalInSysFileReferenceRecords()
893
                        // If it's empty or a uid to another record the FileExtensionFilter will throw an exception or
894
                        // delete the reference record if the file extension of the related record doesn't match.
895
                        if ($table !== 'sys_file_reference' && $field !== 'uid_local') {
896
                            $this->import_data[$table][$ID][$field] = '';
897
                        }
898
                        break;
899
                    case 'flex':
900
                        // Fixed later in setFlexFormRelations()
901
                        // In the meantime we set NO value for flexforms - this is mainly because file references
902
                        // inside will not be processed properly; In fact references will point to no file
903
                        // or existing files (in which case there will be double-references which is a big problem of course!)
904
                        $this->import_data[$table][$ID][$field] = '';
905
                        break;
906
                }
907
            }
908
        } elseif ($table . ':' . $uid != 'pages:0') {
909
            // On root level we don't want this error message.
910
            $this->error('Error: no record was found in data array!');
911
        }
912
    }
913
914
    /**
915
     * Registers the substNEWids in memory.
916
     *
917
     * @param array $substNEWwithIDs From DataHandler to be merged into internal mapping variable in this object
918
     * @see writeRecords()
919
     */
920
    public function addToMapId($substNEWwithIDs)
921
    {
922
        foreach ($this->import_data as $table => $recs) {
923
            foreach ($recs as $id => $value) {
924
                $old_uid = $this->import_newId[$table . ':' . $id]['uid'];
925
                if (isset($substNEWwithIDs[$id])) {
926
                    $this->import_mapId[$table][$old_uid] = $substNEWwithIDs[$id];
927
                } elseif ($this->update) {
928
                    // Map same ID to same ID....
929
                    $this->import_mapId[$table][$old_uid] = $id;
930
                } else {
931
                    // if $this->import_mapId contains already the right mapping, skip the error msg.
932
                    // See special handling of sys_file_metadata in addSingle() => nothing to do
933
                    if (!($table === 'sys_file_metadata' && isset($this->import_mapId[$table][$old_uid]) && $this->import_mapId[$table][$old_uid] == $id)) {
934
                        $this->error('Possible error: ' . $table . ':' . $old_uid . ' had no new id assigned to it. This indicates that the record was not added to database during import. Please check changelog!');
935
                    }
936
                }
937
            }
938
        }
939
    }
940
941
    /**
942
     * Returns a new $TCE object
943
     *
944
     * @return DataHandler $TCE object
945
     */
946
    public function getNewTCE()
947
    {
948
        $tce = GeneralUtility::makeInstance(DataHandler::class);
949
        $tce->dontProcessTransformations = 1;
950
        $tce->enableLogging = $this->enableLogging;
951
        $tce->alternativeFileName = $this->alternativeFileName;
952
        $tce->alternativeFilePath = $this->alternativeFilePath;
953
        return $tce;
954
    }
955
956
    /**
957
     * Cleaning up all the temporary files stored in typo3temp/ folder
958
     */
959
    public function unlinkTempFiles()
960
    {
961
        foreach ($this->unlinkFiles as $fileName) {
962
            if (GeneralUtility::isFirstPartOfStr($fileName, PATH_site . 'typo3temp/')) {
963
                GeneralUtility::unlink_tempfile($fileName);
964
                clearstatcache();
965
                if (is_file($fileName)) {
966
                    $this->error('Error: ' . $fileName . ' was NOT unlinked as it should have been!');
967
                }
968
            } else {
969
                $this->error('Error: ' . $fileName . ' was not in temp-path. Not removed!');
970
            }
971
        }
972
        $this->unlinkFiles = [];
973
    }
974
975
    /***************************
976
     * Import / Relations setting
977
     ***************************/
978
979
    /**
980
     * At the end of the import process all file and DB relations should be set properly (that is relations
981
     * to imported records are all re-created so imported records are correctly related again)
982
     * Relations in flexform fields are processed in setFlexFormRelations() after this function
983
     *
984
     * @see setFlexFormRelations()
985
     */
986
    public function setRelations()
987
    {
988
        $updateData = [];
989
        // import_newId contains a register of all records that was in the import memorys "records" key
990
        foreach ($this->import_newId as $nId => $dat) {
991
            $table = $dat['table'];
992
            $uid = $dat['uid'];
993
            // original UID - NOT the new one!
994
            // If the record has been written and received a new id, then proceed:
995
            if (is_array($this->import_mapId[$table]) && isset($this->import_mapId[$table][$uid])) {
996
                $thisNewUid = BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]);
997
                if (is_array($this->dat['records'][$table . ':' . $uid]['rels'])) {
998
                    // Traverse relation fields of each record
999
                    foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) {
1000
                        // uid_local of sys_file_reference needs no update because the correct reference uid was already written
1001
                        // @see ImportExport::fixUidLocalInSysFileReferenceRecords()
1002
                        if ($table === 'sys_file_reference' && $field === 'uid_local') {
1003
                            continue;
1004
                        }
1005
                        switch ((string)$config['type']) {
1006
                            case 'db':
1007
                                if (is_array($config['itemArray']) && !empty($config['itemArray'])) {
1008
                                    $itemConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
1009
                                    $valArray = $this->setRelations_db($config['itemArray'], $itemConfig);
1010
                                    $updateData[$table][$thisNewUid][$field] = implode(',', $valArray);
1011
                                }
1012
                                break;
1013
                            case 'file':
1014 View Code Duplication
                                if (is_array($config['newValueFiles']) && !empty($config['newValueFiles'])) {
1015
                                    $valArr = [];
1016
                                    foreach ($config['newValueFiles'] as $fI) {
1017
                                        $valArr[] = $this->import_addFileNameToBeCopied($fI);
1018
                                    }
1019
                                    $updateData[$table][$thisNewUid][$field] = implode(',', $valArr);
1020
                                }
1021
                                break;
1022
                        }
1023
                    }
1024
                } else {
1025
                    $this->error('Error: no record was found in data array!');
1026
                }
1027
            } else {
1028
                $this->error('Error: this records is NOT created it seems! (' . $table . ':' . $uid . ')');
1029
            }
1030
        }
1031 View Code Duplication
        if (!empty($updateData)) {
1032
            $tce = $this->getNewTCE();
1033
            $tce->isImporting = true;
1034
            $this->callHook('before_setRelation', [
1035
                'tce' => &$tce,
1036
                'data' => &$updateData
1037
            ]);
1038
            $tce->start($updateData, []);
1039
            $tce->process_datamap();
1040
            $this->callHook('after_setRelations', [
1041
                'tce' => &$tce
1042
            ]);
1043
        }
1044
    }
1045
1046
    /**
1047
     * Maps relations for database
1048
     *
1049
     * @param array $itemArray Array of item sets (table/uid) from a dbAnalysis object
1050
     * @param array $itemConfig Array of TCA config of the field the relation to be set on
1051
     * @return array Array with values [table]_[uid] or [uid] for field of type group / internal_type file_reference. These values have the regular DataHandler-input group/select type which means they will automatically be processed into a uid-list or MM relations.
1052
     */
1053
    public function setRelations_db($itemArray, $itemConfig)
1054
    {
1055
        $valArray = [];
1056
        foreach ($itemArray as $relDat) {
1057
            if (is_array($this->import_mapId[$relDat['table']]) && isset($this->import_mapId[$relDat['table']][$relDat['id']])) {
1058
                // Since non FAL file relation type group internal_type file_reference are handled as reference to
1059
                // sys_file records Datahandler requires the value as uid of the the related sys_file record only
1060
                if ($itemConfig['type'] === 'group' && $itemConfig['internal_type'] === 'file_reference') {
1061
                    $value = $this->import_mapId[$relDat['table']][$relDat['id']];
1062
                } elseif ($itemConfig['type'] === 'input' && isset($itemConfig['wizards']['link'])) {
1063
                    // If an input field has a relation to a sys_file record this need to be converted back to
1064
                    // the public path. But use getPublicUrl here, because could normally only be a local file path.
1065
                    $fileUid = $this->import_mapId[$relDat['table']][$relDat['id']];
1066
                    // Fallback value
1067
                    $value = 'file:' . $fileUid;
1068
                    try {
1069
                        $file = ResourceFactory::getInstance()->retrieveFileOrFolderObject($fileUid);
1070
                    } catch (\Exception $e) {
1071
                        $file = null;
1072
                    }
1073
                    if ($file instanceof FileInterface) {
1074
                        $value = $file->getPublicUrl();
1075
                    }
1076
                } else {
1077
                    $value = $relDat['table'] . '_' . $this->import_mapId[$relDat['table']][$relDat['id']];
1078
                }
1079
                $valArray[] = $value;
1080
            } elseif ($this->isTableStatic($relDat['table']) || $this->isExcluded($relDat['table'], $relDat['id']) || $relDat['id'] < 0) {
1081
                // Checking for less than zero because some select types could contain negative values,
1082
                // eg. fe_groups (-1, -2) and sys_language (-1 = ALL languages). This must be handled on both export and import.
1083
                $valArray[] = $relDat['table'] . '_' . $relDat['id'];
1084
            } else {
1085
                $this->error('Lost relation: ' . $relDat['table'] . ':' . $relDat['id']);
1086
            }
1087
        }
1088
        return $valArray;
1089
    }
1090
1091
    /**
1092
     * Writes the file from import array to temp dir and returns the filename of it.
1093
     *
1094
     * @param array $fI File information with three keys: "filename" = filename without path, "ID_absFile" = absolute filepath to the file (including the filename), "ID" = md5 hash of "ID_absFile
1095
     * @return string|null Absolute filename of the temporary filename of the file. In ->alternativeFileName the original name is set.
1096
     */
1097
    public function import_addFileNameToBeCopied($fI)
1098
    {
1099
        if (is_array($this->dat['files'][$fI['ID']])) {
1100
            $tmpFile = null;
1101
            // check if there is the right file already in the local folder
1102
            if ($this->filesPathForImport !== null) {
1103
                if (is_file($this->filesPathForImport . '/' . $this->dat['files'][$fI['ID']]['content_md5']) &&
1104
                    md5_file($this->filesPathForImport . '/' . $this->dat['files'][$fI['ID']]['content_md5']) === $this->dat['files'][$fI['ID']]['content_md5']) {
1105
                    $tmpFile = $this->filesPathForImport . '/' . $this->dat['files'][$fI['ID']]['content_md5'];
1106
                }
1107
            }
1108
            if ($tmpFile === null) {
1109
                $tmpFile = GeneralUtility::tempnam('import_temp_');
1110
                GeneralUtility::writeFile($tmpFile, $this->dat['files'][$fI['ID']]['content']);
1111
            }
1112
            clearstatcache();
1113
            if (@is_file($tmpFile)) {
1114
                $this->unlinkFiles[] = $tmpFile;
1115
                if (filesize($tmpFile) == $this->dat['files'][$fI['ID']]['filesize']) {
1116
                    $this->alternativeFileName[$tmpFile] = $fI['filename'];
1117
                    $this->alternativeFilePath[$tmpFile] = $this->dat['files'][$fI['ID']]['relFileRef'];
1118
                    return $tmpFile;
1119
                }
1120
                $this->error('Error: temporary file ' . $tmpFile . ' had a size (' . filesize($tmpFile) . ') different from the original (' . $this->dat['files'][$fI['ID']]['filesize'] . ')');
1121
            } else {
1122
                $this->error('Error: temporary file ' . $tmpFile . ' was not written as it should have been!');
1123
            }
1124
        } else {
1125
            $this->error('Error: No file found for ID ' . $fI['ID']);
1126
        }
1127
        return null;
1128
    }
1129
1130
    /**
1131
     * After all DB relations has been set in the end of the import (see setRelations()) then it is time to correct all relations inside of FlexForm fields.
1132
     * The reason for doing this after is that the setting of relations may affect (quite often!) which data structure is used for the flexforms field!
1133
     *
1134
     * @see setRelations()
1135
     */
1136
    public function setFlexFormRelations()
1137
    {
1138
        $updateData = [];
1139
        // import_newId contains a register of all records that was in the import memorys "records" key
1140
        foreach ($this->import_newId as $nId => $dat) {
1141
            $table = $dat['table'];
1142
            $uid = $dat['uid'];
1143
            // original UID - NOT the new one!
1144
            // If the record has been written and received a new id, then proceed:
1145
            if (!isset($this->import_mapId[$table][$uid])) {
1146
                $this->error('Error: this records is NOT created it seems! (' . $table . ':' . $uid . ')');
1147
                continue;
1148
            }
1149
1150
            if (!is_array($this->dat['records'][$table . ':' . $uid]['rels'])) {
1151
                $this->error('Error: no record was found in data array!');
1152
                continue;
1153
            }
1154
            $thisNewUid = BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]);
1155
            // Traverse relation fields of each record
1156
            foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) {
1157
                switch ((string)$config['type']) {
1158
                    case 'flex':
1159
                        // Get XML content and set as default value (string, non-processed):
1160
                        $updateData[$table][$thisNewUid][$field] = $this->dat['records'][$table . ':' . $uid]['data'][$field];
1161
                        // If there has been registered relations inside the flex form field, run processing on the content:
1162
                        if (!empty($config['flexFormRels']['db']) || !empty($config['flexFormRels']['file'])) {
1163
                            $origRecordRow = BackendUtility::getRecord($table, $thisNewUid, '*');
1164
                            // This will fetch the new row for the element (which should be updated with any references to data structures etc.)
1165
                            $fieldTca = $GLOBALS['TCA'][$table]['columns'][$field];
1166
                            if (is_array($origRecordRow) && is_array($fieldTca['config']) && $fieldTca['config']['type'] === 'flex') {
1167
                                // Get current data structure and value array:
1168
                                $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
1169
                                $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
1170
                                    $fieldTca,
1171
                                    $table,
1172
                                    $field,
1173
                                    $origRecordRow
1174
                                );
1175
                                $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
1176
                                $currentValueArray = GeneralUtility::xml2array($updateData[$table][$thisNewUid][$field]);
1177
                                // Do recursive processing of the XML data:
1178
                                $iteratorObj = GeneralUtility::makeInstance(DataHandler::class);
1179
                                $iteratorObj->callBackObj = $this;
1180
                                $currentValueArray['data'] = $iteratorObj->checkValue_flex_procInData(
1181
                                    $currentValueArray['data'],
1182
                                    [],
1183
                                    [],
1184
                                    $dataStructureArray,
1185
                                    [$table, $thisNewUid, $field, $config],
1186
                                    'remapListedDBRecords_flexFormCallBack'
1187
                                );
1188
                                // The return value is set as an array which means it will be processed by DataHandler for file and DB references!
1189
                                if (is_array($currentValueArray['data'])) {
1190
                                    $updateData[$table][$thisNewUid][$field] = $currentValueArray;
1191
                                }
1192
                            }
1193
                        }
1194
                        break;
1195
                }
1196
            }
1197
        }
1198
        if (!empty($updateData)) {
1199
            $tce = $this->getNewTCE();
1200
            $tce->isImporting = true;
1201
            $this->callHook('before_setFlexFormRelations', [
1202
                'tce' => &$tce,
1203
                'data' => &$updateData
1204
            ]);
1205
            $tce->start($updateData, []);
1206
            $tce->process_datamap();
1207
            $this->callHook('after_setFlexFormRelations', [
1208
                'tce' => &$tce
1209
            ]);
1210
        }
1211
    }
1212
1213
    /**
1214
     * Callback function for traversing the FlexForm structure in relation to remapping database relations
1215
     *
1216
     * @param array $pParams Set of parameters in numeric array: table, uid, field
1217
     * @param array $dsConf TCA config for field (from Data Structure of course)
1218
     * @param string $dataValue Field value (from FlexForm XML)
1219
     * @param string $dataValue_ext1 Not used
1220
     * @param string $dataValue_ext2 Not used
1221
     * @param string $path Path of where the data structure of the element is found
1222
     * @return array Array where the "value" key carries the value.
1223
     * @see setFlexFormRelations()
1224
     */
1225
    public function remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)
0 ignored issues
show
Unused Code introduced by
The parameter $dataValue_ext2 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

1225
    public function remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, /** @scrutinizer ignore-unused */ $dataValue_ext2, $path)

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...
Unused Code introduced by
The parameter $dataValue_ext1 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

1225
    public function remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, /** @scrutinizer ignore-unused */ $dataValue_ext1, $dataValue_ext2, $path)

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...
1226
    {
1227
        // Extract parameters:
1228
        list(, , , $config) = $pParams;
1229
        // In case the $path is used as index without a trailing slash we will remove that
1230
        if (!is_array($config['flexFormRels']['db'][$path]) && is_array($config['flexFormRels']['db'][rtrim($path, '/')])) {
1231
            $path = rtrim($path, '/');
1232
        }
1233
        if (is_array($config['flexFormRels']['db'][$path])) {
1234
            $valArray = $this->setRelations_db($config['flexFormRels']['db'][$path], $dsConf);
1235
            $dataValue = implode(',', $valArray);
1236
        }
1237 View Code Duplication
        if (is_array($config['flexFormRels']['file'][$path])) {
1238
            $valArr = [];
1239
            foreach ($config['flexFormRels']['file'][$path] as $fI) {
1240
                $valArr[] = $this->import_addFileNameToBeCopied($fI);
1241
            }
1242
            $dataValue = implode(',', $valArr);
1243
        }
1244
        return ['value' => $dataValue];
1245
    }
1246
1247
    /**************************
1248
     * Import / Soft References
1249
     *************************/
1250
1251
    /**
1252
     * Processing of soft references
1253
     */
1254
    public function processSoftReferences()
1255
    {
1256
        // Initialize:
1257
        $inData = [];
1258
        // Traverse records:
1259
        if (is_array($this->dat['header']['records'])) {
1260
            foreach ($this->dat['header']['records'] as $table => $recs) {
1261
                foreach ($recs as $uid => $thisRec) {
1262
                    // If there are soft references defined, traverse those:
1263
                    if (isset($GLOBALS['TCA'][$table]) && is_array($thisRec['softrefs'])) {
1264
                        // First traversal is to collect softref configuration and split them up based on fields.
1265
                        // This could probably also have been done with the "records" key instead of the header.
1266
                        $fieldsIndex = [];
1267
                        foreach ($thisRec['softrefs'] as $softrefDef) {
1268
                            // If a substitution token is set:
1269
                            if ($softrefDef['field'] && is_array($softrefDef['subst']) && $softrefDef['subst']['tokenID']) {
1270
                                $fieldsIndex[$softrefDef['field']][$softrefDef['subst']['tokenID']] = $softrefDef;
1271
                            }
1272
                        }
1273
                        // The new id:
1274
                        $thisNewUid = BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]);
1275
                        // Now, if there are any fields that require substitution to be done, lets go for that:
1276
                        foreach ($fieldsIndex as $field => $softRefCfgs) {
1277
                            if (is_array($GLOBALS['TCA'][$table]['columns'][$field])) {
1278
                                if ($GLOBALS['TCA'][$table]['columns'][$field]['config']['type'] === 'flex') {
1279
                                    // This will fetch the new row for the element (which should be updated with any references to data structures etc.)
1280
                                    $origRecordRow = BackendUtility::getRecord($table, $thisNewUid, '*');
1281
                                    if (is_array($origRecordRow)) {
1282
                                        // Get current data structure and value array:
1283
                                        $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
1284
                                        $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
1285
                                            $GLOBALS['TCA'][$table]['columns'][$field],
1286
                                            $table,
1287
                                            $field,
1288
                                            $origRecordRow
1289
                                        );
1290
                                        $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
1291
                                        $currentValueArray = GeneralUtility::xml2array($origRecordRow[$field]);
1292
                                        // Do recursive processing of the XML data:
1293
                                        /** @var $iteratorObj DataHandler */
1294
                                        $iteratorObj = GeneralUtility::makeInstance(DataHandler::class);
1295
                                        $iteratorObj->callBackObj = $this;
1296
                                        $currentValueArray['data'] = $iteratorObj->checkValue_flex_procInData($currentValueArray['data'], [], [], $dataStructureArray, [$table, $uid, $field, $softRefCfgs], 'processSoftReferences_flexFormCallBack');
1297
                                        // The return value is set as an array which means it will be processed by DataHandler for file and DB references!
1298
                                        if (is_array($currentValueArray['data'])) {
1299
                                            $inData[$table][$thisNewUid][$field] = $currentValueArray;
1300
                                        }
1301
                                    }
1302
                                } else {
1303
                                    // Get tokenizedContent string and proceed only if that is not blank:
1304
                                    $tokenizedContent = $this->dat['records'][$table . ':' . $uid]['rels'][$field]['softrefs']['tokenizedContent'];
1305
                                    if (strlen($tokenizedContent) && is_array($softRefCfgs)) {
1306
                                        $inData[$table][$thisNewUid][$field] = $this->processSoftReferences_substTokens($tokenizedContent, $softRefCfgs, $table, $uid);
1307
                                    }
1308
                                }
1309
                            }
1310
                        }
1311
                    }
1312
                }
1313
            }
1314
        }
1315
        // Now write to database:
1316
        $tce = $this->getNewTCE();
1317
        $tce->isImporting = true;
1318
        $this->callHook('before_processSoftReferences', [
1319
            'tce' => $tce,
1320
            'data' => &$inData
1321
        ]);
1322
        $tce->enableLogging = true;
1323
        $tce->start($inData, []);
1324
        $tce->process_datamap();
1325
        $this->callHook('after_processSoftReferences', [
1326
            'tce' => $tce
1327
        ]);
1328
    }
1329
1330
    /**
1331
     * Callback function for traversing the FlexForm structure in relation to remapping softreference relations
1332
     *
1333
     * @param array $pParams Set of parameters in numeric array: table, uid, field
1334
     * @param array $dsConf TCA config for field (from Data Structure of course)
1335
     * @param string $dataValue Field value (from FlexForm XML)
1336
     * @param string $dataValue_ext1 Not used
1337
     * @param string $dataValue_ext2 Not used
1338
     * @param string $path Path of where the data structure where the element is found
1339
     * @return array Array where the "value" key carries the value.
1340
     * @see setFlexFormRelations()
1341
     */
1342
    public function processSoftReferences_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)
0 ignored issues
show
Unused Code introduced by
The parameter $dataValue_ext1 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

1342
    public function processSoftReferences_flexFormCallBack($pParams, $dsConf, $dataValue, /** @scrutinizer ignore-unused */ $dataValue_ext1, $dataValue_ext2, $path)

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...
Unused Code introduced by
The parameter $dataValue_ext2 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

1342
    public function processSoftReferences_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, /** @scrutinizer ignore-unused */ $dataValue_ext2, $path)

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...
Unused Code introduced by
The parameter $dsConf 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

1342
    public function processSoftReferences_flexFormCallBack($pParams, /** @scrutinizer ignore-unused */ $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path)

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...
1343
    {
1344
        // Extract parameters:
1345
        list($table, $origUid, $field, $softRefCfgs) = $pParams;
1346
        if (is_array($softRefCfgs)) {
1347
            // First, find all soft reference configurations for this structure path (they are listed flat in the header):
1348
            $thisSoftRefCfgList = [];
1349
            foreach ($softRefCfgs as $sK => $sV) {
1350
                if ($sV['structurePath'] === $path) {
1351
                    $thisSoftRefCfgList[$sK] = $sV;
1352
                }
1353
            }
1354
            // If any was found, do processing:
1355
            if (!empty($thisSoftRefCfgList)) {
1356
                // Get tokenizedContent string and proceed only if that is not blank:
1357
                $tokenizedContent = $this->dat['records'][$table . ':' . $origUid]['rels'][$field]['flexFormRels']['softrefs'][$path]['tokenizedContent'];
1358
                if (strlen($tokenizedContent)) {
1359
                    $dataValue = $this->processSoftReferences_substTokens($tokenizedContent, $thisSoftRefCfgList, $table, $origUid);
1360
                }
1361
            }
1362
        }
1363
        // Return
1364
        return ['value' => $dataValue];
1365
    }
1366
1367
    /**
1368
     * Substition of softreference tokens
1369
     *
1370
     * @param string $tokenizedContent Content of field with soft reference tokens in.
1371
     * @param array $softRefCfgs Soft reference configurations
1372
     * @param string $table Table for which the processing occurs
1373
     * @param string $uid UID of record from table
1374
     * @return string The input content with tokens substituted according to entries in softRefCfgs
1375
     */
1376
    public function processSoftReferences_substTokens($tokenizedContent, $softRefCfgs, $table, $uid)
1377
    {
1378
        // traverse each softref type for this field:
1379
        foreach ($softRefCfgs as $cfg) {
1380
            // Get token ID:
1381
            $tokenID = $cfg['subst']['tokenID'];
1382
            // Default is current token value:
1383
            $insertValue = $cfg['subst']['tokenValue'];
1384
            // Based on mode:
1385
            switch ((string)$this->softrefCfg[$tokenID]['mode']) {
1386
                case 'exclude':
1387
                    // Exclude is a simple passthrough of the value
1388
                    break;
1389
                case 'editable':
1390
                    // Editable always picks up the value from this input array:
1391
                    $insertValue = $this->softrefInputValues[$tokenID];
1392
                    break;
1393
                default:
1394
                    // Mapping IDs/creating files: Based on type, look up new value:
1395
                    switch ((string)$cfg['subst']['type']) {
1396
                        case 'file':
1397
                            // Create / Overwrite file:
1398
                            $insertValue = $this->processSoftReferences_saveFile($cfg['subst']['relFileName'], $cfg, $table, $uid);
1399
                            break;
1400
                        case 'db':
1401
                        default:
1402
                            // Trying to map database element if found in the mapID array:
1403
                            list($tempTable, $tempUid) = explode(':', $cfg['subst']['recordRef']);
1404
                            if (isset($this->import_mapId[$tempTable][$tempUid])) {
1405
                                $insertValue = BackendUtility::wsMapId($tempTable, $this->import_mapId[$tempTable][$tempUid]);
1406
                                // Look if reference is to a page and the original token value was NOT an integer - then we assume is was an alias and try to look up the new one!
1407
                                if ($tempTable === 'pages' && !MathUtility::canBeInterpretedAsInteger($cfg['subst']['tokenValue'])) {
1408
                                    $recWithUniqueValue = BackendUtility::getRecord($tempTable, $insertValue, 'alias');
1409
                                    if ($recWithUniqueValue['alias']) {
1410
                                        $insertValue = $recWithUniqueValue['alias'];
1411
                                    }
1412
                                } elseif (strpos($cfg['subst']['tokenValue'], ':') !== false) {
1413
                                    list($tokenKey) = explode(':', $cfg['subst']['tokenValue']);
1414
                                    $insertValue = $tokenKey . ':' . $insertValue;
1415
                                }
1416
                            }
1417
                    }
1418
            }
1419
            // Finally, swap the soft reference token in tokenized content with the insert value:
1420
            $tokenizedContent = str_replace('{softref:' . $tokenID . '}', $insertValue, $tokenizedContent);
1421
        }
1422
        return $tokenizedContent;
1423
    }
1424
1425
    /**
1426
     * Process a soft reference file
1427
     *
1428
     * @param string $relFileName Old Relative filename
1429
     * @param array $cfg soft reference configuration array
1430
     * @param string $table Table for which the processing occurs
1431
     * @param string $uid UID of record from table
1432
     * @return string New relative filename (value to insert instead of the softref token)
1433
     */
1434
    public function processSoftReferences_saveFile($relFileName, $cfg, $table, $uid)
1435
    {
1436
        if ($fileHeaderInfo = $this->dat['header']['files'][$cfg['file_ID']]) {
1437
            // Initialize; Get directory prefix for file and find possible RTE filename
1438
            $dirPrefix = PathUtility::dirname($relFileName) . '/';
1439
            $rteOrigName = $this->getRTEoriginalFilename(PathUtility::basename($relFileName));
1440
            // If filename looks like an RTE file, and the directory is in "uploads/", then process as a RTE file!
1441
            if ($rteOrigName && GeneralUtility::isFirstPartOfStr($dirPrefix, 'uploads/')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rteOrigName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1442
                // RTE:
1443
                // First, find unique RTE file name:
1444
                if (@is_dir((PATH_site . $dirPrefix))) {
1445
                    // From the "original" RTE filename, produce a new "original" destination filename which is unused.
1446
                    // Even if updated, the image should be unique. Currently the problem with this is that it leaves a lot of unused RTE images...
1447
                    $fileProcObj = $this->getFileProcObj();
1448
                    $origDestName = $fileProcObj->getUniqueName($rteOrigName, PATH_site . $dirPrefix);
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Core\Utility\F...tility::getUniqueName() 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

1448
                    $origDestName = /** @scrutinizer ignore-deprecated */ $fileProcObj->getUniqueName($rteOrigName, PATH_site . $dirPrefix);

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...
1449
                    // Create copy file name:
1450
                    $pI = pathinfo($relFileName);
1451
                    $copyDestName = PathUtility::dirname($origDestName) . '/RTEmagicC_' . substr(PathUtility::basename($origDestName), 10) . '.' . $pI['extension'];
0 ignored issues
show
Bug introduced by
Are you sure substr(TYPO3\CMS\Core\Ut...ame($origDestName), 10) 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

1451
                    $copyDestName = PathUtility::dirname($origDestName) . '/RTEmagicC_' . /** @scrutinizer ignore-type */ substr(PathUtility::basename($origDestName), 10) . '.' . $pI['extension'];
Loading history...
1452
                    if (
1453
                        !@is_file($copyDestName) && !@is_file($origDestName)
1454
                        && $origDestName === GeneralUtility::getFileAbsFileName($origDestName)
1455
                        && $copyDestName === GeneralUtility::getFileAbsFileName($copyDestName)
1456
                    ) {
1457
                        if ($this->dat['header']['files'][$fileHeaderInfo['RTE_ORIG_ID']]) {
1458
                            // Write the copy and original RTE file to the respective filenames:
1459
                            $this->writeFileVerify($copyDestName, $cfg['file_ID'], true);
1460
                            $this->writeFileVerify($origDestName, $fileHeaderInfo['RTE_ORIG_ID'], true);
1461
                            // Return the relative path of the copy file name:
1462
                            return PathUtility::stripPathSitePrefix($copyDestName);
1463
                        }
1464
                        $this->error('ERROR: Could not find original file ID');
1465
                    } else {
1466
                        $this->error('ERROR: The destination filenames "' . $copyDestName . '" and "' . $origDestName . '" either existed or have non-valid names');
1467
                    }
1468
                } else {
1469
                    $this->error('ERROR: "' . PATH_site . $dirPrefix . '" was not a directory, so could not process file "' . $relFileName . '"');
1470
                }
1471
            } elseif (GeneralUtility::isFirstPartOfStr($dirPrefix, $this->fileadminFolderName . '/')) {
1472
                // File in fileadmin/ folder:
1473
                // Create file (and possible resources)
1474
                $newFileName = $this->processSoftReferences_saveFile_createRelFile($dirPrefix, PathUtility::basename($relFileName), $cfg['file_ID'], $table, $uid);
1475
                if (strlen($newFileName)) {
1476
                    $relFileName = $newFileName;
1477
                } else {
1478
                    $this->error('ERROR: No new file created for "' . $relFileName . '"');
1479
                }
1480
            } else {
1481
                $this->error('ERROR: Sorry, cannot operate on non-RTE files which are outside the fileadmin folder.');
1482
            }
1483
        } else {
1484
            $this->error('ERROR: Could not find file ID in header.');
1485
        }
1486
        // Return (new) filename relative to PATH_site:
1487
        return $relFileName;
1488
    }
1489
1490
    /**
1491
     * Create file in directory and return the new (unique) filename
1492
     *
1493
     * @param string $origDirPrefix Directory prefix, relative, with trailing slash
1494
     * @param string $fileName Filename (without path)
1495
     * @param string $fileID File ID from import memory
1496
     * @param string $table Table for which the processing occurs
1497
     * @param string $uid UID of record from table
1498
     * @return string|null New relative filename, if any
1499
     */
1500
    public function processSoftReferences_saveFile_createRelFile($origDirPrefix, $fileName, $fileID, $table, $uid)
1501
    {
1502
        // If the fileID map contains an entry for this fileID then just return the relative filename of that entry;
1503
        // we don't want to write another unique filename for this one!
1504
        if (isset($this->fileIDMap[$fileID])) {
1505
            return PathUtility::stripPathSitePrefix($this->fileIDMap[$fileID]);
1506
        }
1507
        // Verify FileMount access to dir-prefix. Returns the best alternative relative path if any
1508
        $dirPrefix = $this->verifyFolderAccess($origDirPrefix);
1509
        if ($dirPrefix && (!$this->update || $origDirPrefix === $dirPrefix) && $this->checkOrCreateDir($dirPrefix)) {
1510
            $fileHeaderInfo = $this->dat['header']['files'][$fileID];
1511
            $updMode = $this->update && $this->import_mapId[$table][$uid] === $uid && $this->import_mode[$table . ':' . $uid] !== 'as_new';
1512
            // Create new name for file:
1513
            // Must have same ID in map array (just for security, is not really needed) and NOT be set "as_new".
1514
1515
            // Write main file:
1516
            if ($updMode) {
1517
                $newName = PATH_site . $dirPrefix . $fileName;
1518
            } else {
1519
                // Create unique filename:
1520
                $fileProcObj = $this->getFileProcObj();
1521
                $newName = $fileProcObj->getUniqueName($fileName, PATH_site . $dirPrefix);
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Core\Utility\F...tility::getUniqueName() 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

1521
                $newName = /** @scrutinizer ignore-deprecated */ $fileProcObj->getUniqueName($fileName, PATH_site . $dirPrefix);

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...
1522
            }
1523
            if ($this->writeFileVerify($newName, $fileID)) {
1524
                // If the resource was an HTML/CSS file with resources attached, we will write those as well!
1525
                if (is_array($fileHeaderInfo['EXT_RES_ID'])) {
1526
                    $tokenizedContent = $this->dat['files'][$fileID]['tokenizedContent'];
1527
                    $tokenSubstituted = false;
1528
                    $fileProcObj = $this->getFileProcObj();
1529
                    if ($updMode) {
1530
                        foreach ($fileHeaderInfo['EXT_RES_ID'] as $res_fileID) {
1531
                            if ($this->dat['files'][$res_fileID]['filename']) {
1532
                                // Resolve original filename:
1533
                                $relResourceFileName = $this->dat['files'][$res_fileID]['parentRelFileName'];
1534
                                $absResourceFileName = GeneralUtility::resolveBackPath(PATH_site . $origDirPrefix . $relResourceFileName);
1535
                                $absResourceFileName = GeneralUtility::getFileAbsFileName($absResourceFileName);
1536
                                if ($absResourceFileName && GeneralUtility::isFirstPartOfStr($absResourceFileName, PATH_site . $this->fileadminFolderName . '/')) {
1537
                                    $destDir = PathUtility::stripPathSitePrefix(PathUtility::dirname($absResourceFileName) . '/');
1538
                                    if ($this->verifyFolderAccess($destDir, true) && $this->checkOrCreateDir($destDir)) {
1539
                                        $this->writeFileVerify($absResourceFileName, $res_fileID);
1540
                                    } else {
1541
                                        $this->error('ERROR: Could not create file in directory "' . $destDir . '"');
1542
                                    }
1543
                                } else {
1544
                                    $this->error('ERROR: Could not resolve path for "' . $relResourceFileName . '"');
1545
                                }
1546
                                $tokenizedContent = str_replace('{EXT_RES_ID:' . $res_fileID . '}', $relResourceFileName, $tokenizedContent);
1547
                                $tokenSubstituted = true;
1548
                            }
1549
                        }
1550
                    } else {
1551
                        // Create the resouces directory name (filename without extension, suffixed "_FILES")
1552
                        $resourceDir = PathUtility::dirname($newName) . '/' . preg_replace('/\\.[^.]*$/', '', PathUtility::basename($newName)) . '_FILES';
1553
                        if (GeneralUtility::mkdir($resourceDir)) {
1554
                            foreach ($fileHeaderInfo['EXT_RES_ID'] as $res_fileID) {
1555
                                if ($this->dat['files'][$res_fileID]['filename']) {
1556
                                    $absResourceFileName = $fileProcObj->getUniqueName($this->dat['files'][$res_fileID]['filename'], $resourceDir);
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Core\Utility\F...tility::getUniqueName() 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

1556
                                    $absResourceFileName = /** @scrutinizer ignore-deprecated */ $fileProcObj->getUniqueName($this->dat['files'][$res_fileID]['filename'], $resourceDir);

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...
1557
                                    $relResourceFileName = substr($absResourceFileName, strlen(PathUtility::dirname($resourceDir)) + 1);
1558
                                    $this->writeFileVerify($absResourceFileName, $res_fileID);
1559
                                    $tokenizedContent = str_replace('{EXT_RES_ID:' . $res_fileID . '}', $relResourceFileName, $tokenizedContent);
0 ignored issues
show
Bug introduced by
It seems like $relResourceFileName can also be of type false; however, parameter $replace of str_replace() does only seem to accept string|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

1559
                                    $tokenizedContent = str_replace('{EXT_RES_ID:' . $res_fileID . '}', /** @scrutinizer ignore-type */ $relResourceFileName, $tokenizedContent);
Loading history...
1560
                                    $tokenSubstituted = true;
1561
                                }
1562
                            }
1563
                        }
1564
                    }
1565
                    // If substitutions has been made, write the content to the file again:
1566
                    if ($tokenSubstituted) {
1567
                        GeneralUtility::writeFile($newName, $tokenizedContent);
1568
                    }
1569
                }
1570
                return PathUtility::stripPathSitePrefix($newName);
1571
            }
1572
        }
1573
        return null;
1574
    }
1575
1576
    /**
1577
     * Writes a file from the import memory having $fileID to file name $fileName which must be an absolute path inside PATH_site
1578
     *
1579
     * @param string $fileName Absolute filename inside PATH_site to write to
1580
     * @param string $fileID File ID from import memory
1581
     * @param bool $bypassMountCheck Bypasses the checking against filemounts - only for RTE files!
1582
     * @return bool Returns TRUE if it went well. Notice that the content of the file is read again, and md5 from import memory is validated.
1583
     */
1584
    public function writeFileVerify($fileName, $fileID, $bypassMountCheck = false)
1585
    {
1586
        $fileProcObj = $this->getFileProcObj();
1587
        if (!$fileProcObj->actionPerms['addFile']) {
1588
            $this->error('ERROR: You did not have sufficient permissions to write the file "' . $fileName . '"');
1589
            return false;
1590
        }
1591
        // Just for security, check again. Should actually not be necessary.
1592
        if (!$bypassMountCheck) {
1593
            try {
1594
                ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier(dirname($fileName));
1595
            } catch (InsufficientFolderAccessPermissionsException $e) {
1596
                $this->error('ERROR: Filename "' . $fileName . '" was not allowed in destination path!');
1597
                return false;
1598
            }
1599
        }
1600
        $fI = GeneralUtility::split_fileref($fileName);
1601
        if (!$fileProcObj->checkIfAllowed($fI['fileext'], $fI['path'], $fI['file']) && (!$this->allowPHPScripts || !$this->getBackendUser()->isAdmin())) {
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

1601
        if (!/** @scrutinizer ignore-deprecated */ $fileProcObj->checkIfAllowed($fI['fileext'], $fI['path'], $fI['file']) && (!$this->allowPHPScripts || !$this->getBackendUser()->isAdmin())) {

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...
1602
            $this->error('ERROR: Filename "' . $fileName . '" failed against extension check or deny-pattern!');
1603
            return false;
1604
        }
1605
        if (!GeneralUtility::getFileAbsFileName($fileName)) {
1606
            $this->error('ERROR: Filename "' . $fileName . '" was not a valid relative file path!');
1607
            return false;
1608
        }
1609
        if (!$this->dat['files'][$fileID]) {
1610
            $this->error('ERROR: File ID "' . $fileID . '" could not be found');
1611
            return false;
1612
        }
1613
        GeneralUtility::writeFile($fileName, $this->dat['files'][$fileID]['content']);
1614
        $this->fileIDMap[$fileID] = $fileName;
1615
        if (md5(file_get_contents($fileName)) == $this->dat['files'][$fileID]['content_md5']) {
0 ignored issues
show
Bug introduced by
It seems like file_get_contents($fileName) can also be of type false; however, parameter $str of md5() 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

1615
        if (md5(/** @scrutinizer ignore-type */ file_get_contents($fileName)) == $this->dat['files'][$fileID]['content_md5']) {
Loading history...
1616
            return true;
1617
        }
1618
        $this->error('ERROR: File content "' . $fileName . '" was corrupted');
1619
        return false;
1620
    }
1621
1622
    /**
1623
     * Returns TRUE if directory exists  and if it doesn't it will create directory and return TRUE if that succeeded.
1624
     *
1625
     * @param string $dirPrefix Directory to create. Having a trailing slash. Must be in fileadmin/. Relative to PATH_site
1626
     * @return bool TRUE, if directory exists (was created)
1627
     */
1628
    public function checkOrCreateDir($dirPrefix)
1629
    {
1630
        // Split dir path and remove first directory (which should be "fileadmin")
1631
        $filePathParts = explode('/', $dirPrefix);
1632
        $firstDir = array_shift($filePathParts);
1633
        if ($firstDir === $this->fileadminFolderName && GeneralUtility::getFileAbsFileName($dirPrefix)) {
1634
            $pathAcc = '';
1635
            foreach ($filePathParts as $dirname) {
1636
                $pathAcc .= '/' . $dirname;
1637
                if (strlen($dirname)) {
1638
                    if (!@is_dir((PATH_site . $this->fileadminFolderName . $pathAcc))) {
1639
                        if (!GeneralUtility::mkdir((PATH_site . $this->fileadminFolderName . $pathAcc))) {
1640
                            $this->error('ERROR: Directory could not be created....B');
1641
                            return false;
1642
                        }
1643
                    }
1644
                } elseif ($dirPrefix === $this->fileadminFolderName . $pathAcc) {
1645
                    return true;
1646
                } else {
1647
                    $this->error('ERROR: Directory could not be created....A');
1648
                }
1649
            }
1650
        }
1651
        return false;
1652
    }
1653
1654
    /**************************
1655
     * File Input
1656
     *************************/
1657
1658
    /**
1659
     * Loads the header section/all of the $filename into memory
1660
     *
1661
     * @param string $filename Filename, absolute
1662
     * @param bool $all If set, all information is loaded (header, records and files). Otherwise the default is to read only the header information
1663
     * @return bool TRUE if the operation went well
1664
     */
1665
    public function loadFile($filename, $all = false)
1666
    {
1667
        if (!@is_file($filename)) {
1668
            $this->error('Filename not found: ' . $filename);
1669
            return false;
1670
        }
1671
        $fI = pathinfo($filename);
1672
        if (@is_dir($filename . '.files')) {
1673
            if (GeneralUtility::isAllowedAbsPath($filename . '.files')) {
1674
                // copy the folder lowlevel to typo3temp, because the files would be deleted after import
1675
                $temporaryFolderName = $this->getTemporaryFolderName();
1676
                GeneralUtility::copyDirectory($filename . '.files', $temporaryFolderName);
1677
                $this->filesPathForImport = $temporaryFolderName;
1678
            } else {
1679
                $this->error('External import files for the given import source is currently not supported.');
1680
            }
1681
        }
1682
        if (strtolower($fI['extension']) === 'xml') {
1683
            // XML:
1684
            $xmlContent = file_get_contents($filename);
1685
            if (strlen($xmlContent)) {
0 ignored issues
show
Bug introduced by
It seems like $xmlContent can also be of type false; however, parameter $string of strlen() 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

1685
            if (strlen(/** @scrutinizer ignore-type */ $xmlContent)) {
Loading history...
1686
                $this->dat = GeneralUtility::xml2array($xmlContent, '', true);
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...($xmlContent, '', true) can also be of type string. However, the property $dat is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
Bug introduced by
It seems like $xmlContent can also be of type false; however, parameter $string of TYPO3\CMS\Core\Utility\GeneralUtility::xml2array() 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

1686
                $this->dat = GeneralUtility::xml2array(/** @scrutinizer ignore-type */ $xmlContent, '', true);
Loading history...
1687
                if (is_array($this->dat)) {
1688
                    if ($this->dat['_DOCUMENT_TAG'] === 'T3RecordDocument' && is_array($this->dat['header']) && is_array($this->dat['records'])) {
1689
                        $this->loadInit();
1690
                        return true;
1691
                    }
1692
                    $this->error('XML file did not contain proper XML for TYPO3 Import');
1693
                } else {
1694
                    $this->error('XML could not be parsed: ' . $this->dat);
1695
                }
1696
            } else {
1697
                $this->error('Error opening file: ' . $filename);
1698
            }
1699
        } else {
1700
            // T3D
1701
            if ($fd = fopen($filename, 'rb')) {
1702
                $this->dat['header'] = $this->getNextFilePart($fd, 1, 'header');
1703
                if ($all) {
1704
                    $this->dat['records'] = $this->getNextFilePart($fd, 1, 'records');
1705
                    $this->dat['files'] = $this->getNextFilePart($fd, 1, 'files');
1706
                    $this->dat['files_fal'] = $this->getNextFilePart($fd, 1, 'files_fal');
1707
                }
1708
                $this->loadInit();
1709
                return true;
1710
            }
1711
            $this->error('Error opening file: ' . $filename);
1712
1713
            fclose($fd);
0 ignored issues
show
Bug introduced by
It seems like $fd can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

1713
            fclose(/** @scrutinizer ignore-type */ $fd);
Loading history...
1714
        }
1715
        return false;
1716
    }
1717
1718
    /**
1719
     * Returns the next content part form the fileresource (t3d), $fd
1720
     *
1721
     * @param resource $fd File pointer
1722
     * @param bool $unserialize If set, the returned content is unserialized into an array, otherwise you get the raw string
1723
     * @param string $name For error messages this indicates the section of the problem.
1724
     * @return string|null Data string or NULL in case of an error
1725
     * @access private
1726
     * @see loadFile()
1727
     */
1728
    public function getNextFilePart($fd, $unserialize = false, $name = '')
1729
    {
1730
        $initStrLen = 32 + 1 + 1 + 1 + 10 + 1;
1731
        // Getting header data
1732
        $initStr = fread($fd, $initStrLen);
1733
        if (empty($initStr)) {
1734
            $this->error('File does not contain data for "' . $name . '"');
1735
            return null;
1736
        }
1737
        $initStrDat = explode(':', $initStr);
1738
        if (strstr($initStrDat[0], 'Warning')) {
1739
            $this->error('File read error: Warning message in file. (' . $initStr . fgets($fd) . ')');
0 ignored issues
show
Bug introduced by
Are you sure fgets($fd) 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

1739
            $this->error('File read error: Warning message in file. (' . $initStr . /** @scrutinizer ignore-type */ fgets($fd) . ')');
Loading history...
1740
            return null;
1741
        }
1742 View Code Duplication
        if ((string)$initStrDat[3] !== '') {
1743
            $this->error('File read error: InitString had a wrong length. (' . $name . ')');
1744
            return null;
1745
        }
1746
        $datString = fread($fd, (int)$initStrDat[2]);
1747
        fread($fd, 1);
1748
        if (md5($datString) === $initStrDat[0]) {
0 ignored issues
show
Bug introduced by
It seems like $datString can also be of type false; however, parameter $str of md5() 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

1748
        if (md5(/** @scrutinizer ignore-type */ $datString) === $initStrDat[0]) {
Loading history...
1749
            if ($initStrDat[1]) {
1750
                if ($this->compress) {
1751
                    $datString = gzuncompress($datString);
0 ignored issues
show
Bug introduced by
It seems like $datString can also be of type false; however, parameter $data of gzuncompress() 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

1751
                    $datString = gzuncompress(/** @scrutinizer ignore-type */ $datString);
Loading history...
1752
                } else {
1753
                    $this->error('Content read error: This file requires decompression, but this server does not offer gzcompress()/gzuncompress() functions.');
1754
                    return null;
1755
                }
1756
            }
1757
            return $unserialize ? unserialize($datString, ['allowed_classes' => false]) : $datString;
0 ignored issues
show
Bug introduced by
It seems like $datString can also be of type false; however, parameter $str of unserialize() 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

1757
            return $unserialize ? unserialize(/** @scrutinizer ignore-type */ $datString, ['allowed_classes' => false]) : $datString;
Loading history...
1758
        }
1759
        $this->error('MD5 check failed (' . $name . ')');
1760
1761
        return null;
1762
    }
1763
1764
    /**
1765
     * Loads T3D file content into the $this->dat array
1766
     * (This function can be used to test the output strings from ->compileMemoryToFileContent())
1767
     *
1768
     * @param string $filecontent File content
1769
     */
1770
    public function loadContent($filecontent)
1771
    {
1772
        $pointer = 0;
1773
        $this->dat['header'] = $this->getNextContentPart($filecontent, $pointer, 1, 'header');
1774
        $this->dat['records'] = $this->getNextContentPart($filecontent, $pointer, 1, 'records');
1775
        $this->dat['files'] = $this->getNextContentPart($filecontent, $pointer, 1, 'files');
1776
        $this->loadInit();
1777
    }
1778
1779
    /**
1780
     * Returns the next content part from the $filecontent
1781
     *
1782
     * @param string $filecontent File content string
1783
     * @param int $pointer File pointer (where to read from)
1784
     * @param bool $unserialize If set, the returned content is unserialized into an array, otherwise you get the raw string
1785
     * @param string $name For error messages this indicates the section of the problem.
1786
     * @return string|null Data string
1787
     */
1788
    public function getNextContentPart($filecontent, &$pointer, $unserialize = false, $name = '')
1789
    {
1790
        $initStrLen = 32 + 1 + 1 + 1 + 10 + 1;
1791
        // getting header data
1792
        $initStr = substr($filecontent, $pointer, $initStrLen);
1793
        $pointer += $initStrLen;
1794
        $initStrDat = explode(':', $initStr);
0 ignored issues
show
Bug introduced by
It seems like $initStr can also be of type false; however, parameter $string of explode() 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

1794
        $initStrDat = explode(':', /** @scrutinizer ignore-type */ $initStr);
Loading history...
1795 View Code Duplication
        if ((string)$initStrDat[3] !== '') {
1796
            $this->error('Content read error: InitString had a wrong length. (' . $name . ')');
1797
            return null;
1798
        }
1799
        $datString = substr($filecontent, $pointer, (int)$initStrDat[2]);
1800
        $pointer += (int)$initStrDat[2] + 1;
1801
        if (md5($datString) === $initStrDat[0]) {
0 ignored issues
show
Bug introduced by
It seems like $datString can also be of type false; however, parameter $str of md5() 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

1801
        if (md5(/** @scrutinizer ignore-type */ $datString) === $initStrDat[0]) {
Loading history...
1802
            if ($initStrDat[1]) {
1803
                if ($this->compress) {
1804
                    $datString = gzuncompress($datString);
0 ignored issues
show
Bug introduced by
It seems like $datString can also be of type false; however, parameter $data of gzuncompress() 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

1804
                    $datString = gzuncompress(/** @scrutinizer ignore-type */ $datString);
Loading history...
1805
                    return $unserialize ? unserialize($datString, ['allowed_classes' => false]) : $datString;
1806
                }
1807
                $this->error('Content read error: This file requires decompression, but this server does not offer gzcompress()/gzuncompress() functions.');
1808
            }
1809
        } else {
1810
            $this->error('MD5 check failed (' . $name . ')');
1811
        }
1812
        return null;
1813
    }
1814
1815
    /**
1816
     * Setting up the object based on the recently loaded ->dat array
1817
     */
1818
    public function loadInit()
1819
    {
1820
        $this->relStaticTables = (array)$this->dat['header']['relStaticTables'];
1821
        $this->excludeMap = (array)$this->dat['header']['excludeMap'];
1822
        $this->softrefCfg = (array)$this->dat['header']['softrefCfg'];
1823
    }
1824
}
1825