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