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>'; |
|
|
|
|
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); |
|
|
|
|
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; |
|
|
|
|
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
|
|
|
|