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