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