TableController.php 35.1 KB
Newer Older
1
<?php
2
declare(strict_types = 1);
3
4
namespace TYPO3\CMS\Backend\Controller\Wizard;

5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
9
10
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
11
 *
12
13
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
16
 * The TYPO3 project - inspiring people to share!
 */
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;
Nicole Cordes's avatar
Nicole Cordes committed
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
29
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
Nicole Cordes's avatar
Nicole Cordes committed
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Core\Utility\HttpUtility;
Nicole Cordes's avatar
Nicole Cordes committed
32
33
use TYPO3\CMS\Core\Utility\MathUtility;

34
35
36
/**
 * Script Class for rendering the Table Wizard
 */
37
38
class TableController extends AbstractWizardController
{
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    use PublicPropertyDeprecationTrait;

    /**
     * Properties which have been moved to protected status from public
     *
     * @var array
     */
    protected $deprecatedPublicProperties = [
        'content' => 'Using $content of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
        'inputStyle' => 'Using $inputStyle of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
        'xmlStorage' => 'Using $xmlStorage of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
        'numNewRows' => 'Using $numNewRows of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
        'colsFieldName' => 'Using $colsFieldName of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
        'P' => 'Using $P of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
        'TABLECFG' => 'Using $TABLECFG of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
        'tableParsing_quote' => 'Using $tableParsing_quote of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
        'tableParsing_delimiter' => 'Using $tableParsing_delimiter of class TableController from the outside is discouraged, as this variable is only used for internal storage.',
     ];

58
59
60
61
62
    /**
     * Content accumulation for the module.
     *
     * @var string
     */
63
    protected $content;
64

65
66
67
68
69
    /**
     * If TRUE, <input> fields are shown instead of textareas.
     *
     * @var bool
     */
70
    protected $inputStyle = false;
71

72
73
74
75
76
77
78
    /**
     * If set, the string version of the content is interpreted/written as XML
     * instead of the original line-based kind. This variable still needs binding
     * to the wizard parameters - but support is ready!
     *
     * @var int
     */
79
    protected $xmlStorage = 0;
80

81
82
83
84
85
    /**
     * Number of new rows to add in bottom of wizard
     *
     * @var int
     */
86
    protected $numNewRows = 1;
87

88
89
90
91
92
93
    /**
     * Name of field in parent record which MAY contain the number of columns for the table
     * here hardcoded to the value of tt_content. Should be set by FormEngine parameters (from P)
     *
     * @var string
     */
94
    protected $colsFieldName = 'cols';
95

96
97
98
99
100
    /**
     * Wizard parameters, coming from FormEngine linking to the wizard.
     *
     * @var array
     */
101
    protected $P;
102

103
104
105
106
107
    /**
     * The array which is constantly submitted by the multidimensional form of this wizard.
     *
     * @var array
     */
108
    protected $TABLECFG;
109

110
111
112
113
114
115
    /**
     * Table parsing
     * quoting of table cells
     *
     * @var string
     */
116
    protected $tableParsing_quote;
117

118
119
120
121
122
    /**
     * delimiter between table cells
     *
     * @var string
     */
123
    protected $tableParsing_delimiter;
124

125
126
127
128
    /**
     * @var IconFactory
     */
    protected $iconFactory;
129

130
131
132
133
134
135
136
    /**
     * ModuleTemplate object
     *
     * @var ModuleTemplate
     */
    protected $moduleTemplate;

137
138
139
140
141
    /**
     * Constructor
     */
    public function __construct()
    {
142
        $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
143
        $this->getLanguageService()->includeLLFile('EXT:lang/Resources/Private/Language/locallang_wizards.xlf');
144
        $GLOBALS['SOBE'] = $this;
145

146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
        // @deprecated since v9, will be moved out of __construct() in v10
        $this->init($GLOBALS['TYPO3_REQUEST']);
    }

