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