Passed
Push — master ( d5a28b...d12ca0 )
by
unknown
17:01
created

TableController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
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\Template\ModuleTemplateFactory;
25
use TYPO3\CMS\Backend\Utility\BackendUtility;
26
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
27
use TYPO3\CMS\Core\DataHandling\DataHandler;
28
use TYPO3\CMS\Core\Http\HtmlResponse;
29
use TYPO3\CMS\Core\Http\RedirectResponse;
30
use TYPO3\CMS\Core\Imaging\Icon;
31
use TYPO3\CMS\Core\Imaging\IconFactory;
32
use TYPO3\CMS\Core\Page\PageRenderer;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
use TYPO3\CMS\Core\Utility\MathUtility;
35
36
/**
37
 * Script Class for rendering the Table Wizard
38
 * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
39
 */
40
class TableController extends AbstractWizardController
41
{
42
    /**
43
     * If TRUE, <input> fields are shown instead of textareas.
44
     *
45
     * @var bool
46
     */
47
    protected $inputStyle = false;
48
49
    /**
50
     * If set, the string version of the content is interpreted/written as XML
51
     * instead of the original line-based kind. This variable still needs binding
52
     * to the wizard parameters - but support is ready!
53
     *
54
     * @var int
55
     */
56
    protected $xmlStorage = 0;
57
58
    /**
59
     * Number of new rows to add in bottom of wizard
60
     *
61
     * @var int
62
     */
63
    protected $numNewRows = 1;
64
65
    /**
66
     * Name of field in parent record which MAY contain the number of columns for the table
67
     * here hardcoded to the value of tt_content. Should be set by FormEngine parameters (from P)
68
     *
69
     * @var string
70
     */
71
    protected $colsFieldName = 'cols';
72
73
    /**
74
     * Wizard parameters, coming from FormEngine linking to the wizard.
75
     *
76
     * @var array
77
     */
78
    protected $P;
79
80
    /**
81
     * The array which is constantly submitted by the multidimensional form of this wizard.
82
     *
83
     * @var array
84
     */
85
    protected $TABLECFG;
86
87
    /**
88
     * Table parsing
89
     * quoting of table cells
90
     *
91
     * @var string
92
     */
93
    protected $tableParsing_quote;
94
95
    /**
96
     * delimiter between table cells
97
     *
98
     * @var string
99
     */
100
    protected $tableParsing_delimiter;
101
102
    /**
103
     * ModuleTemplate object
104
     *
105
     * @var ModuleTemplate
106
     */
107
    protected $moduleTemplate;
108
109
    protected IconFactory $iconFactory;
110
    protected PageRenderer $pageRenderer;
111
    protected ModuleTemplateFactory $moduleTemplateFactory;
112
113
    public function __construct(
114
        IconFactory $iconFactory,
115
        PageRenderer $pageRenderer,
116
        ModuleTemplateFactory $moduleTemplateFactory
117
    ) {
118
        $this->iconFactory = $iconFactory;
119
        $this->pageRenderer = $pageRenderer;
120
        $this->moduleTemplateFactory = $moduleTemplateFactory;
121
    }
122
123
    /**
124
     * Injects the request object for the current request or subrequest
125
     * As this controller goes only through the main() method, it is rather simple for now
126
     *
127
     * @param ServerRequestInterface $request
128
     * @return ResponseInterface
129
     */
130
    public function mainAction(ServerRequestInterface $request): ResponseInterface
131
    {
132
        $this->moduleTemplate = $this->moduleTemplateFactory->create($request);
133
        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Element/TableWizardElement');
134
        $this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_wizards.xlf', 'table_');
135
        $this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_wizards.xlf');
136
        $this->init($request);
137
138
        $normalizedParams = $request->getAttribute('normalizedParams');
139
        $requestUri = $normalizedParams->getRequestUri();
140
        [$rUri] = explode('#', $requestUri);
141
        $content = '<form action="' . htmlspecialchars($rUri) . '" method="post" id="TableController" name="wizardForm">';
142
        if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
143
            $tableWizard = $this->renderTableWizard($request);
144
145
            if ($tableWizard instanceof RedirectResponse) {
146
                return $tableWizard;
147
            }
148
149
            $content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>'
150
                . '<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

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

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