    /**
     * Injects the request object for the current request or subrequest
     * As this controller goes only through the main() method, it is rather simple for now
     *
     * @param ServerRequestInterface $request
     * @return ResponseInterface
     */
    public function mainAction(ServerRequestInterface $request): ResponseInterface
    {
        $response = $this->renderContent($request);

        if (empty($response)) {
            $response = new HtmlResponse($this->moduleTemplate->renderContent());
        }

        return $response;
    }

    /**
     * Main function, rendering the table wizard
     *
     * @deprecated since v9, will be removed in v10
     */
    public function main()
    {
        trigger_error('Method main() will be replaced by protected method renderContent() in v10. Do not call from other extensions', E_USER_DEPRECATED);

        $response = $this->renderContent($GLOBALS['TYPO3_REQUEST']);

        if ($response instanceof RedirectResponse) {
            HttpUtility::redirect($response->getHeaders()['location'][0]);
        }
    }

    /**
     * Draws the table wizard content
     *
     * @return string HTML content for the form.
     * @throws \RuntimeException
     *
     * @deprecated since v9, will be removed in v10
     */
    public function tableWizard()
    {
        trigger_error('Method tableWizard() will be replaced by protected method renderTableWizard() in v10. Do not call from other extensions', E_USER_DEPRECATED);

        $result = $this->renderTableWizard($GLOBALS['TYPO3_REQUEST']);

        if ($result instanceof RedirectResponse) {
            HttpUtility::redirect($result->getHeaders()['location'][0]);
        }

        return $result;
    }

    /**
     * Will get and return the configuration code string
     * Will also save (and possibly redirect/exit) the content if a save button has been pressed
     *
     * @param array $row Current parent record row
     * @return array Table config code in an array
     *
     * @deprecated since v9, will be removed in v10
     */
    public function getConfigCode($row)
    {
        trigger_error('Method getConfigCode() will be replaced by protected method getConfiguration() in v10. Do not call from other extensions', E_USER_DEPRECATED);

        $result = $this->getConfiguration($row, $GLOBALS['TYPO3_REQUEST']);

        if ($result instanceof RedirectResponse) {
            HttpUtility::redirect($result->getHeaders()['location'][0]);
        }

        return $result;
    }

    /**
     * Creates the HTML for the Table Wizard:
     *
     * @param array $configuration Table config array
     * @return string HTML for the table wizard
     * @internal
     *
     * @deprecated since v9, will be removed in v10
     */
    public function getTableHTML($configuration)
    {
        trigger_error('Method getTableHTML() will be replaced by protected method getTableWizard() in v10. Do not call from other extensions', E_USER_DEPRECATED);
        return $this->getTableWizard($configuration);
    }

    /**
     * Detects if a control button (up/down/around/delete) has been pressed for an item and accordingly it will
     * manipulate the internal TABLECFG array
     *
     * @internal
     *
     * @deprecated since v9, will be removed in v10
     */
    public function changeFunc()
    {
        trigger_error('Method changeFunc() will be replaced by protected method manipulateTable() in v10. Do not call from other extensions', E_USER_DEPRECATED);
        $this->manipulateTable();
    }

    /**
     * Converts the input array to a configuration code string
     *
     * @param array $cfgArr Array of table configuration (follows the input structure from the table wizard POST form)
     * @return string The array converted into a string with line-based configuration.
     * @see cfgString2CfgArray()
     *
     * @deprecated since v9, will be removed in v10
     */
    public function cfgArray2CfgString($cfgArr)
    {
        trigger_error('Method cfgArray2CfgString() will be replaced by protected method configurationArrayToString() in v10. Do not call from other extensions', E_USER_DEPRECATED);
        return $this->configurationArrayToString($cfgArr);
    }

