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