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