    /**
     * Converts the input configuration code string into an array
     *
     * @param string $configurationCode Configuration code
     * @param int $columns Default number of columns
     * @return array Configuration array
     * @see cfgArray2CfgString()
     *
     * @deprecated since v9, will be removed in v10
     */
    public function cfgString2CfgArray($configurationCode, $columns)
    {
        trigger_error('Method cfgString2CfgArray() will be replaced by protected method configurationStringToArray() in v10. Do not call from other extensions', E_USER_DEPRECATED);
        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
295
        $parsedBody = $request->getParsedBody();
        $queryParams = $request->getQueryParams();
296
        // GPvars:
297
298
        $this->P = $parsedBody['P'] ?? $queryParams['P'] ?? null;
        $this->TABLECFG = $parsedBody['TABLE'] ?? $queryParams['TABLE'] ?? null;
299
300
301
302
303
304
305
306
        // Setting options:
        $this->xmlStorage = $this->P['params']['xmlOutput'];
        $this->numNewRows = MathUtility::forceIntegerInRange($this->P['params']['numNewRows'], 1, 50, 5);
        // Textareas or input fields:
        $this->inputStyle = isset($this->TABLECFG['textFields']) ? (bool)$this->TABLECFG['textFields'] : true;
        $this->tableParsing_delimiter = '|';
        $this->tableParsing_quote = '';
    }
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
317
318
        $normalizedParams = $request->getAttribute('normalizedParams');
        $requestUri = $normalizedParams->getRequestUri();
        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
322
323
324
325
326
            $tableWizard = $this->renderTableWizard($request);

            if ($tableWizard instanceof RedirectResponse) {
                return $tableWizard;
            }

327
            $this->content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>'
328
                . '<div>' . $tableWizard . '</div>';
329
        } else {
330
331
            $this->content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>'
                . '<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
344
    /**
     * Create the panel of buttons for submitting the form or otherwise perform operations.
     */
345
    protected function getButtons(): void
346
    {
347
        $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
348
349
        if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
            // CSH
350
351
352
353
            $cshButton = $buttonBar->makeHelpButton()
                ->setModuleName('xMOD_csh_corebe')
                ->setFieldName('wizard_table_wiz');
            $buttonBar->addButton($cshButton);
354
            // Close
355
356
            $closeButton = $buttonBar->makeLinkButton()
                ->setHref($this->P['returnUrl'])
357
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/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
362
363
            $saveButton = $buttonBar->makeInputButton()
                ->setName('_savedok')
                ->setValue('1')
364
                ->setForm('TableController')
365
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
366
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'));
367
            // Save & Close
368
369
370
            $saveAndCloseButton = $buttonBar->makeInputButton()
                ->setName('_saveandclosedok')
                ->setValue('1')
371
                ->setForm('TableController')
372
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveCloseDoc'))
373
374
375
376
377
378
379
380
381
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
                    'actions-document-save-close',
                    Icon::SIZE_SMALL
                ));
            $splitButtonElement = $buttonBar->makeSplitButton()
                ->addItem($saveButton)
                ->addItem($saveAndCloseButton);

            $buttonBar->addButton($splitButtonElement, ButtonBar::BUTTON_POSITION_LEFT, 3);
382
            // Reload
383
384
385
            $reloadButton = $buttonBar->makeInputButton()
                ->setName('_refresh')
                ->setValue('1')
386
                ->setForm('TableController')
387
                ->setTitle($this->getLanguageService()->getLL('forms_refresh'))
388
389
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-refresh', Icon::SIZE_SMALL));
            $buttonBar->addButton($reloadButton);
390
391
        }
    }
392

393
394
395
    /**
     * Draws the table wizard content
     *
396
397
     * @param ServerRequestInterface $request
     * @return string|ResponseInterface HTML content for the form.
398
399
     * @throws \RuntimeException
     */
400
    protected function renderTableWizard(ServerRequestInterface $request)
