Passed
Push — master ( f08d8a...794f3e )
by
unknown
18:25
created

TableController::configurationArrayToString()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(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
18
namespace TYPO3\CMS\Backend\Controller\Wizard;
19
20
use Psr\Http\Message\ResponseInterface;
21
use Psr\Http\Message\ServerRequestInterface;
22
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
23
use TYPO3\CMS\Backend\Template\ModuleTemplate;
24
use TYPO3\CMS\Backend\Utility\BackendUtility;
25
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
26
use TYPO3\CMS\Core\DataHandling\DataHandler;
27
use TYPO3\CMS\Core\Http\HtmlResponse;
28
use TYPO3\CMS\Core\Http\RedirectResponse;
29
use TYPO3\CMS\Core\Imaging\Icon;
30
use TYPO3\CMS\Core\Imaging\IconFactory;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
use 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
 */
38
class 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->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Element/TableWizardElement');
123
        $this->moduleTemplate->getPageRenderer()->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_wizards.xlf', 'table_');
124
        $this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_wizards.xlf');
125
        $this->init($request);
126
127
        $normalizedParams = $request->getAttribute('normalizedParams');
128
        $requestUri = $normalizedParams->getRequestUri();
129
        [$rUri] = explode('#', $requestUri);
130
        $content = '<form action="' . htmlspecialchars($rUri) . '" method="post" id="TableController" name="wizardForm">';
131
        if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
132
            $tableWizard = $this->renderTableWizard($request);
133
134
            if ($tableWizard instanceof RedirectResponse) {
135
                return $tableWizard;
136
            }
137
138
            $content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>'
139
                . '<div>' . $tableWizard . '</div>';
0 ignored issues
show
Bug introduced by
Are you sure $tableWizard of type Psr\Http\Message\ResponseInterface|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

139
                . '<div>' . /** @scrutinizer ignore-type */ $tableWizard . '</div>';
Loading history...
140
        } else {
141
            $content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>'
142
                . '<div><span class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('table_noData')) . '</span></div>';
143
        }
144
        $content .= '</form>';
145
146
        // Setting up the buttons and markers for docHeader
147
        $this->getButtons();
148
        // Build the <body> for the module
149
        $this->moduleTemplate->setContent($content);
150
151
        return new HtmlResponse($this->moduleTemplate->renderContent());
152
    }
153
154
    /**
155
     * Initialization of the class
156
     *
157
     * @param ServerRequestInterface $request
158
     */
159
    protected function init(ServerRequestInterface $request): void
160
    {
161
        $parsedBody = $request->getParsedBody();
162
        $queryParams = $request->getQueryParams();
163
        // GPvars:
164
        $this->P = $parsedBody['P'] ?? $queryParams['P'] ?? null;
165
        $this->TABLECFG = $parsedBody['TABLE'] ?? $queryParams['TABLE'] ?? null;
166
        // Setting options:
167
        $this->xmlStorage = $this->P['params']['xmlOutput'];
168
        $this->numNewRows = MathUtility::forceIntegerInRange($this->P['params']['numNewRows'], 1, 10, 1);
169
        // Textareas or input fields:
170
        $this->inputStyle = (bool)($this->TABLECFG['textFields'] ?? true);
171
        $this->tableParsing_delimiter = '|';
172
        $this->tableParsing_quote = '';
173
    }
174
175
    /**
176
     * Create the panel of buttons for submitting the form or otherwise perform operations.
177
     */
178
    protected function getButtons(): void
179
    {
180
        $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
181
        if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
182
            // CSH
183
            $cshButton = $buttonBar->makeHelpButton()
184
                ->setModuleName('xMOD_csh_corebe')
185
                ->setFieldName('wizard_table_wiz');
186
            $buttonBar->addButton($cshButton);
187
            // Close
188
            $closeButton = $buttonBar->makeLinkButton()
189
                ->setHref($this->P['returnUrl'])
190
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL))
191
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
192
                ->setShowLabelText(true);
193
            $buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
194
            // Save
195
            $saveButton = $buttonBar->makeInputButton()
196
                ->setName('_savedok')
197
                ->setValue('1')
198
                ->setForm('TableController')
199
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
200
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
201
                ->setShowLabelText(true);
202
            $buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
203
            // Reload
204
            $reloadButton = $buttonBar->makeInputButton()
205
                ->setName('_refresh')
206
                ->setValue('1')
207
                ->setForm('TableController')
208
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-refresh', Icon::SIZE_SMALL))
209
                ->setTitle($this->getLanguageService()->getLL('forms_refresh'));
210
            $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT);
211
        }
212
    }
