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