401
402
403
404
405
406
407
408
409
410
411
    {
        if (!$this->checkEditAccess($this->P['table'], $this->P['uid'])) {
            throw new \RuntimeException('Wizard Error: No access', 1349692692);
        }
        // First, check the references by selecting the record:
        $row = BackendUtility::getRecord($this->P['table'], $this->P['uid']);
        if (!is_array($row)) {
            throw new \RuntimeException('Wizard Error: No reference to record', 1294587125);
        }
        // This will get the content of the form configuration code field to us - possibly cleaned up,
        // saved to database etc. if the form has been submitted in the meantime.
412
413
414
415
416
417
        $tableCfgArray = $this->getConfiguration($row, $request);

        if ($tableCfgArray instanceof ResponseInterface) {
            return $tableCfgArray;
        }

418
        // Generation of the Table Wizards HTML code:
419
        $content = $this->getTableWizard($tableCfgArray);
420
421
422
        // Return content:
        return $content;
    }
423

424
425
426
427
428
    /**
     * Will get and return the configuration code string
     * Will also save (and possibly redirect/exit) the content if a save button has been pressed
     *
     * @param array $row Current parent record row
429
430
     * @param ServerRequestInterface $request
     * @return array|ResponseInterface Table config code in an array
431
     */
432
    protected function getConfiguration(array $row, ServerRequestInterface $request)
433
434
    {
        // Get delimiter settings
435
436
        $this->tableParsing_quote = $row['table_enclosure'] ? chr((int)$row['table_enclosure']) : '';
        $this->tableParsing_delimiter = $row['table_delimiter'] ? chr((int)$row['table_delimiter']) : '|';
437
438
439
        // If some data has been submitted, then construct
        if (isset($this->TABLECFG['c'])) {
            // Process incoming:
440
            $this->manipulateTable();
441
442
443
            // Convert to string (either line based or XML):
            if ($this->xmlStorage) {
                // 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
446
447
448
                // Setting cfgArr directly from the input:
                $configuration = $this->TABLECFG['c'];
            } else {
                // Convert the input array to a string of configuration code:
449
                $bodyText = $this->configurationArrayToString($this->TABLECFG['c']);
450
451
                // Create cfgArr from the string based configuration - that way it is cleaned up
                // and any incompatibilities will be removed!
452
                $configuration = $this->configurationStringToArray($bodyText, $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
457
458
459
                // Get DataHandler object:
                /** @var DataHandler $dataHandler */
                $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
                // Put content into the data array:
460
                $data = [];
461
462
463
464
465
466
467
468
469
470
471
                if ($this->P['flexFormPath']) {
                    // Current value of flexForm path:
                    $currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]);
                    /** @var FlexFormTools $flexFormTools */
                    $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
                    $flexFormTools->setArrayValueByPath($this->P['flexFormPath'], $currentFlexFormData, $bodyText);
                    $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $currentFlexFormData;
                } else {
                    $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $bodyText;
                }
                // Perform the update:
472
                $dataHandler->start($data, []);
473
474
                $dataHandler->process_datamap();
                // 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
480
481
482
483
484
485
486
487
488
                }
            }
        } else {
            // If nothing has been submitted, load the $bodyText variable from the selected database row:
            if ($this->xmlStorage) {
                $configuration = GeneralUtility::xml2array($row[$this->P['field']]);
            } else {
                if ($this->P['flexFormPath']) {
                    // Current value of flexForm path:
                    $currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]);
                    /** @var FlexFormTools $flexFormTools */
                    $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
489
490
491
492
                    $configuration = $flexFormTools->getArrayValueByPath(
                        $this->P['flexFormPath'],
                        $currentFlexFormData
                    );
493
                    $configuration = $this->configurationStringToArray($configuration, 0);
494
495
                } else {
                    // Regular line based table configuration:
496
497
                    $columns = $row[$this->colsFieldName] ?? 0;
                    $configuration = $this->configurationStringToArray($row[$this->P['field']], $columns);
498
499
                }
            }
500
            $configuration = is_array($configuration) ? $configuration : [];
