[BUGFIX] Cast uid from database to int in TableWizard
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / Wizard / TableController.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Backend\Controller\Wizard;
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
18 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
20 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
21 use TYPO3\CMS\Backend\Template\ModuleTemplate;
22 use TYPO3\CMS\Backend\Utility\BackendUtility;
23 use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
24 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
25 use TYPO3\CMS\Core\DataHandling\DataHandler;
26 use TYPO3\CMS\Core\Http\HtmlResponse;
27 use TYPO3\CMS\Core\Http\RedirectResponse;
28 use TYPO3\CMS\Core\Imaging\Icon;
29 use TYPO3\CMS\Core\Imaging\IconFactory;
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31 use TYPO3\CMS\Core\Utility\HttpUtility;
32 use TYPO3\CMS\Core\Utility\MathUtility;
33
34 /**
35 * Script Class for rendering the Table Wizard
36 */
37 class TableController extends AbstractWizardController
38 {
39 use PublicPropertyDeprecationTrait;
40
41 /**
42 * Properties which have been moved to protected status from public
43 *
44 * @var array
45 */
46 protected $deprecatedPublicProperties = [
47 'content' => 'Using $content of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
48 'inputStyle' => 'Using $inputStyle of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
49 'xmlStorage' => 'Using $xmlStorage of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
50 'numNewRows' => 'Using $numNewRows of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
51 'colsFieldName' => 'Using $colsFieldName of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
52 'P' => 'Using $P of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
53 'TABLECFG' => 'Using $TABLECFG of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
54 'tableParsing_quote' => 'Using $tableParsing_quote of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
55 'tableParsing_delimiter' => 'Using $tableParsing_delimiter of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
56 ];
57
58 /**
59 * Content accumulation for the module.
60 *
61 * @var string
62 */
63 protected $content;
64
65 /**
66 * If TRUE, <input> fields are shown instead of textareas.
67 *
68 * @var bool
69 */
70 protected $inputStyle = false;
71
72 /**
73 * If set, the string version of the content is interpreted/written as XML
74 * instead of the original line-based kind. This variable still needs binding
75 * to the wizard parameters - but support is ready!
76 *
77 * @var int
78 */
79 protected $xmlStorage = 0;
80
81 /**
82 * Number of new rows to add in bottom of wizard
83 *
84 * @var int
85 */
86 protected $numNewRows = 1;
87
88 /**
89 * Name of field in parent record which MAY contain the number of columns for the table
90 * here hardcoded to the value of tt_content. Should be set by FormEngine parameters (from P)
91 *
92 * @var string
93 */
94 protected $colsFieldName = 'cols';
95
96 /**
97 * Wizard parameters, coming from FormEngine linking to the wizard.
98 *
99 * @var array
100 */
101 protected $P;
102
103 /**
104 * The array which is constantly submitted by the multidimensional form of this wizard.
105 *
106 * @var array
107 */
108 protected $TABLECFG;
109
110 /**
111 * Table parsing
112 * quoting of table cells
113 *
114 * @var string
115 */
116 protected $tableParsing_quote;
117
118 /**
119 * delimiter between table cells
120 *
121 * @var string
122 */
123 protected $tableParsing_delimiter;
124
125 /**
126 * @var IconFactory
127 */
128 protected $iconFactory;
129
130 /**
131 * ModuleTemplate object
132 *
133 * @var ModuleTemplate
134 */
135 protected $moduleTemplate;
136
137 /**
138 * Constructor
139 */
140 public function __construct()
141 {
142 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
143 $this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_wizards.xlf');
144 $GLOBALS['SOBE'] = $this;
145
146 // @deprecated since v9, will be moved out of __construct() in v10
147 $this->init($GLOBALS['TYPO3_REQUEST']);
148 }
149
150 /**
151 * Injects the request object for the current request or subrequest
152 * As this controller goes only through the main() method, it is rather simple for now
153 *
154 * @param ServerRequestInterface $request
155 * @return ResponseInterface
156 */
157 public function mainAction(ServerRequestInterface $request): ResponseInterface
158 {
159 $response = $this->renderContent($request);
160
161 if (empty($response)) {
162 $response = new HtmlResponse($this->moduleTemplate->renderContent());
163 }
164
165 return $response;
166 }
167
168 /**
169 * Main function, rendering the table wizard
170 *
171 * @deprecated since v9, will be removed in v10
172 */
173 public function main()
174 {
175 trigger_error('Method main() will be replaced by protected method renderContent() in v10. Do not call from other extensions', E_USER_DEPRECATED);
176
177 $response = $this->renderContent($GLOBALS['TYPO3_REQUEST']);
178
179 if ($response instanceof RedirectResponse) {
180 HttpUtility::redirect($response->getHeaders()['location'][0]);
181 }
182 }
183
184 /**
185 * Draws the table wizard content
186 *
187 * @return string HTML content for the form.
188 * @throws \RuntimeException
189 *
190 * @deprecated since v9, will be removed in v10
191 */
192 public function tableWizard()
193 {
194 trigger_error('Method tableWizard() will be replaced by protected method renderTableWizard() in v10. Do not call from other extensions', E_USER_DEPRECATED);
195
196 $result = $this->renderTableWizard($GLOBALS['TYPO3_REQUEST']);
197
198 if ($result instanceof RedirectResponse) {
199 HttpUtility::redirect($result->getHeaders()['location'][0]);
200 }
201
202 return $result;
203 }
204
205 /**
206 * Will get and return the configuration code string
207 * Will also save (and possibly redirect/exit) the content if a save button has been pressed
208 *
209 * @param array $row Current parent record row
210 * @return array Table config code in an array
211 *
212 * @deprecated since v9, will be removed in v10
213 */
214 public function getConfigCode($row)
215 {
216 trigger_error('Method getConfigCode() will be replaced by protected method getConfiguration() in v10. Do not call from other extensions', E_USER_DEPRECATED);
217
218 $result = $this->getConfiguration($row, $GLOBALS['TYPO3_REQUEST']);
219
220 if ($result instanceof RedirectResponse) {
221 HttpUtility::redirect($result->getHeaders()['location'][0]);
222 }
223
224 return $result;
225 }
226
227 /**
228 * Creates the HTML for the Table Wizard:
229 *
230 * @param array $configuration Table config array
231 * @return string HTML for the table wizard
232 * @internal
233 *
234 * @deprecated since v9, will be removed in v10
235 */
236 public function getTableHTML($configuration)
237 {
238 trigger_error('Method getTableHTML() will be replaced by protected method getTableWizard() in v10. Do not call from other extensions', E_USER_DEPRECATED);
239 return $this->getTableWizard($configuration);
240 }
241
242 /**
243 * Detects if a control button (up/down/around/delete) has been pressed for an item and accordingly it will
244 * manipulate the internal TABLECFG array
245 *
246 * @internal
247 *
248 * @deprecated since v9, will be removed in v10
249 */
250 public function changeFunc()
251 {
252 trigger_error('Method changeFunc() will be replaced by protected method manipulateTable() in v10. Do not call from other extensions', E_USER_DEPRECATED);
253 $this->manipulateTable();
254 }
255
256 /**
257 * Converts the input array to a configuration code string
258 *
259 * @param array $cfgArr Array of table configuration (follows the input structure from the table wizard POST form)
260 * @return string The array converted into a string with line-based configuration.
261 * @see cfgString2CfgArray()
262 *
263 * @deprecated since v9, will be removed in v10
264 */
265 public function cfgArray2CfgString($cfgArr)
266 {
267 trigger_error('Method cfgArray2CfgString() will be replaced by protected method configurationArrayToString() in v10. Do not call from other extensions', E_USER_DEPRECATED);
268 return $this->configurationArrayToString($cfgArr);
269 }
270
271 /**
272 * Converts the input configuration code string into an array
273 *
274 * @param string $configurationCode Configuration code
275 * @param int $columns Default number of columns
276 * @return array Configuration array
277 * @see cfgArray2CfgString()
278 *
279 * @deprecated since v9, will be removed in v10
280 */
281 public function cfgString2CfgArray($configurationCode, $columns)
282 {
283 trigger_error('Method cfgString2CfgArray() will be replaced by protected method configurationStringToArray() in v10. Do not call from other extensions', E_USER_DEPRECATED);
284 return $this->configurationStringToArray($configurationCode, $columns);
285 }
286
287 /**
288 * Initialization of the class
289 *
290 * @param ServerRequestInterface $request
291 */
292 protected function init(ServerRequestInterface $request): void
293 {
294 $parsedBody = $request->getParsedBody();
295 $queryParams = $request->getQueryParams();
296 // GPvars:
297 $this->P = $parsedBody['P'] ?? $queryParams['P'] ?? null;
298 $this->TABLECFG = $parsedBody['TABLE'] ?? $queryParams['TABLE'] ?? null;
299 // Setting options:
300 $this->xmlStorage = $this->P['params']['xmlOutput'];
301 $this->numNewRows = MathUtility::forceIntegerInRange($this->P['params']['numNewRows'], 1, 50, 5);
302 // Textareas or input fields:
303 $this->inputStyle = isset($this->TABLECFG['textFields']) ? (bool)$this->TABLECFG['textFields'] : true;
304 $this->tableParsing_delimiter = '|';
305 $this->tableParsing_quote = '';
306 }
307
308 /**
309 * Main function, rendering the table wizard
310 *
311 * @param ServerRequestInterface $request
312 * @return ResponseInterface|null
313 */
314 protected function renderContent(ServerRequestInterface $request): ?ResponseInterface
315 {
316 $normalizedParams = $request->getAttribute('normalizedParams');
317 $requestUri = $normalizedParams->getRequestUri();
318 list($rUri) = explode('#', $requestUri);
319 $this->content .= '<form action="' . htmlspecialchars($rUri) . '" method="post" id="TableController" name="wizardForm">';
320 if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
321 $tableWizard = $this->renderTableWizard($request);
322
323 if ($tableWizard instanceof RedirectResponse) {
324 return $tableWizard;
325 }
326
327 $this->content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>'
328 . '<div>' . $tableWizard . '</div>';
329 } else {
330 $this->content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>'
331 . '<div><span class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('table_noData')) . '</span></div>';
332 }
333 $this->content .= '</form>';
334 // Setting up the buttons and markers for docHeader
335 $this->getButtons();
336 // Build the <body> for the module
337 $this->moduleTemplate->setContent($this->content);
338
339 return null;
340 }
341
342 /**
343 * Create the panel of buttons for submitting the form or otherwise perform operations.
344 */
345 protected function getButtons(): void
346 {
347 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
348 if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
349 // CSH
350 $cshButton = $buttonBar->makeHelpButton()
351 ->setModuleName('xMOD_csh_corebe')
352 ->setFieldName('wizard_table_wiz');
353 $buttonBar->addButton($cshButton);
354 // Close
355 $closeButton = $buttonBar->makeLinkButton()
356 ->setHref($this->P['returnUrl'])
357 ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
358 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL));
359 $buttonBar->addButton($closeButton);
360 // Save
361 $saveButton = $buttonBar->makeInputButton()
362 ->setName('_savedok')
363 ->setValue('1')
364 ->setForm('TableController')
365 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
366 ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'));
367 // Save & Close
368 $saveAndCloseButton = $buttonBar->makeInputButton()
369 ->setName('_saveandclosedok')
370 ->setValue('1')
371 ->setForm('TableController')
372 ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.saveCloseDoc'))
373 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
374 'actions-document-save-close',
375 Icon::SIZE_SMALL
376 ));
377 $splitButtonElement = $buttonBar->makeSplitButton()
378 ->addItem($saveButton)
379 ->addItem($saveAndCloseButton);
380
381 $buttonBar->addButton($splitButtonElement, ButtonBar::BUTTON_POSITION_LEFT, 3);
382 // Reload
383 $reloadButton = $buttonBar->makeInputButton()
384 ->setName('_refresh')
385 ->setValue('1')
386 ->setForm('TableController')
387 ->setTitle($this->getLanguageService()->getLL('forms_refresh'))
388 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-refresh', Icon::SIZE_SMALL));
389 $buttonBar->addButton($reloadButton);
390 }
391 }
392
393 /**
394 * Draws the table wizard content
395 *
396 * @param ServerRequestInterface $request
397 * @return string|ResponseInterface HTML content for the form.
398 * @throws \RuntimeException
399 */
400 protected function renderTableWizard(ServerRequestInterface $request)
401 {
402 if (!$this->checkEditAccess($this->P['table'], $this->P['uid'])) {
403 throw new \RuntimeException('Wizard Error: No access', 1349692692);
404 }
405 // First, check the references by selecting the record:
406 $row = BackendUtility::getRecord($this->P['table'], $this->P['uid']);
407 if (!is_array($row)) {
408 throw new \RuntimeException('Wizard Error: No reference to record', 1294587125);
409 }
410 // This will get the content of the form configuration code field to us - possibly cleaned up,
411 // saved to database etc. if the form has been submitted in the meantime.
412 $tableCfgArray = $this->getConfiguration($row, $request);
413
414 if ($tableCfgArray instanceof ResponseInterface) {
415 return $tableCfgArray;
416 }
417
418 // Generation of the Table Wizards HTML code:
419 $content = $this->getTableWizard($tableCfgArray);
420 // Return content:
421 return $content;
422 }
423
424 /**
425 * Will get and return the configuration code string
426 * Will also save (and possibly redirect/exit) the content if a save button has been pressed
427 *
428 * @param array $row Current parent record row
429 * @param ServerRequestInterface $request
430 * @return array|ResponseInterface Table config code in an array
431 */
432 protected function getConfiguration(array $row, ServerRequestInterface $request)
433 {
434 // Get delimiter settings
435 $this->tableParsing_quote = $row['table_enclosure'] ? chr((int)$row['table_enclosure']) : '';
436 $this->tableParsing_delimiter = $row['table_delimiter'] ? chr((int)$row['table_delimiter']) : '|';
437 // If some data has been submitted, then construct
438 if (isset($this->TABLECFG['c'])) {
439 // Process incoming:
440 $this->manipulateTable();
441 // Convert to string (either line based or XML):
442 if ($this->xmlStorage) {
443 // Convert the input array to XML:
444 $bodyText = '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>' . LF . GeneralUtility::array2xml($this->TABLECFG['c'], '', 0, 'T3TableWizard');
445 // Setting cfgArr directly from the input:
446 $configuration = $this->TABLECFG['c'];
447 } else {
448 // Convert the input array to a string of configuration code:
449 $bodyText = $this->configurationArrayToString($this->TABLECFG['c']);
450 // Create cfgArr from the string based configuration - that way it is cleaned up
451 // and any incompatibilities will be removed!
452 $configuration = $this->configurationStringToArray($bodyText, (int)$row[$this->colsFieldName]);
453 }
454 // If a save button has been pressed, then save the new field content:
455 if ($_POST['_savedok'] || $_POST['_saveandclosedok']) {
456 // Get DataHandler object:
457 /** @var DataHandler $dataHandler */
458 $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
459 // Put content into the data array:
460 $data = [];
461 if ($this->P['flexFormPath']) {
462 // Current value of flexForm path:
463 $currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]);
464 /** @var FlexFormTools $flexFormTools */
465 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
466 $flexFormTools->setArrayValueByPath($this->P['flexFormPath'], $currentFlexFormData, $bodyText);
467 $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $currentFlexFormData;
468 } else {
469 $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $bodyText;
470 }
471 // Perform the update:
472 $dataHandler->start($data, []);
473 $dataHandler->process_datamap();
474 // If the save/close button was pressed, then redirect the screen:
475 if ($_POST['_saveandclosedok']) {
476 return new RedirectResponse(GeneralUtility::sanitizeLocalUrl($this->P['returnUrl']));
477 }
478 }
479 } else {
480 // If nothing has been submitted, load the $bodyText variable from the selected database row:
481 if ($this->xmlStorage) {
482 $configuration = GeneralUtility::xml2array($row[$this->P['field']]);
483 } else {
484 if ($this->P['flexFormPath']) {
485 // Current value of flexForm path:
486 $currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]);
487 /** @var FlexFormTools $flexFormTools */
488 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
489 $configuration = $flexFormTools->getArrayValueByPath(
490 $this->P['flexFormPath'],
491 $currentFlexFormData
492 );
493 $configuration = $this->configurationStringToArray($configuration, 0);
494 } else {
495 // Regular line based table configuration:
496 $columns = $row[$this->colsFieldName] ?? 0;
497 $configuration = $this->configurationStringToArray($row[$this->P['field']], (int)$columns);
498 }
499 }
500 $configuration = is_array($configuration) ? $configuration : [];
501 }
502 return $configuration;
503 }
504
505 /**
506 * Creates the HTML for the Table Wizard:
507 *
508 * @param array $configuration Table config array
509 * @return string HTML for the table wizard
510 */
511 protected function getTableWizard(array $configuration): string
512 {
513 // Traverse the rows:
514 $tRows = [];
515 $k = 0;
516 $countLines = count($configuration);
517 foreach ($configuration as $cellArr) {
518 if (is_array($cellArr)) {
519 // Initialize:
520 $cells = [];
521 $a = 0;
522 // Traverse the columns:
523 foreach ($cellArr as $cellContent) {
524 if ($this->inputStyle) {
525 $cells[] = '<input class="form-control" type="text" name="TABLE[c][' . ($k + 1) * 2 . '][' . ($a + 1) * 2 . ']" value="' . htmlspecialchars($cellContent) . '" />';
526 } else {
527 $cellContent = preg_replace('/<br[ ]?[\\/]?>/i', LF, $cellContent);
528 $cells[] = '<textarea class="form-control" rows="6" name="TABLE[c][' . ($k + 1) * 2 . '][' . ($a + 1) * 2 . ']">' . htmlspecialchars($cellContent) . '</textarea>';
529 }
530 // Increment counter:
531 $a++;
532 }
533 // CTRL panel for a table row (move up/down/around):
534 $onClick = 'document.wizardForm.action+=' . GeneralUtility::quoteJSvalue('#ANC_' . (($k + 1) * 2 - 2)) . ';';
535 $onClick = ' onclick="' . htmlspecialchars($onClick) . '"';
536 $ctrl = '';
537 if ($k !== 0) {
538 $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>';
539 } else {
540 $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>';
541 }
542 if ($k + 1 !== $countLines) {
543 $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>';
544 } else {
545 $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>';
546 }
547 $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>';
548 $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>';
549 $tRows[] = '
550 <tr>
551 <td>
552 <a name="ANC_' . ($k + 1) * 2 . '"></a>
553 <span class="btn-group' . ($this->inputStyle ? '' : '-vertical') . '">' . $ctrl . '</span>
554 </td>
555 <td>' . implode('</td>
556 <td>', $cells) . '</td>
557 </tr>';
558 // Increment counter:
559 $k++;
560 }
561 }
562 // CTRL panel for a table column (move left/right/around/delete)
563 $cells = [];
564 $cells[] = '';
565 // Finding first row:
566 $firstRow = reset($configuration);
567 if (is_array($firstRow)) {
568 $cols = count($firstRow);
569 for ($a = 1; $a <= $cols; $a++) {
570 $b = $a * 2;
571 $ctrl = '';
572 if ($a !== 1) {
573 $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>';
574 } else {
575 $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>';
576 }
577 if ($a != $cols) {
578 $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>';
579 } else {
580 $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>';
581 }
582 $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>';
583 $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>';
584 $cells[] = '<span class="btn-group">' . $ctrl . '</span>';
585 }
586 $tRows[] = '
587 <tfoot>
588 <tr>
589 <td>' . implode('</td>
590 <td>', $cells) . '</td>
591 </tr>
592 </tfoot>';
593 }
594 $content = '';
595 $addSubmitOnClick = 'onclick="document.getElementById(\'TableController\').submit();"';
596 // Implode all table rows into a string, wrapped in table tags.
597 $content .= '
598
599 <!-- Table wizard -->
600 <div class="table-fit table-fit-inline-block">
601 <table id="typo3-tablewizard" class="table table-center">
602 ' . implode('', $tRows) . '
603 </table>
604 </div>';
605 // Input type checkbox:
606 $content .= '
607
608 <!-- Input mode check box: -->
609 <div class="checkbox">
610 <input type="hidden" name="TABLE[textFields]" value="0" />
611 <label for="textFields">
612 <input type="checkbox" ' . $addSubmitOnClick . ' name="TABLE[textFields]" id="textFields" value="1"' . ($this->inputStyle ? ' checked="checked"' : '') . ' />
613 ' . $this->getLanguageService()->getLL('table_smallFields') . '
614 </label>
615 </div>';
616 return $content;
617 }
618
619 /**
620 * Detects if a control button (up/down/around/delete) has been pressed for an item and accordingly it will
621 * manipulate the internal TABLECFG array
622 */
623 protected function manipulateTable(): void
624 {
625 if ($this->TABLECFG['col_remove']) {
626 $kk = key($this->TABLECFG['col_remove']);
627 $cmd = 'col_remove';
628 } elseif ($this->TABLECFG['col_add']) {
629 $kk = key($this->TABLECFG['col_add']);
630 $cmd = 'col_add';
631 } elseif ($this->TABLECFG['col_start']) {
632 $kk = key($this->TABLECFG['col_start']);
633 $cmd = 'col_start';
634 } elseif ($this->TABLECFG['col_end']) {
635 $kk = key($this->TABLECFG['col_end']);
636 $cmd = 'col_end';
637 } elseif ($this->TABLECFG['col_left']) {
638 $kk = key($this->TABLECFG['col_left']);
639 $cmd = 'col_left';
640 } elseif ($this->TABLECFG['col_right']) {
641 $kk = key($this->TABLECFG['col_right']);
642 $cmd = 'col_right';
643 } elseif ($this->TABLECFG['row_remove']) {
644 $kk = key($this->TABLECFG['row_remove']);
645 $cmd = 'row_remove';
646 } elseif ($this->TABLECFG['row_add']) {
647 $kk = key($this->TABLECFG['row_add']);
648 $cmd = 'row_add';
649 } elseif ($this->TABLECFG['row_top']) {
650 $kk = key($this->TABLECFG['row_top']);
651 $cmd = 'row_top';
652 } elseif ($this->TABLECFG['row_bottom']) {
653 $kk = key($this->TABLECFG['row_bottom']);
654 $cmd = 'row_bottom';
655 } elseif ($this->TABLECFG['row_up']) {
656 $kk = key($this->TABLECFG['row_up']);
657 $cmd = 'row_up';
658 } elseif ($this->TABLECFG['row_down']) {
659 $kk = key($this->TABLECFG['row_down']);
660 $cmd = 'row_down';
661 } else {
662 $kk = '';
663 $cmd = '';
664 }
665 if ($cmd && MathUtility::canBeInterpretedAsInteger($kk)) {
666 if (strpos($cmd, 'row_') === 0) {
667 switch ($cmd) {
668 case 'row_remove':
669 unset($this->TABLECFG['c'][$kk]);
670 break;
671 case 'row_add':
672 for ($a = 1; $a <= $this->numNewRows; $a++) {
673 // Checking if set: The point is that any new row between existing rows
674 // will be TRUE after one row is added while if rows are added in the bottom
675 // of the table there will be no existing rows to stop the addition of new rows
676 // which means it will add up to $this->numNewRows rows then.
677 if (!isset($this->TABLECFG['c'][$kk + $a])) {
678 $this->TABLECFG['c'][$kk + $a] = [];
679 } else {
680 break;
681 }
682 }
683 break;
684 case 'row_top':
685 $this->TABLECFG['c'][1] = $this->TABLECFG['c'][$kk];
686 unset($this->TABLECFG['c'][$kk]);
687 break;
688 case 'row_bottom':
689 $this->TABLECFG['c'][10000000] = $this->TABLECFG['c'][$kk];
690 unset($this->TABLECFG['c'][$kk]);
691 break;
692 case 'row_up':
693 $this->TABLECFG['c'][$kk - 3] = $this->TABLECFG['c'][$kk];
694 unset($this->TABLECFG['c'][$kk]);
695 break;
696 case 'row_down':
697 $this->TABLECFG['c'][$kk + 3] = $this->TABLECFG['c'][$kk];
698 unset($this->TABLECFG['c'][$kk]);
699 break;
700 }
701 ksort($this->TABLECFG['c']);
702 }
703 if (strpos($cmd, 'col_') === 0) {
704 foreach ($this->TABLECFG['c'] as $cAK => $value) {
705 switch ($cmd) {
706 case 'col_remove':
707 unset($this->TABLECFG['c'][$cAK][$kk]);
708 break;
709 case 'col_add':
710 $this->TABLECFG['c'][$cAK][$kk + 1] = '';
711 break;
712 case 'col_start':
713 $this->TABLECFG['c'][$cAK][1] = $this->TABLECFG['c'][$cAK][$kk];
714 unset($this->TABLECFG['c'][$cAK][$kk]);
715 break;
716 case 'col_end':
717 $this->TABLECFG['c'][$cAK][1000000] = $this->TABLECFG['c'][$cAK][$kk];
718 unset($this->TABLECFG['c'][$cAK][$kk]);
719 break;
720 case 'col_left':
721 $this->TABLECFG['c'][$cAK][$kk - 3] = $this->TABLECFG['c'][$cAK][$kk];
722 unset($this->TABLECFG['c'][$cAK][$kk]);
723 break;
724 case 'col_right':
725 $this->TABLECFG['c'][$cAK][$kk + 3] = $this->TABLECFG['c'][$cAK][$kk];
726 unset($this->TABLECFG['c'][$cAK][$kk]);
727 break;
728 }
729 ksort($this->TABLECFG['c'][$cAK]);
730 }
731 }
732 }
733 // Convert line breaks to <br /> tags:
734 foreach ($this->TABLECFG['c'] as $a => $value) {
735 foreach ($this->TABLECFG['c'][$a] as $b => $value2) {
736 $this->TABLECFG['c'][$a][$b] = str_replace(
737 [CR, LF],
738 ['', '<br />'],
739 $this->TABLECFG['c'][$a][$b]
740 );
741 }
742 }
743 }
744
745 /**
746 * Converts the input array to a configuration code string
747 *
748 * @param array $cfgArr Array of table configuration (follows the input structure from the table wizard POST form)
749 * @return string The array converted into a string with line-based configuration.
750 * @see configurationStringToArray()
751 */
752 protected function configurationArrayToString(array $cfgArr): string
753 {
754 $inLines = [];
755 // Traverse the elements of the table wizard and transform the settings into configuration code.
756 foreach ($cfgArr as $valueA) {
757 $thisLine = [];
758 foreach ($valueA as $valueB) {
759 $thisLine[] = $this->tableParsing_quote
760 . str_replace($this->tableParsing_delimiter, '', $valueB) . $this->tableParsing_quote;
761 }
762 $inLines[] = implode($this->tableParsing_delimiter, $thisLine);
763 }
764 // Finally, implode the lines into a string:
765 return implode(LF, $inLines);
766 }
767
768 /**
769 * Converts the input configuration code string into an array
770 *
771 * @param string $configurationCode Configuration code
772 * @param int $columns Default number of columns
773 * @return array Configuration array
774 * @see configurationArrayToString()
775 */
776 protected function configurationStringToArray(string $configurationCode, int $columns): array
777 {
778 // Explode lines in the configuration code - each line is a table row.
779 $tableLines = explode(LF, $configurationCode);
780 // Setting number of columns
781 // auto...
782 if (!$columns && trim($tableLines[0])) {
783 $columns = count(explode($this->tableParsing_delimiter, $tableLines[0]));
784 }
785 $columns = $columns ?: 4;
786 // Traverse the number of table elements:
787 $configurationArray = [];
788 foreach ($tableLines as $key => $value) {
789 // Initialize:
790 $valueParts = explode($this->tableParsing_delimiter, $value);
791 // Traverse columns:
792 for ($a = 0; $a < $columns; $a++) {
793 if ($this->tableParsing_quote
794 && $valueParts[$a][0] === $this->tableParsing_quote
795 && substr($valueParts[$a], -1, 1) === $this->tableParsing_quote
796 ) {
797 $valueParts[$a] = substr(trim($valueParts[$a]), 1, -1);
798 }
799 $configurationArray[$key][$a] = (string)$valueParts[$a];
800 }
801 }
802 return $configurationArray;
803 }
804 }