TYPO3  7.6
TableController.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Backend\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 
29 
34 {
40  public $content;
41 
47  public $inputStyle = false;
48 
56  public $xmlStorage = 0;
57 
63  public $numNewRows = 1;
64 
71  public $colsFieldName = 'cols';
72 
78  public $P;
79 
85  public $TABLECFG;
86 
94 
101 
105  protected $iconFactory;
106 
110  public function __construct()
111  {
112  parent::__construct();
113  $this->getLanguageService()->includeLLFile('EXT:lang/locallang_wizards.xlf');
114  $GLOBALS['SOBE'] = $this;
115 
116  $this->init();
117  }
118 
124  protected function init()
125  {
126  // GPvars:
127  $this->P = GeneralUtility::_GP('P');
128  $this->TABLECFG = GeneralUtility::_GP('TABLE');
129  // Setting options:
130  $this->xmlStorage = $this->P['params']['xmlOutput'];
131  $this->numNewRows = MathUtility::forceIntegerInRange($this->P['params']['numNewRows'], 1, 50, 5);
132  // Textareas or input fields:
133  $this->inputStyle = isset($this->TABLECFG['textFields']) ? (bool)$this->TABLECFG['textFields'] : true;
134  // Setting form tag:
135  list($rUri) = explode('#', GeneralUtility::getIndpEnv('REQUEST_URI'));
136  $this->tableParsing_delimiter = '|';
137  $this->tableParsing_quote = '';
138  }
139 
149  {
150  $this->main();
151  $response->getBody()->write($this->moduleTemplate->renderContent());
152  return $response;
153  }
154 
160  public function main()
161  {
162  $this->content .= '<form action="' . htmlspecialchars($rUri) . '" method="post" id="TableController" name="wizardForm">';
163  if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
164  $this->content .= '<h2>' . $this->getLanguageService()->getLL('table_title', true) . '</h2>'
165  . '<div>' . $this->tableWizard() . '</div>';
166  } else {
167  $this->content .= '<h2>' . $this->getLanguageService()->getLL('table_title', true) . '</h2>'
168  . '<div><span class="text-danger">' . $this->getLanguageService()->getLL('table_noData', true) . '</span></div>';;
169  }
170  $this->content .= '</form>';
171  // Setting up the buttons and markers for docHeader
172  $this->getButtons();
173  // Build the <body> for the module
174  $this->moduleTemplate->setContent($this->content);
175  }
176 
183  public function printContent()
184  {
186  echo $this->content;
187  }
188 
192  protected function getButtons()
193  {
194  $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
195  if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
196  // CSH
197  $cshButton = $buttonBar->makeHelpButton()
198  ->setModuleName('xMOD_csh_corebe')
199  ->setFieldName('wizard_table_wiz');
200  $buttonBar->addButton($cshButton);
201  // Close
202  $closeButton = $buttonBar->makeLinkButton()
203  ->setHref($this->P['returnUrl'])
204  ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:rm.closeDoc', true))
205  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-close', Icon::SIZE_SMALL));
206  $buttonBar->addButton($closeButton);
207  // Save
208  $saveButton = $buttonBar->makeInputButton()
209  ->setName('_savedok')
210  ->setValue('1')
211  ->setForm('TableController')
212  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
213  ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDoc', true));
214  // Save & Close
215  $saveAndCloseButton = $buttonBar->makeInputButton()
216  ->setName('_saveandclosedok')
217  ->setValue('1')
218  ->setForm('TableController')
219  ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveCloseDoc', true))
220  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
221  'actions-document-save-close',
223  ));
224  $splitButtonElement = $buttonBar->makeSplitButton()
225  ->addItem($saveButton)
226  ->addItem($saveAndCloseButton);
227 
228  $buttonBar->addButton($splitButtonElement, ButtonBar::BUTTON_POSITION_LEFT, 3);
229  // Reload
230  $reloadButton = $buttonBar->makeInputButton()
231  ->setName('_refresh')
232  ->setValue('1')
233  ->setForm('TableController')
234  ->setTitle($this->getLanguageService()->getLL('forms_refresh', true))
235  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-refresh', Icon::SIZE_SMALL));
236  $buttonBar->addButton($reloadButton);
237  }
238  }
239 
246  public function tableWizard()
247  {
248  if (!$this->checkEditAccess($this->P['table'], $this->P['uid'])) {
249  throw new \RuntimeException('Wizard Error: No access', 1349692692);
250  }
251  // First, check the references by selecting the record:
252  $row = BackendUtility::getRecord($this->P['table'], $this->P['uid']);
253  if (!is_array($row)) {
254  throw new \RuntimeException('Wizard Error: No reference to record', 1294587125);
255  }
256  // This will get the content of the form configuration code field to us - possibly cleaned up,
257  // saved to database etc. if the form has been submitted in the meantime.
258  $tableCfgArray = $this->getConfigCode($row);
259  // Generation of the Table Wizards HTML code:
260  $content = $this->getTableHTML($tableCfgArray);
261  // Return content:
262  return $content;
263  }
264 
265  /*
266  *
267  * Helper functions
268  *
269  */
270 
279  public function getConfigCode($row)
280  {
281  // Get delimiter settings
282  $flexForm = GeneralUtility::xml2array($row['pi_flexform']);
283  if (is_array($flexForm)) {
284  $this->tableParsing_quote = $flexForm['data']['s_parsing']['lDEF']['tableparsing_quote']['vDEF'] ? chr((int)$flexForm['data']['s_parsing']['lDEF']['tableparsing_quote']['vDEF']) : '';
285  $this->tableParsing_delimiter = $flexForm['data']['s_parsing']['lDEF']['tableparsing_delimiter']['vDEF'] ? chr((int)$flexForm['data']['s_parsing']['lDEF']['tableparsing_delimiter']['vDEF']) : '|';
286  }
287  // If some data has been submitted, then construct
288  if (isset($this->TABLECFG['c'])) {
289  // Process incoming:
290  $this->changeFunc();
291  // Convert to string (either line based or XML):
292  if ($this->xmlStorage) {
293  // Convert the input array to XML:
294  $bodyText = GeneralUtility::array2xml_cs($this->TABLECFG['c'], 'T3TableWizard');
295  // Setting cfgArr directly from the input:
296  $configuration = $this->TABLECFG['c'];
297  } else {
298  // Convert the input array to a string of configuration code:
299  $bodyText = $this->cfgArray2CfgString($this->TABLECFG['c']);
300  // Create cfgArr from the string based configuration - that way it is cleaned up
301  // and any incompatibilities will be removed!
302  $configuration = $this->cfgString2CfgArray($bodyText, $row[$this->colsFieldName]);
303  }
304  // If a save button has been pressed, then save the new field content:
305  if ($_POST['_savedok'] || $_POST['_saveandclosedok']) {
306  // Get DataHandler object:
308  $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
309  $dataHandler->stripslashes_values = false;
310  // Put content into the data array:
311  $data = array();
312  if ($this->P['flexFormPath']) {
313  // Current value of flexForm path:
314  $currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]);
316  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
317  $flexFormTools->setArrayValueByPath($this->P['flexFormPath'], $currentFlexFormData, $bodyText);
318  $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $currentFlexFormData;
319  } else {
320  $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $bodyText;
321  }
322  // Perform the update:
323  $dataHandler->start($data, array());
324  $dataHandler->process_datamap();
325  // If the save/close button was pressed, then redirect the screen:
326  if ($_POST['_saveandclosedok']) {
328  }
329  }
330  } else {
331  // If nothing has been submitted, load the $bodyText variable from the selected database row:
332  if ($this->xmlStorage) {
333  $configuration = GeneralUtility::xml2array($row[$this->P['field']]);
334  } else {
335  if ($this->P['flexFormPath']) {
336  // Current value of flexForm path:
337  $currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]);
339  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
340  $configuration = $flexFormTools->getArrayValueByPath(
341  $this->P['flexFormPath'],
342  $currentFlexFormData
343  );
344  $configuration = $this->cfgString2CfgArray($configuration, 0);
345  } else {
346  // Regular line based table configuration:
347  $configuration = $this->cfgString2CfgArray($row[$this->P['field']], $row[$this->colsFieldName]);
348  }
349  }
350  $configuration = is_array($configuration) ? $configuration : array();
351  }
352  return $configuration;
353  }
354 
362  public function getTableHTML($configuration)
363  {
364  // Traverse the rows:
365  $tRows = array();
366  $k = 0;
367  $countLines = count($configuration);
368  foreach ($configuration as $cellArr) {
369  if (is_array($cellArr)) {
370  // Initialize:
371  $cells = array();
372  $a = 0;
373  // Traverse the columns:
374  foreach ($cellArr as $cellContent) {
375  if ($this->inputStyle) {
376  $cells[] = '<input class="form-control" type="text" name="TABLE[c][' . ($k + 1) * 2 . '][' . ($a + 1) * 2 . ']" value="' . htmlspecialchars($cellContent) . '" />';
377  } else {
378  $cellContent = preg_replace('/<br[ ]?[\\/]?>/i', LF, $cellContent);
379  $cells[] = '<textarea class="form-control" rows="6" name="TABLE[c][' . ($k + 1) * 2 . '][' . ($a + 1) * 2 . ']">' . htmlspecialchars($cellContent) . '</textarea>';
380  }
381  // Increment counter:
382  $a++;
383  }
384  // CTRL panel for a table row (move up/down/around):
385  $onClick = 'document.wizardForm.action+=' . GeneralUtility::quoteJSvalue('#ANC_' . (($k + 1) * 2 - 2)) . ';';
386  $onClick = ' onclick="' . htmlspecialchars($onClick) . '"';
387  $ctrl = '';
388  if ($k !== 0) {
389  $ctrl .= '<button class="btn btn-default" name="TABLE[row_up][' . ($k + 1) * 2 . ']" title="' . $this->getLanguageService()->getLL('table_up', true) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-up"></span></button>';
390  } else {
391  $ctrl .= '<button class="btn btn-default" name="TABLE[row_bottom][' . ($k + 1) * 2 . ']" title="' . $this->getLanguageService()->getLL('table_bottom', true) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-double-down"></span></button>';
392  }
393  if ($k + 1 !== $countLines) {
394  $ctrl .= '<button class="btn btn-default" name="TABLE[row_down][' . ($k + 1) * 2 . ']" title="' . $this->getLanguageService()->getLL('table_down', true) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-down"></span></button>';
395  } else {
396  $ctrl .= '<button class="btn btn-default" name="TABLE[row_top][' . ($k + 1) * 2 . ']" title="' . $this->getLanguageService()->getLL('table_top', true) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-double-up"></span></button>';
397  }
398  $ctrl .= '<button class="btn btn-default" name="TABLE[row_remove][' . ($k + 1) * 2 . ']" title="' . $this->getLanguageService()->getLL('table_removeRow', true) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-trash"></span></button>';
399  $ctrl .= '<button class="btn btn-default" name="TABLE[row_add][' . ($k + 1) * 2 . ']" title="' . $this->getLanguageService()->getLL('table_addRow', true) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-plus"></span></button>';
400  $tRows[] = '
401  <tr>
402  <td>
403  <a name="ANC_' . ($k + 1) * 2 . '"></a>
404  <span class="btn-group' . ($this->inputStyle ? '' : '-vertical') . '">' . $ctrl . '</span>
405  </td>
406  <td>' . implode('</td>
407  <td>', $cells) . '</td>
408  </tr>';
409  // Increment counter:
410  $k++;
411  }
412  }
413  // CTRL panel for a table column (move left/right/around/delete)
414  $cells = array();
415  $cells[] = '';
416  // Finding first row:
417  $firstRow = reset($configuration);
418  if (is_array($firstRow)) {
419  $cols = count($firstRow);
420  for ($a = 1; $a <= $cols; $a++) {
421  $b = $a * 2;
422  $ctrl = '';
423  if ($a !== 1) {
424  $ctrl .= '<button class="btn btn-default" name="TABLE[col_left][' . $b . ']" title="' . $this->getLanguageService()->getLL('table_left', true) . '"><span class="t3-icon fa fa-fw fa-angle-left"></span></button>';
425  } else {
426  $ctrl .= '<button class="btn btn-default" name="TABLE[col_end][' . $b . ']" title="' . $this->getLanguageService()->getLL('table_end', true) . '"><span class="t3-icon fa fa-fw fa-angle-double-right"></span></button>';
427  }
428  if ($a != $cols) {
429  $ctrl .= '<button class="btn btn-default" name="TABLE[col_right][' . $b . ']" title="' . $this->getLanguageService()->getLL('table_right', true) . '"><span class="t3-icon fa fa-fw fa-angle-right"></span></button>';
430  } else {
431  $ctrl .= '<button class="btn btn-default" name="TABLE[col_start][' . $b . ']" title="' . $this->getLanguageService()->getLL('table_start', true) . '"><span class="t3-icon fa fa-fw fa-angle-double-left"></span></button>';
432  }
433  $ctrl .= '<button class="btn btn-default" name="TABLE[col_remove][' . $b . ']" title="' . $this->getLanguageService()->getLL('table_removeColumn', true) . '"><span class="t3-icon fa fa-fw fa-trash"></span></button>';
434  $ctrl .= '<button class="btn btn-default" name="TABLE[col_add][' . $b . ']" title="' . $this->getLanguageService()->getLL('table_addColumn', true) . '"><span class="t3-icon fa fa-fw fa-plus"></span></button>';
435  $cells[] = '<span class="btn-group">' . $ctrl . '</span>';
436  }
437  $tRows[] = '
438  <tfoot>
439  <tr>
440  <td>' . implode('</td>
441  <td>', $cells) . '</td>
442  </tr>
443  </tfoot>';
444  }
445  $content = '';
446  $addSubmitOnClick = 'onclick="document.getElementById(\'TableController\').submit();"';
447  // Implode all table rows into a string, wrapped in table tags.
448  $content .= '
449 
450  <!-- Table wizard -->
451  <div class="table-fit table-fit-inline-block">
452  <table id="typo3-tablewizard" class="table table-center">
453  ' . implode('', $tRows) . '
454  </table>
455  </div>';
456  // Input type checkbox:
457  $content .= '
458 
459  <!-- Input mode check box: -->
460  <div class="checkbox">
461  <input type="hidden" name="TABLE[textFields]" value="0" />
462  <label for="textFields">
463  <input type="checkbox" ' . $addSubmitOnClick . ' name="TABLE[textFields]" id="textFields" value="1"' . ($this->inputStyle ? ' checked="checked"' : '') . ' />
464  ' . $this->getLanguageService()->getLL('table_smallFields') . '
465  </label>
466  </div>';
467  return $content;
468  }
469 
477  public function changeFunc()
478  {
479  if ($this->TABLECFG['col_remove']) {
480  $kk = key($this->TABLECFG['col_remove']);
481  $cmd = 'col_remove';
482  } elseif ($this->TABLECFG['col_add']) {
483  $kk = key($this->TABLECFG['col_add']);
484  $cmd = 'col_add';
485  } elseif ($this->TABLECFG['col_start']) {
486  $kk = key($this->TABLECFG['col_start']);
487  $cmd = 'col_start';
488  } elseif ($this->TABLECFG['col_end']) {
489  $kk = key($this->TABLECFG['col_end']);
490  $cmd = 'col_end';
491  } elseif ($this->TABLECFG['col_left']) {
492  $kk = key($this->TABLECFG['col_left']);
493  $cmd = 'col_left';
494  } elseif ($this->TABLECFG['col_right']) {
495  $kk = key($this->TABLECFG['col_right']);
496  $cmd = 'col_right';
497  } elseif ($this->TABLECFG['row_remove']) {
498  $kk = key($this->TABLECFG['row_remove']);
499  $cmd = 'row_remove';
500  } elseif ($this->TABLECFG['row_add']) {
501  $kk = key($this->TABLECFG['row_add']);
502  $cmd = 'row_add';
503  } elseif ($this->TABLECFG['row_top']) {
504  $kk = key($this->TABLECFG['row_top']);
505  $cmd = 'row_top';
506  } elseif ($this->TABLECFG['row_bottom']) {
507  $kk = key($this->TABLECFG['row_bottom']);
508  $cmd = 'row_bottom';
509  } elseif ($this->TABLECFG['row_up']) {
510  $kk = key($this->TABLECFG['row_up']);
511  $cmd = 'row_up';
512  } elseif ($this->TABLECFG['row_down']) {
513  $kk = key($this->TABLECFG['row_down']);
514  $cmd = 'row_down';
515  } else {
516  $kk = '';
517  $cmd = '';
518  }
519  if ($cmd && MathUtility::canBeInterpretedAsInteger($kk)) {
520  if (StringUtility::beginsWith($cmd, 'row_')) {
521  switch ($cmd) {
522  case 'row_remove':
523  unset($this->TABLECFG['c'][$kk]);
524  break;
525  case 'row_add':
526  for ($a = 1; $a <= $this->numNewRows; $a++) {
527  // Checking if set: The point is that any new row between existing rows
528  // will be TRUE after one row is added while if rows are added in the bottom
529  // of the table there will be no existing rows to stop the addition of new rows
530  // which means it will add up to $this->numNewRows rows then.
531  if (!isset($this->TABLECFG['c'][($kk + $a)])) {
532  $this->TABLECFG['c'][$kk + $a] = array();
533  } else {
534  break;
535  }
536  }
537  break;
538  case 'row_top':
539  $this->TABLECFG['c'][1] = $this->TABLECFG['c'][$kk];
540  unset($this->TABLECFG['c'][$kk]);
541  break;
542  case 'row_bottom':
543  $this->TABLECFG['c'][10000000] = $this->TABLECFG['c'][$kk];
544  unset($this->TABLECFG['c'][$kk]);
545  break;
546  case 'row_up':
547  $this->TABLECFG['c'][$kk - 3] = $this->TABLECFG['c'][$kk];
548  unset($this->TABLECFG['c'][$kk]);
549  break;
550  case 'row_down':
551  $this->TABLECFG['c'][$kk + 3] = $this->TABLECFG['c'][$kk];
552  unset($this->TABLECFG['c'][$kk]);
553  break;
554  }
555  ksort($this->TABLECFG['c']);
556  }
557  if (StringUtility::beginsWith($cmd, 'col_')) {
558  foreach ($this->TABLECFG['c'] as $cAK => $value) {
559  switch ($cmd) {
560  case 'col_remove':
561  unset($this->TABLECFG['c'][$cAK][$kk]);
562  break;
563  case 'col_add':
564  $this->TABLECFG['c'][$cAK][$kk + 1] = '';
565  break;
566  case 'col_start':
567  $this->TABLECFG['c'][$cAK][1] = $this->TABLECFG['c'][$cAK][$kk];
568  unset($this->TABLECFG['c'][$cAK][$kk]);
569  break;
570  case 'col_end':
571  $this->TABLECFG['c'][$cAK][1000000] = $this->TABLECFG['c'][$cAK][$kk];
572  unset($this->TABLECFG['c'][$cAK][$kk]);
573  break;
574  case 'col_left':
575  $this->TABLECFG['c'][$cAK][$kk - 3] = $this->TABLECFG['c'][$cAK][$kk];
576  unset($this->TABLECFG['c'][$cAK][$kk]);
577  break;
578  case 'col_right':
579  $this->TABLECFG['c'][$cAK][$kk + 3] = $this->TABLECFG['c'][$cAK][$kk];
580  unset($this->TABLECFG['c'][$cAK][$kk]);
581  break;
582  }
583  ksort($this->TABLECFG['c'][$cAK]);
584  }
585  }
586  }
587  // Convert line breaks to <br /> tags:
588  foreach ($this->TABLECFG['c'] as $a => $value) {
589  foreach ($this->TABLECFG['c'][$a] as $b => $value2) {
590  $this->TABLECFG['c'][$a][$b] = str_replace(
591  LF,
592  '<br />',
593  str_replace(CR, '', $this->TABLECFG['c'][$a][$b])
594  );
595  }
596  }
597  }
598 
606  public function cfgArray2CfgString($cfgArr)
607  {
608  $inLines = array();
609  // Traverse the elements of the table wizard and transform the settings into configuration code.
610  foreach ($cfgArr as $valueA) {
611  $thisLine = array();
612  foreach ($valueA as $valueB) {
613  $thisLine[] = $this->tableParsing_quote
614  . str_replace($this->tableParsing_delimiter, '', $valueB) . $this->tableParsing_quote;
615  }
616  $inLines[] = implode($this->tableParsing_delimiter, $thisLine);
617  }
618  // Finally, implode the lines into a string:
619  return implode(LF, $inLines);
620  }
621 
630  public function cfgString2CfgArray($configurationCode, $columns)
631  {
632  // Explode lines in the configuration code - each line is a table row.
633  $tableLines = explode(LF, $configurationCode);
634  // Setting number of columns
635  // auto...
636  if (!$columns && trim($tableLines[0])) {
637  $columns = count(explode($this->tableParsing_delimiter, $tableLines[0]));
638  }
639  $columns = $columns ?: 4;
640  // Traverse the number of table elements:
641  $configurationArray = array();
642  foreach ($tableLines as $key => $value) {
643  // Initialize:
644  $valueParts = explode($this->tableParsing_delimiter, $value);
645  // Traverse columns:
646  for ($a = 0; $a < $columns; $a++) {
647  if ($this->tableParsing_quote
648  && $valueParts[$a][0] === $this->tableParsing_quote
649  && substr($valueParts[$a], -1, 1) === $this->tableParsing_quote
650  ) {
651  $valueParts[$a] = substr(trim($valueParts[$a]), 1, -1);
652  }
653  $configurationArray[$key][$a] = $valueParts[$a];
654  }
655  }
656  return $configurationArray;
657  }
658 }