501
502
503
        }
        return $configuration;
    }
504

505
506
507
508
509
510
    /**
     * Creates the HTML for the Table Wizard:
     *
     * @param array $configuration Table config array
     * @return string HTML for the table wizard
     */
511
    protected function getTableWizard(array $configuration): string
512
513
    {
        // Traverse the rows:
514
        $tRows = [];
515
516
517
518
519
        $k = 0;
        $countLines = count($configuration);
        foreach ($configuration as $cellArr) {
            if (is_array($cellArr)) {
                // Initialize:
520
                $cells = [];
521
522
523
524
                $a = 0;
                // Traverse the columns:
                foreach ($cellArr as $cellContent) {
                    if ($this->inputStyle) {
525
                        $cells[] = '<input class="form-control" type="text" name="TABLE[c][' . ($k + 1) * 2 . '][' . ($a + 1) * 2 . ']" value="' . htmlspecialchars($cellContent) . '" />';
526
527
                    } else {
                        $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
531
532
533
534
535
536
537
                    }
                    // Increment counter:
                    $a++;
                }
                // CTRL panel for a table row (move up/down/around):
                $onClick = 'document.wizardForm.action+=' . GeneralUtility::quoteJSvalue('#ANC_' . (($k + 1) * 2 - 2)) . ';';
                $onClick = ' onclick="' . htmlspecialchars($onClick) . '"';
                $ctrl = '';
                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
548
                $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>';
                $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[] = '
Benjamin Kott's avatar
Benjamin Kott committed
550
551
552
553
554
					<tr>
						<td>
							<a name="ANC_' . ($k + 1) * 2 . '"></a>
							<span class="btn-group' . ($this->inputStyle ? '' : '-vertical') . '">' . $ctrl . '</span>
						</td>
555
556
						<td>' . implode('</td>
						<td>', $cells) . '</td>
557
					</tr>';
558
559
560
561
562
                // Increment counter:
                $k++;
            }
        }
        // CTRL panel for a table column (move left/right/around/delete)
563
        $cells = [];
564
565
566
567
568
569
570
571
572
        $cells[] = '';
        // Finding first row:
        $firstRow = reset($configuration);
        if (is_array($firstRow)) {
            $cols = count($firstRow);
            for ($a = 1; $a <= $cols; $a++) {
                $b = $a * 2;
                $ctrl = '';
                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
583
                $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>';
                $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
585
586
                $cells[] = '<span class="btn-group">' . $ctrl . '</span>';
            }
            $tRows[] = '
Benjamin Kott's avatar
Benjamin Kott committed
587
588
589
590
591
592
				<tfoot>
					<tr>
						<td>' . implode('</td>
						<td>', $cells) . '</td>
					</tr>
				</tfoot>';
593
594
        }
        $content = '';
595
        $addSubmitOnClick = 'onclick="document.getElementById(\'TableController\').submit();"';
596
597
        // Implode all table rows into a string, wrapped in table tags.
        $content .= '
598

599
			<!-- Table wizard -->
Benjamin Kott's avatar
Benjamin Kott committed
600
601
602
603
604
			<div class="table-fit table-fit-inline-block">
				<table id="typo3-tablewizard" class="table table-center">
					' . implode('', $tRows) . '
				</table>
			</div>';
605
606
        // Input type checkbox:
        $content .= '
607

608
			<!-- Input mode check box: -->
Benjamin Kott's avatar
Benjamin Kott committed
609
610
611
			<div class="checkbox">
				<input type="hidden" name="TABLE[textFields]" value="0" />
				<label for="textFields">
612
					<input type="checkbox" ' . $addSubmitOnClick . ' name="TABLE[textFields]" id="textFields" value="1"' . ($this->inputStyle ? ' checked="checked"' : '') . ' />
Benjamin Kott's avatar
Benjamin Kott committed
613
614
615
					' . $this->getLanguageService()->getLL('table_smallFields') . '
				</label>
			</div>';
616
617
        return $content;
    }
