[TASK] Remove superfluous parenthesis in sysexts
[Packages/TYPO3.CMS.git] / typo3 / sysext / impexp / classes / ImportExport.php
1 <?php
2 namespace TYPO3\CMS\Impexp;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /**
30 * T3D file Import/Export library (TYPO3 Record Document)
31 *
32 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
33 */
34 /**
35 * EXAMPLE for using the impexp-class for exporting stuff:
36 *
37 * Create and initialize:
38 * $this->export = t3lib_div::makeInstance('tx_impexp');
39 * $this->export->init();
40 * Set which tables relations we will allow:
41 * $this->export->relOnlyTables[]="tt_news"; // exclusively includes. See comment in the class
42 *
43 * Adding records:
44 * $this->export->export_addRecord("pages", $this->pageinfo);
45 * $this->export->export_addRecord("pages", t3lib_BEfunc::getRecord("pages", 38));
46 * $this->export->export_addRecord("pages", t3lib_BEfunc::getRecord("pages", 39));
47 * $this->export->export_addRecord("tt_content", t3lib_BEfunc::getRecord("tt_content", 12));
48 * $this->export->export_addRecord("tt_content", t3lib_BEfunc::getRecord("tt_content", 74));
49 * $this->export->export_addRecord("sys_template", t3lib_BEfunc::getRecord("sys_template", 20));
50 *
51 * Adding all the relations (recursively in 5 levels so relations has THEIR relations registered as well)
52 * for($a=0;$a<5;$a++) {
53 * $addR = $this->export->export_addDBRelations($a);
54 * if (!count($addR)) break;
55 * }
56 *
57 * Finally load all the files.
58 * $this->export->export_addFilesFromRelations(); // MUST be after the DBrelations are set so that file from ALL added records are included!
59 *
60 * Write export
61 * $out = $this->export->compileMemoryToFileContent();
62 */
63 /**
64 * T3D file Import/Export library (TYPO3 Record Document)
65 *
66 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
67 * @package TYPO3
68 * @subpackage tx_impexp
69 */
70 class ImportExport {
71
72 // Configuration, general
73 // If set, static relations (not exported) will be shown in overview as well
74 /**
75 * @todo Define visibility
76 */
77 public $showStaticRelations = FALSE;
78
79 // Name of the "fileadmin" folder where files for export/import should be located
80 /**
81 * @todo Define visibility
82 */
83 public $fileadminFolderName = 'fileadmin';
84
85 // Whether "import" or "export" mode of object. Set through init() function
86 /**
87 * @todo Define visibility
88 */
89 public $mode = '';
90
91 // Updates all records that has same UID instead of creating new!
92 /**
93 * @todo Define visibility
94 */
95 public $update = FALSE;
96
97 // Is set by importData() when an import has been done.
98 /**
99 * @todo Define visibility
100 */
101 public $doesImport = FALSE;
102
103 // Configuration, import
104 // If set to a page-record, then the preview display of the content will expect this page-record to be the target
105 // for the import and accordingly display validation information. This triggers the visual view of the
106 // import/export memory to validate if import is possible
107 /**
108 * @todo Define visibility
109 */
110 public $display_import_pid_record = '';
111
112 // Used to register the forged UID values for imported records that we want to create with the same UIDs as in the import file. Admin-only feature.
113 /**
114 * @todo Define visibility
115 */
116 public $suggestedInsertUids = array();
117
118 // Setting import modes during update state: as_new, exclude, force_uid
119 /**
120 * @todo Define visibility
121 */
122 public $import_mode = array();
123
124 // If set, PID correct is ignored globally
125 /**
126 * @todo Define visibility
127 */
128 public $global_ignore_pid = FALSE;
129
130 // If set, all UID values are forced! (update or import)
131 /**
132 * @todo Define visibility
133 */
134 public $force_all_UIDS = FALSE;
135
136 // If set, a diff-view column is added to the overview.
137 /**
138 * @todo Define visibility
139 */
140 public $showDiff = FALSE;
141
142 // If set, and if the user is admin, allow the writing of PHP scripts to fileadmin/ area.
143 /**
144 * @todo Define visibility
145 */
146 public $allowPHPScripts = FALSE;
147
148 // Disable logging when importing
149 /**
150 * @todo Define visibility
151 */
152 public $enableLogging = FALSE;
153
154 // Array of values to substitute in editable softreferences.
155 /**
156 * @todo Define visibility
157 */
158 public $softrefInputValues = array();
159
160 // Mapping between the fileID from import memory and the final filenames they are written to.
161 /**
162 * @todo Define visibility
163 */
164 public $fileIDMap = array();
165
166 // Configuration, export
167 // 1MB max file size
168 /**
169 * @todo Define visibility
170 */
171 public $maxFileSize = 1000000;
172
173 // 1MB max record size
174 /**
175 * @todo Define visibility
176 */
177 public $maxRecordSize = 1000000;
178
179 // 10MB max export size
180 /**
181 * @todo Define visibility
182 */
183 public $maxExportSize = 10000000;
184
185 // add table names here which are THE ONLY ones which will be included into export if found as relations. '_ALL' will allow all tables.
186 /**
187 * @todo Define visibility
188 */
189 public $relOnlyTables = array();
190
191 // add tables names here which should not be exported with the file. (Where relations should be mapped to same UIDs in target system).
192 /**
193 * @todo Define visibility
194 */
195 public $relStaticTables = array();
196
197 // Exclude map. Keys are table:uid pairs and if set, records are not added to the export.
198 /**
199 * @todo Define visibility
200 */
201 public $excludeMap = array();
202
203 // Soft Reference Token ID modes.
204 /**
205 * @todo Define visibility
206 */
207 public $softrefCfg = array();
208
209 // Listing extension dependencies.
210 /**
211 * @todo Define visibility
212 */
213 public $extensionDependencies = array();
214
215 // Set by user: If set, compression in t3d files is disabled
216 /**
217 * @todo Define visibility
218 */
219 public $dontCompress = 0;
220
221 // Boolean, if set, HTML file resources are included.
222 /**
223 * @todo Define visibility
224 */
225 public $includeExtFileResources = 0;
226
227 // Files with external media (HTML/css style references inside)
228 /**
229 * @todo Define visibility
230 */
231 public $extFileResourceExtensions = 'html,htm,css';
232
233 // Internal, dynamic:
234 // After records are written this array is filled with [table][original_uid] = [new_uid]
235 /**
236 * @todo Define visibility
237 */
238 public $import_mapId = array();
239
240 // Keys are [tablename]:[new NEWxxx ids (or when updating it is uids)] while values are arrays with table/uid of the original record it is based on. By the array keys the new ids can be looked up inside tcemain
241 /**
242 * @todo Define visibility
243 */
244 public $import_newId = array();
245
246 // Page id map for page tree (import)
247 /**
248 * @todo Define visibility
249 */
250 public $import_newId_pids = array();
251
252 // Internal data accumulation for writing records during import
253 /**
254 * @todo Define visibility
255 */
256 public $import_data = array();
257
258 // Error log.
259 /**
260 * @todo Define visibility
261 */
262 public $errorLog = array();
263
264 // Cache for record paths
265 /**
266 * @todo Define visibility
267 */
268 public $cache_getRecordPath = array();
269
270 // Cache of checkPID values.
271 /**
272 * @todo Define visibility
273 */
274 public $checkPID_cache = array();
275
276 // Set internally if the gzcompress function exists
277 /**
278 * @todo Define visibility
279 */
280 public $compress = 0;
281
282 // Internal import/export memory
283 /**
284 * @todo Define visibility
285 */
286 public $dat = array();
287
288 /**
289 * File processing object
290 *
291 * @var \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility
292 * @todo Define visibility
293 */
294 public $fileProcObj = '';
295
296 /**************************
297 *
298 * Initialize
299 *
300 *************************/
301 /**
302 * Init the object, both import and export
303 *
304 * @param boolean $dontCompress If set, compression in t3d files is disabled
305 * @param string $mode Mode of usage, either "import" or "export
306 * @return void
307 * @todo Define visibility
308 */
309 public function init($dontCompress = 0, $mode = '') {
310 $this->compress = function_exists('gzcompress');
311 $this->dontCompress = $dontCompress;
312 $this->mode = $mode;
313 }
314
315 /**************************
316 *
317 * Export / Init + Meta Data
318 *
319 *************************/
320 /**
321 * Set header basics
322 *
323 * @return void
324 * @todo Define visibility
325 */
326 public function setHeaderBasics() {
327 // Initializing:
328 if (is_array($this->softrefCfg)) {
329 foreach ($this->softrefCfg as $key => $value) {
330 if (!strlen($value['mode'])) {
331 unset($this->softrefCfg[$key]);
332 }
333 }
334 }
335 // Setting in header memory:
336 // Version of file format
337 $this->dat['header']['XMLversion'] = '1.0';
338 // Initialize meta data array (to put it in top of file)
339 $this->dat['header']['meta'] = array();
340 // Add list of tables to consider static
341 $this->dat['header']['relStaticTables'] = $this->relStaticTables;
342 // The list of excluded records
343 $this->dat['header']['excludeMap'] = $this->excludeMap;
344 // Soft Reference mode for elements
345 $this->dat['header']['softrefCfg'] = $this->softrefCfg;
346 // List of extensions the import depends on.
347 $this->dat['header']['extensionDependencies'] = $this->extensionDependencies;
348 }
349
350 /**
351 * Set charset
352 *
353 * @param string $charset Charset for the content in the export. During import the character set will be converted if the target system uses another charset.
354 * @return void
355 * @todo Define visibility
356 */
357 public function setCharset($charset) {
358 $this->dat['header']['charset'] = $charset;
359 }
360
361 /**
362 * Sets meta data
363 *
364 * @param string $title Title of the export
365 * @param string $description Description of the export
366 * @param string $notes Notes about the contents
367 * @param string $packager_username Backend Username of the packager (the guy making the export)
368 * @param string $packager_name Real name of the packager
369 * @param string $packager_email Email of the packager
370 * @return void
371 * @todo Define visibility
372 */
373 public function setMetaData($title, $description, $notes, $packager_username, $packager_name, $packager_email) {
374 $this->dat['header']['meta'] = array(
375 'title' => $title,
376 'description' => $description,
377 'notes' => $notes,
378 'packager_username' => $packager_username,
379 'packager_name' => $packager_name,
380 'packager_email' => $packager_email,
381 'TYPO3_version' => TYPO3_version,
382 'created' => strftime('%A %e. %B %Y', $GLOBALS['EXEC_TIME'])
383 );
384 }
385
386 /**
387 * Sets a thumbnail image to the exported file
388 *
389 * @param string $imgFilepath Filename reference, gif, jpg, png. Absolute path.
390 * @return void
391 * @todo Define visibility
392 */
393 public function addThumbnail($imgFilepath) {
394 if (@is_file($imgFilepath)) {
395 $imgInfo = @getimagesize($imgFilepath);
396 if (is_array($imgInfo)) {
397 $fileContent = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($imgFilepath);
398 $this->dat['header']['thumbnail'] = array(
399 'imgInfo' => $imgInfo,
400 'content' => $fileContent,
401 'filesize' => strlen($fileContent),
402 'filemtime' => filemtime($imgFilepath),
403 'filename' => basename($imgFilepath)
404 );
405 }
406 }
407 }
408
409 /**************************
410 *
411 * Export / Init Page tree
412 *
413 *************************/
414 /**
415 * Sets the page-tree array in the export header and returns the array in a flattened version
416 *
417 * @param array $idH Hierarchy of ids, the page tree: array([uid] => array("uid" => [uid], "subrow" => array(.....)), [uid] => ....)
418 * @return array The hierarchical page tree converted to a one-dimensional list of pages
419 * @todo Define visibility
420 */
421 public function setPageTree($idH) {
422 $this->dat['header']['pagetree'] = $this->unsetExcludedSections($idH);
423 return $this->flatInversePageTree($this->dat['header']['pagetree']);
424 }
425
426 /**
427 * Removes entries in the page tree which are found in ->excludeMap[]
428 *
429 * @param array $idH Page uid hierarchy
430 * @return array Modified input array
431 * @access private
432 * @see setPageTree()
433 * @todo Define visibility
434 */
435 public function unsetExcludedSections($idH) {
436 if (is_array($idH)) {
437 foreach ($idH as $k => $v) {
438 if ($this->excludeMap['pages:' . $idH[$k]['uid']]) {
439 unset($idH[$k]);
440 } elseif (is_array($idH[$k]['subrow'])) {
441 $idH[$k]['subrow'] = $this->unsetExcludedSections($idH[$k]['subrow']);
442 }
443 }
444 }
445 return $idH;
446 }
447
448 /**
449 * Recursively flattening the idH array (for setPageTree() function)
450 *
451 * @param array $idH Page uid hierarchy
452 * @param array $a Accumulation array of pages (internal, don't set from outside)
453 * @return array Array with uid-uid pairs for all pages in the page tree.
454 * @see flatInversePageTree_pid()
455 * @todo Define visibility
456 */
457 public function flatInversePageTree($idH, $a = array()) {
458 if (is_array($idH)) {
459 $idH = array_reverse($idH);
460 foreach ($idH as $k => $v) {
461 $a[$v['uid']] = $v['uid'];
462 if (is_array($v['subrow'])) {
463 $a = $this->flatInversePageTree($v['subrow'], $a);
464 }
465 }
466 }
467 return $a;
468 }
469
470 /**
471 * Recursively flattening the idH array (for setPageTree() function), setting PIDs as values
472 *
473 * @param array $idH Page uid hierarchy
474 * @param array $a Accumulation array of pages (internal, don't set from outside)
475 * @param integer $pid PID value (internal)
476 * @return array Array with uid-pid pairs for all pages in the page tree.
477 * @see flatInversePageTree()
478 * @todo Define visibility
479 */
480 public function flatInversePageTree_pid($idH, $a = array(), $pid = -1) {
481 if (is_array($idH)) {
482 $idH = array_reverse($idH);
483 foreach ($idH as $k => $v) {
484 $a[$v['uid']] = $pid;
485 if (is_array($v['subrow'])) {
486 $a = $this->flatInversePageTree_pid($v['subrow'], $a, $v['uid']);
487 }
488 }
489 }
490 return $a;
491 }
492
493 /**************************
494 *
495 * Export
496 *
497 *************************/
498 /**
499 * Adds the record $row from $table.
500 * No checking for relations done here. Pure data.
501 *
502 * @param string $table Table name
503 * @param array $row Record row.
504 * @param integer $relationLevel (Internal) if the record is added as a relation, this is set to the "level" it was on.
505 * @return void
506 * @todo Define visibility
507 */
508 public function export_addRecord($table, $row, $relationLevel = 0) {
509 \TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL($table, $row);
510 if (strcmp($table, '') && is_array($row) && $row['uid'] > 0 && !$this->excludeMap[($table . ':' . $row['uid'])]) {
511 if ($this->checkPID($table === 'pages' ? $row['uid'] : $row['pid'])) {
512 if (!isset($this->dat['records'][($table . ':' . $row['uid'])])) {
513 // Prepare header info:
514 $headerInfo = array();
515 $headerInfo['uid'] = $row['uid'];
516 $headerInfo['pid'] = $row['pid'];
517 $headerInfo['title'] = \TYPO3\CMS\Core\Utility\GeneralUtility::fixed_lgd_cs(\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle($table, $row), 40);
518 $headerInfo['size'] = strlen(serialize($row));
519 if ($relationLevel) {
520 $headerInfo['relationLevel'] = $relationLevel;
521 }
522 // If record content is not too large in size, set the header content and add the rest:
523 if ($headerInfo['size'] < $this->maxRecordSize) {
524 // Set the header summary:
525 $this->dat['header']['records'][$table][$row['uid']] = $headerInfo;
526 // Create entry in the PID lookup:
527 $this->dat['header']['pid_lookup'][$row['pid']][$table][$row['uid']] = 1;
528 // Initialize reference index object:
529 $refIndexObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\ReferenceIndex');
530 // Yes to workspace overlays for exporting....
531 $refIndexObj->WSOL = TRUE;
532 // Data:
533 $this->dat['records'][$table . ':' . $row['uid']] = array();
534 $this->dat['records'][$table . ':' . $row['uid']]['data'] = $row;
535 $this->dat['records'][$table . ':' . $row['uid']]['rels'] = $refIndexObj->getRelations($table, $row);
536 $this->errorLog = array_merge($this->errorLog, $refIndexObj->errorLog);
537 // Merge error logs.
538 // Add information about the relations in the record in the header:
539 $this->dat['header']['records'][$table][$row['uid']]['rels'] = $this->flatDBrels($this->dat['records'][$table . ':' . $row['uid']]['rels']);
540 // Add information about the softrefs to header:
541 $this->dat['header']['records'][$table][$row['uid']]['softrefs'] = $this->flatSoftRefs($this->dat['records'][$table . ':' . $row['uid']]['rels']);
542 } else {
543 $this->error('Record ' . $table . ':' . $row['uid'] . ' was larger than maxRecordSize (' . \TYPO3\CMS\Core\Utility\GeneralUtility::formatSize($this->maxRecordSize) . ')');
544 }
545 } else {
546 $this->error('Record ' . $table . ':' . $row['uid'] . ' already added.');
547 }
548 } else {
549 $this->error('Record ' . $table . ':' . $row['uid'] . ' was outside your DB mounts!');
550 }
551 }
552 }
553
554 /**
555 * This analyses the existing added records, finds all database relations to records and adds these records to the export file.
556 * This function can be called repeatedly until it returns an empty array. In principle it should not allow to infinite recursivity, but you better set a limit...
557 * Call this BEFORE the ext_addFilesFromRelations (so files from added relations are also included of course)
558 *
559 * @param integer $relationLevel Recursion level
560 * @return array overview of relations found and added: Keys [table]:[uid], values array with table and id
561 * @see export_addFilesFromRelations()
562 * @todo Define visibility
563 */
564 public function export_addDBRelations($relationLevel = 0) {
565 // Initialize:
566 $addR = array();
567 // Traverse all "rels" registered for "records"
568 if (is_array($this->dat['records'])) {
569 foreach ($this->dat['records'] as $k => $value) {
570 if (is_array($this->dat['records'][$k])) {
571 foreach ($this->dat['records'][$k]['rels'] as $fieldname => $vR) {
572 // For all DB types of relations:
573 if ($vR['type'] == 'db') {
574 foreach ($vR['itemArray'] as $fI) {
575 $this->export_addDBRelations_registerRelation($fI, $addR);
576 }
577 }
578 // For all flex/db types of relations:
579 if ($vR['type'] == 'flex') {
580 // DB relations in flex form fields:
581 if (is_array($vR['flexFormRels']['db'])) {
582 foreach ($vR['flexFormRels']['db'] as $subList) {
583 foreach ($subList as $fI) {
584 $this->export_addDBRelations_registerRelation($fI, $addR);
585 }
586 }
587 }
588 // DB oriented soft references in flex form fields:
589 if (is_array($vR['flexFormRels']['softrefs'])) {
590 foreach ($vR['flexFormRels']['softrefs'] as $subList) {
591 foreach ($subList['keys'] as $spKey => $elements) {
592 foreach ($elements as $el) {
593 if ($el['subst']['type'] === 'db' && $this->includeSoftref($el['subst']['tokenID'])) {
594 list($tempTable, $tempUid) = explode(':', $el['subst']['recordRef']);
595 $fI = array(
596 'table' => $tempTable,
597 'id' => $tempUid
598 );
599 $this->export_addDBRelations_registerRelation($fI, $addR, $el['subst']['tokenID']);
600 }
601 }
602 }
603 }
604 }
605 }
606 // In any case, if there are soft refs:
607 if (is_array($vR['softrefs']['keys'])) {
608 foreach ($vR['softrefs']['keys'] as $spKey => $elements) {
609 foreach ($elements as $el) {
610 if ($el['subst']['type'] === 'db' && $this->includeSoftref($el['subst']['tokenID'])) {
611 list($tempTable, $tempUid) = explode(':', $el['subst']['recordRef']);
612 $fI = array(
613 'table' => $tempTable,
614 'id' => $tempUid
615 );
616 $this->export_addDBRelations_registerRelation($fI, $addR, $el['subst']['tokenID']);
617 }
618 }
619 }
620 }
621 }
622 }
623 }
624 } else {
625 $this->error('There were no records available.');
626 }
627 // Now, if there were new records to add, do so:
628 if (count($addR)) {
629 foreach ($addR as $fI) {
630 // Get and set record:
631 $row = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($fI['table'], $fI['id']);
632 if (is_array($row)) {
633 $this->export_addRecord($fI['table'], $row, $relationLevel + 1);
634 }
635 // Set status message
636 // Relation pointers always larger than zero except certain "select" types with negative values pointing to uids - but that is not supported here.
637 if ($fI['id'] > 0) {
638 $rId = $fI['table'] . ':' . $fI['id'];
639 if (!isset($this->dat['records'][$rId])) {
640 $this->dat['records'][$rId] = 'NOT_FOUND';
641 $this->error('Relation record ' . $rId . ' was not found!');
642 }
643 }
644 }
645 }
646 // Return overview of relations found and added
647 return $addR;
648 }
649
650 /**
651 * Helper function for export_addDBRelations()
652 *
653 * @param array $fI Array with table/id keys to add
654 * @param array $addR Add array, passed by reference to be modified
655 * @param string $tokenID Softref Token ID, if applicable.
656 * @return void
657 * @see export_addDBRelations()
658 * @todo Define visibility
659 */
660 public function export_addDBRelations_registerRelation($fI, &$addR, $tokenID = '') {
661 $rId = $fI['table'] . ':' . $fI['id'];
662 if (isset($GLOBALS['TCA'][$fI['table']]) && !$this->isTableStatic($fI['table']) && !$this->isExcluded($fI['table'], $fI['id']) && (!$tokenID || $this->includeSoftref($tokenID)) && $this->inclRelation($fI['table'])) {
663 if (!isset($this->dat['records'][$rId])) {
664 // Set this record to be included since it is not already.
665 $addR[$rId] = $fI;
666 }
667 }
668 }
669
670 /**
671 * This adds all files in relations.
672 * Call this method AFTER adding all records including relations.
673 *
674 * @return void
675 * @see export_addDBRelations()
676 * @todo Define visibility
677 */
678 public function export_addFilesFromRelations() {
679 // Traverse all "rels" registered for "records"
680 if (is_array($this->dat['records'])) {
681 foreach ($this->dat['records'] as $k => $value) {
682 if (is_array($this->dat['records'][$k]['rels'])) {
683 foreach ($this->dat['records'][$k]['rels'] as $fieldname => $vR) {
684 // For all file type relations:
685 if ($vR['type'] == 'file') {
686 foreach ($vR['newValueFiles'] as $key => $fI) {
687 $this->export_addFile($fI, $k, $fieldname);
688 // Remove the absolute reference to the file so it doesn't expose absolute paths from source server:
689 unset($this->dat['records'][$k]['rels'][$fieldname]['newValueFiles'][$key]['ID_absFile']);
690 }
691 }
692 // For all flex type relations:
693 if ($vR['type'] == 'flex') {
694 if (is_array($vR['flexFormRels']['file'])) {
695 foreach ($vR['flexFormRels']['file'] as $key => $subList) {
696 foreach ($subList as $subKey => $fI) {
697 $this->export_addFile($fI, $k, $fieldname);
698 // Remove the absolute reference to the file so it doesn't expose absolute paths from source server:
699 unset($this->dat['records'][$k]['rels'][$fieldname]['flexFormRels']['file'][$key][$subKey]['ID_absFile']);
700 }
701 }
702 }
703 // DB oriented soft references in flex form fields:
704 if (is_array($vR['flexFormRels']['softrefs'])) {
705 foreach ($vR['flexFormRels']['softrefs'] as $key => $subList) {
706 foreach ($subList['keys'] as $spKey => $elements) {
707 foreach ($elements as $subKey => $el) {
708 if ($el['subst']['type'] === 'file' && $this->includeSoftref($el['subst']['tokenID'])) {
709 // Create abs path and ID for file:
710 $ID_absFile = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName(PATH_site . $el['subst']['relFileName']);
711 $ID = md5($ID_absFile);
712 if ($ID_absFile) {
713 if (!$this->dat['files'][$ID]) {
714 $fI = array(
715 'filename' => basename($ID_absFile),
716 'ID_absFile' => $ID_absFile,
717 'ID' => $ID,
718 'relFileName' => $el['subst']['relFileName']
719 );
720 $this->export_addFile($fI, '_SOFTREF_');
721 }
722 $this->dat['records'][$k]['rels'][$fieldname]['flexFormRels']['softrefs'][$key]['keys'][$spKey][$subKey]['file_ID'] = $ID;
723 }
724 }
725 }
726 }
727 }
728 }
729 }
730 // In any case, if there are soft refs:
731 if (is_array($vR['softrefs']['keys'])) {
732 foreach ($vR['softrefs']['keys'] as $spKey => $elements) {
733 foreach ($elements as $subKey => $el) {
734 if ($el['subst']['type'] === 'file' && $this->includeSoftref($el['subst']['tokenID'])) {
735 // Create abs path and ID for file:
736 $ID_absFile = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName(PATH_site . $el['subst']['relFileName']);
737 $ID = md5($ID_absFile);
738 if ($ID_absFile) {
739 if (!$this->dat['files'][$ID]) {
740 $fI = array(
741 'filename' => basename($ID_absFile),
742 'ID_absFile' => $ID_absFile,
743 'ID' => $ID,
744 'relFileName' => $el['subst']['relFileName']
745 );
746 $this->export_addFile($fI, '_SOFTREF_');
747 }
748 $this->dat['records'][$k]['rels'][$fieldname]['softrefs']['keys'][$spKey][$subKey]['file_ID'] = $ID;
749 }
750 }
751 }
752 }
753 }
754 }
755 }
756 }
757 } else {
758 $this->error('There were no records available.');
759 }
760 }
761
762 /**
763 * Adds a files content to the export memory
764 *
765 * @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". "relFileName" is optional for files attached to records, but mandatory for soft referenced files (since the relFileName determines where such a file should be stored!)
766 * @param string $recordRef If the file is related to a record, this is the id on the form [table]:[id]. Information purposes only.
767 * @param string $fieldname If the file is related to a record, this is the field name it was related to. Information purposes only.
768 * @return void
769 * @todo Define visibility
770 */
771 public function export_addFile($fI, $recordRef = '', $fieldname = '') {
772 if (@is_file($fI['ID_absFile'])) {
773 if (filesize($fI['ID_absFile']) < $this->maxFileSize) {
774 $fileRec = array();
775 $fileRec['filesize'] = filesize($fI['ID_absFile']);
776 $fileRec['filename'] = basename($fI['ID_absFile']);
777 $fileRec['filemtime'] = filemtime($fI['ID_absFile']);
778 //for internal type file_reference
779 $fileRec['relFileRef'] = substr($fI['ID_absFile'], strlen(PATH_site));
780 if ($recordRef) {
781 $fileRec['record_ref'] = $recordRef . '/' . $fieldname;
782 }
783 if ($fI['relFileName']) {
784 $fileRec['relFileName'] = $fI['relFileName'];
785 }
786 // Setting this data in the header
787 $this->dat['header']['files'][$fI['ID']] = $fileRec;
788 // ... and for the recordlisting, why not let us know WHICH relations there was...
789 if ($recordRef && $recordRef !== '_SOFTREF_') {
790 $refParts = explode(':', $recordRef, 2);
791 if (!is_array($this->dat['header']['records'][$refParts[0]][$refParts[1]]['filerefs'])) {
792 $this->dat['header']['records'][$refParts[0]][$refParts[1]]['filerefs'] = array();
793 }
794 $this->dat['header']['records'][$refParts[0]][$refParts[1]]['filerefs'][] = $fI['ID'];
795 }
796 // ... and finally add the heavy stuff:
797 $fileRec['content'] = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($fI['ID_absFile']);
798 $fileRec['content_md5'] = md5($fileRec['content']);
799 $this->dat['files'][$fI['ID']] = $fileRec;
800 // For soft references, do further processing:
801 if ($recordRef === '_SOFTREF_') {
802 // RTE files?
803 if ($RTEoriginal = $this->getRTEoriginalFilename(basename($fI['ID_absFile']))) {
804 $RTEoriginal_absPath = dirname($fI['ID_absFile']) . '/' . $RTEoriginal;
805 if (@is_file($RTEoriginal_absPath)) {
806 $RTEoriginal_ID = md5($RTEoriginal_absPath);
807 $fileRec = array();
808 $fileRec['filesize'] = filesize($RTEoriginal_absPath);
809 $fileRec['filename'] = basename($RTEoriginal_absPath);
810 $fileRec['filemtime'] = filemtime($RTEoriginal_absPath);
811 $fileRec['record_ref'] = '_RTE_COPY_ID:' . $fI['ID'];
812 $this->dat['header']['files'][$fI['ID']]['RTE_ORIG_ID'] = $RTEoriginal_ID;
813 // Setting this data in the header
814 $this->dat['header']['files'][$RTEoriginal_ID] = $fileRec;
815 // ... and finally add the heavy stuff:
816 $fileRec['content'] = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($RTEoriginal_absPath);
817 $fileRec['content_md5'] = md5($fileRec['content']);
818 $this->dat['files'][$RTEoriginal_ID] = $fileRec;
819 } else {
820 $this->error('RTE original file "' . substr($RTEoriginal_absPath, strlen(PATH_site)) . '" was not found!');
821 }
822 }
823 // Files with external media?
824 // This is only done with files grabbed by a softreference parser since it is deemed improbable that hard-referenced files should undergo this treatment.
825 $html_fI = pathinfo(basename($fI['ID_absFile']));
826 if ($this->includeExtFileResources && \TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->extFileResourceExtensions, strtolower($html_fI['extension']))) {
827 $uniquePrefix = '###' . md5($GLOBALS['EXEC_TIME']) . '###';
828 if (strtolower($html_fI['extension']) === 'css') {
829 $prefixedMedias = explode($uniquePrefix, preg_replace('/(url[[:space:]]*\\([[:space:]]*["\']?)([^"\')]*)(["\']?[[:space:]]*\\))/i', '\\1' . $uniquePrefix . '\\2' . $uniquePrefix . '\\3', $fileRec['content']));
830 } else {
831 // html, htm:
832 $htmlParser = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Html\\HtmlParser');
833 $prefixedMedias = explode($uniquePrefix, $htmlParser->prefixResourcePath($uniquePrefix, $fileRec['content'], array(), $uniquePrefix));
834 }
835 $htmlResourceCaptured = FALSE;
836 foreach ($prefixedMedias as $k => $v) {
837 if ($k % 2) {
838 $EXTres_absPath = \TYPO3\CMS\Core\Utility\GeneralUtility::resolveBackPath(dirname($fI['ID_absFile']) . '/' . $v);
839 $EXTres_absPath = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($EXTres_absPath);
840 if ($EXTres_absPath && \TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($EXTres_absPath, PATH_site . $this->fileadminFolderName . '/') && @is_file($EXTres_absPath)) {
841 $htmlResourceCaptured = TRUE;
842 $EXTres_ID = md5($EXTres_absPath);
843 $this->dat['header']['files'][$fI['ID']]['EXT_RES_ID'][] = $EXTres_ID;
844 $prefixedMedias[$k] = '{EXT_RES_ID:' . $EXTres_ID . '}';
845 // Add file to memory if it is not set already:
846 if (!isset($this->dat['header']['files'][$EXTres_ID])) {
847 $fileRec = array();
848 $fileRec['filesize'] = filesize($EXTres_absPath);
849 $fileRec['filename'] = basename($EXTres_absPath);
850 $fileRec['filemtime'] = filemtime($EXTres_absPath);
851 $fileRec['record_ref'] = '_EXT_PARENT_:' . $fI['ID'];
852 // Media relative to the HTML file.
853 $fileRec['parentRelFileName'] = $v;
854 // Setting this data in the header
855 $this->dat['header']['files'][$EXTres_ID] = $fileRec;
856 // ... and finally add the heavy stuff:
857 $fileRec['content'] = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($EXTres_absPath);
858 $fileRec['content_md5'] = md5($fileRec['content']);
859 $this->dat['files'][$EXTres_ID] = $fileRec;
860 }
861 }
862 }
863 }
864 if ($htmlResourceCaptured) {
865 $this->dat['files'][$fI['ID']]['tokenizedContent'] = implode('', $prefixedMedias);
866 }
867 }
868 }
869 } else {
870 $this->error($fI['ID_absFile'] . ' was larger (' . \TYPO3\CMS\Core\Utility\GeneralUtility::formatSize(filesize($fI['ID_absFile'])) . ') than the maxFileSize (' . \TYPO3\CMS\Core\Utility\GeneralUtility::formatSize($this->maxFileSize) . ')! Skipping.');
871 }
872 } else {
873 $this->error($fI['ID_absFile'] . ' was not a file! Skipping.');
874 }
875 }
876
877 /**
878 * DB relations flattend to 1-dim array.
879 * The list will be unique, no table/uid combination will appear twice.
880 *
881 * @param array $dbrels 2-dim Array of database relations organized by table key
882 * @return array 1-dim array where entries are table:uid and keys are array with table/id
883 * @todo Define visibility
884 */
885 public function flatDBrels($dbrels) {
886 $list = array();
887 foreach ($dbrels as $dat) {
888 if ($dat['type'] == 'db') {
889 foreach ($dat['itemArray'] as $i) {
890 $list[$i['table'] . ':' . $i['id']] = $i;
891 }
892 }
893 if ($dat['type'] == 'flex' && is_array($dat['flexFormRels']['db'])) {
894 foreach ($dat['flexFormRels']['db'] as $subList) {
895 foreach ($subList as $i) {
896 $list[$i['table'] . ':' . $i['id']] = $i;
897 }
898 }
899 }
900 }
901 return $list;
902 }
903
904 /**
905 * Soft References flattend to 1-dim array.
906 *
907 * @param array $dbrels 2-dim Array of database relations organized by table key
908 * @return array 1-dim array where entries are arrays with properties of the soft link found and keys are a unique combination of field, spKey, structure path if applicable and token ID
909 * @todo Define visibility
910 */
911 public function flatSoftRefs($dbrels) {
912 $list = array();
913 foreach ($dbrels as $field => $dat) {
914 if (is_array($dat['softrefs']['keys'])) {
915 foreach ($dat['softrefs']['keys'] as $spKey => $elements) {
916 if (is_array($elements)) {
917 foreach ($elements as $subKey => $el) {
918 $lKey = $field . ':' . $spKey . ':' . $subKey;
919 $list[$lKey] = array_merge(array('field' => $field, 'spKey' => $spKey), $el);
920 // Add file_ID key to header - slightly "risky" way of doing this because if the calculation changes for the same value in $this->records[...] this will not work anymore!
921 if ($el['subst'] && $el['subst']['relFileName']) {
922 $list[$lKey]['file_ID'] = md5(PATH_site . $el['subst']['relFileName']);
923 }
924 }
925 }
926 }
927 }
928 if ($dat['type'] == 'flex' && is_array($dat['flexFormRels']['softrefs'])) {
929 foreach ($dat['flexFormRels']['softrefs'] as $structurePath => $subSoftrefs) {
930 if (is_array($subSoftrefs['keys'])) {
931 foreach ($subSoftrefs['keys'] as $spKey => $elements) {
932 foreach ($elements as $subKey => $el) {
933 $lKey = $field . ':' . $structurePath . ':' . $spKey . ':' . $subKey;
934 $list[$lKey] = array_merge(array('field' => $field, 'spKey' => $spKey, 'structurePath' => $structurePath), $el);
935 // Add file_ID key to header - slightly "risky" way of doing this because if the calculation changes for the same value in $this->records[...] this will not work anymore!
936 if ($el['subst'] && $el['subst']['relFileName']) {
937 $list[$lKey]['file_ID'] = md5(PATH_site . $el['subst']['relFileName']);
938 }
939 }
940 }
941 }
942 }
943 }
944 }
945 return $list;
946 }
947
948 /**************************
949 *
950 * File Output
951 *
952 *************************/
953 /**
954 * This compiles and returns the data content for an exported file
955 *
956 * @param string $type Type of output; "xml" gives xml, otherwise serialized array, possibly compressed.
957 * @return string The output file stream
958 * @todo Define visibility
959 */
960 public function compileMemoryToFileContent($type = '') {
961 if ($type == 'xml') {
962 $out = $this->createXML();
963 } else {
964 $compress = $this->doOutputCompress();
965 $out = '';
966 // adding header:
967 $out .= $this->addFilePart(serialize($this->dat['header']), $compress);
968 // adding records:
969 $out .= $this->addFilePart(serialize($this->dat['records']), $compress);
970 // adding files:
971 $out .= $this->addFilePart(serialize($this->dat['files']), $compress);
972 }
973 return $out;
974 }
975
976 /**
977 * Creates XML string from input array
978 *
979 * @return string XML content
980 * @todo Define visibility
981 */
982 public function createXML() {
983 // Options:
984 $options = array(
985 'alt_options' => array(
986 '/header' => array(
987 'disableTypeAttrib' => TRUE,
988 'clearStackPath' => TRUE,
989 'parentTagMap' => array(
990 'files' => 'file',
991 'records' => 'table',
992 'table' => 'rec',
993 'rec:rels' => 'relations',
994 'relations' => 'element',
995 'filerefs' => 'file',
996 'pid_lookup' => 'page_contents',
997 'header:relStaticTables' => 'static_tables',
998 'static_tables' => 'tablename',
999 'excludeMap' => 'item',
1000 'softrefCfg' => 'softrefExportMode',
1001 'extensionDependencies' => 'extkey',
1002 'softrefs' => 'softref_element'
1003 ),
1004 'alt_options' => array(
1005 '/pagetree' => array(
1006 'disableTypeAttrib' => TRUE,
1007 'useIndexTagForNum' => 'node',
1008 'parentTagMap' => array(
1009 'node:subrow' => 'node'
1010 )
1011 ),
1012 '/pid_lookup/page_contents' => array(
1013 'disableTypeAttrib' => TRUE,
1014 'parentTagMap' => array(
1015 'page_contents' => 'table'
1016 ),
1017 'grandParentTagMap' => array(
1018 'page_contents/table' => 'item'
1019 )
1020 )
1021 )
1022 ),
1023 '/records' => array(
1024 'disableTypeAttrib' => TRUE,
1025 'parentTagMap' => array(
1026 'records' => 'tablerow',
1027 'tablerow:data' => 'fieldlist',
1028 'tablerow:rels' => 'related',
1029 'related' => 'field',
1030 'field:itemArray' => 'relations',
1031 'field:newValueFiles' => 'filerefs',
1032 'field:flexFormRels' => 'flexform',
1033 'relations' => 'element',
1034 'filerefs' => 'file',
1035 'flexform:db' => 'db_relations',
1036 'flexform:file' => 'file_relations',
1037 'flexform:softrefs' => 'softref_relations',
1038 'softref_relations' => 'structurePath',
1039 'db_relations' => 'path',
1040 'file_relations' => 'path',
1041 'path' => 'element',
1042 'keys' => 'softref_key',
1043 'softref_key' => 'softref_element'
1044 ),
1045 'alt_options' => array(
1046 '/records/tablerow/fieldlist' => array(
1047 'useIndexTagForAssoc' => 'field'
1048 )
1049 )
1050 ),
1051 '/files' => array(
1052 'disableTypeAttrib' => TRUE,
1053 'parentTagMap' => array(
1054 'files' => 'file'
1055 )
1056 )
1057 )
1058 );
1059 // Creating XML file from $outputArray:
1060 $charset = $this->dat['header']['charset'] ? $this->dat['header']['charset'] : 'utf-8';
1061 $XML = '<?xml version="1.0" encoding="' . $charset . '" standalone="yes" ?>' . LF;
1062 $XML .= \TYPO3\CMS\Core\Utility\GeneralUtility::array2xml($this->dat, '', 0, 'T3RecordDocument', 0, $options);
1063 return $XML;
1064 }
1065
1066 /**
1067 * Returns TRUE if the output should be compressed.
1068 *
1069 * @return boolean TRUE if compression is possible AND requested.
1070 * @todo Define visibility
1071 */
1072 public function doOutputCompress() {
1073 return $this->compress && !$this->dontCompress;
1074 }
1075
1076 /**
1077 * Returns a content part for a filename being build.
1078 *
1079 * @param array $data Data to store in part
1080 * @param boolean $compress Compress file?
1081 * @return string Content stream.
1082 * @todo Define visibility
1083 */
1084 public function addFilePart($data, $compress = FALSE) {
1085 if ($compress) {
1086 $data = gzcompress($data);
1087 }
1088 return md5($data) . ':' . ($compress ? '1' : '0') . ':' . str_pad(strlen($data), 10, '0', STR_PAD_LEFT) . ':' . $data . ':';
1089 }
1090
1091 /***********************
1092 *
1093 * Import
1094 *
1095 ***********************/
1096 /**
1097 * Imports the internal data array to $pid.
1098 *
1099 * @param integer $pid Page ID in which to import the content
1100 * @return void
1101 * @todo Define visibility
1102 */
1103 public function importData($pid) {
1104 // Set this flag to indicate that an import is being/has been done.
1105 $this->doesImport = 1;
1106 // Initialize:
1107 // 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.
1108 $this->import_mapId = array();
1109 $this->import_newId = array();
1110 $this->import_newId_pids = array();
1111 // Temporary files stack initialized:
1112 $this->unlinkFiles = array();
1113 $this->alternativeFileName = array();
1114 $this->alternativeFilePath = array();
1115 // Write records, first pages, then the rest
1116 // Fields with "hard" relations to database, files and flexform fields are kept empty during this run
1117 $this->writeRecords_pages($pid);
1118 $this->writeRecords_records($pid);
1119 // Finally all the file and DB record references must be fixed. This is done after all records have supposedly been written to database:
1120 // $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.
1121 $this->setRelations();
1122 // 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):
1123 $this->setFlexFormRelations();
1124 // Unlink temporary files:
1125 $this->unlinkTempFiles();
1126 // Finally, traverse all records and process softreferences with substitution attributes.
1127 $this->processSoftReferences();
1128 }
1129
1130 /**
1131 * Writing pagetree/pages to database:
1132 *
1133 * @param integer $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
1134 * @return void
1135 * @see writeRecords_records()
1136 * @todo Define visibility
1137 */
1138 public function writeRecords_pages($pid) {
1139 // First, write page structure if any:
1140 if (is_array($this->dat['header']['records']['pages'])) {
1141 // $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.
1142 $pageRecords = $this->dat['header']['records']['pages'];
1143 $this->import_data = array();
1144 // First add page tree if any
1145 if (is_array($this->dat['header']['pagetree'])) {
1146 $pagesFromTree = $this->flatInversePageTree($this->dat['header']['pagetree']);
1147 foreach ($pagesFromTree as $uid) {
1148 $thisRec = $this->dat['header']['records']['pages'][$uid];
1149 // PID: Set the main $pid, unless a NEW-id is found
1150 $setPid = isset($this->import_newId_pids[$thisRec['pid']]) ? $this->import_newId_pids[$thisRec['pid']] : $pid;
1151 $this->addSingle('pages', $uid, $setPid);
1152 unset($pageRecords[$uid]);
1153 }
1154 }
1155 // Then add all remaining pages not in tree on root level:
1156 if (count($pageRecords)) {
1157 $remainingPageUids = array_keys($pageRecords);
1158 foreach ($remainingPageUids as $pUid) {
1159 $this->addSingle('pages', $pUid, $pid);
1160 }
1161 }
1162 // Now write to database:
1163 $tce = $this->getNewTCE();
1164 $this->callHook('before_writeRecordsPages', array(
1165 'tce' => &$tce,
1166 'data' => &$this->import_data
1167 ));
1168 $tce->suggestedInsertUids = $this->suggestedInsertUids;
1169 $tce->start($this->import_data, array());
1170 $tce->process_datamap();
1171 $this->callHook('after_writeRecordsPages', array(
1172 'tce' => &$tce
1173 ));
1174 // post-processing: Registering new ids (end all tcemain sessions with this)
1175 $this->addToMapId($tce->substNEWwithIDs);
1176 // In case of an update, order pages from the page tree correctly:
1177 if ($this->update && is_array($this->dat['header']['pagetree'])) {
1178 $this->writeRecords_pages_order($pid);
1179 }
1180 }
1181 }
1182
1183 /**
1184 * Organize all updated pages in page tree so they are related like in the import file
1185 * Only used for updates and when $this->dat['header']['pagetree'] is an array.
1186 *
1187 * @param integer $pid Page id in which to import
1188 * @return void
1189 * @access private
1190 * @see writeRecords_pages(), writeRecords_records_order()
1191 * @todo Define visibility
1192 */
1193 public function writeRecords_pages_order($pid) {
1194 $cmd_data = array();
1195 // Get uid-pid relations and traverse them in order to map to possible new IDs
1196 $pidsFromTree = $this->flatInversePageTree_pid($this->dat['header']['pagetree']);
1197 foreach ($pidsFromTree as $origPid => $newPid) {
1198 if ($newPid >= 0 && $this->dontIgnorePid('pages', $origPid)) {
1199 // If the page had a new id (because it was created) use that instead!
1200 if (substr($this->import_newId_pids[$origPid], 0, 3) === 'NEW') {
1201 if ($this->import_mapId['pages'][$origPid]) {
1202 $mappedPid = $this->import_mapId['pages'][$origPid];
1203 $cmd_data['pages'][$mappedPid]['move'] = $newPid;
1204 }
1205 } else {
1206 $cmd_data['pages'][$origPid]['move'] = $newPid;
1207 }
1208 }
1209 }
1210 // Execute the move commands if any:
1211 if (count($cmd_data)) {
1212 $tce = $this->getNewTCE();
1213 $this->callHook('before_writeRecordsPagesOrder', array(
1214 'tce' => &$tce,
1215 'data' => &$cmd_data
1216 ));
1217 $tce->start(array(), $cmd_data);
1218 $tce->process_cmdmap();
1219 $this->callHook('after_writeRecordsPagesOrder', array(
1220 'tce' => &$tce
1221 ));
1222 }
1223 }
1224
1225 /**
1226 * Write all database records except pages (writtein in writeRecords_pages())
1227 *
1228 * @param integer $pid Page id in which to import
1229 * @return void
1230 * @see writeRecords_pages()
1231 * @todo Define visibility
1232 */
1233 public function writeRecords_records($pid) {
1234 // Write the rest of the records
1235 $this->import_data = array();
1236 if (is_array($this->dat['header']['records'])) {
1237 foreach ($this->dat['header']['records'] as $table => $recs) {
1238 if ($table != 'pages') {
1239 foreach ($recs as $uid => $thisRec) {
1240 // PID: Set the main $pid, unless a NEW-id is found
1241 $setPid = isset($this->import_mapId['pages'][$thisRec['pid']]) ? $this->import_mapId['pages'][$thisRec['pid']] : $pid;
1242 if (is_array($GLOBALS['TCA'][$table]) && $GLOBALS['TCA'][$table]['ctrl']['rootLevel']) {
1243 $setPid = 0;
1244 }
1245 // Add record:
1246 $this->addSingle($table, $uid, $setPid);
1247 }
1248 }
1249 }
1250 } else {
1251 $this->error('Error: No records defined in internal data array.');
1252 }
1253 // Now write to database:
1254 $tce = $this->getNewTCE();
1255 $this->callHook('before_writeRecordsRecords', array(
1256 'tce' => &$tce,
1257 'data' => &$this->import_data
1258 ));
1259 $tce->suggestedInsertUids = $this->suggestedInsertUids;
1260 // Because all records are being submitted in their correct order with positive pid numbers - and so we should reverse submission order internally.
1261 $tce->reverseOrder = 1;
1262 $tce->start($this->import_data, array());
1263 $tce->process_datamap();
1264 $this->callHook('after_writeRecordsRecords', array(
1265 'tce' => &$tce
1266 ));
1267 // post-processing: Removing files and registering new ids (end all tcemain sessions with this)
1268 $this->addToMapId($tce->substNEWwithIDs);
1269 // In case of an update, order pages from the page tree correctly:
1270 if ($this->update) {
1271 $this->writeRecords_records_order($pid);
1272 }
1273 }
1274
1275 /**
1276 * Organize all updated record to their new positions.
1277 * Only used for updates
1278 *
1279 * @param integer $mainPid Main PID into which we import.
1280 * @return void
1281 * @access private
1282 * @see writeRecords_records(), writeRecords_pages_order()
1283 * @todo Define visibility
1284 */
1285 public function writeRecords_records_order($mainPid) {
1286 $cmd_data = array();
1287 if (is_array($this->dat['header']['pagetree'])) {
1288 $pagesFromTree = $this->flatInversePageTree($this->dat['header']['pagetree']);
1289 } else {
1290 $pagesFromTree = array();
1291 }
1292 if (is_array($this->dat['header']['pid_lookup'])) {
1293 foreach ($this->dat['header']['pid_lookup'] as $pid => $recList) {
1294 $newPid = isset($this->import_mapId['pages'][$pid]) ? $this->import_mapId['pages'][$pid] : $mainPid;
1295 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($newPid)) {
1296 foreach ($recList as $tableName => $uidList) {
1297 // If $mainPid===$newPid then we are on root level and we can consider to move pages as well!
1298 // (they will not be in the page tree!)
1299 if (($tableName != 'pages' || !$pagesFromTree[$pid]) && is_array($uidList)) {
1300 $uidList = array_reverse(array_keys($uidList));
1301 foreach ($uidList as $uid) {
1302 if ($this->dontIgnorePid($tableName, $uid)) {
1303 $cmd_data[$tableName][$uid]['move'] = $newPid;
1304 } else {
1305
1306 }
1307 }
1308 }
1309 }
1310 }
1311 }
1312 }
1313 // Execute the move commands if any:
1314 if (count($cmd_data)) {
1315 $tce = $this->getNewTCE();
1316 $this->callHook('before_writeRecordsRecordsOrder', array(
1317 'tce' => &$tce,
1318 'data' => &$cmd_data
1319 ));
1320 $tce->start(array(), $cmd_data);
1321 $tce->process_cmdmap();
1322 $this->callHook('after_writeRecordsRecordsOrder', array(
1323 'tce' => &$tce
1324 ));
1325 }
1326 }
1327
1328 /**
1329 * Adds a single record to the $importData array. Also copies files to tempfolder.
1330 * However all File/DB-references and flexform field contents are set to blank for now! That is done with setRelations() later
1331 *
1332 * @param string $table Table name (from import memory)
1333 * @param integer $uid Record UID (from import memory)
1334 * @param integer $pid Page id
1335 * @return void
1336 * @see writeRecords()
1337 * @todo Define visibility
1338 */
1339 public function addSingle($table, $uid, $pid) {
1340 if ($this->import_mode[$table . ':' . $uid] !== 'exclude') {
1341 $record = $this->dat['records'][$table . ':' . $uid]['data'];
1342 if (is_array($record)) {
1343 if ($this->update && $this->doesRecordExist($table, $uid) && $this->import_mode[$table . ':' . $uid] !== 'as_new') {
1344 $ID = $uid;
1345 } else {
1346 $ID = uniqid('NEW');
1347 }
1348 $this->import_newId[$table . ':' . $ID] = array('table' => $table, 'uid' => $uid);
1349 if ($table == 'pages') {
1350 $this->import_newId_pids[$uid] = $ID;
1351 }
1352 // Set main record data:
1353 $this->import_data[$table][$ID] = $record;
1354 $this->import_data[$table][$ID]['tx_impexp_origuid'] = $this->import_data[$table][$ID]['uid'];
1355 // Reset permission data:
1356 if ($table === 'pages') {
1357 // Have to reset the user/group IDs so pages are owned by importing user. Otherwise strange things may happen for non-admins!
1358 unset($this->import_data[$table][$ID]['perms_userid']);
1359 unset($this->import_data[$table][$ID]['perms_groupid']);
1360 }
1361 // PID and UID:
1362 unset($this->import_data[$table][$ID]['uid']);
1363 // Updates:
1364 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($ID)) {
1365 unset($this->import_data[$table][$ID]['pid']);
1366 } else {
1367 // Inserts:
1368 $this->import_data[$table][$ID]['pid'] = $pid;
1369 if (($this->import_mode[$table . ':' . $uid] === 'force_uid' && $this->update || $this->force_all_UIDS) && $GLOBALS['BE_USER']->isAdmin()) {
1370 $this->import_data[$table][$ID]['uid'] = $uid;
1371 $this->suggestedInsertUids[$table . ':' . $uid] = 'DELETE';
1372 }
1373 }
1374 // Setting db/file blank:
1375 foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) {
1376 switch ((string) $config['type']) {
1377 case 'db':
1378
1379 case 'file':
1380 // Fixed later in ->setRelations() [because we need to know ALL newly created IDs before we can map relations!]
1381 // In the meantime we set NO values for relations:
1382 $this->import_data[$table][$ID][$field] = '';
1383 break;
1384 case 'flex':
1385 // Fixed later in setFlexFormRelations()
1386 // In the meantime we set NO value for flexforms - this is mainly because file references inside will not be processed properly; In fact references will point to no file or existing files (in which case there will be double-references which is a big problem of course!)
1387 $this->import_data[$table][$ID][$field] = '';
1388 break;
1389 }
1390 }
1391 } elseif ($table . ':' . $uid != 'pages:0') {
1392 // On root level we don't want this error message.
1393 $this->error('Error: no record was found in data array!', 1);
1394 }
1395 }
1396 }
1397
1398 /**
1399 * Registers the substNEWids in memory.
1400 *
1401 * @param array $substNEWwithIDs From tcemain to be merged into internal mapping variable in this object
1402 * @return void
1403 * @see writeRecords()
1404 * @todo Define visibility
1405 */
1406 public function addToMapId($substNEWwithIDs) {
1407 foreach ($this->import_data as $table => $recs) {
1408 foreach ($recs as $id => $value) {
1409 $old_uid = $this->import_newId[$table . ':' . $id]['uid'];
1410 if (isset($substNEWwithIDs[$id])) {
1411 $this->import_mapId[$table][$old_uid] = $substNEWwithIDs[$id];
1412 } elseif ($this->update) {
1413 // Map same ID to same ID....
1414 $this->import_mapId[$table][$old_uid] = $id;
1415 } else {
1416 $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!', 1);
1417 }
1418 }
1419 }
1420 }
1421
1422 /**
1423 * Returns a new $TCE object
1424 *
1425 * @return object $TCE object
1426 * @todo Define visibility
1427 */
1428 public function getNewTCE() {
1429 $tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandler\\DataHandler');
1430 $tce->stripslashes_values = 0;
1431 $tce->dontProcessTransformations = 1;
1432 $tce->enableLogging = $this->enableLogging;
1433 $tce->alternativeFileName = $this->alternativeFileName;
1434 $tce->alternativeFilePath = $this->alternativeFilePath;
1435 return $tce;
1436 }
1437
1438 /**
1439 * Cleaning up all the temporary files stored in typo3temp/ folder
1440 *
1441 * @return void
1442 * @todo Define visibility
1443 */
1444 public function unlinkTempFiles() {
1445 foreach ($this->unlinkFiles as $fileName) {
1446 if (\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($fileName, PATH_site . 'typo3temp/')) {
1447 \TYPO3\CMS\Core\Utility\GeneralUtility::unlink_tempfile($fileName);
1448 clearstatcache();
1449 if (is_file($fileName)) {
1450 $this->error('Error: ' . $fileName . ' was NOT unlinked as it should have been!', 1);
1451 }
1452 } else {
1453 $this->error('Error: ' . $fileName . ' was not in temp-path. Not removed!', 1);
1454 }
1455 }
1456 $this->unlinkFiles = array();
1457 }
1458
1459 /***************************
1460 *
1461 * Import / Relations setting
1462 *
1463 ***************************/
1464 /**
1465 * At the end of the import process all file and DB relations should be set properly (that is relations to imported records are all re-created so imported records are correctly related again)
1466 * Relations in flexform fields are processed in setFlexFormRelations() after this function
1467 *
1468 * @return void
1469 * @see setFlexFormRelations()
1470 * @todo Define visibility
1471 */
1472 public function setRelations() {
1473 $updateData = array();
1474 // import_newId contains a register of all records that was in the import memorys "records" key
1475 foreach ($this->import_newId as $nId => $dat) {
1476 $table = $dat['table'];
1477 $uid = $dat['uid'];
1478 // original UID - NOT the new one!
1479 // If the record has been written and received a new id, then proceed:
1480 if (is_array($this->import_mapId[$table]) && isset($this->import_mapId[$table][$uid])) {
1481 $thisNewUid = \TYPO3\CMS\Backend\Utility\BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]);
1482 if (is_array($this->dat['records'][$table . ':' . $uid]['rels'])) {
1483 // Traverse relation fields of each record
1484 foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) {
1485 switch ((string) $config['type']) {
1486 case 'db':
1487 if (is_array($config['itemArray']) && count($config['itemArray'])) {
1488 $valArray = $this->setRelations_db($config['itemArray']);
1489 $updateData[$table][$thisNewUid][$field] = implode(',', $valArray);
1490 }
1491 break;
1492 case 'file':
1493 if (is_array($config['newValueFiles']) && count($config['newValueFiles'])) {
1494 $valArr = array();
1495 foreach ($config['newValueFiles'] as $fI) {
1496 $valArr[] = $this->import_addFileNameToBeCopied($fI);
1497 }
1498 $updateData[$table][$thisNewUid][$field] = implode(',', $valArr);
1499 }
1500 break;
1501 }
1502 }
1503 } else {
1504 $this->error('Error: no record was found in data array!', 1);
1505 }
1506 } else {
1507 $this->error('Error: this records is NOT created it seems! (' . $table . ':' . $uid . ')', 1);
1508 }
1509 }
1510 if (count($updateData)) {
1511 $tce = $this->getNewTCE();
1512 $this->callHook('before_setRelation', array(
1513 'tce' => &$tce,
1514 'data' => &$updateData
1515 ));
1516 $tce->start($updateData, array());
1517 $tce->process_datamap();
1518 $this->callHook('after_setRelations', array(
1519 'tce' => &$tce
1520 ));
1521 }
1522 }
1523
1524 /**
1525 * Maps relations for database
1526 *
1527 * @param array $itemArray Array of item sets (table/uid) from a dbAnalysis object
1528 * @return array Array with values [table]_[uid]. These values have the regular tcemain-input group/select type which means they will automatically be processed into a uid-list or MM relations.
1529 * @todo Define visibility
1530 */
1531 public function setRelations_db($itemArray) {
1532 $valArray = array();
1533 foreach ($itemArray as $relDat) {
1534 if (is_array($this->import_mapId[$relDat['table']]) && isset($this->import_mapId[$relDat['table']][$relDat['id']])) {
1535 $valArray[] = $relDat['table'] . '_' . $this->import_mapId[$relDat['table']][$relDat['id']];
1536 } elseif ($this->isTableStatic($relDat['table']) || $this->isExcluded($relDat['table'], $relDat['id']) || $relDat['id'] < 0) {
1537 // Checking for less than zero because some select types could contain negative values, eg. fe_groups (-1, -2) and sys_language (-1 = ALL languages). This must be handled on both export and import.
1538 $valArray[] = $relDat['table'] . '_' . $relDat['id'];
1539 } else {
1540 $this->error('Lost relation: ' . $relDat['table'] . ':' . $relDat['id'], 1);
1541 }
1542 }
1543 return $valArray;
1544 }
1545
1546 /**
1547 * Writes the file from import array to temp dir and returns the filename of it.
1548 *
1549 * @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
1550 * @return string Absolute filename of the temporary filename of the file. In ->alternativeFileName the original name is set.
1551 * @todo Define visibility
1552 */
1553 public function import_addFileNameToBeCopied($fI) {
1554 if (is_array($this->dat['files'][$fI['ID']])) {
1555 $tmpFile = \TYPO3\CMS\Core\Utility\GeneralUtility::tempnam('import_temp_');
1556 \TYPO3\CMS\Core\Utility\GeneralUtility::writeFile($tmpFile, $this->dat['files'][$fI['ID']]['content']);
1557 clearstatcache();
1558 if (@is_file($tmpFile)) {
1559 $this->unlinkFiles[] = $tmpFile;
1560 if (filesize($tmpFile) == $this->dat['files'][$fI['ID']]['filesize']) {
1561 $this->alternativeFileName[$tmpFile] = $fI['filename'];
1562 $this->alternativeFilePath[$tmpFile] = $this->dat['files'][$fI['ID']]['relFileRef'];
1563 return $tmpFile;
1564 } else {
1565 $this->error('Error: temporary file ' . $tmpFile . ' had a size (' . filesize($tmpFile) . ') different from the original (' . $this->dat['files'][$fI['ID']]['filesize'] . ')', 1);
1566 }
1567 } else {
1568 $this->error('Error: temporary file ' . $tmpFile . ' was not written as it should have been!', 1);
1569 }
1570 } else {
1571 $this->error('Error: No file found for ID ' . $fI['ID'], 1);
1572 }
1573 }
1574
1575 /**
1576 * 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.
1577 * 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!
1578 *
1579 * @return void
1580 * @see setRelations()
1581 * @todo Define visibility
1582 */
1583 public function setFlexFormRelations() {
1584 $updateData = array();
1585 // import_newId contains a register of all records that was in the import memorys "records" key
1586 foreach ($this->import_newId as $nId => $dat) {
1587 $table = $dat['table'];
1588 $uid = $dat['uid'];
1589 // original UID - NOT the new one!
1590 // If the record has been written and received a new id, then proceed:
1591 if (is_array($this->import_mapId[$table]) && isset($this->import_mapId[$table][$uid])) {
1592 $thisNewUid = \TYPO3\CMS\Backend\Utility\BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]);
1593 if (is_array($this->dat['records'][$table . ':' . $uid]['rels'])) {
1594 \TYPO3\CMS\Core\Utility\GeneralUtility::loadTCA($table);
1595 // Traverse relation fields of each record
1596 foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) {
1597 switch ((string) $config['type']) {
1598 case 'flex':
1599 // Get XML content and set as default value (string, non-processed):
1600 $updateData[$table][$thisNewUid][$field] = $this->dat['records'][$table . ':' . $uid]['data'][$field];
1601 // If there has been registered relations inside the flex form field, run processing on the content:
1602 if (count($config['flexFormRels']['db']) || count($config['flexFormRels']['file'])) {
1603 $origRecordRow = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $thisNewUid, '*');
1604 // This will fetch the new row for the element (which should be updated with any references to data structures etc.)
1605 $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
1606 if (is_array($origRecordRow) && is_array($conf) && $conf['type'] === 'flex') {
1607 // Get current data structure and value array:
1608 $dataStructArray = \TYPO3\CMS\Backend\Utility\BackendUtility::getFlexFormDS($conf, $origRecordRow, $table);
1609 $currentValueArray = \TYPO3\CMS\Core\Utility\GeneralUtility::xml2array($updateData[$table][$thisNewUid][$field]);
1610 // Do recursive processing of the XML data:
1611 $iteratorObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandler\\DataHandler');
1612 $iteratorObj->callBackObj = $this;
1613 $currentValueArray['data'] = $iteratorObj->checkValue_flex_procInData($currentValueArray['data'], array(), array(), $dataStructArray, array($table, $thisNewUid, $field, $config), 'remapListedDBRecords_flexFormCallBack');
1614 // The return value is set as an array which means it will be processed by tcemain for file and DB references!
1615 if (is_array($currentValueArray['data'])) {
1616 $updateData[$table][$thisNewUid][$field] = $currentValueArray;
1617 }
1618 }
1619 }
1620 break;
1621 }
1622 }
1623 } else {
1624 $this->error('Error: no record was found in data array!', 1);
1625 }
1626 } else {
1627 $this->error('Error: this records is NOT created it seems! (' . $table . ':' . $uid . ')', 1);
1628 }
1629 }
1630 if (count($updateData)) {
1631 $tce = $this->getNewTCE();
1632 $this->callHook('before_setFlexFormRelations', array(
1633 'tce' => &$tce,
1634 'data' => &$updateData
1635 ));
1636 $tce->start($updateData, array());
1637 $tce->process_datamap();
1638 $this->callHook('after_setFlexFormRelations', array(
1639 'tce' => &$tce
1640 ));
1641 }
1642 }
1643
1644 /**
1645 * Callback function for traversing the FlexForm structure in relation to remapping database relations
1646 *
1647 * @param array $pParams Set of parameters in numeric array: table, uid, field
1648 * @param array $dsConf TCA config for field (from Data Structure of course)
1649 * @param string $dataValue Field value (from FlexForm XML)
1650 * @param string $dataValue_ext1 Not used
1651 * @param string $dataValue_ext2 Not used
1652 * @param string $path Path of where the data structure of the element is found
1653 * @return array Array where the "value" key carries the value.
1654 * @see setFlexFormRelations()
1655 * @todo Define visibility
1656 */
1657 public function remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path) {
1658 // Extract parameters:
1659 list($table, $uid, $field, $config) = $pParams;
1660 // In case the $path is used as index without a trailing slash we will remove that
1661 if (!is_array($config['flexFormRels']['db'][$path]) && is_array($config['flexFormRels']['db'][rtrim($path, '/')])) {
1662 $path = rtrim($path, '/');
1663 }
1664 if (is_array($config['flexFormRels']['db'][$path])) {
1665 $valArray = $this->setRelations_db($config['flexFormRels']['db'][$path]);
1666 $dataValue = implode(',', $valArray);
1667 }
1668 if (is_array($config['flexFormRels']['file'][$path])) {
1669 foreach ($config['flexFormRels']['file'][$path] as $fI) {
1670 $valArr[] = $this->import_addFileNameToBeCopied($fI);
1671 }
1672 $dataValue = implode(',', $valArr);
1673 }
1674 return array('value' => $dataValue);
1675 }
1676
1677 /**************************
1678 *
1679 * Import / Soft References
1680 *
1681 *************************/
1682 /**
1683 * Processing of soft references
1684 *
1685 * @return void
1686 * @todo Define visibility
1687 */
1688 public function processSoftReferences() {
1689 // Initialize:
1690 $inData = array();
1691 // Traverse records:
1692 if (is_array($this->dat['header']['records'])) {
1693 foreach ($this->dat['header']['records'] as $table => $recs) {
1694 foreach ($recs as $uid => $thisRec) {
1695 // If there are soft references defined, traverse those:
1696 if (isset($GLOBALS['TCA'][$table]) && is_array($thisRec['softrefs'])) {
1697 \TYPO3\CMS\Core\Utility\GeneralUtility::loadTCA($table);
1698 // First traversal is to collect softref configuration and split them up based on fields. This could probably also have been done with the "records" key instead of the header.
1699 $fieldsIndex = array();
1700 foreach ($thisRec['softrefs'] as $softrefDef) {
1701 // If a substitution token is set:
1702 if ($softrefDef['field'] && is_array($softrefDef['subst']) && $softrefDef['subst']['tokenID']) {
1703 $fieldsIndex[$softrefDef['field']][$softrefDef['subst']['tokenID']] = $softrefDef;
1704 }
1705 }
1706 // The new id:
1707 $thisNewUid = \TYPO3\CMS\Backend\Utility\BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]);
1708 // Now, if there are any fields that require substitution to be done, lets go for that:
1709 foreach ($fieldsIndex as $field => $softRefCfgs) {
1710 if (is_array($GLOBALS['TCA'][$table]['columns'][$field])) {
1711 $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
1712 if ($conf['type'] === 'flex') {
1713 // This will fetch the new row for the element (which should be updated with any references to data structures etc.)
1714 $origRecordRow = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $thisNewUid, '*');
1715 if (is_array($origRecordRow)) {
1716 // Get current data structure and value array:
1717 $dataStructArray = \TYPO3\CMS\Backend\Utility\BackendUtility::getFlexFormDS($conf, $origRecordRow, $table);
1718 $currentValueArray = \TYPO3\CMS\Core\Utility\GeneralUtility::xml2array($origRecordRow[$field]);
1719 // Do recursive processing of the XML data:
1720 /** @var $iteratorObj \TYPO3\CMS\Core\DataHandler\DataHandler */
1721 $iteratorObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandler\\DataHandler');
1722 $iteratorObj->callBackObj = $this;
1723 $currentValueArray['data'] = $iteratorObj->checkValue_flex_procInData($currentValueArray['data'], array(), array(), $dataStructArray, array($table, $uid, $field, $softRefCfgs), 'processSoftReferences_flexFormCallBack');
1724 // The return value is set as an array which means it will be processed by tcemain for file and DB references!
1725 if (is_array($currentValueArray['data'])) {
1726 $inData[$table][$thisNewUid][$field] = $currentValueArray;
1727 }
1728 }
1729 } else {
1730 // Get tokenizedContent string and proceed only if that is not blank:
1731 $tokenizedContent = $this->dat['records'][$table . ':' . $uid]['rels'][$field]['softrefs']['tokenizedContent'];
1732 if (strlen($tokenizedContent) && is_array($softRefCfgs)) {
1733 $inData[$table][$thisNewUid][$field] = $this->processSoftReferences_substTokens($tokenizedContent, $softRefCfgs, $table, $uid);
1734 }
1735 }
1736 }
1737 }
1738 }
1739 }
1740 }
1741 }
1742 // Now write to database:
1743 $tce = $this->getNewTCE();
1744 $this->callHook('before_processSoftReferences', array(
1745 'tce' => &$tce,
1746 'data' => &$inData
1747 ));
1748 $tce->enableLogging = TRUE;
1749 $tce->start($inData, array());
1750 $tce->process_datamap();
1751 $this->callHook('after_processSoftReferences', array(
1752 'tce' => &$tce
1753 ));
1754 }
1755
1756 /**
1757 * Callback function for traversing the FlexForm structure in relation to remapping softreference relations
1758 *
1759 * @param array $pParams Set of parameters in numeric array: table, uid, field
1760 * @param array $dsConf TCA config for field (from Data Structure of course)
1761 * @param string $dataValue Field value (from FlexForm XML)
1762 * @param string $dataValue_ext1 Not used
1763 * @param string $dataValue_ext2 Not used
1764 * @param string $path Path of where the data structure where the element is found
1765 * @return array Array where the "value" key carries the value.
1766 * @see setFlexFormRelations()
1767 * @todo Define visibility
1768 */
1769 public function processSoftReferences_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path) {
1770 // Extract parameters:
1771 list($table, $origUid, $field, $softRefCfgs) = $pParams;
1772 if (is_array($softRefCfgs)) {
1773 // First, find all soft reference configurations for this structure path (they are listed flat in the header):
1774 $thisSoftRefCfgList = array();
1775 foreach ($softRefCfgs as $sK => $sV) {
1776 if ($sV['structurePath'] === $path) {
1777 $thisSoftRefCfgList[$sK] = $sV;
1778 }
1779 }
1780 // If any was found, do processing:
1781 if (count($thisSoftRefCfgList)) {
1782 // Get tokenizedContent string and proceed only if that is not blank:
1783 $tokenizedContent = $this->dat['records'][$table . ':' . $origUid]['rels'][$field]['flexFormRels']['softrefs'][$path]['tokenizedContent'];
1784 if (strlen($tokenizedContent)) {
1785 $dataValue = $this->processSoftReferences_substTokens($tokenizedContent, $thisSoftRefCfgList, $table, $origUid);
1786 }
1787 }
1788 }
1789 // Return
1790 return array('value' => $dataValue);
1791 }
1792
1793 /**
1794 * Substition of softreference tokens
1795 *
1796 * @param string $tokenizedContent Content of field with soft reference tokens in.
1797 * @param array $softRefCfgs Soft reference configurations
1798 * @param string $table Table for which the processing occurs
1799 * @param string $uid UID of record from table
1800 * @return string The input content with tokens substituted according to entries in softRefCfgs
1801 * @todo Define visibility
1802 */
1803 public function processSoftReferences_substTokens($tokenizedContent, $softRefCfgs, $table, $uid) {
1804 // traverse each softref type for this field:
1805 foreach ($softRefCfgs as $cfg) {
1806 // Get token ID:
1807 $tokenID = $cfg['subst']['tokenID'];
1808 // Default is current token value:
1809 $insertValue = $cfg['subst']['tokenValue'];
1810 // Based on mode:
1811 switch ((string) $this->softrefCfg[$tokenID]['mode']) {
1812 case 'exclude':
1813 // Exclude is a simple passthrough of the value
1814 break;
1815 case 'editable':
1816 // Editable always picks up the value from this input array:
1817 $insertValue = $this->softrefInputValues[$tokenID];
1818 break;
1819 default:
1820 // Mapping IDs/creating files: Based on type, look up new value:
1821 switch ((string) $cfg['subst']['type']) {
1822 case 'db':
1823
1824 default:
1825 // Trying to map database element if found in the mapID array:
1826 list($tempTable, $tempUid) = explode(':', $cfg['subst']['recordRef']);
1827 if (isset($this->import_mapId[$tempTable][$tempUid])) {
1828 $insertValue = \TYPO3\CMS\Backend\Utility\BackendUtility::wsMapId($tempTable, $this->import_mapId[$tempTable][$tempUid]);
1829 // 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!
1830 if ($tempTable === 'pages' && !\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($cfg['subst']['tokenValue'])) {
1831 $recWithUniqueValue = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($tempTable, $insertValue, 'alias');
1832 if ($recWithUniqueValue['alias']) {
1833 $insertValue = $recWithUniqueValue['alias'];
1834 }
1835 }
1836 }
1837 break;
1838 break;
1839 case 'file':
1840 // Create / Overwrite file:
1841 $insertValue = $this->processSoftReferences_saveFile($cfg['subst']['relFileName'], $cfg, $table, $uid);
1842 break;
1843 }
1844 break;
1845 }
1846 // Finally, swap the soft reference token in tokenized content with the insert value:
1847 $tokenizedContent = str_replace('{softref:' . $tokenID . '}', $insertValue, $tokenizedContent);
1848 }
1849 return $tokenizedContent;
1850 }
1851
1852 /**
1853 * Process a soft reference file
1854 *
1855 * @param string $relFileName Old Relative filename
1856 * @param array $cfg soft reference configuration array
1857 * @param string $table Table for which the processing occurs
1858 * @param string $uid UID of record from table
1859 * @return string New relative filename (value to insert instead of the softref token)
1860 * @todo Define visibility
1861 */
1862 public function processSoftReferences_saveFile($relFileName, $cfg, $table, $uid) {
1863 if ($fileHeaderInfo = $this->dat['header']['files'][$cfg['file_ID']]) {
1864 // Initialize; Get directory prefix for file and find possible RTE filename
1865 $dirPrefix = dirname($relFileName) . '/';
1866 $rteOrigName = $this->getRTEoriginalFilename(basename($relFileName));
1867 // If filename looks like an RTE file, and the directory is in "uploads/", then process as a RTE file!
1868 if ($rteOrigName && \TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($dirPrefix, 'uploads/')) {
1869 // RTE:
1870 // First, find unique RTE file name:
1871 if (@is_dir((PATH_site . $dirPrefix))) {
1872 // From the "original" RTE filename, produce a new "original" destination filename which is unused. Even if updated, the image should be unique. Currently the problem with this is that it leaves a lot of unused RTE images...
1873 $fileProcObj = $this->getFileProcObj();
1874 $origDestName = $fileProcObj->getUniqueName($rteOrigName, PATH_site . $dirPrefix);
1875 // Create copy file name:
1876 $pI = pathinfo($relFileName);
1877 $copyDestName = dirname($origDestName) . '/RTEmagicC_' . substr(basename($origDestName), 10) . '.' . $pI['extension'];
1878 if (!@is_file($copyDestName) && !@is_file($origDestName) && $origDestName === \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($origDestName) && $copyDestName === \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($copyDestName)) {
1879 if ($this->dat['header']['files'][$fileHeaderInfo['RTE_ORIG_ID']]) {
1880 // Write the copy and original RTE file to the respective filenames:
1881 $this->writeFileVerify($copyDestName, $cfg['file_ID'], TRUE);
1882 $this->writeFileVerify($origDestName, $fileHeaderInfo['RTE_ORIG_ID'], TRUE);
1883 // Return the relative path of the copy file name:
1884 return substr($copyDestName, strlen(PATH_site));
1885 } else {
1886 $this->error('ERROR: Could not find original file ID');
1887 }
1888 } else {
1889 $this->error('ERROR: The destination filenames "' . $copyDestName . '" and "' . $origDestName . '" either existed or have non-valid names');
1890 }
1891 } else {
1892 $this->error('ERROR: "' . PATH_site . $dirPrefix . '" was not a directory, so could not process file "' . $relFileName . '"');
1893 }
1894 } elseif (\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($dirPrefix, $this->fileadminFolderName . '/')) {
1895 // File in fileadmin/ folder:
1896 // Create file (and possible resources)
1897 $newFileName = $this->processSoftReferences_saveFile_createRelFile($dirPrefix, basename($relFileName), $cfg['file_ID'], $table, $uid);
1898 if (strlen($newFileName)) {
1899 $relFileName = $newFileName;
1900 } else {
1901 $this->error('ERROR: No new file created for "' . $relFileName . '"');
1902 }
1903 } else {
1904 $this->error('ERROR: Sorry, cannot operate on non-RTE files which are outside the fileadmin folder.');
1905 }
1906 } else {
1907 $this->error('ERROR: Could not find file ID in header.');
1908 }
1909 // Return (new) filename relative to PATH_site:
1910 return $relFileName;
1911 }
1912
1913 /**
1914 * Create file in directory and return the new (unique) filename
1915 *
1916 * @param string $origDirPrefix Directory prefix, relative, with trailing slash
1917 * @param string $fileName Filename (without path)
1918 * @param string $fileID File ID from import memory
1919 * @param string $table Table for which the processing occurs
1920 * @param string $uid UID of record from table
1921 * @return string New relative filename, if any
1922 * @todo Define visibility
1923 */
1924 public function processSoftReferences_saveFile_createRelFile($origDirPrefix, $fileName, $fileID, $table, $uid) {
1925 // If the fileID map contains an entry for this fileID then just return the relative filename of that entry; we don't want to write another unique filename for this one!
1926 if ($this->fileIDMap[$fileID]) {
1927 return substr($this->fileIDMap[$fileID], strlen(PATH_site));
1928 }
1929 // Verify FileMount access to dir-prefix. Returns the best alternative relative path if any
1930 $dirPrefix = $this->verifyFolderAccess($origDirPrefix);
1931 if ($dirPrefix && (!$this->update || $origDirPrefix === $dirPrefix) && $this->checkOrCreateDir($dirPrefix)) {
1932 $fileHeaderInfo = $this->dat['header']['files'][$fileID];
1933 $updMode = $this->update && $this->import_mapId[$table][$uid] === $uid && $this->import_mode[$table . ':' . $uid] !== 'as_new';
1934 // Create new name for file:
1935 // Must have same ID in map array (just for security, is not really needed) and NOT be set "as_new".
1936 if ($updMode) {
1937 $newName = PATH_site . $dirPrefix . $fileName;
1938 } else {
1939 // Create unique filename:
1940 $fileProcObj = $this->getFileProcObj();
1941 $newName = $fileProcObj->getUniqueName($fileName, PATH_site . $dirPrefix);
1942 }
1943 // Write main file:
1944 if ($this->writeFileVerify($newName, $fileID)) {
1945 // If the resource was an HTML/CSS file with resources attached, we will write those as well!
1946 if (is_array($fileHeaderInfo['EXT_RES_ID'])) {
1947 $tokenizedContent = $this->dat['files'][$fileID]['tokenizedContent'];
1948 $tokenSubstituted = FALSE;
1949 $fileProcObj = $this->getFileProcObj();
1950 if ($updMode) {
1951 foreach ($fileHeaderInfo['EXT_RES_ID'] as $res_fileID) {
1952 if ($this->dat['files'][$res_fileID]['filename']) {
1953 // Resolve original filename:
1954 $relResourceFileName = $this->dat['files'][$res_fileID]['parentRelFileName'];
1955 $absResourceFileName = \TYPO3\CMS\Core\Utility\GeneralUtility::resolveBackPath(PATH_site . $origDirPrefix . $relResourceFileName);
1956 $absResourceFileName = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($absResourceFileName);
1957 if ($absResourceFileName && \TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($absResourceFileName, PATH_site . $this->fileadminFolderName . '/')) {
1958 $destDir = substr(dirname($absResourceFileName) . '/', strlen(PATH_site));
1959 if ($this->verifyFolderAccess($destDir, TRUE) && $this->checkOrCreateDir($destDir)) {
1960 $this->writeFileVerify($absResourceFileName, $res_fileID);
1961 } else {
1962 $this->error('ERROR: Could not create file in directory "' . $destDir . '"');
1963 }
1964 } else {
1965 $this->error('ERROR: Could not resolve path for "' . $relResourceFileName . '"');
1966 }
1967 $tokenizedContent = str_replace('{EXT_RES_ID:' . $res_fileID . '}', $relResourceFileName, $tokenizedContent);
1968 $tokenSubstituted = TRUE;
1969 }
1970 }
1971 } else {
1972 // Create the resouces directory name (filename without extension, suffixed "_FILES")
1973 $resourceDir = dirname($newName) . '/' . preg_replace('/\\.[^.]*$/', '', basename($newName)) . '_FILES';
1974 if (\TYPO3\CMS\Core\Utility\GeneralUtility::mkdir($resourceDir)) {
1975 foreach ($fileHeaderInfo['EXT_RES_ID'] as $res_fileID) {
1976 if ($this->dat['files'][$res_fileID]['filename']) {
1977 $absResourceFileName = $fileProcObj->getUniqueName($this->dat['files'][$res_fileID]['filename'], $resourceDir);
1978 $relResourceFileName = substr($absResourceFileName, strlen(dirname($resourceDir)) + 1);
1979 $this->writeFileVerify($absResourceFileName, $res_fileID);
1980 $tokenizedContent = str_replace('{EXT_RES_ID:' . $res_fileID . '}', $relResourceFileName, $tokenizedContent);
1981 $tokenSubstituted = TRUE;
1982 }
1983 }
1984 }
1985 }
1986 // If substitutions has been made, write the content to the file again:
1987 if ($tokenSubstituted) {
1988 \TYPO3\CMS\Core\Utility\GeneralUtility::writeFile($newName, $tokenizedContent);
1989 }
1990 }
1991 return substr($newName, strlen(PATH_site));
1992 }
1993 }
1994 }
1995
1996 /**
1997 * Writes a file from the import memory having $fileID to file name $fileName which must be an absolute path inside PATH_site
1998 *
1999 * @param string $fileName Absolute filename inside PATH_site to write to
2000 * @param string $fileID File ID from import memory
2001 * @param boolean $bypassMountCheck Bypasses the checking against filemounts - only for RTE files!
2002 * @return boolean Returns TRUE if it went well. Notice that the content of the file is read again, and md5 from import memory is validated.
2003 * @todo Define visibility
2004 */
2005 public function writeFileVerify($fileName, $fileID, $bypassMountCheck = FALSE) {
2006 $fileProcObj = $this->getFileProcObj();
2007 if ($fileProcObj->actionPerms['newFile']) {
2008 // Just for security, check again. Should actually not be necessary.
2009 if ($fileProcObj->checkPathAgainstMounts($fileName) || $bypassMountCheck) {
2010 $fI = \TYPO3\CMS\Core\Utility\GeneralUtility::split_fileref($fileName);
2011 if ($fileProcObj->checkIfAllowed($fI['fileext'], $fI['path'], $fI['file']) || $this->allowPHPScripts && $GLOBALS['BE_USER']->isAdmin()) {
2012 if (\TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($fileName)) {
2013 if ($this->dat['files'][$fileID]) {
2014 \TYPO3\CMS\Core\Utility\GeneralUtility::writeFile($fileName, $this->dat['files'][$fileID]['content']);
2015 $this->fileIDMap[$fileID] = $fileName;
2016 if (md5(\TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($fileName)) == $this->dat['files'][$fileID]['content_md5']) {
2017 return TRUE;
2018 } else {
2019 $this->error('ERROR: File content "' . $fileName . '" was corrupted');
2020 }
2021 } else {
2022 $this->error('ERROR: File ID "' . $fileID . '" could not be found');
2023 }
2024 } else {
2025 $this->error('ERROR: Filename "' . $fileName . '" was not a valid relative file path!');
2026 }
2027 } else {
2028 $this->error('ERROR: Filename "' . $fileName . '" failed against extension check or deny-pattern!');
2029 }
2030 } else {
2031 $this->error('ERROR: Filename "' . $fileName . '" was not allowed in destination path!');
2032 }
2033 } else {
2034 $this->error('ERROR: You did not have sufficient permissions to write the file "' . $fileName . '"');
2035 }
2036 }
2037
2038 /**
2039 * Returns TRUE if directory exists and if it doesn't it will create directory and return TRUE if that succeeded.
2040 *
2041 * @param string $dirPrefix Directory to create. Having a trailing slash. Must be in fileadmin/. Relative to PATH_site
2042 * @return boolean TRUE, if directory exists (was created)
2043 * @todo Define visibility
2044 */
2045 public function checkOrCreateDir($dirPrefix) {
2046 // Split dir path and remove first directory (which should be "fileadmin")
2047 $filePathParts = explode('/', $dirPrefix);
2048 $firstDir = array_shift($filePathParts);
2049 if ($firstDir === $this->fileadminFolderName && \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($dirPrefix)) {
2050 $pathAcc = '';
2051 foreach ($filePathParts as $dirname) {
2052 $pathAcc .= '/' . $dirname;
2053 if (strlen($dirname)) {
2054 if (!@is_dir((PATH_site . $this->fileadminFolderName . $pathAcc))) {
2055 if (!\TYPO3\CMS\Core\Utility\GeneralUtility::mkdir((PATH_site . $this->fileadminFolderName . $pathAcc))) {
2056 $this->error('ERROR: Directory could not be created....B');
2057 return FALSE;
2058 }
2059 }
2060 } elseif ($dirPrefix === $this->fileadminFolderName . $pathAcc) {
2061 return TRUE;
2062 } else {
2063 $this->error('ERROR: Directory could not be created....A');
2064 }
2065 }
2066 }
2067 }
2068
2069 /**
2070 * Verifies that the input path (relative to PATH_site) is found in the backend users filemounts.
2071 * If it doesn't it will try to find another relative filemount for the user and return an alternative path prefix for the file.
2072 *
2073 * @param string $dirPrefix Path relative to PATH_site
2074 * @param boolean $noAlternative If set, Do not look for alternative path! Just return FALSE
2075 * @return string If a path is available that will be returned, otherwise FALSE.
2076 * @todo Define visibility
2077 */
2078 public function verifyFolderAccess($dirPrefix, $noAlternative = FALSE) {
2079 $fileProcObj = $this->getFileProcObj();
2080 // Check, if dirPrefix is inside a valid Filemount for user:
2081 $result = $fileProcObj->checkPathAgainstMounts(PATH_site . $dirPrefix);
2082 // If not, try to find another relative filemount and use that instead:
2083 if (!$result) {
2084 if ($noAlternative) {
2085 return FALSE;
2086 }
2087 // Find first web folder:
2088 $result = $fileProcObj->findFirstWebFolder();
2089 // If that succeeded, return the path to it:
2090 if ($result) {
2091 // Remove the "fileadmin/" prefix of input path - and append the rest to the return value:
2092 if (\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($dirPrefix, $this->fileadminFolderName . '/')) {
2093 $dirPrefix = substr($dirPrefix, strlen($this->fileadminFolderName . '/'));
2094 }
2095 return substr($fileProcObj->mounts[$result]['path'] . $dirPrefix, strlen(PATH_site));
2096 }
2097 } else {
2098 return $dirPrefix;
2099 }
2100 }
2101
2102 /**************************
2103 *
2104 * File Input
2105 *
2106 *************************/
2107 /**
2108 * Loads the header section/all of the $filename into memory
2109 *
2110 * @param string $filename Filename, absolute
2111 * @param boolean $all If set, all information is loaded (header, records and files). Otherwise the default is to read only the header information
2112 * @return boolean TRUE if the operation went well
2113 * @todo Define visibility
2114 */
2115 public function loadFile($filename, $all = 0) {
2116 if (@is_file($filename)) {
2117 $fI = pathinfo($filename);
2118 if (strtolower($fI['extension']) == 'xml') {
2119 // XML:
2120 $xmlContent = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($filename);
2121 if (strlen($xmlContent)) {
2122 $this->dat = \TYPO3\CMS\Core\Utility\GeneralUtility::xml2array($xmlContent, '', TRUE);
2123 if (is_array($this->dat)) {
2124 if ($this->dat['_DOCUMENT_TAG'] === 'T3RecordDocument' && is_array($this->dat['header']) && is_array($this->dat['records'])) {
2125 $this->loadInit();
2126 return TRUE;
2127 } else {
2128 $this->error('XML file did not contain proper XML for TYPO3 Import');
2129 }
2130 } else {
2131 $this->error('XML could not be parsed: ' . $this->dat);
2132 }
2133 } else {
2134 $this->error('Error opening file: ' . $filename);
2135 }
2136 } else {
2137 // T3D
2138 if ($fd = fopen($filename, 'rb')) {
2139 $this->dat['header'] = $this->getNextFilePart($fd, 1, 'header');
2140 if ($all) {
2141 $this->dat['records'] = $this->getNextFilePart($fd, 1, 'records');
2142 $this->dat['files'] = $this->getNextFilePart($fd, 1, 'files');
2143 }
2144 $this->loadInit();
2145 return TRUE;
2146 } else {
2147 $this->error('Error opening file: ' . $filename);
2148 }
2149 fclose($fd);
2150 }
2151 } else {
2152 $this->error('Filename not found: ' . $filename);
2153 }
2154 return FALSE;
2155 }
2156
2157 /**
2158 * Returns the next content part form the fileresource (t3d), $fd
2159 *
2160 * @param pointer $fd File pointer
2161 * @param boolean $unserialize If set, the returned content is unserialized into an array, otherwise you get the raw string
2162 * @param string $name For error messages this indicates the section of the problem.
2163 * @return string Data string
2164 * @access private
2165 * @see loadFile()
2166 * @todo Define visibility
2167 */
2168 public function getNextFilePart($fd, $unserialize = 0, $name = '') {
2169 $initStrLen = 32 + 1 + 1 + 1 + 10 + 1;
2170 // Getting header data
2171 $initStr = fread($fd, $initStrLen);
2172 $initStrDat = explode(':', $initStr);
2173 if (strstr($initStrDat[0], 'Warning') == FALSE) {
2174 if (!strcmp($initStrDat[3], '')) {
2175 $datString = fread($fd, intval($initStrDat[2]));
2176 fread($fd, 1);
2177 if (!strcmp(md5($datString), $initStrDat[0])) {
2178 if ($initStrDat[1]) {
2179 if ($this->compress) {
2180 $datString = gzuncompress($datString);
2181 } else {
2182 $this->error('Content read error: This file requires decompression, but this server does not offer gzcompress()/gzuncompress() functions.', 1);
2183 }
2184 }
2185 return $unserialize ? unserialize($datString) : $datString;
2186 } else {
2187 $this->error('MD5 check failed (' . $name . ')');
2188 }
2189 } else {
2190 $this->error('File read error: InitString had a wrong length. (' . $name . ')');
2191 }
2192 } else {
2193 $this->error('File read error: Warning message in file. (' . $initStr . fgets($fd) . ')');
2194 }
2195 }
2196
2197 /**
2198 * Loads T3D file content into the $this->dat array
2199 * (This function can be used to test the output strings from ->compileMemoryToFileContent())
2200 *
2201 * @param string $filecontent File content
2202 * @return void
2203 * @todo Define visibility
2204 */
2205 public function loadContent($filecontent) {
2206 $pointer = 0;
2207 $this->dat['header'] = $this->getNextContentPart($filecontent, $pointer, 1, 'header');
2208 $this->dat['records'] = $this->getNextContentPart($filecontent, $pointer, 1, 'records');
2209 $this->dat['files'] = $this->getNextContentPart($filecontent, $pointer, 1, 'files');
2210 $this->loadInit();
2211 }
2212
2213 /**
2214 * Returns the next content part from the $filecontent
2215 *
2216 * @param string $filecontent File content string
2217 * @param integer $pointer File pointer (where to read from)
2218 * @param boolean $unserialize If set, the returned content is unserialized into an array, otherwise you get the raw string
2219 * @param string $name For error messages this indicates the section of the problem.
2220 * @return string Data string
2221 * @todo Define visibility
2222 */
2223 public function getNextContentPart($filecontent, &$pointer, $unserialize = 0, $name = '') {
2224 $initStrLen = 32 + 1 + 1 + 1 + 10 + 1;
2225 // getting header data
2226 $initStr = substr($filecontent, $pointer, $initStrLen);
2227 $pointer += $initStrLen;
2228 $initStrDat = explode(':', $initStr);
2229 if (!strcmp($initStrDat[3], '')) {
2230 $datString = substr($filecontent, $pointer, intval($initStrDat[2]));
2231 $pointer += intval($initStrDat[2]) + 1;
2232 if (!strcmp(md5($datString), $initStrDat[0])) {
2233 if ($initStrDat[1]) {
2234 if ($this->compress) {
2235 $datString = gzuncompress($datString);
2236 } else {
2237 $this->error('Content read error: This file requires decompression, but this server does not offer gzcompress()/gzuncompress() functions.', 1);
2238 }
2239 }
2240 return $unserialize ? unserialize($datString) : $datString;
2241 } else {
2242 $this->error('MD5 check failed (' . $name . ')');
2243 }
2244 } else {
2245 $this->error('Content read error: InitString had a wrong length. (' . $name . ')');
2246 }
2247 }
2248
2249 /**
2250 * Setting up the object based on the recently loaded ->dat array
2251 *
2252 * @return void
2253 * @todo Define visibility
2254 */
2255 public function loadInit() {
2256 $this->relStaticTables = (array) $this->dat['header']['relStaticTables'];
2257 $this->excludeMap = (array) $this->dat['header']['excludeMap'];
2258 $this->softrefCfg = (array) $this->dat['header']['softrefCfg'];
2259 $this->extensionDependencies = (array) $this->dat['header']['extensionDependencies'];
2260 $this->fixCharsets();
2261 }
2262
2263 /**
2264 * Fix charset of import memory if different from system charset
2265 *
2266 * @return void
2267 * @see loadInit()
2268 * @todo Define visibility
2269 */
2270 public function fixCharsets() {
2271 global $LANG;
2272 $importCharset = $this->dat['header']['charset'];
2273 if ($importCharset) {
2274 if ($importCharset !== $LANG->charSet) {
2275 $this->error('CHARSET: Converting charset of input file (' . $importCharset . ') to the system charset (' . $LANG->charSet . ')');
2276 // Convert meta data:
2277 if (is_array($this->dat['header']['meta'])) {
2278 $LANG->csConvObj->convArray($this->dat['header']['meta'], $importCharset, $LANG->charSet);
2279 }
2280 // Convert record headers:
2281 if (is_array($this->dat['header']['records'])) {
2282 $LANG->csConvObj->convArray($this->dat['header']['records'], $importCharset, $LANG->charSet);
2283 }
2284 // Convert records themselves:
2285 if (is_array($this->dat['records'])) {
2286 $LANG->csConvObj->convArray($this->dat['records'], $importCharset, $LANG->charSet);
2287 }
2288 }
2289 } else {
2290 $this->error('CHARSET: No charset found in import file!');
2291 }
2292 }
2293
2294 /********************************************************
2295 *
2296 * Visual rendering of import/export memory, $this->dat
2297 *
2298 ********************************************************/
2299 /**
2300 * Displays an overview of the header-content.
2301 *
2302 * @return string HTML content
2303 * @todo Define visibility
2304 */
2305 public function displayContentOverview() {
2306 global $LANG;
2307 // Check extension dependencies:
2308 if (is_array($this->dat['header']['extensionDependencies'])) {
2309 foreach ($this->dat['header']['extensionDependencies'] as $extKey) {
2310 if (!\TYPO3\CMS\Core\Extension\ExtensionManager::isLoaded($extKey)) {
2311 $this->error('DEPENDENCY: The extension with key "' . $extKey . '" must be installed!');
2312 }
2313 }
2314 }
2315 // Probably this is done to save memory space?
2316 unset($this->dat['files']);
2317 // Traverse header:
2318 $this->remainHeader = $this->dat['header'];
2319 if (is_array($this->remainHeader)) {
2320 // If there is a page tree set, show that:
2321 if (is_array($this->dat['header']['pagetree'])) {
2322 reset($this->dat['header']['pagetree']);
2323 $lines = array();
2324 $this->traversePageTree($this->dat['header']['pagetree'], $lines);
2325 $rows = array();
2326 $rows[] = '
2327 <tr class="bgColor5 tableheader">
2328 <td>' . $LANG->getLL('impexpcore_displaycon_controls', 1) . '</td>
2329 <td>' . $LANG->getLL('impexpcore_displaycon_title', 1) . '</td>
2330 <td>' . $LANG->getLL('impexpcore_displaycon_size', 1) . '</td>
2331 <td>' . $LANG->getLL('impexpcore_displaycon_message', 1) . '</td>
2332 ' . ($this->update ? '<td>' . $LANG->getLL('impexpcore_displaycon_updateMode', 1) . '</td>' : '') . '
2333 ' . ($this->update ? '<td>' . $LANG->getLL('impexpcore_displaycon_currentPath', 1) . '</td>' : '') . '
2334 ' . ($this->showDiff ? '<td>' . $LANG->getLL('impexpcore_displaycon_result', 1) . '</td>' : '') . '
2335 </tr>';
2336 foreach ($lines as $r) {
2337 $rows[] = '
2338 <tr class="' . $r['class'] . '">
2339 <td>' . $this->renderControls($r) . '</td>
2340 <td nowrap="nowrap">' . $r['preCode'] . $r['title'] . '</td>
2341 <td nowrap="nowrap">' . \TYPO3\CMS\Core\Utility\GeneralUtility::formatSize($r['size']) . '</td>
2342 <td nowrap="nowrap">' . ($r['msg'] && !$this->doesImport ? '<span class="typo3-red">' . htmlspecialchars($r['msg']) . '</span>' : '') . '</td>
2343 ' . ($this->update ? '<td nowrap="nowrap">' . $r['updateMode'] . '</td>' : '') . '
2344 ' . ($this->update ? '<td nowrap="nowrap">' . $r['updatePath'] . '</td>' : '') . '
2345 ' . ($this->showDiff ? '<td>' . $r['showDiffContent'] . '</td>' : '') . '
2346 </tr>';
2347 }
2348 $out = '
2349 <strong>' . $LANG->getLL('impexpcore_displaycon_insidePagetree', 1) . '</strong>
2350 <br /><br />
2351 <table border="0" cellpadding="0" cellspacing="1">' . implode('', $rows) . '</table>
2352 <br /><br />';
2353 }
2354 // Print remaining records that were not contained inside the page tree:
2355 $lines = array();
2356 if (is_array($this->remainHeader['records'])) {
2357 if (is_array($this->remainHeader['records']['pages'])) {
2358 $this->traversePageRecords($this->remainHeader['records']['pages'], $lines);
2359 }
2360 $this->traverseAllRecords($this->remainHeader['records'], $lines);
2361 if (count($lines)) {
2362 $rows = array();
2363 $rows[] = '
2364 <tr class="bgColor5 tableheader">
2365 <td>' . $LANG->getLL('impexpcore_displaycon_controls', 1) . '</td>
2366 <td>' . $LANG->getLL('impexpcore_displaycon_title', 1) . '</td>
2367 <td>' . $LANG->getLL('impexpcore_displaycon_size', 1) . '</td>
2368 <td>' . $LANG->getLL('impexpcore_displaycon_message', 1) . '</td>
2369 ' . ($this->update ? '<td>' . $LANG->getLL('impexpcore_displaycon_updateMode', 1) . '</td>' : '') . '
2370 ' . ($this->update ? '<td>' . $LANG->getLL('impexpcore_displaycon_currentPath', 1) . '</td>' : '') . '
2371 ' . ($this->showDiff ? '<td>' . $LANG->getLL('impexpcore_displaycon_result', 1) . '</td>' : '') . '
2372 </tr>';
2373 foreach ($lines as $r) {
2374 $rows[] = '<tr class="' . $r['class'] . '">
2375 <td>' . $this->renderControls($r) . '</td>
2376 <td nowrap="nowrap">' . $r['preCode'] . $r['title'] . '</td>
2377 <td nowrap="nowrap">' . \TYPO3\CMS\Core\Utility\GeneralUtility::formatSize($r['size']) . '</td>
2378 <td nowrap="nowrap">' . ($r['msg'] && !$this->doesImport ? '<span class="typo3-red">' . htmlspecialchars($r['msg']) . '</span>' : '') . '</td>
2379 ' . ($this->update ? '<td nowrap="nowrap">' . $r['updateMode'] . '</td>' : '') . '
2380 ' . ($this->update ? '<td nowrap="nowrap">' . $r['updatePath'] . '</td>' : '') . '
2381 ' . ($this->showDiff ? '<td>' . $r['showDiffContent'] . '</td>' : '') . '
2382 </tr>';
2383 }
2384 $out .= '
2385 <strong>' . $LANG->getLL('impexpcore_singlereco_outsidePagetree', 1) . '</strong>
2386 <br /><br />
2387 <table border="0" cellpadding="0" cellspacing="1">' . implode('', $rows) . '</table>';
2388 }
2389 }
2390 }
2391 return $out;
2392 }
2393
2394 /**
2395 * Go through page tree for display
2396 *
2397 * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
2398 * @param array $lines Output lines array (is passed by reference and modified)
2399 * @param string $preCode Pre-HTML code
2400 * @return void
2401 * @todo Define visibility
2402 */
2403 public function traversePageTree($pT, &$lines, $preCode = '') {
2404 foreach ($pT as $k => $v) {
2405 // Add this page:
2406 $this->singleRecordLines('pages', $k, $lines, $preCode);
2407 // Subrecords:
2408 if (is_array($this->dat['header']['pid_lookup'][$k])) {
2409 foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) {
2410 if ($t != 'pages') {
2411 foreach ($recUidArr as $ruid => $value) {
2412 $this->singleRecordLines($t, $ruid, $lines, $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;');
2413 }
2414 }
2415 }
2416 unset($this->remainHeader['pid_lookup'][$k]);
2417 }
2418 // Subpages, called recursively:
2419 if (is_array($v['subrow'])) {
2420 $this->traversePageTree($v['subrow'], $lines, $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;');
2421 }
2422 }
2423 }
2424
2425 /**
2426 * Go through remaining pages (not in tree)
2427 *
2428 * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
2429 * @param array $lines Output lines array (is passed by reference and modified)
2430 * @return void
2431 * @todo Define visibility
2432 */
2433 public function traversePageRecords($pT, &$lines) {
2434 foreach ($pT as $k => $rHeader) {
2435 $this->singleRecordLines('pages', $k, $lines, '', 1);
2436 // Subrecords:
2437 if (is_array($this->dat['header']['pid_lookup'][$k])) {
2438 foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) {
2439 if ($t != 'pages') {
2440 foreach ($recUidArr as $ruid => $value) {
2441 $this->singleRecordLines($t, $ruid, $lines, '&nbsp;&nbsp;&nbsp;&nbsp;');
2442 }
2443 }
2444 }
2445 unset($this->remainHeader['pid_lookup'][$k]);
2446 }
2447 }
2448 }
2449
2450 /**
2451 * Go through ALL records (if the pages are displayed first, those will not be amoung these!)
2452 *
2453 * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
2454 * @param array $lines Output lines array (is passed by reference and modified)
2455 * @return void
2456 * @todo Define visibility
2457 */
2458 public function traverseAllRecords($pT, &$lines) {
2459 foreach ($pT as $t => $recUidArr) {
2460 if ($t != 'pages') {
2461 $preCode = '';
2462 foreach ($recUidArr as $ruid => $value) {
2463 $this->singleRecordLines($t, $ruid, $lines, $preCode, 1);
2464 }
2465 }
2466 }
2467 }
2468
2469 /**
2470 * Add entries for a single record
2471 *
2472 * @param string $table Table name
2473 * @param integer $uid Record uid
2474 * @param array $lines Output lines array (is passed by reference and modified)
2475 * @param string $preCode Pre-HTML code
2476 * @param boolean $checkImportInPidRecord If you want import validation, you can set this so it checks if the import can take place on the specified page.
2477 * @return void
2478 * @todo Define visibility
2479 */
2480 public function singleRecordLines($table, $uid, &$lines, $preCode, $checkImportInPidRecord = 0) {
2481 global $LANG;
2482 // Get record:
2483 $record = $this->dat['header']['records'][$table][$uid];
2484 unset($this->remainHeader['records'][$table][$uid]);
2485 if (!is_array($record) && !($table === 'pages' && !$uid)) {
2486 $this->error('MISSING RECORD: ' . $table . ':' . $uid, 1);
2487 }
2488 // Begin to create the line arrays information record, pInfo:
2489 $pInfo = array();
2490 $pInfo['ref'] = $table . ':' . $uid;
2491 // Unknown table name:
2492 if ($table === '_SOFTREF_') {
2493 $pInfo['preCode'] = $preCode;
2494 $pInfo['title'] = '<em>' . $GLOBALS['LANG']->getLL('impexpcore_singlereco_softReferencesFiles', 1) . '</em>';
2495 } elseif (!isset($GLOBALS['TCA'][$table])) {
2496 // Unknown table name:
2497 $pInfo['preCode'] = $preCode;
2498 $pInfo['msg'] = 'UNKNOWN TABLE \'' . $pInfo['ref'] . '\'';
2499 $pInfo['title'] = '<em>' . htmlspecialchars($record['title']) . '</em>';
2500 } else {
2501 // Otherwise, set table icon and title.
2502 // Import Validation (triggered by $this->display_import_pid_record) will show messages if import is not possible of various items.
2503 if (is_array($this->display_import_pid_record)) {
2504 if ($checkImportInPidRecord) {
2505 if (!$GLOBALS['BE_USER']->doesUserHaveAccess($this->display_import_pid_record, ($table === 'pages' ? 8 : 16))) {
2506 $pInfo['msg'] .= '\'' . $pInfo['ref'] . '\' cannot be INSERTED on this page! ';
2507 }
2508 if (!$this->checkDokType($table, $this->display_import_pid_record['doktype']) && !$GLOBALS['TCA'][$table]['ctrl']['rootLevel']) {
2509 $pInfo['msg'] .= '\'' . $table . '\' cannot be INSERTED on this page type (change page type to \'Folder\'.) ';
2510 }
2511 }
2512 if (!$GLOBALS['BE_USER']->check('tables_modify', $table)) {
2513 $pInfo['msg'] .= 'You are not allowed to CREATE \'' . $table . '\' tables! ';
2514 }
2515 if ($GLOBALS['TCA'][$table]['ctrl']['readOnly']) {
2516 $pInfo['msg'] .= 'TABLE \'' . $table . '\' is READ ONLY! ';
2517 }
2518 if ($GLOBALS['TCA'][$table]['ctrl']['adminOnly'] && !$GLOBALS['BE_USER']->isAdmin()) {
2519 $pInfo['msg'] .= 'TABLE \'' . $table . '\' is ADMIN ONLY! ';
2520 }
2521 if ($GLOBALS['TCA'][$table]['ctrl']['is_static']) {
2522 $pInfo['msg'] .= 'TABLE \'' . $table . '\' is a STATIC TABLE! ';
2523 }
2524 if ($GLOBALS['TCA'][$table]['ctrl']['rootLevel']) {
2525 $pInfo['msg'] .= 'TABLE \'' . $table . '\' will be inserted on ROOT LEVEL! ';
2526 }
2527 $diffInverse = FALSE;
2528 if ($this->update) {
2529 // In case of update-PREVIEW we swap the diff-sources.
2530 $diffInverse = TRUE;
2531 $recInf = $this->doesRecordExist($table, $uid, $this->showDiff ? '*' : '');
2532 $pInfo['updatePath'] = $recInf ? htmlspecialchars($this->getRecordPath($recInf['pid'])) : '<strong>NEW!</strong>';
2533 // Mode selector:
2534 $optValues = array();
2535 $optValues[] = $recInf ? $LANG->getLL('impexpcore_singlereco_update') : $LANG->getLL('impexpcore_singlereco_insert');
2536 if ($recInf) {
2537 $optValues['as_new'] = $LANG->getLL('impexpcore_singlereco_importAsNew');
2538 }
2539 if ($recInf) {
2540 if (!$this->global_ignore_pid) {
2541 $optValues['ignore_pid'] = $LANG->getLL('impexpcore_singlereco_ignorePid');
2542 } else {
2543 $optValues['respect_pid'] = $LANG->getLL('impexpcore_singlereco_respectPid');
2544 }
2545 }
2546 if (!$recInf && $GLOBALS['BE_USER']->isAdmin()) {
2547 $optValues['force_uid'] = sprintf($LANG->getLL('impexpcore_singlereco_forceUidSAdmin'), $uid);
2548 }
2549 $optValues['exclude'] = $LANG->getLL('impexpcore_singlereco_exclude');
2550 $pInfo['updateMode'] = $this->renderSelectBox('tx_impexp[import_mode][' . $table . ':' . $uid . ']', $this->import_mode[$table . ':' . $uid], $optValues);
2551 }
2552 // Diff vieiw:
2553 if ($this->showDiff) {
2554 // For IMPORTS, get new id:
2555 if ($newUid = $this->import_mapId[$table][$uid]) {
2556 $diffInverse = FALSE;
2557 $recInf = $this->doesRecordExist($table, $newUid, '*');
2558 \TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL($table, $recInf);
2559 }
2560 if (is_array($recInf)) {
2561 $pInfo['showDiffContent'] = $this->compareRecords($recInf, $this->dat['records'][$table . ':' . $uid]['data'], $table, $diffInverse);
2562 }
2563 }
2564 }
2565 $pInfo['preCode'] = $preCode . \t3lib_iconworks::getSpriteIconForRecord($table, (array) $this->dat['records'][($table . ':' . $uid)]['data'], array('title' => htmlspecialchars(($table . ':' . $uid))));
2566 $pInfo['title'] = htmlspecialchars($record['title']);
2567 // View page:
2568 if ($table === 'pages') {
2569 $viewID = $this->mode === 'export' ? $uid : ($this->doesImport ? $this->import_mapId['pages'][$uid] : 0);
2570 if ($viewID) {
2571 $pInfo['title'] = '<a href="#" onclick="' . htmlspecialchars(\TYPO3\CMS\Backend\Utility\BackendUtility::viewOnClick($viewID, $GLOBALS['BACK_PATH'])) . 'return false;">' . $pInfo['title'] . '</a>';
2572 }
2573 }
2574 }
2575 $pInfo['class'] = $table == 'pages' ? 'bgColor4-20' : 'bgColor4';
2576 $pInfo['type'] = 'record';
2577 $pInfo['size'] = $record['size'];
2578 $lines[] = $pInfo;
2579 // File relations:
2580 if (is_array($record['filerefs'])) {
2581 $this->addFiles($record['filerefs'], $lines, $preCode);
2582 }
2583 // DB relations
2584 if (is_array($record['rels'])) {
2585 $this->addRelations($record['rels'], $lines, $preCode);
2586 }
2587 // Soft ref
2588 if (count($record['softrefs'])) {
2589 $preCode_A = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;';
2590 $preCode_B = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
2591 foreach ($record['softrefs'] as $info) {
2592 $pInfo = array();
2593 $pInfo['preCode'] = $preCode_A . \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIcon('status-status-reference-soft');
2594 $pInfo['title'] = '<em>' . $info['field'] . ', "' . $info['spKey'] . '" </em>: <span title="' . htmlspecialchars($info['matchString']) . '">' . htmlspecialchars(\TYPO3\CMS\Core\Utility\GeneralUtility::fixed_lgd_cs($info['matchString'], 60)) . '</span>';
2595 if ($info['subst']['type']) {
2596 if (strlen($info['subst']['title'])) {
2597 $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . $LANG->getLL('impexpcore_singlereco_title', 1) . '</strong> ' . htmlspecialchars(\TYPO3\CMS\Core\Utility\GeneralUtility::fixed_lgd_cs($info['subst']['title'], 60));
2598 }
2599 if (strlen($info['subst']['description'])) {
2600 $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . $LANG->getLL('impexpcore_singlereco_descr', 1) . '</strong> ' . htmlspecialchars(\TYPO3\CMS\Core\Utility\GeneralUtility::fixed_lgd_cs($info['subst']['description'], 60));
2601 }
2602 $pInfo['title'] .= '<br/>' . $preCode_B . ($info['subst']['type'] == 'file' ? $LANG->getLL('impexpcore_singlereco_filename', 1) . ' <strong>' . $info['subst']['relFileName'] . '</strong>' : '') . ($info['subst']['type'] == 'string' ? $LANG->getLL('impexpcore_singlereco_value', 1) . ' <strong>' . $info['subst']['tokenValue'] . '</strong>' : '') . ($info['subst']['type'] == 'db' ? $LANG->getLL('impexpcore_softrefsel_record', 1) . ' <strong>' . $info['subst']['recordRef'] . '</strong>' : '');
2603 }
2604 $pInfo['ref'] = 'SOFTREF';
2605 $pInfo['size'] = '';
2606 $pInfo['class'] = 'bgColor3';
2607 $pInfo['type'] = 'softref';
2608 $pInfo['_softRefInfo'] = $info;
2609 $pInfo['type'] = 'softref';
2610 if ($info['error'] && !\TYPO3\CMS\Core\Utility\GeneralUtility::inList('editable,exclude', $this->softrefCfg[$info['subst']['tokenID']]['mode'])) {
2611 $pInfo['msg'] .= $info['error'];
2612 }
2613 $lines[] = $pInfo;
2614 // Add relations:
2615 if ($info['subst']['type'] == 'db') {
2616 list($tempTable, $tempUid) = explode(':', $info['subst']['recordRef']);
2617 $this->addRelations(array(array('table' => $tempTable, 'id' => $tempUid, 'tokenID' => $info['subst']['tokenID'])), $lines, $preCode_B, array(), '');
2618 }
2619 // Add files:
2620 if ($info['subst']['type'] == 'file') {
2621 $this->addFiles(array($info['file_ID']), $lines, $preCode_B, '', $info['subst']['tokenID']);
2622 }
2623 }
2624 }
2625 }
2626
2627 /**
2628 * Add DB relations entries for a record's rels-array
2629 *
2630 * @param array $rels Array of relations
2631 * @param array $lines Output lines array (is passed by reference and modified)
2632 * @param string $preCode Pre-HTML code
2633 * @param array $recurCheck Recursivity check stack
2634 * @param string $htmlColorClass Alternative HTML color class to use.
2635 * @return void
2636 * @access private
2637 * @see singleRecordLines()
2638 * @todo Define visibility
2639 */
2640 public function addRelations($rels, &$lines, $preCode, $recurCheck = array(), $htmlColorClass = '') {
2641 foreach ($rels as $dat) {
2642 $table = $dat['table'];
2643 $uid = $dat['id'];
2644 $pInfo = array();
2645 $Iprepend = '';
2646 $staticFixed = FALSE;
2647 $pInfo['ref'] = $table . ':' . $uid;
2648 if (!in_array($pInfo['ref'], $recurCheck)) {
2649 if ($uid > 0) {
2650 $record = $this->dat['header']['records'][$table][$uid];
2651 if (!is_array($record)) {
2652 if ($this->isTableStatic($table) || $this->isExcluded($table, $uid) || $dat['tokenID'] && !$this->includeSoftref($dat['tokenID'])) {
2653 $pInfo['title'] = htmlspecialchars('STATIC: ' . $pInfo['ref']);
2654 $Iprepend = '_static';
2655 $staticFixed = TRUE;
2656 } else {
2657 $doesRE = $this->doesRecordExist($table, $uid);
2658 $lostPath = $this->getRecordPath($table === 'pages' ? $doesRE['uid'] : $doesRE['pid']);
2659 $pInfo['title'] = htmlspecialchars($pInfo['ref']);
2660 $pInfo['title'] = '<span title="' . htmlspecialchars($lostPath) . '">' . $pInfo['title'] . '</span>';
2661 $pInfo['msg'] = 'LOST RELATION' . (!$doesRE ? ' (Record not found!)' : ' (Path: ' . $lostPath . ')');
2662 $Iprepend = '_lost';
2663 }
2664 } else {
2665 $pInfo['title'] = htmlspecialchars($record['title']);
2666 $pInfo['title'] = '<span title="' . htmlspecialchars($this->getRecordPath(($table === 'pages' ? $record['uid'] : $record['pid']))) . '">' . $pInfo['title'] . '</span>';
2667 }
2668 } else {
2669 // Negative values in relation fields. This is typically sys_language fields, fe_users fields etc. They are static values. They CAN theoretically be negative pointers to uids in other tables but this is so rarely used that it is not supported
2670 $pInfo['title'] = htmlspecialchars('FIXED: ' . $pInfo['ref']);
2671 $staticFixed = TRUE;
2672 }
2673 $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;<img' . \TYPO3\CMS\Backend\Utility\IconUtility::skinImg($GLOBALS['BACK_PATH'], ('gfx/rel_db' . $Iprepend . '.gif'), 'width="13" height="12"') . ' align="top" title="' . htmlspecialchars($pInfo['ref']) . '" alt="" />';
2674 $pInfo['class'] = $htmlColorClass ? $htmlColorClass : 'bgColor3';
2675 $pInfo['type'] = 'rel';
2676 if (!$staticFixed || $this->showStaticRelations) {
2677 $lines[] = $pInfo;
2678 if (is_array($record) && is_array($record['rels'])) {
2679 $this->addRelations($record['rels'], $lines, $preCode . '&nbsp;&nbsp;', array_merge($recurCheck, array($pInfo['ref'])), $htmlColorClass);
2680 }
2681 }
2682 } else {
2683 $this->error($pInfo['ref'] . ' was recursive...');
2684 }
2685 }
2686 }
2687
2688 /**
2689 * Add file relation entries for a record's rels-array
2690 *
2691 * @param array $rels Array of file IDs
2692 * @param array $lines Output lines array (is passed by reference and modified)
2693 * @param string $preCode Pre-HTML code
2694 * @param string $htmlColorClass Alternative HTML color class to use.
2695 * @param string $tokenID Token ID if this is a softreference (in which case it only makes sense with a single element in the $rels array!)
2696 * @return void
2697 * @access private
2698 * @see singleRecordLines()
2699 * @todo Define visibility
2700 */
2701 public function addFiles($rels, &$lines, $preCode, $htmlColorClass = '', $tokenID = '') {
2702 foreach ($rels as $ID) {
2703 // Process file:
2704 $pInfo = array();
2705 $fI = $this->dat['header']['files'][$ID];
2706 if (!is_array($fI)) {
2707 if (!$tokenID || $this->includeSoftref($tokenID)) {
2708 $pInfo['msg'] = 'MISSING FILE: ' . $ID;
2709 $this->error