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