618

619
    /**
620
621
     * Detects if a control button (up/down/around/delete) has been pressed for an item and accordingly it will
     * manipulate the internal TABLECFG array
622
     */
623
    protected function manipulateTable(): void
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
    {
        if ($this->TABLECFG['col_remove']) {
            $kk = key($this->TABLECFG['col_remove']);
            $cmd = 'col_remove';
        } elseif ($this->TABLECFG['col_add']) {
            $kk = key($this->TABLECFG['col_add']);
            $cmd = 'col_add';
        } elseif ($this->TABLECFG['col_start']) {
            $kk = key($this->TABLECFG['col_start']);
            $cmd = 'col_start';
        } elseif ($this->TABLECFG['col_end']) {
            $kk = key($this->TABLECFG['col_end']);
            $cmd = 'col_end';
        } elseif ($this->TABLECFG['col_left']) {
            $kk = key($this->TABLECFG['col_left']);
            $cmd = 'col_left';
        } elseif ($this->TABLECFG['col_right']) {
            $kk = key($this->TABLECFG['col_right']);
            $cmd = 'col_right';
        } elseif ($this->TABLECFG['row_remove']) {
            $kk = key($this->TABLECFG['row_remove']);
            $cmd = 'row_remove';
        } elseif ($this->TABLECFG['row_add']) {
            $kk = key($this->TABLECFG['row_add']);
            $cmd = 'row_add';
        } elseif ($this->TABLECFG['row_top']) {
            $kk = key($this->TABLECFG['row_top']);
            $cmd = 'row_top';
        } elseif ($this->TABLECFG['row_bottom']) {
            $kk = key($this->TABLECFG['row_bottom']);
            $cmd = 'row_bottom';
        } elseif ($this->TABLECFG['row_up']) {
            $kk = key($this->TABLECFG['row_up']);
            $cmd = 'row_up';
        } elseif ($this->TABLECFG['row_down']) {
            $kk = key($this->TABLECFG['row_down']);
            $cmd = 'row_down';
        } else {
            $kk = '';
            $cmd = '';
        }
        if ($cmd && MathUtility::canBeInterpretedAsInteger($kk)) {
666
            if (strpos($cmd, 'row_') === 0) {
667
668
669
670
671
672
673
674
675
676
                switch ($cmd) {
                    case 'row_remove':
                        unset($this->TABLECFG['c'][$kk]);
                        break;
                    case 'row_add':
                        for ($a = 1; $a <= $this->numNewRows; $a++) {
                            // Checking if set: The point is that any new row between existing rows
                            // will be TRUE after one row is added while if rows are added in the bottom
                            // of the table there will be no existing rows to stop the addition of new rows
                            // 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
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
                            } else {
                                break;
                            }
                        }
                        break;
                    case 'row_top':
                        $this->TABLECFG['c'][1] = $this->TABLECFG['c'][$kk];
                        unset($this->TABLECFG['c'][$kk]);
                        break;
                    case 'row_bottom':
                        $this->TABLECFG['c'][10000000] = $this->TABLECFG['c'][$kk];
                        unset($this->TABLECFG['c'][$kk]);
                        break;
                    case 'row_up':
                        $this->TABLECFG['c'][$kk - 3] = $this->TABLECFG['c'][$kk];
                        unset($this->TABLECFG['c'][$kk]);
                        break;
                    case 'row_down':
                        $this->TABLECFG['c'][$kk + 3] = $this->TABLECFG['c'][$kk];
                        unset($this->TABLECFG['c'][$kk]);
                        break;
                }
                ksort($this->TABLECFG['c']);
            }
703
            if (strpos($cmd, 'col_') === 0) {
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
                foreach ($this->TABLECFG['c'] as $cAK => $value) {
                    switch ($cmd) {
                        case 'col_remove':
                            unset($this->TABLECFG['c'][$cAK][$kk]);
                            break;
                        case 'col_add':
                            $this->TABLECFG['c'][$cAK][$kk + 1] = '';
                            break;
                        case 'col_start':
                            $this->TABLECFG['c'][$cAK][1] = $this->TABLECFG['c'][$cAK][$kk];
                            unset($this->TABLECFG['c'][$cAK][$kk]);
                            break;
                        case 'col_end':
                            $this->TABLECFG['c'][$cAK][1000000] = $this->TABLECFG['c'][$cAK][$kk];
                            unset($this->TABLECFG['c'][$cAK][$kk]);
                            break;
                        case 'col_left':
                            $this->TABLECFG['c'][$cAK][$kk - 3] = $this->TABLECFG['c'][$cAK][$kk];
                            unset($this->TABLECFG['c'][$cAK][$kk]);
                            break;
                        case 'col_right':
                            $this->TABLECFG['c'][$cAK][$kk + 3] = $this->TABLECFG['c'][$cAK][$kk];
                            unset($this->TABLECFG['c'][$cAK][$kk]);
                            break;
                    }
                    ksort($this->TABLECFG['c'][$cAK]);
                }
            }
        }
        // Convert line breaks to <br /> tags:
        foreach ($this->TABLECFG['c'] as $a => $value) {
            foreach ($this->TABLECFG['c'][$a] as $b => $value2) {
736
737
738
739
740
                $this->TABLECFG['c'][$a][$b] = str_replace(
                    LF,
                    '<br />',
                    str_replace(CR, '', $this->TABLECFG['c'][$a][$b])
                );
741
742
743
            }
        }
    }
