1<?php 2namespace TYPO3\CMS\Impexp; 3 4/* 5 * This file is part of the TYPO3 CMS project. 6 * 7 * It is free software; you can redistribute it and/or modify it under 8 * the terms of the GNU General Public License, either version 2 9 * of the License, or any later version. 10 * 11 * For the full copyright and license information, please read the 12 * LICENSE.txt file that was distributed with this source code. 13 * 14 * The TYPO3 project - inspiring people to share! 15 */ 16 17use TYPO3\CMS\Backend\Utility\BackendUtility; 18use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; 19use TYPO3\CMS\Core\Core\Environment; 20use TYPO3\CMS\Core\Imaging\Icon; 21use TYPO3\CMS\Core\Imaging\IconFactory; 22use TYPO3\CMS\Core\Localization\LanguageService; 23use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException; 24use TYPO3\CMS\Core\Resource\ResourceFactory; 25use TYPO3\CMS\Core\Type\Bitmask\Permission; 26use TYPO3\CMS\Core\Utility\DebugUtility; 27use TYPO3\CMS\Core\Utility\DiffUtility; 28use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; 29use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility; 30use TYPO3\CMS\Core\Utility\GeneralUtility; 31use TYPO3\CMS\Core\Utility\PathUtility; 32 33/** 34 * EXAMPLE for using the impexp-class for exporting stuff: 35 * 36 * Create and initialize: 37 * $this->export = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Impexp\ImportExport::class); 38 * $this->export->init(); 39 * Set which tables relations we will allow: 40 * $this->export->relOnlyTables[]="tt_news"; // exclusively includes. See comment in the class 41 * 42 * Adding records: 43 * $this->export->export_addRecord("pages", $this->pageinfo); 44 * $this->export->export_addRecord("pages", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("pages", 38)); 45 * $this->export->export_addRecord("pages", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("pages", 39)); 46 * $this->export->export_addRecord("tt_content", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("tt_content", 12)); 47 * $this->export->export_addRecord("tt_content", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("tt_content", 74)); 48 * $this->export->export_addRecord("sys_template", \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord("sys_template", 20)); 49 * 50 * Adding all the relations (recursively in 5 levels so relations has THEIR relations registered as well) 51 * for($a=0;$a<5;$a++) { 52 * $addR = $this->export->export_addDBRelations($a); 53 * if (empty($addR)) break; 54 * } 55 * 56 * Finally load all the files. 57 * $this->export->export_addFilesFromRelations(); // MUST be after the DBrelations are set so that file from ALL added records are included! 58 * 59 * Write export 60 * $out = $this->export->compileMemoryToFileContent(); 61 * @internal this is not part of TYPO3's Core API. 62 */ 63 64/** 65 * T3D file Import/Export library (TYPO3 Record Document) 66 */ 67abstract class ImportExport 68{ 69 /** 70 * If set, static relations (not exported) will be shown in overview as well 71 * 72 * @var bool 73 */ 74 public $showStaticRelations = false; 75 76 /** 77 * Name of the "fileadmin" folder where files for export/import should be located 78 * 79 * @var string 80 */ 81 public $fileadminFolderName = ''; 82 83 /** 84 * Whether "import" or "export" mode of object. Set through init() function 85 * 86 * @var string 87 */ 88 public $mode = ''; 89 90 /** 91 * Updates all records that has same UID instead of creating new! 92 * 93 * @var bool 94 */ 95 public $update = false; 96 97 /** 98 * Is set by importData() when an import has been done. 99 * 100 * @var bool 101 */ 102 public $doesImport = false; 103 104 /** 105 * If set to a page-record, then the preview display of the content will expect this page-record to be the target 106 * for the import and accordingly display validation information. This triggers the visual view of the 107 * import/export memory to validate if import is possible 108 * 109 * @var array 110 */ 111 public $display_import_pid_record = []; 112 113 /** 114 * Setting import modes during update state: as_new, exclude, force_uid 115 * 116 * @var array 117 */ 118 public $import_mode = []; 119 120 /** 121 * If set, PID correct is ignored globally 122 * 123 * @var bool 124 */ 125 public $global_ignore_pid = false; 126 127 /** 128 * If set, all UID values are forced! (update or import) 129 * 130 * @var bool 131 */ 132 public $force_all_UIDS = false; 133 134 /** 135 * If set, a diff-view column is added to the overview. 136 * 137 * @var bool 138 */ 139 public $showDiff = false; 140 141 /** 142 * If set, and if the user is admin, allow the writing of PHP scripts to fileadmin/ area. 143 * 144 * @var bool 145 */ 146 public $allowPHPScripts = false; 147 148 /** 149 * Array of values to substitute in editable softreferences. 150 * 151 * @var array 152 */ 153 public $softrefInputValues = []; 154 155 /** 156 * Mapping between the fileID from import memory and the final filenames they are written to. 157 * 158 * @var array 159 */ 160 public $fileIDMap = []; 161 162 /** 163 * Add table names here which are THE ONLY ones which will be included 164 * into export if found as relations. '_ALL' will allow all tables. 165 * 166 * @var array 167 */ 168 public $relOnlyTables = []; 169 170 /** 171 * Add tables names here which should not be exported with the file. 172 * (Where relations should be mapped to same UIDs in target system). 173 * 174 * @var array 175 */ 176 public $relStaticTables = []; 177 178 /** 179 * Exclude map. Keys are table:uid pairs and if set, records are not added to the export. 180 * 181 * @var array 182 */ 183 public $excludeMap = []; 184 185 /** 186 * Soft Reference Token ID modes. 187 * 188 * @var array 189 */ 190 public $softrefCfg = []; 191 192 /** 193 * Listing extension dependencies. 194 * 195 * @var array 196 */ 197 public $extensionDependencies = []; 198 199 /** 200 * After records are written this array is filled with [table][original_uid] = [new_uid] 201 * 202 * @var array 203 */ 204 public $import_mapId = []; 205 206 /** 207 * Error log. 208 * 209 * @var array 210 */ 211 public $errorLog = []; 212 213 /** 214 * Cache for record paths 215 * 216 * @var array 217 */ 218 public $cache_getRecordPath = []; 219 220 /** 221 * Cache of checkPID values. 222 * 223 * @var array 224 */ 225 public $checkPID_cache = []; 226 227 /** 228 * Set internally if the gzcompress function exists 229 * Used by ImportExportController 230 * 231 * @var bool 232 */ 233 public $compress = false; 234 235 /** 236 * Internal import/export memory 237 * 238 * @var array 239 */ 240 public $dat = []; 241 242 /** 243 * File processing object 244 * 245 * @var ExtendedFileUtility 246 */ 247 protected $fileProcObj; 248 249 /** 250 * @var array 251 */ 252 protected $remainHeader = []; 253 254 /** 255 * @var IconFactory 256 */ 257 protected $iconFactory; 258 259 /** 260 * Flag to control whether all disabled records and their children are excluded (true) or included (false). Defaults 261 * to the old behaviour of including everything. 262 * 263 * @var bool 264 */ 265 protected $excludeDisabledRecords = false; 266 267 /** 268 * The constructor 269 */ 270 public function __construct() 271 { 272 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class); 273 } 274 275 /************************** 276 * Initialize 277 *************************/ 278 279 /** 280 * Init the object, both import and export 281 */ 282 public function init() 283 { 284 $this->compress = function_exists('gzcompress'); 285 $this->fileadminFolderName = !empty($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir']) ? rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') : 'fileadmin'; 286 } 287 288 /******************************************************** 289 * Visual rendering of import/export memory, $this->dat 290 ********************************************************/ 291 292 /** 293 * Displays an overview of the header-content. 294 * 295 * @return array The view data 296 */ 297 public function displayContentOverview() 298 { 299 if (!isset($this->dat['header'])) { 300 return []; 301 } 302 // Check extension dependencies: 303 foreach ($this->dat['header']['extensionDependencies'] as $extKey) { 304 if (!empty($extKey) && !ExtensionManagementUtility::isLoaded($extKey)) { 305 $this->error('DEPENDENCY: The extension with key "' . $extKey . '" must be installed!'); 306 } 307 } 308 309 // Probably this is done to save memory space? 310 unset($this->dat['files']); 311 312 $viewData = []; 313 // Traverse header: 314 $this->remainHeader = $this->dat['header']; 315 // If there is a page tree set, show that: 316 if (is_array($this->dat['header']['pagetree'])) { 317 reset($this->dat['header']['pagetree']); 318 $lines = []; 319 $this->traversePageTree($this->dat['header']['pagetree'], $lines); 320 321 $viewData['dat'] = $this->dat; 322 $viewData['update'] = $this->update; 323 $viewData['showDiff'] = $this->showDiff; 324 if (!empty($lines)) { 325 foreach ($lines as &$r) { 326 $r['controls'] = $this->renderControls($r); 327 $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : ''); 328 } 329 $viewData['pagetreeLines'] = $lines; 330 } else { 331 $viewData['pagetreeLines'] = []; 332 } 333 } 334 // Print remaining records that were not contained inside the page tree: 335 if (is_array($this->remainHeader['records'])) { 336 $lines = []; 337 if (is_array($this->remainHeader['records']['pages'])) { 338 $this->traversePageRecords($this->remainHeader['records']['pages'], $lines); 339 } 340 $this->traverseAllRecords($this->remainHeader['records'], $lines); 341 if (!empty($lines)) { 342 foreach ($lines as &$r) { 343 $r['controls'] = $this->renderControls($r); 344 $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : ''); 345 } 346 $viewData['remainingRecords'] = $lines; 347 } 348 } 349 350 return $viewData; 351 } 352 353 /** 354 * Go through page tree for display 355 * 356 * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree] 357 * @param array $lines Output lines array (is passed by reference and modified) 358 * @param string $preCode Pre-HTML code 359 */ 360 public function traversePageTree($pT, &$lines, $preCode = '') 361 { 362 foreach ($pT as $k => $v) { 363 if ($this->excludeDisabledRecords === true && !$this->isActive('pages', $k)) { 364 $this->excludePageAndRecords($k, $v); 365 continue; 366 } 367 368 // Add this page: 369 $this->singleRecordLines('pages', $k, $lines, $preCode); 370 // Subrecords: 371 if (is_array($this->dat['header']['pid_lookup'][$k])) { 372 foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) { 373 if ($t !== 'pages') { 374 foreach ($recUidArr as $ruid => $value) { 375 $this->singleRecordLines($t, $ruid, $lines, $preCode . ' '); 376 } 377 } 378 } 379 unset($this->remainHeader['pid_lookup'][$k]); 380 } 381 // Subpages, called recursively: 382 if (is_array($v['subrow'])) { 383 $this->traversePageTree($v['subrow'], $lines, $preCode . ' '); 384 } 385 } 386 } 387 388 /** 389 * Test whether a record is active (i.e. not hidden) 390 * 391 * @param string $table Name of the records' database table 392 * @param int $uid Database uid of the record 393 * @return bool true if the record is active, false otherwise 394 */ 395 protected function isActive($table, $uid) 396 { 397 return 398 !isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']) 399 || !(bool)$this->dat['records'][$table . ':' . $uid]['data'][ 400 $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled'] 401 ]; 402 } 403 404 /** 405 * Exclude a page, its sub pages (recursively) and records placed in them from this import/export 406 * 407 * @param int $pageUid Uid of the page to exclude 408 * @param array $pageTree Page tree array with uid/subrow (from ->dat[header][pagetree] 409 */ 410 protected function excludePageAndRecords($pageUid, $pageTree) 411 { 412 // Prevent having this page appear in "remaining records" table 413 unset($this->remainHeader['records']['pages'][$pageUid]); 414 415 // Subrecords 416 if (is_array($this->dat['header']['pid_lookup'][$pageUid])) { 417 foreach ($this->dat['header']['pid_lookup'][$pageUid] as $table => $recordData) { 418 if ($table !== 'pages') { 419 foreach (array_keys($recordData) as $uid) { 420 unset($this->remainHeader['records'][$table][$uid]); 421 } 422 } 423 } 424 unset($this->remainHeader['pid_lookup'][$pageUid]); 425 } 426 // Subpages excluded recursively 427 if (is_array($pageTree['subrow'])) { 428 foreach ($pageTree['subrow'] as $subPageUid => $subPageTree) { 429 $this->excludePageAndRecords($subPageUid, $subPageTree); 430 } 431 } 432 } 433 434 /** 435 * Go through remaining pages (not in tree) 436 * 437 * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree] 438 * @param array $lines Output lines array (is passed by reference and modified) 439 */ 440 public function traversePageRecords($pT, &$lines) 441 { 442 foreach ($pT as $k => $rHeader) { 443 $this->singleRecordLines('pages', $k, $lines, '', 1); 444 // Subrecords: 445 if (is_array($this->dat['header']['pid_lookup'][$k])) { 446 foreach ($this->dat['header']['pid_lookup'][$k] as $t => $recUidArr) { 447 if ($t !== 'pages') { 448 foreach ($recUidArr as $ruid => $value) { 449 $this->singleRecordLines($t, $ruid, $lines, ' '); 450 } 451 } 452 } 453 unset($this->remainHeader['pid_lookup'][$k]); 454 } 455 } 456 } 457 458 /** 459 * Go through ALL records (if the pages are displayed first, those will not be among these!) 460 * 461 * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree] 462 * @param array $lines Output lines array (is passed by reference and modified) 463 */ 464 public function traverseAllRecords($pT, &$lines) 465 { 466 foreach ($pT as $t => $recUidArr) { 467 $this->addGeneralErrorsByTable($t); 468 if ($t !== 'pages') { 469 $preCode = ''; 470 foreach ($recUidArr as $ruid => $value) { 471 $this->singleRecordLines($t, $ruid, $lines, $preCode, 1); 472 } 473 } 474 } 475 } 476 477 /** 478 * Log general error message for a given table 479 * 480 * @param string $table database table name 481 */ 482 protected function addGeneralErrorsByTable($table) 483 { 484 if ($this->update && $table === 'sys_file') { 485 $this->error('Updating sys_file records is not supported! They will be imported as new records!'); 486 } 487 if ($this->force_all_UIDS && $table === 'sys_file') { 488 $this->error('Forcing uids of sys_file records is not supported! They will be imported as new records!'); 489 } 490 } 491 492 /** 493 * Add entries for a single record 494 * 495 * @param string $table Table name 496 * @param int $uid Record uid 497 * @param array $lines Output lines array (is passed by reference and modified) 498 * @param string $preCode Pre-HTML code 499 * @param bool $checkImportInPidRecord If you want import validation, you can set this so it checks if the import can take place on the specified page. 500 */ 501 public function singleRecordLines($table, $uid, &$lines, $preCode, $checkImportInPidRecord = false) 502 { 503 // Get record: 504 $record = $this->dat['header']['records'][$table][$uid]; 505 unset($this->remainHeader['records'][$table][$uid]); 506 if (!is_array($record) && !($table === 'pages' && !$uid)) { 507 $this->error('MISSING RECORD: ' . $table . ':' . $uid); 508 } 509 // Begin to create the line arrays information record, pInfo: 510 $pInfo = []; 511 $pInfo['ref'] = $table . ':' . $uid; 512 // Unknown table name: 513 $lang = $this->getLanguageService(); 514 if ($table === '_SOFTREF_') { 515 $pInfo['preCode'] = $preCode; 516 $pInfo['title'] = '<em>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_softReferencesFiles')) . '</em>'; 517 } elseif (!isset($GLOBALS['TCA'][$table])) { 518 // Unknown table name: 519 $pInfo['preCode'] = $preCode; 520 $pInfo['msg'] = 'UNKNOWN TABLE \'' . $pInfo['ref'] . '\''; 521 $pInfo['title'] = '<em>' . htmlspecialchars($record['title']) . '</em>'; 522 } else { 523 // prepare data attribute telling whether the record is active or hidden, allowing frontend bulk selection 524 $pInfo['active'] = $this->isActive($table, $uid) ? 'active' : 'hidden'; 525 526 // Otherwise, set table icon and title. 527 // Import Validation (triggered by $this->display_import_pid_record) will show messages if import is not possible of various items. 528 if (is_array($this->display_import_pid_record) && !empty($this->display_import_pid_record)) { 529 if ($checkImportInPidRecord) { 530 if (!$this->getBackendUser()->doesUserHaveAccess($this->display_import_pid_record, ($table === 'pages' ? 8 : 16))) { 531 $pInfo['msg'] .= '\'' . $pInfo['ref'] . '\' cannot be INSERTED on this page! '; 532 } 533 if (!$this->checkDokType($table, $this->display_import_pid_record['doktype']) && !$GLOBALS['TCA'][$table]['ctrl']['rootLevel']) { 534 $pInfo['msg'] .= '\'' . $table . '\' cannot be INSERTED on this page type (change page type to \'Folder\'.) '; 535 } 536 } 537 if (!$this->getBackendUser()->check('tables_modify', $table)) { 538 $pInfo['msg'] .= 'You are not allowed to CREATE \'' . $table . '\' tables! '; 539 } 540 if ($GLOBALS['TCA'][$table]['ctrl']['readOnly']) { 541 $pInfo['msg'] .= 'TABLE \'' . $table . '\' is READ ONLY! '; 542 } 543 if ($GLOBALS['TCA'][$table]['ctrl']['adminOnly'] && !$this->getBackendUser()->isAdmin()) { 544 $pInfo['msg'] .= 'TABLE \'' . $table . '\' is ADMIN ONLY! '; 545 } 546 if ($GLOBALS['TCA'][$table]['ctrl']['is_static']) { 547 $pInfo['msg'] .= 'TABLE \'' . $table . '\' is a STATIC TABLE! '; 548 } 549 if ((int)$GLOBALS['TCA'][$table]['ctrl']['rootLevel'] === 1) { 550 $pInfo['msg'] .= 'TABLE \'' . $table . '\' will be inserted on ROOT LEVEL! '; 551 } 552 $diffInverse = false; 553 $recInf = null; 554 if ($this->update) { 555 // In case of update-PREVIEW we swap the diff-sources. 556 $diffInverse = true; 557 $recInf = $this->doesRecordExist($table, $uid, $this->showDiff ? '*' : ''); 558 $pInfo['updatePath'] = $recInf ? htmlspecialchars($this->getRecordPath($recInf['pid'])) : '<strong>NEW!</strong>'; 559 // Mode selector: 560 $optValues = []; 561 $optValues[] = $recInf ? $lang->getLL('impexpcore_singlereco_update') : $lang->getLL('impexpcore_singlereco_insert'); 562 if ($recInf) { 563 $optValues['as_new'] = $lang->getLL('impexpcore_singlereco_importAsNew'); 564 } 565 if ($recInf) { 566 if (!$this->global_ignore_pid) { 567 $optValues['ignore_pid'] = $lang->getLL('impexpcore_singlereco_ignorePid'); 568 } else { 569 $optValues['respect_pid'] = $lang->getLL('impexpcore_singlereco_respectPid'); 570 } 571 } 572 if (!$recInf && $this->getBackendUser()->isAdmin()) { 573 $optValues['force_uid'] = sprintf($lang->getLL('impexpcore_singlereco_forceUidSAdmin'), $uid); 574 } 575 $optValues['exclude'] = $lang->getLL('impexpcore_singlereco_exclude'); 576 if ($table === 'sys_file') { 577 $pInfo['updateMode'] = ''; 578 } else { 579 $pInfo['updateMode'] = $this->renderSelectBox('tx_impexp[import_mode][' . $table . ':' . $uid . ']', $this->import_mode[$table . ':' . $uid], $optValues); 580 } 581 } 582 // Diff view: 583 if ($this->showDiff) { 584 // For IMPORTS, get new id: 585 if ($newUid = $this->import_mapId[$table][$uid]) { 586 $diffInverse = false; 587 $recInf = $this->doesRecordExist($table, $newUid, '*'); 588 BackendUtility::workspaceOL($table, $recInf); 589 } 590 if (is_array($recInf)) { 591 $pInfo['showDiffContent'] = $this->compareRecords($recInf, $this->dat['records'][$table . ':' . $uid]['data'], $table, $diffInverse); 592 } 593 } 594 } 595 $pInfo['preCode'] = $preCode . '<span title="' . htmlspecialchars($table . ':' . $uid) . '">' 596 . $this->iconFactory->getIconForRecord($table, (array)$this->dat['records'][$table . ':' . $uid]['data'], Icon::SIZE_SMALL)->render() 597 . '</span>'; 598 $pInfo['title'] = htmlspecialchars($record['title']); 599 // View page: 600 if ($table === 'pages') { 601 $viewID = $this->mode === 'export' ? $uid : ($this->doesImport ? $this->import_mapId['pages'][$uid] : 0); 602 if ($viewID) { 603 $pInfo['title'] = '<a href="#" onclick="' . htmlspecialchars(BackendUtility::viewOnClick($viewID)) . 'return false;">' . $pInfo['title'] . '</a>'; 604 } 605 } 606 } 607 $pInfo['type'] = 'record'; 608 $lines[] = $pInfo; 609 // File relations: 610 if (is_array($record['filerefs'])) { 611 $this->addFiles($record['filerefs'], $lines, $preCode); 612 } 613 // DB relations 614 if (is_array($record['rels'])) { 615 $this->addRelations($record['rels'], $lines, $preCode); 616 } 617 // Soft ref 618 if (!empty($record['softrefs'])) { 619 $preCode_A = $preCode . ' '; 620 $preCode_B = $preCode . ' '; 621 foreach ($record['softrefs'] as $info) { 622 $pInfo = []; 623 $pInfo['preCode'] = $preCode_A . $this->iconFactory->getIcon('status-reference-soft', Icon::SIZE_SMALL)->render(); 624 $pInfo['title'] = '<em>' . $info['field'] . ', "' . $info['spKey'] . '" </em>: <span title="' . htmlspecialchars($info['matchString']) . '">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['matchString'], 60)) . '</span>'; 625 if ($info['subst']['type']) { 626 if (strlen($info['subst']['title'])) { 627 $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_title')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['title'], 60)); 628 } 629 if (strlen($info['subst']['description'])) { 630 $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_descr')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['description'], 60)); 631 } 632 $pInfo['title'] .= '<br/>' . $preCode_B . ($info['subst']['type'] === 'file' ? htmlspecialchars($lang->getLL('impexpcore_singlereco_filename')) . ' <strong>' . $info['subst']['relFileName'] . '</strong>' : '') . ($info['subst']['type'] === 'string' ? htmlspecialchars($lang->getLL('impexpcore_singlereco_value')) . ' <strong>' . $info['subst']['tokenValue'] . '</strong>' : '') . ($info['subst']['type'] === 'db' ? htmlspecialchars($lang->getLL('impexpcore_softrefsel_record')) . ' <strong>' . $info['subst']['recordRef'] . '</strong>' : ''); 633 } 634 $pInfo['ref'] = 'SOFTREF'; 635 $pInfo['type'] = 'softref'; 636 $pInfo['_softRefInfo'] = $info; 637 $pInfo['type'] = 'softref'; 638 $mode = $this->softrefCfg[$info['subst']['tokenID']]['mode']; 639 if ($info['error'] && $mode !== 'editable' && $mode !== 'exclude') { 640 $pInfo['msg'] .= $info['error']; 641 } 642 $lines[] = $pInfo; 643 // Add relations: 644 if ($info['subst']['type'] === 'db') { 645 list($tempTable, $tempUid) = explode(':', $info['subst']['recordRef']); 646 $this->addRelations([['table' => $tempTable, 'id' => $tempUid, 'tokenID' => $info['subst']['tokenID']]], $lines, $preCode_B, [], ''); 647 } 648 // Add files: 649 if ($info['subst']['type'] === 'file') { 650 $this->addFiles([$info['file_ID']], $lines, $preCode_B, '', $info['subst']['tokenID']); 651 } 652 } 653 } 654 } 655 656 /** 657 * Add DB relations entries for a record's rels-array 658 * 659 * @param array $rels Array of relations 660 * @param array $lines Output lines array (is passed by reference and modified) 661 * @param string $preCode Pre-HTML code 662 * @param array $recurCheck Recursivity check stack 663 * @param string $htmlColorClass Alternative HTML color class to use. 664 * @internal 665 * @see singleRecordLines() 666 */ 667 public function addRelations($rels, &$lines, $preCode, $recurCheck = [], $htmlColorClass = '') 668 { 669 foreach ($rels as $dat) { 670 $table = $dat['table']; 671 $uid = $dat['id']; 672 $pInfo = []; 673 $pInfo['ref'] = $table . ':' . $uid; 674 if (in_array($pInfo['ref'], $recurCheck)) { 675 continue; 676 } 677 $iconName = 'status-status-checked'; 678 $iconClass = ''; 679 $staticFixed = false; 680 $record = null; 681 if ($uid > 0) { 682 $record = $this->dat['header']['records'][$table][$uid]; 683 if (!is_array($record)) { 684 if ($this->isTableStatic($table) || $this->isExcluded($table, $uid) || $dat['tokenID'] && !$this->includeSoftref($dat['tokenID'])) { 685 $pInfo['title'] = htmlspecialchars('STATIC: ' . $pInfo['ref']); 686 $iconClass = 'text-info'; 687 $staticFixed = true; 688 } else { 689 $doesRE = $this->doesRecordExist($table, $uid); 690 $lostPath = $this->getRecordPath($table === 'pages' ? $doesRE['uid'] : $doesRE['pid']); 691 $pInfo['title'] = htmlspecialchars($pInfo['ref']); 692 $pInfo['title'] = '<span title="' . htmlspecialchars($lostPath) . '">' . $pInfo['title'] . '</span>'; 693 $pInfo['msg'] = 'LOST RELATION' . (!$doesRE ? ' (Record not found!)' : ' (Path: ' . $lostPath . ')'); 694 $iconClass = 'text-danger'; 695 $iconName = 'status-dialog-warning'; 696 } 697 } else { 698 $pInfo['title'] = htmlspecialchars($record['title']); 699 $pInfo['title'] = '<span title="' . htmlspecialchars($this->getRecordPath(($table === 'pages' ? $record['uid'] : $record['pid']))) . '">' . $pInfo['title'] . '</span>'; 700 } 701 } else { 702 // 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 703 $pInfo['title'] = htmlspecialchars('FIXED: ' . $pInfo['ref']); 704 $staticFixed = true; 705 } 706 707 $icon = '<span class="' . $iconClass . '" title="' . htmlspecialchars($pInfo['ref']) . '">' . $this->iconFactory->getIcon($iconName, Icon::SIZE_SMALL)->render() . '</span>'; 708 709 $pInfo['preCode'] = $preCode . ' ' . $icon; 710 $pInfo['type'] = 'rel'; 711 if (!$staticFixed || $this->showStaticRelations) { 712 $lines[] = $pInfo; 713 if (is_array($record) && is_array($record['rels'])) { 714 $this->addRelations($record['rels'], $lines, $preCode . ' ', array_merge($recurCheck, [$pInfo['ref']])); 715 } 716 } 717 } 718 } 719 720 /** 721 * Add file relation entries for a record's rels-array 722 * 723 * @param array $rels Array of file IDs 724 * @param array $lines Output lines array (is passed by reference and modified) 725 * @param string $preCode Pre-HTML code 726 * @param string $htmlColorClass Alternative HTML color class to use. 727 * @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!) 728 * @internal 729 * @see singleRecordLines() 730 */ 731 public function addFiles($rels, &$lines, $preCode, $htmlColorClass = '', $tokenID = '') 732 { 733 foreach ($rels as $ID) { 734 // Process file: 735 $pInfo = []; 736 $fI = $this->dat['header']['files'][$ID]; 737 if (!is_array($fI)) { 738 if (!$tokenID || $this->includeSoftref($tokenID)) { 739 $pInfo['msg'] = 'MISSING FILE: ' . $ID; 740 $this->error('MISSING FILE: ' . $ID); 741 } else { 742 return; 743 } 744 } 745 $pInfo['preCode'] = $preCode . ' ' . $this->iconFactory->getIcon('status-reference-hard', Icon::SIZE_SMALL)->render(); 746 $pInfo['title'] = htmlspecialchars($fI['filename']); 747 $pInfo['ref'] = 'FILE'; 748 $pInfo['type'] = 'file'; 749 // If import mode and there is a non-RTE softreference, check the destination directory: 750 if ($this->mode === 'import' && $tokenID && !$fI['RTE_ORIG_ID']) { 751 if (isset($fI['parentRelFileName'])) { 752 $pInfo['msg'] = 'Seems like this file is already referenced from within an HTML/CSS file. That takes precedence. '; 753 } else { 754 $testDirPrefix = PathUtility::dirname($fI['relFileName']) . '/'; 755 $testDirPrefix2 = $this->verifyFolderAccess($testDirPrefix); 756 if (!$testDirPrefix2) { 757 $pInfo['msg'] = 'ERROR: There are no available filemounts to write file in! '; 758 } elseif ($testDirPrefix !== $testDirPrefix2) { 759 $pInfo['msg'] = 'File will be attempted written to "' . $testDirPrefix2 . '". '; 760 } 761 } 762 // Check if file exists: 763 if (file_exists(Environment::getPublicPath() . '/' . $fI['relFileName'])) { 764 if ($this->update) { 765 $pInfo['updatePath'] .= 'File exists.'; 766 } else { 767 $pInfo['msg'] .= 'File already exists! '; 768 } 769 } 770 // Check extension: 771 $fileProcObj = $this->getFileProcObj(); 772 if ($fileProcObj->actionPerms['addFile']) { 773 $testFI = GeneralUtility::split_fileref(Environment::getPublicPath() . '/' . $fI['relFileName']); 774 if (!$this->allowPHPScripts && !$fileProcObj->checkIfAllowed($testFI['fileext'], $testFI['path'], $testFI['file'])) { 775 $pInfo['msg'] .= 'File extension was not allowed!'; 776 } 777 } else { 778 $pInfo['msg'] = 'You user profile does not allow you to create files on the server!'; 779 } 780 } 781 $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$ID]); 782 $lines[] = $pInfo; 783 unset($this->remainHeader['files'][$ID]); 784 // RTE originals: 785 if ($fI['RTE_ORIG_ID']) { 786 $ID = $fI['RTE_ORIG_ID']; 787 $pInfo = []; 788 $fI = $this->dat['header']['files'][$ID]; 789 if (!is_array($fI)) { 790 $pInfo['msg'] = 'MISSING RTE original FILE: ' . $ID; 791 $this->error('MISSING RTE original FILE: ' . $ID); 792 } 793 $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$ID]); 794 $pInfo['preCode'] = $preCode . ' ' . $this->iconFactory->getIcon('status-reference-hard', Icon::SIZE_SMALL)->render(); 795 $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Original)</em>'; 796 $pInfo['ref'] = 'FILE'; 797 $pInfo['type'] = 'file'; 798 $lines[] = $pInfo; 799 unset($this->remainHeader['files'][$ID]); 800 } 801 // External resources: 802 if (is_array($fI['EXT_RES_ID'])) { 803 foreach ($fI['EXT_RES_ID'] as $extID) { 804 $pInfo = []; 805 $fI = $this->dat['header']['files'][$extID]; 806 if (!is_array($fI)) { 807 $pInfo['msg'] = 'MISSING External Resource FILE: ' . $extID; 808 $this->error('MISSING External Resource FILE: ' . $extID); 809 } else { 810 $pInfo['updatePath'] = $fI['parentRelFileName']; 811 } 812 $pInfo['showDiffContent'] = PathUtility::stripPathSitePrefix($this->fileIDMap[$extID]); 813 $pInfo['preCode'] = $preCode . ' ' . $this->iconFactory->getIcon('actions-insert-reference', Icon::SIZE_SMALL)->render(); 814 $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Resource)</em>'; 815 $pInfo['ref'] = 'FILE'; 816 $pInfo['type'] = 'file'; 817 $lines[] = $pInfo; 818 unset($this->remainHeader['files'][$extID]); 819 } 820 } 821 } 822 } 823 824 /** 825 * Verifies that a table is allowed on a certain doktype of a page 826 * 827 * @param string $checkTable Table name to check 828 * @param int $doktype doktype value. 829 * @return bool TRUE if OK 830 */ 831 public function checkDokType($checkTable, $doktype) 832 { 833 $allowedTableList = $GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'] ?? $GLOBALS['PAGES_TYPES']['default']['allowedTables']; 834 $allowedArray = GeneralUtility::trimExplode(',', $allowedTableList, true); 835 // If all tables or the table is listed as an allowed type, return TRUE 836 if (strstr($allowedTableList, '*') || in_array($checkTable, $allowedArray)) { 837 return true; 838 } 839 return false; 840 } 841 842 /** 843 * Render input controls for import or export 844 * 845 * @param array $r Configuration for element 846 * @return string HTML 847 */ 848 public function renderControls($r) 849 { 850 if ($this->mode === 'export') { 851 if ($r['type'] === 'record') { 852 return '<input type="checkbox" class="t3js-exclude-checkbox" name="tx_impexp[exclude][' . $r['ref'] . ']" id="checkExclude' . $r['ref'] . '" value="1" /> <label for="checkExclude' . $r['ref'] . '">' . htmlspecialchars($this->getLanguageService()->getLL('impexpcore_singlereco_exclude')) . '</label>'; 853 } 854 return $r['type'] === 'softref' ? $this->softrefSelector($r['_softRefInfo']) : ''; 855 } 856 // During import 857 // For softreferences with editable fields: 858 if ($r['type'] === 'softref' && is_array($r['_softRefInfo']['subst']) && $r['_softRefInfo']['subst']['tokenID']) { 859 $tokenID = $r['_softRefInfo']['subst']['tokenID']; 860 $cfg = $this->softrefCfg[$tokenID]; 861 if ($cfg['mode'] === 'editable') { 862 return (strlen($cfg['title']) ? '<strong>' . htmlspecialchars($cfg['title']) . '</strong><br/>' : '') . htmlspecialchars($cfg['description']) . '<br/> 863 <input type="text" name="tx_impexp[softrefInputValues][' . $tokenID . ']" value="' . htmlspecialchars($this->softrefInputValues[$tokenID] ?? $cfg['defValue']) . '" />'; 864 } 865 } 866 867 return ''; 868 } 869 870 /** 871 * Selectorbox with export options for soft references 872 * 873 * @param array $cfg Softref configuration array. An export box is shown only if a substitution scheme is found for the soft reference. 874 * @return string Selector box HTML 875 */ 876 public function softrefSelector($cfg) 877 { 878 // Looking for file ID if any: 879 $fI = $cfg['file_ID'] ? $this->dat['header']['files'][$cfg['file_ID']] : []; 880 // Substitution scheme has to be around and RTE images MUST be exported. 881 if (is_array($cfg['subst']) && $cfg['subst']['tokenID'] && !$fI['RTE_ORIG_ID']) { 882 // Create options: 883 $optValues = []; 884 $optValues[''] = ''; 885 $optValues['editable'] = $this->getLanguageService()->getLL('impexpcore_softrefsel_editable'); 886 $optValues['exclude'] = $this->getLanguageService()->getLL('impexpcore_softrefsel_exclude'); 887 // Get current value: 888 $value = $this->softrefCfg[$cfg['subst']['tokenID']]['mode']; 889 // Render options selector: 890 $selectorbox = $this->renderSelectBox('tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][mode]', $value, $optValues) . '<br/>'; 891 if ($value === 'editable') { 892 $descriptionField = ''; 893 // Title: 894 if (strlen($cfg['subst']['title'])) { 895 $descriptionField .= ' 896 <input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][title]" value="' . htmlspecialchars($cfg['subst']['title']) . '" /> 897 <strong>' . htmlspecialchars($cfg['subst']['title']) . '</strong><br/>'; 898 } 899 // Description: 900 if (!strlen($cfg['subst']['description'])) { 901 $descriptionField .= ' 902 ' . htmlspecialchars($this->getLanguageService()->getLL('impexpcore_printerror_description')) . '<br/> 903 <input type="text" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($this->softrefCfg[$cfg['subst']['tokenID']]['description']) . '" />'; 904 } else { 905 $descriptionField .= ' 906 907 <input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($cfg['subst']['description']) . '" />' . htmlspecialchars($cfg['subst']['description']); 908 } 909 // Default Value: 910 $descriptionField .= '<input type="hidden" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][defValue]" value="' . htmlspecialchars($cfg['subst']['tokenValue']) . '" />'; 911 } else { 912 $descriptionField = ''; 913 } 914 return $selectorbox . $descriptionField; 915 } 916 return ''; 917 } 918 919 /** 920 * Verifies that the input path relative to public web path is found in the backend users filemounts. 921 * If it doesn't it will try to find another relative filemount for the user and return an alternative path prefix for the file. 922 * 923 * @param string $dirPrefix Path relative to public web path 924 * @param bool $noAlternative If set, Do not look for alternative path! Just return FALSE 925 * @return string|bool If a path is available that will be returned, otherwise FALSE. 926 */ 927 public function verifyFolderAccess($dirPrefix, $noAlternative = false) 928 { 929 // Check the absolute path for public web path, if the user has access - no problem 930 try { 931 ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($dirPrefix); 932 return $dirPrefix; 933 } catch (InsufficientFolderAccessPermissionsException $e) { 934 // Check all storages available for the user as alternative 935 if (!$noAlternative) { 936 $fileStorages = $this->getBackendUser()->getFileStorages(); 937 foreach ($fileStorages as $fileStorage) { 938 try { 939 $folder = $fileStorage->getFolder(rtrim($dirPrefix, '/')); 940 return $folder->getPublicUrl(); 941 } catch (InsufficientFolderAccessPermissionsException $e) { 942 } 943 } 944 } 945 } 946 return false; 947 } 948 949 /***************************** 950 * Helper functions of kinds 951 *****************************/ 952 953 /** 954 * @return string 955 */ 956 protected function getTemporaryFolderName() 957 { 958 $temporaryPath = Environment::getVarPath() . '/transient/'; 959 do { 960 $temporaryFolderName = $temporaryPath . 'export_temp_files_' . mt_rand(1, PHP_INT_MAX); 961 } while (is_dir($temporaryFolderName)); 962 GeneralUtility::mkdir($temporaryFolderName); 963 return $temporaryFolderName; 964 } 965 966 /** 967 * Recursively flattening the idH array 968 * 969 * @param array $idH Page uid hierarchy 970 * @param array $a Accumulation array of pages (internal, don't set from outside) 971 * @return array Array with uid-uid pairs for all pages in the page tree. 972 * @see Import::flatInversePageTree_pid() 973 */ 974 public function flatInversePageTree($idH, $a = []) 975 { 976 if (is_array($idH)) { 977 $idH = array_reverse($idH); 978 foreach ($idH as $k => $v) { 979 $a[$v['uid']] = $v['uid']; 980 if (is_array($v['subrow'])) { 981 $a = $this->flatInversePageTree($v['subrow'], $a); 982 } 983 } 984 } 985 return $a; 986 } 987 988 /** 989 * Returns TRUE if the input table name is to be regarded as a static relation (that is, not exported etc). 990 * 991 * @param string $table Table name 992 * @return bool TRUE, if table is marked static 993 */ 994 public function isTableStatic($table) 995 { 996 if (is_array($GLOBALS['TCA'][$table])) { 997 return $GLOBALS['TCA'][$table]['ctrl']['is_static'] || in_array($table, $this->relStaticTables) || in_array('_ALL', $this->relStaticTables); 998 } 999 return false; 1000 } 1001 1002 /** 1003 * Returns TRUE if the input table name is to be included as relation 1004 * 1005 * @param string $table Table name 1006 * @return bool TRUE, if table is marked static 1007 */ 1008 public function inclRelation($table) 1009 { 1010 return is_array($GLOBALS['TCA'][$table]) 1011 && (in_array($table, $this->relOnlyTables) || in_array('_ALL', $this->relOnlyTables)) 1012 && $this->getBackendUser()->check('tables_select', $table); 1013 } 1014 1015 /** 1016 * Returns TRUE if the element should be excluded as static record. 1017 * 1018 * @param string $table Table name 1019 * @param int $uid UID value 1020 * @return bool TRUE, if table is marked static 1021 */ 1022 public function isExcluded($table, $uid) 1023 { 1024 return (bool)$this->excludeMap[$table . ':' . $uid]; 1025 } 1026 1027 /** 1028 * Returns TRUE if soft reference should be included in exported file. 1029 * 1030 * @param string $tokenID Token ID for soft reference 1031 * @return bool TRUE if softreference media should be included 1032 */ 1033 public function includeSoftref($tokenID) 1034 { 1035 $mode = $this->softrefCfg[$tokenID]['mode']; 1036 return $tokenID && $mode !== 'exclude' && $mode !== 'editable'; 1037 } 1038 1039 /** 1040 * Checking if a PID is in the webmounts of the user 1041 * 1042 * @param int $pid Page ID to check 1043 * @return bool TRUE if OK 1044 */ 1045 public function checkPID($pid) 1046 { 1047 if (!isset($this->checkPID_cache[$pid])) { 1048 $this->checkPID_cache[$pid] = (bool)$this->getBackendUser()->isInWebMount($pid); 1049 } 1050 return $this->checkPID_cache[$pid]; 1051 } 1052 1053 /** 1054 * Checks if the position of an updated record is configured to be corrected. This can be disabled globally and changed for elements individually. 1055 * 1056 * @param string $table Table name 1057 * @param int $uid Uid or record 1058 * @return bool TRUE if the position of the record should be updated to match the one in the import structure 1059 */ 1060 public function dontIgnorePid($table, $uid) 1061 { 1062 return $this->import_mode[$table . ':' . $uid] !== 'ignore_pid' && (!$this->global_ignore_pid || $this->import_mode[$table . ':' . $uid] === 'respect_pid'); 1063 } 1064 1065 /** 1066 * Checks if the record exists 1067 * 1068 * @param string $table Table name 1069 * @param int $uid UID of record 1070 * @param string $fields Field list to select. Default is "uid,pid 1071 * @return array Result of \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord() which means the record if found, otherwise FALSE 1072 */ 1073 public function doesRecordExist($table, $uid, $fields = '') 1074 { 1075 return BackendUtility::getRecord($table, $uid, $fields ? $fields : 'uid,pid'); 1076 } 1077 1078 /** 1079 * Returns the page title path of a PID value. Results are cached internally 1080 * 1081 * @param int $pid Record PID to check 1082 * @return string The path for the input PID 1083 */ 1084 public function getRecordPath($pid) 1085 { 1086 if (!isset($this->cache_getRecordPath[$pid])) { 1087 $clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW); 1088 $this->cache_getRecordPath[$pid] = (string)BackendUtility::getRecordPath($pid, $clause, 20); 1089 } 1090 return $this->cache_getRecordPath[$pid]; 1091 } 1092 1093 /** 1094 * Makes a selector-box from optValues 1095 * 1096 * @param string $prefix Form element name 1097 * @param string $value Current value 1098 * @param array $optValues Options to display (key/value pairs) 1099 * @return string HTML select element 1100 */ 1101 public function renderSelectBox($prefix, $value, $optValues) 1102 { 1103 $opt = []; 1104 $isSelFlag = 0; 1105 foreach ($optValues as $k => $v) { 1106 $sel = (string)$k === (string)$value ? ' selected="selected"' : ''; 1107 if ($sel) { 1108 $isSelFlag++; 1109 } 1110 $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>'; 1111 } 1112 if (!$isSelFlag && (string)$value !== '') { 1113 $opt[] = '<option value="' . htmlspecialchars($value) . '" selected="selected">' . htmlspecialchars('[\'' . $value . '\']') . '</option>'; 1114 } 1115 return '<select name="' . $prefix . '">' . implode('', $opt) . '</select>'; 1116 } 1117 1118 /** 1119 * Compares two records, the current database record and the one from the import memory. 1120 * Will return HTML code to show any differences between them! 1121 * 1122 * @param array $databaseRecord Database record, all fields (new values) 1123 * @param array $importRecord Import memorys record for the same table/uid, all fields (old values) 1124 * @param string $table The table name of the record 1125 * @param bool $inverseDiff Inverse the diff view (switch red/green, needed for pre-update difference view) 1126 * @return string HTML 1127 */ 1128 public function compareRecords($databaseRecord, $importRecord, $table, $inverseDiff = false) 1129 { 1130 // Initialize: 1131 $output = []; 1132 $diffUtility = GeneralUtility::makeInstance(DiffUtility::class); 1133 // Check if both inputs are records: 1134 if (is_array($databaseRecord) && is_array($importRecord)) { 1135 // Traverse based on database record 1136 foreach ($databaseRecord as $fN => $value) { 1137 if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') { 1138 if (isset($importRecord[$fN])) { 1139 if (trim($databaseRecord[$fN]) !== trim($importRecord[$fN])) { 1140 // Create diff-result: 1141 $output[$fN] = $diffUtility->makeDiffDisplay(BackendUtility::getProcessedValue($table, $fN, !$inverseDiff ? $importRecord[$fN] : $databaseRecord[$fN], 0, 1, 1), BackendUtility::getProcessedValue($table, $fN, !$inverseDiff ? $databaseRecord[$fN] : $importRecord[$fN], 0, 1, 1)); 1142 } 1143 unset($importRecord[$fN]); 1144 } 1145 } 1146 } 1147 // Traverse remaining in import record: 1148 foreach ($importRecord as $fN => $value) { 1149 if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') { 1150 $output[$fN] = '<strong>Field missing</strong> in database'; 1151 } 1152 } 1153 // Create output: 1154 if (!empty($output)) { 1155 $tRows = []; 1156 foreach ($output as $fN => $state) { 1157 $tRows[] = ' 1158 <tr> 1159 <td>' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fN]['label'])) . ' (' . htmlspecialchars($fN) . ')</td> 1160 <td>' . $state . '</td> 1161 </tr> 1162 '; 1163 } 1164 $output = '<table class="table table-striped table-hover">' . implode('', $tRows) . '</table>'; 1165 } else { 1166 $output = 'Match'; 1167 } 1168 return '<strong class="text-nowrap">[' . htmlspecialchars($table . ':' . $importRecord['uid'] . ' => ' . $databaseRecord['uid']) . ']:</strong> ' . $output; 1169 } 1170 return 'ERROR: One of the inputs were not an array!'; 1171 } 1172 1173 /** 1174 * Creates the original file name for a copy-RTE image (magic type) 1175 * 1176 * @param string $string RTE copy filename, eg. "RTEmagicC_user_pm_icon_01.gif.gif 1177 * @return string|null RTE original filename, eg. "RTEmagicP_user_pm_icon_01.gif". If the input filename was NOT prefixed RTEmagicC_ as RTE images would be, NULL is returned! 1178 */ 1179 public function getRTEoriginalFilename($string) 1180 { 1181 // If "magic image": 1182 if (GeneralUtility::isFirstPartOfStr($string, 'RTEmagicC_')) { 1183 // Find original file: 1184 $pI = pathinfo(substr($string, strlen('RTEmagicC_'))); 1185 $filename = substr($pI['basename'], 0, -strlen('.' . $pI['extension'])); 1186 $origFilePath = 'RTEmagicP_' . $filename; 1187 return $origFilePath; 1188 } 1189 return null; 1190 } 1191 1192 /** 1193 * Returns file processing object, initialized only once. 1194 * 1195 * @return ExtendedFileUtility File processor object 1196 */ 1197 public function getFileProcObj() 1198 { 1199 if ($this->fileProcObj === null) { 1200 $this->fileProcObj = GeneralUtility::makeInstance(ExtendedFileUtility::class); 1201 $this->fileProcObj->setActionPermissions(); 1202 } 1203 return $this->fileProcObj; 1204 } 1205 1206 /** 1207 * Call Hook 1208 * 1209 * @param string $name Name of the hook 1210 * @param array $params Array with params 1211 */ 1212 public function callHook($name, $params) 1213 { 1214 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/impexp/class.tx_impexp.php'][$name] ?? [] as $hook) { 1215 GeneralUtility::callUserFunction($hook, $params, $this); 1216 } 1217 } 1218 1219 /** 1220 * Set flag to control whether disabled records and their children are excluded (true) or included (false). Defaults 1221 * to the old behaviour of including everything. 1222 * 1223 * @param bool $excludeDisabledRecords Set to true if if all disabled records should be excluded, false otherwise 1224 * @return \TYPO3\CMS\Impexp\ImportExport $this for fluent calls 1225 */ 1226 public function setExcludeDisabledRecords($excludeDisabledRecords = false) 1227 { 1228 $this->excludeDisabledRecords = $excludeDisabledRecords; 1229 return $this; 1230 } 1231 1232 /***************************** 1233 * Error handling 1234 *****************************/ 1235 1236 /** 1237 * Sets error message in the internal error log 1238 * 1239 * @param string $msg Error message 1240 */ 1241 public function error($msg) 1242 { 1243 $this->errorLog[] = $msg; 1244 } 1245 1246 /** 1247 * Returns a table with the error-messages. 1248 * 1249 * @return string HTML print of error log 1250 */ 1251 public function printErrorLog() 1252 { 1253 return !empty($this->errorLog) ? DebugUtility::viewArray($this->errorLog) : ''; 1254 } 1255 1256 /** 1257 * @return BackendUserAuthentication 1258 */ 1259 protected function getBackendUser() 1260 { 1261 return $GLOBALS['BE_USER']; 1262 } 1263 1264 /** 1265 * @return LanguageService 1266 */ 1267 protected function getLanguageService() 1268 { 1269 return $GLOBALS['LANG']; 1270 } 1271} 1272