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