744

745
746
747
748
749
    /**
     * Converts the input array to a configuration code string
     *
     * @param array $cfgArr Array of table configuration (follows the input structure from the table wizard POST form)
     * @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
756
        // Traverse the elements of the table wizard and transform the settings into configuration code.
        foreach ($cfgArr as $valueA) {
757
            $thisLine = [];
758
            foreach ($valueA as $valueB) {
759
760
                $thisLine[] = $this->tableParsing_quote
                    . str_replace($this->tableParsing_delimiter, '', $valueB) . $this->tableParsing_quote;
761
762
763
764
765
766
            }
            $inLines[] = implode($this->tableParsing_delimiter, $thisLine);
        }
        // Finally, implode the lines into a string:
        return implode(LF, $inLines);
    }
767

768
769
770
771
772
773
    /**
     * Converts the input configuration code string into an array
     *
     * @param string $configurationCode Configuration code
     * @param int $columns Default number of columns
     * @return array Configuration array
774
     * @see configurationArrayToString()
775
     */
776
    protected function configurationStringToArray(string $configurationCode, int $columns): array
777
778
779
780
781
782
783
784
785
786
    {
        // Explode lines in the configuration code - each line is a table row.
        $tableLines = explode(LF, $configurationCode);
        // Setting number of columns
        // auto...
        if (!$columns && trim($tableLines[0])) {
            $columns = count(explode($this->tableParsing_delimiter, $tableLines[0]));
        }
        $columns = $columns ?: 4;
        // Traverse the number of table elements:
787
        $configurationArray = [];
788
789
790
791
792
        foreach ($tableLines as $key => $value) {
            // Initialize:
            $valueParts = explode($this->tableParsing_delimiter, $value);
            // Traverse columns:
            for ($a = 0; $a < $columns; $a++) {
793
794
795
796
                if ($this->tableParsing_quote
                    && $valueParts[$a][0] === $this->tableParsing_quote
                    && substr($valueParts[$a], -1, 1) === $this->tableParsing_quote
                ) {
797
798
                    $valueParts[$a] = substr(trim($valueParts[$a]), 1, -1);
                }
799
                $configurationArray[$key][$a] = (string)$valueParts[$a];
800
801
802
803
            }
        }
        return $configurationArray;
    }
804
}