[TASK] compatibility6: Decouple language labels, styles and images
[Packages/TYPO3.CMS.git] / typo3 / sysext / compatibility6 / Classes / Controller / Wizard / FormsController.php
1 <?php
2 namespace TYPO3\CMS\Compatibility6\Controller\Wizard;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ServerRequestInterface;
18 use Psr\Http\Message\ResponseInterface;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20 use TYPO3\CMS\Core\Imaging\Icon;
21 use TYPO3\CMS\Core\Imaging\IconFactory;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23
24 /**
25 * API comments:
26 *
27 * The form wizard can help you to create forms - it allows you to create almost any kind of HTML form elements and in any order and amount.
28 *
29 * The format for the resulting configuration code can be either a line-based configuration. That can look like this:
30 *
31 * Your name: | *name=input | (input your name here!)
32 * Your Email: | *email=input
33 * Your address: | address=textarea,40,10
34 * Your Haircolor: | hair=radio |
35 * upload | attachment=file
36 * | quoted_printable=hidden | 0
37 * | formtype_mail=submit | Send form
38 * | html_enabled=hidden
39 * | subject=hidden | This is the subject
40 *
41 *
42 * Alternatively it can be XML. The same configuration from above looks like this in XML:
43 *
44 * <T3FormWizard>
45 * <n2>
46 * <type>input</type>
47 * <label>Your name:</label>
48 * <required>1</required>
49 * <fieldname>name</fieldname>
50 * <size></size>
51 * <max></max>
52 * <default>(input your name here!)</default>
53 * </n2>
54 * <n4>
55 * <type>input</type>
56 * <label>Your Email:</label>
57 * <required>1</required>
58 * <fieldname>email</fieldname>
59 * <size></size>
60 * <max></max>
61 * <default></default>
62 * </n4>
63 * <n6>
64 * <type>textarea</type>
65 * <label>Your address:</label>
66 * <fieldname>address</fieldname>
67 * <cols>40</cols>
68 * <rows>10</rows>
69 * <default></default>
70 * </n6>
71 * <n8>
72 * <type>radio</type>
73 * <label>Your Haircolor:</label>
74 * <fieldname>hair</fieldname>
75 * <options></options>
76 * </n8>
77 * <n10>
78 * <type>file</type>
79 * <label>upload</label>
80 * <fieldname>attachment</fieldname>
81 * <size></size>
82 * </n10>
83 * <n12>
84 * <type>hidden</type>
85 * <label></label>
86 * <fieldname>quoted_printable</fieldname>
87 * <default>0</default>
88 * </n12>
89 * <n2000>
90 * <fieldname>formtype_mail</fieldname>
91 * <type>submit</type>
92 * <default>Send form</default>
93 * </n2000>
94 * <n2002>
95 * <fieldname>html_enabled</fieldname>
96 * <type>hidden</type>
97 * </n2002>
98 * <n2004>
99 * <fieldname>subject</fieldname>
100 * <type>hidden</type>
101 * <default>This is the subject</default>
102 * </n2004>
103 * <n20>
104 * <content></content>
105 * </n20>
106 * </T3FormWizard>
107 *
108 *
109 * The XML/phpArray structure is the internal format of the wizard.
110 */
111 class FormsController extends \TYPO3\CMS\Backend\Controller\Wizard\AbstractWizardController
112 {
113 /**
114 * document template object
115 *
116 * @var \TYPO3\CMS\Backend\Template\DocumentTemplate
117 */
118 public $doc;
119
120 /**
121 * Content accumulation for the module.
122 *
123 * @var string
124 */
125 public $content;
126
127 /**
128 * Used to numerate attachments automatically.
129 *
130 * @var int
131 */
132 public $attachmentCounter = 0;
133
134 /**
135 * If set, the string version of the content is interpreted/written as XML instead of
136 * the original linebased kind. This variable still needs binding to the wizard parameters
137 * - but support is ready!
138 *
139 * @var int
140 */
141 public $xmlStorage = 0;
142
143 /**
144 * Wizard parameters, coming from TCEforms linking to the wizard.
145 *
146 * @var array
147 */
148 public $P;
149
150 /**
151 * The array which is constantly submitted by the multidimensional form of this wizard.
152 *
153 * @var array
154 */
155 public $FORMCFG;
156
157 /**
158 * Indicates if the form is of a dedicated type, like "formtype_mail" (for tt_content element "Form")
159 *
160 * @var string
161 */
162 public $special;
163
164 /**
165 * @var IconFactory
166 */
167 protected $iconFactory;
168
169 /**
170 * Constructor
171 */
172 public function __construct()
173 {
174 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
175 $this->getLanguageService()->includeLLFile('EXT:compatibility6/Resources/Private/Language/locallang_wizards.xlf');
176 $GLOBALS['SOBE'] = $this;
177
178 $this->init();
179 }
180
181 /**
182 * Initialization the class
183 *
184 * @return void
185 */
186 protected function init()
187 {
188 // GPvars:
189 $this->P = GeneralUtility::_GP('P');
190 $this->special = GeneralUtility::_GP('special');
191 $this->FORMCFG = GeneralUtility::_GP('FORMCFG');
192 // Setting options:
193 $this->xmlStorage = $this->P['params']['xmlOutput'];
194 // Document template object:
195 $this->doc = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Template\DocumentTemplate::class);
196 $this->doc->setModuleTemplate('EXT:compatibility6/Resources/Private/Templates/Wizard/Forms.html');
197 // Setting form tag:
198 list($rUri) = explode('#', GeneralUtility::getIndpEnv('REQUEST_URI'));
199 $this->doc->form = '<form action="' . htmlspecialchars($rUri) . '" method="post" name="wizardForm">';
200 }
201
202 /**
203 * Injects the request object for the current request or subrequest
204 * As this controller goes only through the main() method, it is rather simple for now
205 *
206 * @param ServerRequestInterface $request the current request
207 * @param ResponseInterface $response
208 * @return ResponseInterface the response with the content
209 */
210 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
211 {
212 $this->main();
213
214 $response->getBody()->write($this->content);
215 return $response;
216 }
217
218 /**
219 * Main function for rendering the form wizard HTML
220 *
221 * @return void
222 */
223 public function main()
224 {
225 if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
226 $this->content .= '<h2>' . $this->getLanguageService()->getLL('forms_title', true) . '</h2><div>' . $this->formsWizard() . '</div>';
227 } else {
228 $this->content .= '<h2>' . $this->getLanguageService()->getLL('forms_title', true) . '<div><span class="text-danger">' . $this->getLanguageService()->getLL('table_noData', true) . '</span></div>';
229 }
230 // Setting up the buttons and markers for docheader
231 $docHeaderButtons = $this->getButtons();
232 $markers['CSH'] = $docHeaderButtons['csh'];
233 $markers['CONTENT'] = $this->content;
234 // Build the <body> for the module
235 $this->content = $this->doc->startPage('Form Wizard');
236 $this->content .= $this->doc->moduleBody($this->pageinfo, $docHeaderButtons, $markers);
237 $this->content .= $this->doc->endPage();
238 $this->content = $this->doc->insertStylesAndJS($this->content);
239 }
240
241 /**
242 * Outputting the accumulated content to screen
243 *
244 * @return void
245 * @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8, use mainAction() instead
246 */
247 public function printContent()
248 {
249 GeneralUtility::logDeprecatedFunction();
250 echo $this->content;
251 }
252
253 /**
254 * Create the panel of buttons for submitting the form or otherwise perform operations.
255 *
256 * @return array All available buttons as an assoc. array
257 */
258 protected function getButtons()
259 {
260 $buttons = array(
261 'csh' => '',
262 'csh_buttons' => '',
263 'close' => '',
264 'save' => '',
265 'save_close' => '',
266 'reload' => ''
267 );
268 if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
269 // CSH
270 $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'wizard_forms_wiz');
271 // CSH Buttons
272 $buttons['csh_buttons'] = BackendUtility::cshItem('xMOD_csh_corebe', 'wizard_forms_wiz_buttons');
273 // Close
274 $buttons['close'] = '<button class="c-inputButton" name="closedok" value="1" title=' . $this->getLanguageService()->sL('LLL:EXT:compatibility6/Resources/Private/Language/locallang.xlf:closeDoc', true) . '>' . $this->iconFactory->getIcon('actions-document-close', Icon::SIZE_SMALL)->render() . '</button>';
275 // Save
276 $buttons['save'] = '<button class="c-inputButton" name="savedok" value="1" title=' . $this->getLanguageService()->sL('LLL:EXT:compatibility6/Resources/Private/Language/locallang.xlf:saveDoc', true) . '>' . $this->iconFactory->getIcon('actions-document-save', Icon::SIZE_SMALL)->render() . '</button>';
277 // Save & Close
278 $buttons['save_close'] = '<button class="c-inputButton" name="saveandclosedok" value="1" title=' . $this->getLanguageService()->sL('LLL:EXT:compatibility6/Resources/Private/Language/locallang.xlf:saveCloseDoc', true) . '>' . $this->iconFactory->getIcon('actions-document-save-close', Icon::SIZE_SMALL)->render() . '</button>';
279 // Reload
280 $buttons['reload'] = '<button class="c-inputButton" name="_refresh" value="1" title="' . $this->getLanguageService()->getLL('forms_refresh', true) . '">' . $this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)->render() . '</button>';
281 }
282 return $buttons;
283 }
284
285 /**
286 * Draws the form wizard content
287 *
288 * @return string HTML content for the form.
289 */
290 public function formsWizard()
291 {
292 if (!$this->checkEditAccess($this->P['table'], $this->P['uid'])) {
293 throw new \RuntimeException('Wizard Error: No access', 1385807526);
294 }
295 // First, check the references by selecting the record:
296 $row = BackendUtility::getRecord($this->P['table'], $this->P['uid']);
297 if (!is_array($row)) {
298 throw new \RuntimeException('Wizard Error: No reference to record', 1294587124);
299 }
300 // This will get the content of the form configuration code field to us - possibly
301 // cleaned up, saved to database etc. if the form has been submitted in the meantime.
302 $formCfgArray = $this->getConfigCode($row);
303 // Generation of the Form Wizards HTML code:
304 $content = $this->getFormHTML($formCfgArray, $row);
305 // Return content:
306 return $content;
307 }
308
309 /****************************
310 *
311 * Helper functions
312 *
313 ***************************/
314 /**
315 * Will get and return the configuration code string
316 * Will also save (and possibly redirect/exit) the content if a save button has been pressed
317 *
318 * @param array $row Current parent record row (passed by value!)
319 * @return array Configuration Array
320 * @access private
321 */
322 public function getConfigCode(&$row)
323 {
324 // If some data has been submitted, then construct
325 if (isset($this->FORMCFG['c'])) {
326 // Process incoming:
327 $this->changeFunc();
328 // Convert to string (either line based or XML):
329 if ($this->xmlStorage) {
330 // Convert the input array to XML:
331 $bodyText = GeneralUtility::array2xml_cs($this->FORMCFG['c'], 'T3FormWizard');
332 // Setting cfgArr directly from the input:
333 $cfgArr = $this->FORMCFG['c'];
334 } else {
335 // Convert the input array to a string of configuration code:
336 $bodyText = $this->cfgArray2CfgString($this->FORMCFG['c']);
337 // Create cfgArr from the string based configuration - that way it is cleaned
338 // up and any incompatibilities will be removed!
339 $cfgArr = $this->cfgString2CfgArray($bodyText);
340 }
341 // If a save button has been pressed, then save the new field content:
342 if (isset($_POST['savedok']) || isset($_POST['saveandclosedok'])) {
343 // Make TCEmain object:
344 $tce = GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class);
345 $tce->stripslashes_values = 0;
346 // Put content into the data array:
347 $data = array();
348 $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $bodyText;
349 if ($this->special == 'formtype_mail') {
350 $data[$this->P['table']][$this->P['uid']]['subheader'] = $this->FORMCFG['recipient'];
351 }
352 // Perform the update:
353 $tce->start($data, array());
354 $tce->process_datamap();
355 // Re-load the record content:
356 $row = BackendUtility::getRecord($this->P['table'], $this->P['uid']);
357 }
358 // If the save/close or close button was pressed, then redirect the screen:
359 if (isset($_POST['saveandclosedok']) || isset($_POST['closedok'])) {
360 \TYPO3\CMS\Core\Utility\HttpUtility::redirect(GeneralUtility::sanitizeLocalUrl($this->P['returnUrl']));
361 }
362 } else {
363 // If nothing has been submitted, load the $bodyText variable from the selected database row:
364 if ($this->xmlStorage) {
365 $cfgArr = GeneralUtility::xml2array($row[$this->P['field']]);
366 } else {
367 // Regular linebased form configuration:
368 $cfgArr = $this->cfgString2CfgArray($row[$this->P['field']]);
369 }
370 $cfgArr = is_array($cfgArr) ? $cfgArr : array();
371 }
372 // Return configuration code:
373 return $cfgArr;
374 }
375
376 /**
377 * Creates the HTML for the Form Wizard:
378 *
379 * @param string $formCfgArray Form config array
380 * @param array $row Current parent record array
381 * @return string HTML for the form wizard
382 * @access private
383 */
384 public function getFormHTML($formCfgArray, $row)
385 {
386 // Initialize variables:
387 $specParts = array();
388 $hiddenFields = array();
389 $tRows = array();
390 // Set header row:
391 $cells = array(
392 $this->getLanguageService()->getLL('forms_preview', true) . ':',
393 $this->getLanguageService()->getLL('forms_element', true) . ':',
394 $this->getLanguageService()->getLL('forms_config', true) . ':'
395 );
396 $tRows[] = '
397 <tr id="typo3-formWizardHeader">
398 <th>&nbsp;</th>
399 <th><strong>' . implode('</strong></th>
400 <th><strong>', $cells) . '</strong></th>
401 </tr>';
402 // Traverse the number of form elements:
403 $k = 0;
404 foreach ($formCfgArray as $confData) {
405 // Initialize:
406 $cells = array();
407 // If there is a configuration line which is active, then render it:
408 if (!isset($confData['comment'])) {
409 // Special parts:
410 if ($this->special == 'formtype_mail' && GeneralUtility::inList('formtype_mail,subject,html_enabled', $confData['fieldname'])) {
411 $specParts[$confData['fieldname']] = $confData['default'];
412 } else {
413 // Render title/field preview COLUMN
414 $cells[] = $confData['type'] != 'hidden' ? '<strong>' . htmlspecialchars($confData['label']) . '</strong>' : '';
415 // Render general type/title COLUMN:
416 $temp_cells = array();
417 // Field type selector:
418 $opt = array();
419 $opt[] = '<option value=""></option>';
420 $types = explode(',', 'input,textarea,select,check,radio,password,file,hidden,submit,property,label');
421 foreach ($types as $t) {
422 $opt[] = '
423 <option value="' . $t . '"' . ($confData['type'] == $t ? ' selected="selected"' : '') . '>' . $this->getLanguageService()->getLL(('forms_type_' . $t), true) . '</option>';
424 }
425 $temp_cells[$this->getLanguageService()->getLL('forms_type')] = '
426 <select name="FORMCFG[c][' . ($k + 1) * 2 . '][type]">
427 ' . implode('
428 ', $opt) . '
429 </select>';
430 // Title field:
431 if (!GeneralUtility::inList('hidden,submit', $confData['type'])) {
432 $temp_cells[$this->getLanguageService()->getLL('forms_label')] = '<input type="text"' . $this->doc->formWidth(15) . ' name="FORMCFG[c][' . ($k + 1) * 2 . '][label]" value="' . htmlspecialchars($confData['label']) . '" />';
433 }
434 // Required checkbox:
435 if (!GeneralUtility::inList('check,hidden,submit,label', $confData['type'])) {
436 $temp_cells[$this->getLanguageService()->getLL('forms_required')] = '<input type="checkbox" name="FORMCFG[c][' . ($k + 1) * 2 . '][required]" value="1"' . ($confData['required'] ? ' checked="checked"' : '') . ' title="' . $this->getLanguageService()->getLL('forms_required', true) . '" />';
437 }
438 // Put sub-items together into table cell:
439 $cells[] = $this->formatCells($temp_cells);
440 // Render specific field configuration COLUMN:
441 $temp_cells = array();
442 // Fieldname
443 if ($this->special == 'formtype_mail' && $confData['type'] == 'file') {
444 $confData['fieldname'] = 'attachment' . ++$this->attachmentCounter;
445 }
446 if (!GeneralUtility::inList('label', $confData['type'])) {
447 $temp_cells[$this->getLanguageService()->getLL('forms_fieldName')] = '<input type="text"' . $this->doc->formWidth(10) . ' name="FORMCFG[c][' . ($k + 1) * 2 . '][fieldname]" value="' . htmlspecialchars($confData['fieldname']) . '" title="' . $this->getLanguageService()->getLL('forms_fieldName', true) . '" />';
448 }
449 // Field configuration depending on the fields type:
450 switch ((string)$confData['type']) {
451 case 'textarea':
452 $temp_cells[$this->getLanguageService()->getLL('forms_cols')] = '<input type="text"' . $this->doc->formWidth(5) . ' name="FORMCFG[c][' . ($k + 1) * 2 . '][cols]" value="' . htmlspecialchars($confData['cols']) . '" title="' . $this->getLanguageService()->getLL('forms_cols', true) . '" />';
453 $temp_cells[$this->getLanguageService()->getLL('forms_rows')] = '<input type="text"' . $this->doc->formWidth(5) . ' name="FORMCFG[c][' . ($k + 1) * 2 . '][rows]" value="' . htmlspecialchars($confData['rows']) . '" title="' . $this->getLanguageService()->getLL('forms_rows', true) . '" />';
454 $temp_cells[$this->getLanguageService()->getLL('forms_extra')] = '<input type="checkbox" name="FORMCFG[c][' . ($k + 1) * 2 . '][extra]" value="OFF"' . ($confData['extra'] == 'OFF' ? ' checked="checked"' : '') . ' title="' . $this->getLanguageService()->getLL('forms_extra', true) . '" />';
455 break;
456 case 'input':
457
458 case 'password':
459 $temp_cells[$this->getLanguageService()->getLL('forms_size')] = '<input type="text"' . $this->doc->formWidth(5) . ' name="FORMCFG[c][' . ($k + 1) * 2 . '][size]" value="' . htmlspecialchars($confData['size']) . '" title="' . $this->getLanguageService()->getLL('forms_size', true) . '" />';
460 $temp_cells[$this->getLanguageService()->getLL('forms_max')] = '<input type="text"' . $this->doc->formWidth(5) . ' name="FORMCFG[c][' . ($k + 1) * 2 . '][max]" value="' . htmlspecialchars($confData['max']) . '" title="' . $this->getLanguageService()->getLL('forms_max', true) . '" />';
461 break;
462 case 'file':
463 $temp_cells[$this->getLanguageService()->getLL('forms_size')] = '<input type="text"' . $this->doc->formWidth(5) . ' name="FORMCFG[c][' . ($k + 1) * 2 . '][size]" value="' . htmlspecialchars($confData['size']) . '" title="' . $this->getLanguageService()->getLL('forms_size', true) . '" />';
464 break;
465 case 'select':
466 $temp_cells[$this->getLanguageService()->getLL('forms_size')] = '<input type="text"' . $this->doc->formWidth(5) . ' name="FORMCFG[c][' . ($k + 1) * 2 . '][size]" value="' . htmlspecialchars($confData['size']) . '" title="' . $this->getLanguageService()->getLL('forms_size', true) . '" />';
467 $temp_cells[$this->getLanguageService()->getLL('forms_autosize')] = '<input type="checkbox" name="FORMCFG[c][' . ($k + 1) * 2 . '][autosize]" value="1"' . ($confData['autosize'] ? ' checked="checked"' : '') . ' title="' . $this->getLanguageService()->getLL('forms_autosize', true) . '" />';
468 $temp_cells[$this->getLanguageService()->getLL('forms_multiple')] = '<input type="checkbox" name="FORMCFG[c][' . ($k + 1) * 2 . '][multiple]" value="1"' . ($confData['multiple'] ? ' checked="checked"' : '') . ' title="' . $this->getLanguageService()->getLL('forms_multiple', true) . '" />';
469 break;
470 }
471 // Field configuration depending on the fields type:
472 switch ((string)$confData['type']) {
473 case 'textarea':
474
475 case 'input':
476
477 case 'password':
478 if (trim($confData['specialEval']) !== '') {
479 $hiddenFields[] = '<input type="hidden" name="FORMCFG[c][' . ($k + 1) * 2 . '][specialEval]" value="' . htmlspecialchars($confData['specialEval']) . '" />';
480 }
481 break;
482 }
483 // Default data
484 if ($confData['type'] == 'select' || $confData['type'] == 'radio') {
485 $temp_cells[$this->getLanguageService()->getLL('forms_options')] = '<textarea ' . $this->doc->formWidth(15) . ' rows="4" name="FORMCFG[c][' . ($k + 1) * 2 . '][options]" title="' . $this->getLanguageService()->getLL('forms_options', true) . '">' . htmlspecialchars($confData['default']) . '</textarea>';
486 } elseif ($confData['type'] == 'check') {
487 $temp_cells[$this->getLanguageService()->getLL('forms_checked')] = '<input type="checkbox" name="FORMCFG[c][' . ($k + 1) * 2 . '][default]" value="1"' . (trim($confData['default']) ? ' checked="checked"' : '') . ' title="' . $this->getLanguageService()->getLL('forms_checked', true) . '" />';
488 } elseif ($confData['type'] && $confData['type'] != 'file') {
489 $temp_cells[$this->getLanguageService()->getLL('forms_default')] = '<input type="text"' . $this->doc->formWidth(15) . ' name="FORMCFG[c][' . ($k + 1) * 2 . '][default]" value="' . htmlspecialchars($confData['default']) . '" title="' . $this->getLanguageService()->getLL('forms_default', true) . '" />';
490 }
491 $cells[] = $confData['type'] ? $this->formatCells($temp_cells) : '';
492 // CTRL panel for an item (move up/down/around):
493 $ctrl = '';
494 $onClick = 'document.wizardForm.action+=\'#ANC_' . (($k + 1) * 2 - 2) . '\';';
495 $onClick = ' onclick="' . htmlspecialchars($onClick) . '"';
496 // @todo $inputStyle undefined
497 $brTag = $inputStyle ? '' : '<br />';
498 if ($k != 1) {
499 $ctrl .= '<button name="FORMCFG[row_top][' . ($k + 1) * 2 . ']"' . $onClick . ' title="' . $this->getLanguageService()->getLL('table_top', true) . '">' . $this->iconFactory->getIcon('actions-move-to-top', Icon::SIZE_SMALL)->render() . '</button>' . $brTag;
500 $ctrl .= '<button name="FORMCFG[row_up][' . ($k + 1) * 2 . ']"' . $onClick . ' title="' . $this->getLanguageService()->getLL('table_up', true) . '">' . $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render() . '</button>' . $brTag;
501 }
502 $ctrl .= '<button name="FORMCFG[row_remove][' . ($k + 1) * 2 . ']" ' . $onClick . ' title = "' . $this->getLanguageService()->getLL('table_removeRow', true) . '">' . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</button>' . $brTag;
503
504 if ($k != (count($formCfgArray)/2)) {
505 $ctrl .= '<button name="FORMCFG[row_down][' . ($k + 1) * 2 . ']"' . $onClick . ' title="' . $this->getLanguageService()->getLL('table_down', true) . '">' . $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render() . '</button>' . $brTag;
506 $ctrl .= '<button name="FORMCFG[row_bottom][' . ($k + 1) * 2 . ']"' . $onClick . ' title="' . $this->getLanguageService()->getLL('table_bottom', true) . '">' . $this->iconFactory->getIcon('actions-move-to-bottom', Icon::SIZE_SMALL)->render() . '</button>' . $brTag;
507 }
508
509 $ctrl .= '<button name="FORMCFG[row_add][' . ($k + 1) * 2 . ']"' . $onClick . ' title="' . $this->getLanguageService()->getLL('table_addRow', true) . '">' . $this->iconFactory->getIcon('actions-template-new', Icon::SIZE_SMALL)->render() . '</button>' . $brTag;
510 $ctrl = '<span class="c-wizButtonsV">' . $ctrl . '</span>';
511 // Finally, put together the full row from the generated content above:
512 $tRows[] = '
513 <tr>
514 <td><a name="ANC_' . ($k + 1) * 2 . '"></a>' . $ctrl . '</td>
515 <td>' . implode('</td>
516 <td valign="top">', $cells) . '</td>
517 </tr>';
518 }
519 } else {
520 $hiddenFields[] = '<input type="hidden" name="FORMCFG[c][' . ($k + 1) * 2 . '][comment]" value="' . htmlspecialchars($confData['comment']) . '" />';
521 }
522 // Increment counter:
523 $k++;
524 }
525 // If the form is of the special type "formtype_mail" (used for tt_content elements):
526 if ($this->special == 'formtype_mail') {
527 // Blank spacer:
528 $tRows[] = '
529 <tr>
530 <td colspan="4">&nbsp;</td>
531 </tr>';
532 // Header:
533 $tRows[] = '
534 <tr>
535 <th colspan="4"><h4>' . $this->getLanguageService()->getLL('forms_special_eform', true) . ': ' . BackendUtility::cshItem('xMOD_csh_corebe', 'wizard_forms_wiz_formmail_info') . '</h4></th>
536 </tr>';
537 // "FORM type":
538 $tRows[] = '
539 <tr>
540 <td colspan="2">&nbsp;</td>
541 <td>' . $this->getLanguageService()->getLL('forms_eform_formtype_mail', true) . ':</td>
542 <td>
543 <input type="hidden" name="FORMCFG[c][' . 1000 * 2 . '][fieldname]" value="formtype_mail" />
544 <input type="hidden" name="FORMCFG[c][' . 1000 * 2 . '][type]" value="submit" />
545 <input type="text"' . $this->doc->formWidth(15) . ' name="FORMCFG[c][' . 1000 * 2 . '][default]" value="' . htmlspecialchars($specParts['formtype_mail']) . '" />
546 </td>
547 </tr>';
548 // "Send HTML mail":
549 $tRows[] = '
550 <tr>
551 <td colspan="2">&nbsp;</td>
552 <td>' . $this->getLanguageService()->getLL('forms_eform_html_enabled', true) . ':</td>
553 <td>
554 <input type="hidden" name="FORMCFG[c][' . 1001 * 2 . '][fieldname]" value="html_enabled" />
555 <input type="hidden" name="FORMCFG[c][' . 1001 * 2 . '][type]" value="hidden" />
556 <input type="checkbox" name="FORMCFG[c][' . 1001 * 2 . '][default]" value="1"' . ($specParts['html_enabled'] ? ' checked="checked"' : '') . ' />
557 </td>
558 </tr>';
559 // "Subject":
560 $tRows[] = '
561 <tr>
562 <td colspan="2">&nbsp;</td>
563 <td>' . $this->getLanguageService()->getLL('forms_eform_subject', true) . ':</td>
564 <td>
565 <input type="hidden" name="FORMCFG[c][' . 1002 * 2 . '][fieldname]" value="subject" />
566 <input type="hidden" name="FORMCFG[c][' . 1002 * 2 . '][type]" value="hidden" />
567 <input type="text"' . $this->doc->formWidth(15) . ' name="FORMCFG[c][' . 1002 * 2 . '][default]" value="' . htmlspecialchars($specParts['subject']) . '" />
568 </td>
569 </tr>';
570 // Recipient:
571 $tRows[] = '
572 <tr>
573 <td colspan="2">&nbsp;</td>
574 <td>' . $this->getLanguageService()->getLL('forms_eform_recipient', true) . ':</td>
575 <td>
576 <input type="text"' . $this->doc->formWidth(15) . ' name="FORMCFG[recipient]" value="' . htmlspecialchars($row['subheader']) . '" />
577 </td>
578 </tr>';
579 }
580 $content = '';
581 // Implode all table rows into a string, wrapped in table tags.
582 $content .= '
583
584 <!--
585 Form wizard
586 -->
587 <table class="table table-bordered table-condensed" id="typo3-formwizard">
588 ' . implode('', $tRows) . '
589 </table>';
590 // Add hidden fields:
591 $content .= implode('', $hiddenFields);
592 // Return content:
593 return $content;
594 }
595
596 /**
597 * Detects if a control button (up/down/around/delete) has been pressed for an item and accordingly it will manipulate the internal FORMCFG array
598 *
599 * @return void
600 * @access private
601 */
602 public function changeFunc()
603 {
604 if ($this->FORMCFG['row_remove']) {
605 $kk = key($this->FORMCFG['row_remove']);
606 $cmd = 'row_remove';
607 } elseif ($this->FORMCFG['row_add']) {
608 $kk = key($this->FORMCFG['row_add']);
609 $cmd = 'row_add';
610 } elseif ($this->FORMCFG['row_top']) {
611 $kk = key($this->FORMCFG['row_top']);
612 $cmd = 'row_top';
613 } elseif ($this->FORMCFG['row_bottom']) {
614 $kk = key($this->FORMCFG['row_bottom']);
615 $cmd = 'row_bottom';
616 } elseif ($this->FORMCFG['row_up']) {
617 $kk = key($this->FORMCFG['row_up']);
618 $cmd = 'row_up';
619 } elseif ($this->FORMCFG['row_down']) {
620 $kk = key($this->FORMCFG['row_down']);
621 $cmd = 'row_down';
622 }
623 if ($cmd && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($kk)) {
624 if (substr($cmd, 0, 4) == 'row_') {
625 switch ($cmd) {
626 case 'row_remove':
627 unset($this->FORMCFG['c'][$kk]);
628 break;
629 case 'row_add':
630 $this->FORMCFG['c'][$kk + 1] = array();
631 break;
632 case 'row_top':
633 $this->FORMCFG['c'][1] = $this->FORMCFG['c'][$kk];
634 unset($this->FORMCFG['c'][$kk]);
635 break;
636 case 'row_bottom':
637 $this->FORMCFG['c'][1000000] = $this->FORMCFG['c'][$kk];
638 unset($this->FORMCFG['c'][$kk]);
639 break;
640 case 'row_up':
641 $this->FORMCFG['c'][$kk - 3] = $this->FORMCFG['c'][$kk];
642 unset($this->FORMCFG['c'][$kk]);
643 break;
644 case 'row_down':
645 $this->FORMCFG['c'][$kk + 3] = $this->FORMCFG['c'][$kk];
646 unset($this->FORMCFG['c'][$kk]);
647 break;
648 }
649 ksort($this->FORMCFG['c']);
650 }
651 }
652 }
653
654 /**
655 * Converts the input array to a configuration code string
656 *
657 * @param array $cfgArr Array of form configuration (follows the input structure from the form wizard POST form)
658 * @return string The array converted into a string with line-based configuration.
659 * @see cfgString2CfgArray()
660 */
661 public function cfgArray2CfgString($cfgArr)
662 {
663 // Initialize:
664 $inLines = array();
665 // Traverse the elements of the form wizard and transform the settings into configuration code.
666 foreach ($cfgArr as $vv) {
667 // If "content" is found, then just pass it over.
668 if ($vv['comment']) {
669 $inLines[] = trim($vv['comment']);
670 } else {
671 // Begin to put together the single-line configuration code of this field:
672 // Reset:
673 $thisLine = array();
674 // Set Label:
675 $thisLine[0] = str_replace('|', '', $vv['label']);
676 // Set Type:
677 if ($vv['type']) {
678 $thisLine[1] = ($vv['required'] ? '*' : '') . str_replace(',', '', (($vv['fieldname'] ? $vv['fieldname'] . '=' : '') . $vv['type']));
679 // Default:
680 $tArr = array('', '', '', '', '', '');
681 switch ((string)$vv['type']) {
682 case 'textarea':
683 if ((int)$vv['cols']) {
684 $tArr[0] = (int)$vv['cols'];
685 }
686 if ((int)$vv['rows']) {
687 $tArr[1] = (int)$vv['rows'];
688 }
689 if (trim($vv['extra'])) {
690 $tArr[2] = trim($vv['extra']);
691 }
692 if ($vv['specialEval'] !== '') {
693 // Preset blank default value so position 3 can get a value...
694 $thisLine[2] = '';
695 $thisLine[3] = $vv['specialEval'];
696 }
697 break;
698 case 'input':
699 case 'password':
700 if ((int)$vv['size']) {
701 $tArr[0] = (int)$vv['size'];
702 }
703 if ((int)$vv['max']) {
704 $tArr[1] = (int)$vv['max'];
705 }
706 if ($vv['specialEval'] !== '') {
707 // Preset blank default value so position 3 can get a value...
708 $thisLine[2] = '';
709 $thisLine[3] = $vv['specialEval'];
710 }
711 break;
712 case 'file':
713 if ((int)$vv['size']) {
714 $tArr[0] = (int)$vv['size'];
715 }
716 break;
717 case 'select':
718 if ((int)$vv['size']) {
719 $tArr[0] = (int)$vv['size'];
720 }
721 if ($vv['autosize']) {
722 $tArr[0] = 'auto';
723 }
724 if ($vv['multiple']) {
725 $tArr[1] = 'm';
726 }
727 break;
728 }
729 $tArr = $this->cleanT($tArr);
730 if (!empty($tArr)) {
731 $thisLine[1] .= ',' . implode(',', $tArr);
732 }
733 $thisLine[1] = str_replace('|', '', $thisLine[1]);
734 // Default:
735 if ($vv['type'] == 'select' || $vv['type'] == 'radio') {
736 $options = str_replace(',', '', $vv['options']);
737 $options = str_replace(
738 array(CRLF, CR, LF),
739 ', ',
740 $options);
741 $thisLine[2] = $options;
742 } elseif ($vv['type'] == 'check') {
743 if ($vv['default']) {
744 $thisLine[2] = 1;
745 }
746 } elseif (trim($vv['default']) !== '') {
747 $thisLine[2] = $vv['default'];
748 }
749 if (isset($thisLine[2])) {
750 $thisLine[2] = str_replace('|', '', $thisLine[2]);
751 }
752 }
753 // Compile the final line:
754 $inLines[] = preg_replace('/[
755
756 ]*/', '', implode(' | ', $thisLine));
757 }
758 }
759 // Finally, implode the lines into a string, and return it:
760 return implode(LF, $inLines);
761 }
762
763 /**
764 * Converts the input configuration code string into an array
765 *
766 * @param string $cfgStr Configuration code
767 * @return array Configuration array
768 * @see cfgArray2CfgString()
769 */
770 public function cfgString2CfgArray($cfgStr)
771 {
772 // Traverse the number of form elements:
773 $tLines = explode(LF, $cfgStr);
774 $attachmentCounter = 0;
775 foreach ($tLines as $k => $v) {
776 // Initialize:
777 $confData = array();
778 $val = trim($v);
779 // Accept a line as configuration if a) it is blank(! - because blank lines indicates new,
780 // unconfigured fields) or b) it is NOT a comment.
781 if (!$val || strcspn($val, '#/')) {
782 // Split:
783 $parts = GeneralUtility::trimExplode('|', $val);
784 // Label:
785 $confData['label'] = trim($parts[0]);
786 // Field:
787 $fParts = GeneralUtility::trimExplode(',', $parts[1]);
788 $fParts[0] = trim($fParts[0]);
789 if ($fParts[0][0] === '*') {
790 $confData['required'] = 1;
791 $fParts[0] = substr($fParts[0], 1);
792 }
793 $typeParts = GeneralUtility::trimExplode('=', $fParts[0]);
794 $confData['type'] = trim(strtolower(end($typeParts)));
795 if ($confData['type']) {
796 if (count($typeParts) === 1) {
797 $confData['fieldname'] = substr(preg_replace('/[^a-zA-Z0-9_]/', '', str_replace(' ', '_', trim($parts[0]))), 0, 30);
798 // Attachment names...
799 if ($confData['type'] == 'file') {
800 $confData['fieldname'] = 'attachment' . $attachmentCounter;
801 $attachmentCounter = (int)$attachmentCounter + 1;
802 }
803 } else {
804 $confData['fieldname'] = str_replace(' ', '_', trim($typeParts[0]));
805 }
806 switch ((string)$confData['type']) {
807 case 'select':
808 case 'radio':
809 $confData['default'] = implode(LF, GeneralUtility::trimExplode(',', $parts[2]));
810 break;
811 default:
812 $confData['default'] = trim($parts[2]);
813 }
814 // Field configuration depending on the fields type:
815 switch ((string)$confData['type']) {
816 case 'textarea':
817 $confData['cols'] = $fParts[1];
818 $confData['rows'] = $fParts[2];
819 $confData['extra'] = strtoupper($fParts[3]) == 'OFF' ? 'OFF' : '';
820 $confData['specialEval'] = trim($parts[3]);
821 break;
822 case 'input':
823 case 'password':
824 $confData['size'] = $fParts[1];
825 $confData['max'] = $fParts[2];
826 $confData['specialEval'] = trim($parts[3]);
827 break;
828 case 'file':
829 $confData['size'] = $fParts[1];
830 break;
831 case 'select':
832 $confData['size'] = (int)$fParts[1] ? $fParts[1] : '';
833 $confData['autosize'] = strtolower(trim($fParts[1])) === 'auto' ? 1 : 0;
834 $confData['multiple'] = strtolower(trim($fParts[2])) === 'm' ? 1 : 0;
835 break;
836 }
837 }
838 } else {
839 // No configuration, only a comment:
840 $confData = array(
841 'comment' => $val
842 );
843 }
844 // Adding config array:
845 $cfgArr[] = $confData;
846 }
847 // Return cfgArr
848 return $cfgArr;
849 }
850
851 /**
852 * Removes any "trailing elements" in the array which consists of whitespace (little like trim() does for strings, so this does for arrays)
853 *
854 * @param array $tArr Single dim array
855 * @return array Processed array
856 * @access private
857 */
858 public function cleanT($tArr)
859 {
860 for ($a = count($tArr); $a > 0; $a--) {
861 if ((string)$tArr[$a - 1] !== '') {
862 break;
863 } else {
864 unset($tArr[$a - 1]);
865 }
866 }
867 return $tArr;
868 }
869
870 /**
871 * Wraps items in $fArr in table cells/rows, displaying them vertically.
872 *
873 * @param array $fArr Array of label/HTML pairs.
874 * @return string HTML table
875 * @access private
876 */
877 public function formatCells($fArr)
878 {
879 // Traverse the elements in $fArr and wrap them in table cells:
880 $lines = array();
881 foreach ($fArr as $l => $c) {
882 $lines[] = '
883 <tr>
884 <td nowrap="nowrap">' . htmlspecialchars(($l . ':')) . '&nbsp;</td>
885 <td>' . $c . '</td>
886 </tr>';
887 }
888 $lines[] = '
889 <tr>
890 <td colspan="2"></td>
891 </tr>';
892 // Wrap in table and return:
893 return '
894 <table>
895 ' . implode('', $lines) . '
896 </table>';
897 }
898 }