213
214
    /**
215
     * Draws the table wizard content
216
     *
217
     * @param ServerRequestInterface $request
218
     * @return string|ResponseInterface HTML content for the form.
219
     * @throws \RuntimeException
220
     */
221
    protected function renderTableWizard(ServerRequestInterface $request)
222
    {
223
        if (!$this->checkEditAccess($this->P['table'], $this->P['uid'])) {
224
            throw new \RuntimeException('Wizard Error: No access', 1349692692);
225
        }
226
        // First, check the references by selecting the record:
227
        $row = BackendUtility::getRecord($this->P['table'], $this->P['uid']);
228
        if (!is_array($row)) {
229
            throw new \RuntimeException('Wizard Error: No reference to record', 1294587125);
230
        }
231
        // This will get the content of the form configuration code field to us - possibly cleaned up,
232
        // saved to database etc. if the form has been submitted in the meantime.
233
        $tableCfgArray = $this->getConfiguration($row, $request);
234
235
        if ($tableCfgArray instanceof ResponseInterface) {
236
            return $tableCfgArray;
237
        }
238
239
        // Generation of the Table Wizards HTML code:
240
        $content = $this->getTableWizard($tableCfgArray);
241
        // Return content:
242
        return $content;
243
    }
244
245
    /**
246
     * Will get and return the configuration code string
247
     * Will also save (and possibly redirect/exit) the content if a save button has been pressed
248
     *
249
     * @param array $row Current parent record row
250
     * @param ServerRequestInterface $request
251
     * @return array|ResponseInterface Table config code in an array
252
     */
253
    protected function getConfiguration(array $row, ServerRequestInterface $request)
254
    {
255
        // Get delimiter settings
256
        $this->tableParsing_quote = $row['table_enclosure'] ? chr((int)$row['table_enclosure']) : '';
257
        $this->tableParsing_delimiter = $row['table_delimiter'] ? chr((int)$row['table_delimiter']) : '|';
258
        // If some data has been submitted, then construct
259
        if (isset($this->TABLECFG['c'])) {
260
            // Process incoming:
261
            $this->manipulateTable();
262
            // Convert to string (either line based or XML):
263
            if ($this->xmlStorage) {
264
                // Convert the input array to XML:
265
                $bodyText = '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>' . LF . GeneralUtility::array2xml($this->TABLECFG['c'], '', 0, 'T3TableWizard');
266
                // Setting cfgArr directly from the input:
267
                $configuration = $this->TABLECFG['c'];
268
            } else {
269
                // Convert the input array to a string of configuration code:
270
                $bodyText = $this->configurationArrayToString($this->TABLECFG['c']);
271
                // Create cfgArr from the string based configuration - that way it is cleaned up
272
                // and any incompatibilities will be removed!
273
                $configuration = $this->configurationStringToArray($bodyText, (int)$row[$this->colsFieldName]);
274
            }
275
            // If a save button has been pressed, then save the new field content:
276
            if ($_POST['_savedok'] || $_POST['_saveandclosedok']) {
277
                // Get DataHandler object:
278
                /** @var DataHandler $dataHandler */
279
                $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
280
                // Put content into the data array:
281
                $data = [];
282
                if ($this->P['flexFormPath']) {
283
                    // Current value of flexForm path:
284
                    $currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]);
285
                    /** @var FlexFormTools $flexFormTools */
286
                    $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
287
                    $flexFormTools->setArrayValueByPath($this->P['flexFormPath'], $currentFlexFormData, $bodyText);
288
                    $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $currentFlexFormData;
289
                } else {
290
                    $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $bodyText;
291
                }
292
                // Perform the update:
293
                $dataHandler->start($data, []);
294
                $dataHandler->process_datamap();
295
                // If the save/close button was pressed, then redirect the screen:
296
                if ($_POST['_saveandclosedok']) {
297
                    return new RedirectResponse(GeneralUtility::sanitizeLocalUrl($this->P['returnUrl']));
298
                }
299
            }
300
        } else {
301
            // If nothing has been submitted, load the $bodyText variable from the selected database row:
302
            if ($this->xmlStorage) {
303
                $configuration = GeneralUtility::xml2array($row[$this->P['field']]);
304
            } else {
305
                if ($this->P['flexFormPath']) {
306
                    // Current value of flexForm path:
307
                    $currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]);
308
                    /** @var FlexFormTools $flexFormTools */
309
                    $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
310
                    $configuration = $flexFormTools->getArrayValueByPath(
311
                        $this->P['flexFormPath'],
312
                        $currentFlexFormData
313
                    );
314
                    $configuration = $this->configurationStringToArray($configuration, 0);
0 ignored issues
show
Bug introduced by
It seems like $configuration can also be of type null; however, parameter $configurationCode of TYPO3\CMS\Backend\Contro...gurationStringToArray() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

314
                    $configuration = $this->configurationStringToArray(/** @scrutinizer ignore-type */ $configuration, 0);
Loading history...
315
                } else {
316
                    // Regular line based table configuration:
317
                    $columns = $row[$this->colsFieldName] ?? 0;
318
                    $configuration = $this->configurationStringToArray($row[$this->P['field']] ?? '', (int)$columns);
319
                }
