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