1<?php 2 3declare(strict_types=1); 4 5/* 6 * This file is part of the TYPO3 CMS project. 7 * 8 * It is free software; you can redistribute it and/or modify it under 9 * the terms of the GNU General Public License, either version 2 10 * of the License, or any later version. 11 * 12 * For the full copyright and license information, please read the 13 * LICENSE.txt file that was distributed with this source code. 14 * 15 * The TYPO3 project - inspiring people to share! 16 */ 17 18namespace TYPO3\CMS\Backend\Controller\Wizard; 19 20use Psr\Http\Message\ResponseInterface; 21use Psr\Http\Message\ServerRequestInterface; 22use TYPO3\CMS\Backend\Template\Components\ButtonBar; 23use TYPO3\CMS\Backend\Template\ModuleTemplate; 24use TYPO3\CMS\Backend\Utility\BackendUtility; 25use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools; 26use TYPO3\CMS\Core\DataHandling\DataHandler; 27use TYPO3\CMS\Core\Http\HtmlResponse; 28use TYPO3\CMS\Core\Http\RedirectResponse; 29use TYPO3\CMS\Core\Imaging\Icon; 30use TYPO3\CMS\Core\Imaging\IconFactory; 31use TYPO3\CMS\Core\Utility\GeneralUtility; 32use TYPO3\CMS\Core\Utility\MathUtility; 33 34/** 35 * Script Class for rendering the Table Wizard 36 * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API. 37 */ 38class TableController extends AbstractWizardController 39{ 40 /** 41 * If TRUE, <input> fields are shown instead of textareas. 42 * 43 * @var bool 44 */ 45 protected $inputStyle = false; 46 47 /** 48 * If set, the string version of the content is interpreted/written as XML 49 * instead of the original line-based kind. This variable still needs binding 50 * to the wizard parameters - but support is ready! 51 * 52 * @var int 53 */ 54 protected $xmlStorage = 0; 55 56 /** 57 * Number of new rows to add in bottom of wizard 58 * 59 * @var int 60 */ 61 protected $numNewRows = 1; 62 63 /** 64 * Name of field in parent record which MAY contain the number of columns for the table 65 * here hardcoded to the value of tt_content. Should be set by FormEngine parameters (from P) 66 * 67 * @var string 68 */ 69 protected $colsFieldName = 'cols'; 70 71 /** 72 * Wizard parameters, coming from FormEngine linking to the wizard. 73 * 74 * @var array 75 */ 76 protected $P; 77 78 /** 79 * The array which is constantly submitted by the multidimensional form of this wizard. 80 * 81 * @var array 82 */ 83 protected $TABLECFG; 84 85 /** 86 * Table parsing 87 * quoting of table cells 88 * 89 * @var string 90 */ 91 protected $tableParsing_quote; 92 93 /** 94 * delimiter between table cells 95 * 96 * @var string 97 */ 98 protected $tableParsing_delimiter; 99 100 /** 101 * @var IconFactory 102 */ 103 protected $iconFactory; 104 105 /** 106 * ModuleTemplate object 107 * 108 * @var ModuleTemplate 109 */ 110 protected $moduleTemplate; 111 112 /** 113 * Injects the request object for the current request or subrequest 114 * As this controller goes only through the main() method, it is rather simple for now 115 * 116 * @param ServerRequestInterface $request 117 * @return ResponseInterface 118 */ 119 public function mainAction(ServerRequestInterface $request): ResponseInterface 120 { 121 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class); 122 $this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_wizards.xlf'); 123 $this->init($request); 124 125 $normalizedParams = $request->getAttribute('normalizedParams'); 126 $requestUri = $normalizedParams->getRequestUri(); 127 [$rUri] = explode('#', $requestUri); 128 $content = '<form action="' . htmlspecialchars($rUri) . '" method="post" id="TableController" name="wizardForm">'; 129 if ($this->P['table'] && $this->P['field'] && $this->P['uid']) { 130 $tableWizard = $this->renderTableWizard($request); 131 132 if ($tableWizard instanceof RedirectResponse) { 133 return $tableWizard; 134 } 135 136 $content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>' 137 . '<div>' . $tableWizard . '</div>'; 138 } else { 139 $content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>' 140 . '<div><span class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('table_noData')) . '</span></div>'; 141 } 142 $content .= '</form>'; 143 144 // Setting up the buttons and markers for docHeader 145 $this->getButtons(); 146 // Build the <body> for the module 147 $this->moduleTemplate->setContent($content); 148 149 return new HtmlResponse($this->moduleTemplate->renderContent()); 150 } 151 152 /** 153 * Initialization of the class 154 * 155 * @param ServerRequestInterface $request 156 */ 157 protected function init(ServerRequestInterface $request): void 158 { 159 $parsedBody = $request->getParsedBody(); 160 $queryParams = $request->getQueryParams(); 161 // GPvars: 162 $this->P = $parsedBody['P'] ?? $queryParams['P'] ?? null; 163 $this->TABLECFG = $parsedBody['TABLE'] ?? $queryParams['TABLE'] ?? null; 164 // Setting options: 165 $this->xmlStorage = $this->P['params']['xmlOutput']; 166 $this->numNewRows = MathUtility::forceIntegerInRange($this->P['params']['numNewRows'], 1, 50, 5); 167 // Textareas or input fields: 168 $this->inputStyle = isset($this->TABLECFG['textFields']) ? (bool)$this->TABLECFG['textFields'] : true; 169 $this->tableParsing_delimiter = '|'; 170 $this->tableParsing_quote = ''; 171 } 172 173 /** 174 * Create the panel of buttons for submitting the form or otherwise perform operations. 175 */ 176 protected function getButtons(): void 177 { 178 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); 179 if ($this->P['table'] && $this->P['field'] && $this->P['uid']) { 180 // CSH 181 $cshButton = $buttonBar->makeHelpButton() 182 ->setModuleName('xMOD_csh_corebe') 183 ->setFieldName('wizard_table_wiz'); 184 $buttonBar->addButton($cshButton); 185 // Close 186 $closeButton = $buttonBar->makeLinkButton() 187 ->setHref($this->P['returnUrl']) 188 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL)) 189 ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc')) 190 ->setShowLabelText(true); 191 $buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 1); 192 // Save 193 $saveButton = $buttonBar->makeInputButton() 194 ->setName('_savedok') 195 ->setValue('1') 196 ->setForm('TableController') 197 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL)) 198 ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.saveDoc')) 199 ->setShowLabelText(true); 200 $buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 2); 201 // Reload 202 $reloadButton = $buttonBar->makeInputButton() 203 ->setName('_refresh') 204 ->setValue('1') 205 ->setForm('TableController') 206 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-refresh', Icon::SIZE_SMALL)) 207 ->setTitle($this->getLanguageService()->getLL('forms_refresh')); 208 $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT); 209 } 210 } 211 212 /** 213 * Draws the table wizard content 214 * 215 * @param ServerRequestInterface $request 216 * @return string|ResponseInterface HTML content for the form. 217 * @throws \RuntimeException 218 */ 219 protected function renderTableWizard(ServerRequestInterface $request) 220 { 221 if (!$this->checkEditAccess($this->P['table'], $this->P['uid'])) { 222 throw new \RuntimeException('Wizard Error: No access', 1349692692); 223 } 224 // First, check the references by selecting the record: 225 $row = BackendUtility::getRecord($this->P['table'], $this->P['uid']); 226 if (!is_array($row)) { 227 throw new \RuntimeException('Wizard Error: No reference to record', 1294587125); 228 } 229 // This will get the content of the form configuration code field to us - possibly cleaned up, 230 // saved to database etc. if the form has been submitted in the meantime. 231 $tableCfgArray = $this->getConfiguration($row, $request); 232 233 if ($tableCfgArray instanceof ResponseInterface) { 234 return $tableCfgArray; 235 } 236 237 // Generation of the Table Wizards HTML code: 238 $content = $this->getTableWizard($tableCfgArray); 239 // Return content: 240 return $content; 241 } 242 243 /** 244 * Will get and return the configuration code string 245 * Will also save (and possibly redirect/exit) the content if a save button has been pressed 246 * 247 * @param array $row Current parent record row 248 * @param ServerRequestInterface $request 249 * @return array|ResponseInterface Table config code in an array 250 */ 251 protected function getConfiguration(array $row, ServerRequestInterface $request) 252 { 253 // Get delimiter settings 254 $this->tableParsing_quote = $row['table_enclosure'] ? chr((int)$row['table_enclosure']) : ''; 255 $this->tableParsing_delimiter = $row['table_delimiter'] ? chr((int)$row['table_delimiter']) : '|'; 256 // If some data has been submitted, then construct 257 if (isset($this->TABLECFG['c'])) { 258 // Process incoming: 259 $this->manipulateTable(); 260 // Convert to string (either line based or XML): 261 if ($this->xmlStorage) { 262 // Convert the input array to XML: 263 $bodyText = '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>' . LF . GeneralUtility::array2xml($this->TABLECFG['c'], '', 0, 'T3TableWizard'); 264 // Setting cfgArr directly from the input: 265 $configuration = $this->TABLECFG['c']; 266 } else { 267 // Convert the input array to a string of configuration code: 268 $bodyText = $this->configurationArrayToString($this->TABLECFG['c']); 269 // Create cfgArr from the string based configuration - that way it is cleaned up 270 // and any incompatibilities will be removed! 271 $configuration = $this->configurationStringToArray($bodyText, (int)$row[$this->colsFieldName]); 272 } 273 // If a save button has been pressed, then save the new field content: 274 if ($_POST['_savedok'] || $_POST['_saveandclosedok']) { 275 // Get DataHandler object: 276 /** @var DataHandler $dataHandler */ 277 $dataHandler = GeneralUtility::makeInstance(DataHandler::class); 278 // Put content into the data array: 279 $data = []; 280 if ($this->P['flexFormPath']) { 281 // Current value of flexForm path: 282 $currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]); 283 /** @var FlexFormTools $flexFormTools */ 284 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class); 285 $flexFormTools->setArrayValueByPath($this->P['flexFormPath'], $currentFlexFormData, $bodyText); 286 $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $currentFlexFormData; 287 } else { 288 $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $bodyText; 289 } 290 // Perform the update: 291 $dataHandler->start($data, []); 292 $dataHandler->process_datamap(); 293 // If the save/close button was pressed, then redirect the screen: 294 if ($_POST['_saveandclosedok']) { 295 return new RedirectResponse(GeneralUtility::sanitizeLocalUrl($this->P['returnUrl'])); 296 } 297 } 298 } else { 299 // If nothing has been submitted, load the $bodyText variable from the selected database row: 300 if ($this->xmlStorage) { 301 $configuration = GeneralUtility::xml2array($row[$this->P['field']]); 302 } else { 303 if ($this->P['flexFormPath']) { 304 // Current value of flexForm path: 305 $currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]); 306 /** @var FlexFormTools $flexFormTools */ 307 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class); 308 $configuration = $flexFormTools->getArrayValueByPath( 309 $this->P['flexFormPath'], 310 $currentFlexFormData 311 ); 312 $configuration = $this->configurationStringToArray((string)$configuration, 0); 313 } else { 314 // Regular line based table configuration: 315 $columns = $row[$this->colsFieldName] ?? 0; 316 $configuration = $this->configurationStringToArray((string)($row[$this->P['field']] ?? ''), (int)$columns); 317 } 318 } 319 $configuration = is_array($configuration) ? $configuration : []; 320 } 321 return $configuration; 322 } 323 324 /** 325 * Creates the HTML for the Table Wizard: 326 * 327 * @param array $configuration Table config array 328 * @return string HTML for the table wizard 329 */ 330 protected function getTableWizard(array $configuration): string 331 { 332 // Traverse the rows: 333 $tRows = []; 334 $k = 0; 335 $countLines = count($configuration); 336 foreach ($configuration as $cellArr) { 337 if (is_array($cellArr)) { 338 // Initialize: 339 $cells = []; 340 $a = 0; 341 // Traverse the columns: 342 foreach ($cellArr as $cellContent) { 343 if ($this->inputStyle) { 344 $cells[] = '<input class="form-control" type="text" name="TABLE[c][' . ($k + 1) * 2 . '][' . ($a + 1) * 2 . ']" value="' . htmlspecialchars($cellContent) . '" />'; 345 } else { 346 $cellContent = preg_replace('/<br[ ]?[\\/]?>/i', LF, $cellContent); 347 $cells[] = '<textarea class="form-control" rows="6" name="TABLE[c][' . ($k + 1) * 2 . '][' . ($a + 1) * 2 . ']">' . htmlspecialchars($cellContent) . '</textarea>'; 348 } 349 // Increment counter: 350 $a++; 351 } 352 // CTRL panel for a table row (move up/down/around): 353 $onClick = 'document.wizardForm.action+=' . GeneralUtility::quoteJSvalue('#ANC_' . (($k + 1) * 2 - 2)) . ';'; 354 $onClick = ' onclick="' . htmlspecialchars($onClick) . '"'; 355 $ctrl = ''; 356 if ($k !== 0) { 357 $ctrl .= '<button class="btn btn-default" name="TABLE[row_up][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_up')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-up"></span></button>'; 358 } else { 359 $ctrl .= '<button class="btn btn-default" name="TABLE[row_bottom][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_bottom')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-double-down"></span></button>'; 360 } 361 if ($k + 1 !== $countLines) { 362 $ctrl .= '<button class="btn btn-default" name="TABLE[row_down][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_down')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-down"></span></button>'; 363 } else { 364 $ctrl .= '<button class="btn btn-default" name="TABLE[row_top][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_top')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-double-up"></span></button>'; 365 } 366 $ctrl .= '<button class="btn btn-default" name="TABLE[row_remove][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_removeRow')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-trash"></span></button>'; 367 $ctrl .= '<button class="btn btn-default" name="TABLE[row_add][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_addRow')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-plus"></span></button>'; 368 $tRows[] = ' 369 <tr> 370 <td> 371 <a name="ANC_' . ($k + 1) * 2 . '"></a> 372 <span class="btn-group' . ($this->inputStyle ? '' : '-vertical') . '">' . $ctrl . '</span> 373 </td> 374 <td>' . implode('</td> 375 <td>', $cells) . '</td> 376 </tr>'; 377 // Increment counter: 378 $k++; 379 } 380 } 381 // CTRL panel for a table column (move left/right/around/delete) 382 $cells = []; 383 $cells[] = ''; 384 // Finding first row: 385 $firstRow = reset($configuration); 386 if (is_array($firstRow)) { 387 $cols = count($firstRow); 388 for ($a = 1; $a <= $cols; $a++) { 389 $b = $a * 2; 390 $ctrl = ''; 391 if ($a !== 1) { 392 $ctrl .= '<button class="btn btn-default" name="TABLE[col_left][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_left')) . '"><span class="t3-icon fa fa-fw fa-angle-left"></span></button>'; 393 } else { 394 $ctrl .= '<button class="btn btn-default" name="TABLE[col_end][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_end')) . '"><span class="t3-icon fa fa-fw fa-angle-double-right"></span></button>'; 395 } 396 if ($a != $cols) { 397 $ctrl .= '<button class="btn btn-default" name="TABLE[col_right][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_right')) . '"><span class="t3-icon fa fa-fw fa-angle-right"></span></button>'; 398 } else { 399 $ctrl .= '<button class="btn btn-default" name="TABLE[col_start][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_start')) . '"><span class="t3-icon fa fa-fw fa-angle-double-left"></span></button>'; 400 } 401 $ctrl .= '<button class="btn btn-default" name="TABLE[col_remove][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_removeColumn')) . '"><span class="t3-icon fa fa-fw fa-trash"></span></button>'; 402 $ctrl .= '<button class="btn btn-default" name="TABLE[col_add][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_addColumn')) . '"><span class="t3-icon fa fa-fw fa-plus"></span></button>'; 403 $cells[] = '<span class="btn-group">' . $ctrl . '</span>'; 404 } 405 $tRows[] = ' 406 <tfoot> 407 <tr> 408 <td>' . implode('</td> 409 <td>', $cells) . '</td> 410 </tr> 411 </tfoot>'; 412 } 413 $content = ''; 414 // Implode all table rows into a string, wrapped in table tags. 415 $content .= ' 416 417 <!-- Table wizard --> 418 <div class="table-fit table-fit-inline-block"> 419 <table id="typo3-tablewizard" class="table table-center"> 420 ' . implode('', $tRows) . ' 421 </table> 422 </div>'; 423 // Input type checkbox: 424 $content .= ' 425 426 <!-- Input mode check box: --> 427 <div class="checkbox"> 428 <input type="hidden" name="TABLE[textFields]" value="0" /> 429 <label for="textFields"> 430 <input type="checkbox" data-global-event="change" data-action-submit="$form" name="TABLE[textFields]" id="textFields" value="1"' . ($this->inputStyle ? ' checked="checked"' : '') . ' /> 431 ' . $this->getLanguageService()->getLL('table_smallFields') . ' 432 </label> 433 </div>'; 434 return $content; 435 } 436 437 /** 438 * Detects if a control button (up/down/around/delete) has been pressed for an item and accordingly it will 439 * manipulate the internal TABLECFG array 440 */ 441 protected function manipulateTable(): void 442 { 443 if ($this->TABLECFG['col_remove']) { 444 $kk = key($this->TABLECFG['col_remove']); 445 $cmd = 'col_remove'; 446 } elseif ($this->TABLECFG['col_add']) { 447 $kk = key($this->TABLECFG['col_add']); 448 $cmd = 'col_add'; 449 } elseif ($this->TABLECFG['col_start']) { 450 $kk = key($this->TABLECFG['col_start']); 451 $cmd = 'col_start'; 452 } elseif ($this->TABLECFG['col_end']) { 453 $kk = key($this->TABLECFG['col_end']); 454 $cmd = 'col_end'; 455 } elseif ($this->TABLECFG['col_left']) { 456 $kk = key($this->TABLECFG['col_left']); 457 $cmd = 'col_left'; 458 } elseif ($this->TABLECFG['col_right']) { 459 $kk = key($this->TABLECFG['col_right']); 460 $cmd = 'col_right'; 461 } elseif ($this->TABLECFG['row_remove']) { 462 $kk = key($this->TABLECFG['row_remove']); 463 $cmd = 'row_remove'; 464 } elseif ($this->TABLECFG['row_add']) { 465 $kk = key($this->TABLECFG['row_add']); 466 $cmd = 'row_add'; 467 } elseif ($this->TABLECFG['row_top']) { 468 $kk = key($this->TABLECFG['row_top']); 469 $cmd = 'row_top'; 470 } elseif ($this->TABLECFG['row_bottom']) { 471 $kk = key($this->TABLECFG['row_bottom']); 472 $cmd = 'row_bottom'; 473 } elseif ($this->TABLECFG['row_up']) { 474 $kk = key($this->TABLECFG['row_up']); 475 $cmd = 'row_up'; 476 } elseif ($this->TABLECFG['row_down']) { 477 $kk = key($this->TABLECFG['row_down']); 478 $cmd = 'row_down'; 479 } else { 480 $kk = ''; 481 $cmd = ''; 482 } 483 if ($cmd && MathUtility::canBeInterpretedAsInteger($kk)) { 484 if (strpos($cmd, 'row_') === 0) { 485 switch ($cmd) { 486 case 'row_remove': 487 unset($this->TABLECFG['c'][$kk]); 488 break; 489 case 'row_add': 490 for ($a = 1; $a <= $this->numNewRows; $a++) { 491 // Checking if set: The point is that any new row between existing rows 492 // will be TRUE after one row is added while if rows are added in the bottom 493 // of the table there will be no existing rows to stop the addition of new rows 494 // which means it will add up to $this->numNewRows rows then. 495 if (!isset($this->TABLECFG['c'][$kk + $a])) { 496 $this->TABLECFG['c'][$kk + $a] = []; 497 } else { 498 break; 499 } 500 } 501 break; 502 case 'row_top': 503 $this->TABLECFG['c'][1] = $this->TABLECFG['c'][$kk]; 504 unset($this->TABLECFG['c'][$kk]); 505 break; 506 case 'row_bottom': 507 $this->TABLECFG['c'][10000000] = $this->TABLECFG['c'][$kk]; 508 unset($this->TABLECFG['c'][$kk]); 509 break; 510 case 'row_up': 511 $this->TABLECFG['c'][$kk - 3] = $this->TABLECFG['c'][$kk]; 512 unset($this->TABLECFG['c'][$kk]); 513 break; 514 case 'row_down': 515 $this->TABLECFG['c'][$kk + 3] = $this->TABLECFG['c'][$kk]; 516 unset($this->TABLECFG['c'][$kk]); 517 break; 518 } 519 ksort($this->TABLECFG['c']); 520 } 521 if (strpos($cmd, 'col_') === 0) { 522 foreach ($this->TABLECFG['c'] as $cAK => $value) { 523 switch ($cmd) { 524 case 'col_remove': 525 unset($this->TABLECFG['c'][$cAK][$kk]); 526 break; 527 case 'col_add': 528 $this->TABLECFG['c'][$cAK][$kk + 1] = ''; 529 break; 530 case 'col_start': 531 $this->TABLECFG['c'][$cAK][1] = $this->TABLECFG['c'][$cAK][$kk]; 532 unset($this->TABLECFG['c'][$cAK][$kk]); 533 break; 534 case 'col_end': 535 $this->TABLECFG['c'][$cAK][1000000] = $this->TABLECFG['c'][$cAK][$kk]; 536 unset($this->TABLECFG['c'][$cAK][$kk]); 537 break; 538 case 'col_left': 539 $this->TABLECFG['c'][$cAK][$kk - 3] = $this->TABLECFG['c'][$cAK][$kk]; 540 unset($this->TABLECFG['c'][$cAK][$kk]); 541 break; 542 case 'col_right': 543 $this->TABLECFG['c'][$cAK][$kk + 3] = $this->TABLECFG['c'][$cAK][$kk]; 544 unset($this->TABLECFG['c'][$cAK][$kk]); 545 break; 546 } 547 ksort($this->TABLECFG['c'][$cAK]); 548 } 549 } 550 } 551 // Convert line breaks to <br /> tags: 552 foreach ($this->TABLECFG['c'] as $a => $value) { 553 foreach ($this->TABLECFG['c'][$a] as $b => $value2) { 554 $this->TABLECFG['c'][$a][$b] = str_replace( 555 [CR, LF], 556 ['', '<br />'], 557 $this->TABLECFG['c'][$a][$b] 558 ); 559 } 560 } 561 } 562 563 /** 564 * Converts the input array to a configuration code string 565 * 566 * @param array $cfgArr Array of table configuration (follows the input structure from the table wizard POST form) 567 * @return string The array converted into a string with line-based configuration. 568 * @see configurationStringToArray() 569 */ 570 protected function configurationArrayToString(array $cfgArr): string 571 { 572 $inLines = []; 573 // Traverse the elements of the table wizard and transform the settings into configuration code. 574 foreach ($cfgArr as $valueA) { 575 $thisLine = []; 576 foreach ($valueA as $valueB) { 577 $thisLine[] = $this->tableParsing_quote 578 . str_replace($this->tableParsing_delimiter, '', $valueB) . $this->tableParsing_quote; 579 } 580 $inLines[] = implode($this->tableParsing_delimiter, $thisLine); 581 } 582 // Finally, implode the lines into a string: 583 return implode(LF, $inLines); 584 } 585 586 /** 587 * Converts the input configuration code string into an array 588 * 589 * @param string $configurationCode Configuration code 590 * @param int $columns Default number of columns 591 * @return array Configuration array 592 * @see configurationArrayToString() 593 */ 594 protected function configurationStringToArray(string $configurationCode, int $columns): array 595 { 596 // Explode lines in the configuration code - each line is a table row. 597 $tableLines = explode(LF, $configurationCode); 598 // Setting number of columns 599 // auto... 600 if (!$columns && trim($tableLines[0])) { 601 $exploded = explode($this->tableParsing_delimiter, $tableLines[0]); 602 $columns = is_array($exploded) ? count($exploded) : 0; 603 } 604 $columns = $columns ?: 4; 605 // Traverse the number of table elements: 606 $configurationArray = []; 607 foreach ($tableLines as $key => $value) { 608 // Initialize: 609 $valueParts = explode($this->tableParsing_delimiter, $value); 610 // Traverse columns: 611 for ($a = 0; $a < $columns; $a++) { 612 if ($this->tableParsing_quote 613 && $valueParts[$a][0] === $this->tableParsing_quote 614 && substr($valueParts[$a], -1, 1) === $this->tableParsing_quote 615 ) { 616 $valueParts[$a] = substr(trim($valueParts[$a]), 1, -1); 617 } 618 $configurationArray[$key][$a] = (string)$valueParts[$a]; 619 } 620 } 621 return $configurationArray; 622 } 623} 624