320
            }
321
            $configuration = is_array($configuration) ? $configuration : [];
322
        }
323
        return $configuration;
324
    }
325
326
    /**
327
     * Creates the HTML for the Table Wizard:
328
     *
329
     * @param array $configuration Table config array
330
     * @return string HTML for the table wizard
331
     */
332
    protected function getTableWizard(array $configuration): string
333
    {
334
        return sprintf(
335
            '<typo3-backend-table-wizard %s></typo3-backend-table-wizard>',
336
            GeneralUtility::implodeAttributes([
337
                'id' => 'typo3-tablewizard',
338
                'type' => $this->inputStyle ? 'input' : 'textarea',
339
                'append-rows' => $this->numNewRows,
340
                'table' => GeneralUtility::jsonEncodeForHtmlAttribute($configuration, false),
341
            ], true)
342
        );
343
    }
344
345
    /**
346
     * Detects if a control button (up/down/around/delete) has been pressed for an item and accordingly it will
347
     * manipulate the internal TABLECFG array
348
     */
349
    protected function manipulateTable(): void
350
    {
351
        // Convert line breaks to <br /> tags:
352
        foreach ($this->TABLECFG['c'] as $a => $value) {
353
            foreach ($this->TABLECFG['c'][$a] as $b => $value2) {
354
                $this->TABLECFG['c'][$a][$b] = str_replace(
355
                    [CR, LF],
356
                    ['', '<br />'],
357
                    $this->TABLECFG['c'][$a][$b]
358
                );
359
            }
360
        }
361
    }
362
363
    /**
364
     * Converts the input array to a configuration code string
365
     *
366
     * @param array $cfgArr Array of table configuration (follows the input structure from the table wizard POST form)
367
     * @return string The array converted into a string with line-based configuration.
368
     * @see configurationStringToArray()
369
     */
370
    protected function configurationArrayToString(array $cfgArr): string
371
    {
372
        $inLines = [];
373
        // Traverse the elements of the table wizard and transform the settings into configuration code.
374
        foreach ($cfgArr as $valueA) {
375
            $thisLine = [];
376
            foreach ($valueA as $valueB) {
377
                $thisLine[] = $this->tableParsing_quote
378
                    . str_replace($this->tableParsing_delimiter, '', $valueB) . $this->tableParsing_quote;
379
            }
380
            $inLines[] = implode($this->tableParsing_delimiter, $thisLine);
381
        }
382
        // Finally, implode the lines into a string:
383
        return implode(LF, $inLines);
384
    }
385
386
    /**
387
     * Converts the input configuration code string into an array
388
     *
389
     * @param string $configurationCode Configuration code
390
     * @param int $columns Default number of columns
391
     * @return array Configuration array
392
     * @see configurationArrayToString()
393
     */
394
    protected function configurationStringToArray(string $configurationCode, int $columns): array
395
    {
396
        // Explode lines in the configuration code - each line is a table row.
397
        $tableLines = explode(LF, $configurationCode);
398
        // Setting number of columns
399
        // auto...
400
        if (!$columns && trim($tableLines[0])) {
401
            $exploded = explode($this->tableParsing_delimiter, $tableLines[0]);
402
            $columns = is_array($exploded) ? count($exploded) : 0;
0 ignored issues
show
introduced by
The condition is_array($exploded) is always true.
Loading history...
403
        }
404
        $columns = $columns ?: 4;
405
        // Traverse the number of table elements:
406
        $configurationArray = [];
407
        foreach ($tableLines as $key => $value) {
408
            // Initialize:
409
            $valueParts = explode($this->tableParsing_delimiter, $value);
410
            // Traverse columns:
411
            for ($a = 0; $a < $columns; $a++) {
412
                if ($this->tableParsing_quote
413
                    && $valueParts[$a][0] === $this->tableParsing_quote
414
                    && substr($valueParts[$a], -1, 1) === $this->tableParsing_quote
415
                ) {
416
                    $valueParts[$a] = substr(trim($valueParts[$a]), 1, -1);
417
                }
418
                $configurationArray[$key][$a] = (string)$valueParts[$a];
419
            }
420
        }
421
        return $configurationArray;
422
    }
423
}
424