TYPO3 CMS  TYPO3_8-7
TableController.php
Go to the documentation of this file.
1 <?php
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 
28 
33 {
39  public $content;
40 
46  public $inputStyle = false;
47 
55  public $xmlStorage = 0;
56 
62  public $numNewRows = 1;
63 
70  public $colsFieldName = 'cols';
71 
77  public $P;
78 
84  public $TABLECFG;
85 
93 
100 
104  protected $iconFactory;
105 
109  public function __construct()
110  {
111  parent::__construct();
112  $this->getLanguageService()->includeLLFile('EXT:lang/Resources/Private/Language/locallang_wizards.xlf');
113  $GLOBALS['SOBE'] = $this;
114 
115  $this->init();
116  }
117 
121  protected function init()
122  {
123  // GPvars:
124  $this->P = GeneralUtility::_GP('P');
125  $this->TABLECFG = GeneralUtility::_GP('TABLE');
126  // Setting options:
127  $this->xmlStorage = $this->P['params']['xmlOutput'];
128  $this->numNewRows = MathUtility::forceIntegerInRange($this->P['params']['numNewRows'], 1, 50, 5);
129  // Textareas or input fields:
130  $this->inputStyle = isset($this->TABLECFG['textFields']) ? (bool)$this->TABLECFG['textFields'] : true;
131  $this->tableParsing_delimiter = '|';
132  $this->tableParsing_quote = '';
133  }
134 
143  public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
144  {
145  $this->main();
146  $response->getBody()->write($this->moduleTemplate->renderContent());
147  return $response;
148  }
149 
153  public function main()
154  {
155  list($rUri) = explode('#', GeneralUtility::getIndpEnv('REQUEST_URI'));
156  $this->content .= '<form action="' . htmlspecialchars($rUri) . '" method="post" id="TableController" name="wizardForm">';
157  if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
158  $this->content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>'
159  . '<div>' . $this->tableWizard() . '</div>';
160  } else {
161  $this->content .= '<h2>' . htmlspecialchars($this->getLanguageService()->getLL('table_title')) . '</h2>'
162  . '<div><span class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('table_noData')) . '</span></div>';
163  }
164  $this->content .= '</form>';
165  // Setting up the buttons and markers for docHeader
166  $this->getButtons();
167  // Build the <body> for the module
168  $this->moduleTemplate->setContent($this->content);
169  }
170 
174  protected function getButtons()
175  {
176  $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
177  if ($this->P['table'] && $this->P['field'] && $this->P['uid']) {
178  // CSH
179  $cshButton = $buttonBar->makeHelpButton()
180  ->setModuleName('xMOD_csh_corebe')
181  ->setFieldName('wizard_table_wiz');
182  $buttonBar->addButton($cshButton);
183  // Close
184  $closeButton = $buttonBar->makeLinkButton()
185  ->setHref($this->P['returnUrl'])
186  ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
187  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL));
188  $buttonBar->addButton($closeButton);
189  // Save
190  $saveButton = $buttonBar->makeInputButton()
191  ->setName('_savedok')
192  ->setValue('1')
193  ->setForm('TableController')
194  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
195  ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'));
196  // Save & Close
197  $saveAndCloseButton = $buttonBar->makeInputButton()
198  ->setName('_saveandclosedok')
199  ->setValue('1')
200  ->setForm('TableController')
201  ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveCloseDoc'))
202  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
203  'actions-document-save-close',
205  ));
206  $splitButtonElement = $buttonBar->makeSplitButton()
207  ->addItem($saveButton)
208  ->addItem($saveAndCloseButton);
209 
210  $buttonBar->addButton($splitButtonElement, ButtonBar::BUTTON_POSITION_LEFT, 3);
211  // Reload
212  $reloadButton = $buttonBar->makeInputButton()
213  ->setName('_refresh')
214  ->setValue('1')
215  ->setForm('TableController')
216  ->setTitle($this->getLanguageService()->getLL('forms_refresh'))
217  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-refresh', Icon::SIZE_SMALL));
218  $buttonBar->addButton($reloadButton);
219  }
220  }
221 
228  public function tableWizard()
229  {
230  if (!$this->checkEditAccess($this->P['table'], $this->P['uid'])) {
231  throw new \RuntimeException('Wizard Error: No access', 1349692692);
232  }
233  // First, check the references by selecting the record:
234  $row = BackendUtility::getRecord($this->P['table'], $this->P['uid']);
235  if (!is_array($row)) {
236  throw new \RuntimeException('Wizard Error: No reference to record', 1294587125);
237  }
238  // This will get the content of the form configuration code field to us - possibly cleaned up,
239  // saved to database etc. if the form has been submitted in the meantime.
240  $tableCfgArray = $this->getConfigCode($row);
241  // Generation of the Table Wizards HTML code:
242  $content = $this->getTableHTML($tableCfgArray);
243  // Return content:
244  return $content;
245  }
246 
247  /*
248  *
249  * Helper functions
250  *
251  */
252 
261  public function getConfigCode($row)
262  {
263  // Get delimiter settings
264  $this->tableParsing_quote = $row['table_enclosure'] ? chr((int)$row['table_enclosure']) : '';
265  $this->tableParsing_delimiter = $row['table_delimiter'] ? chr((int)$row['table_delimiter']) : '|';
266  // If some data has been submitted, then construct
267  if (isset($this->TABLECFG['c'])) {
268  // Process incoming:
269  $this->changeFunc();
270  // Convert to string (either line based or XML):
271  if ($this->xmlStorage) {
272  // Convert the input array to XML:
273  $bodyText = '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>' . LF . GeneralUtility::array2xml($this->TABLECFG['c'], '', 0, 'T3TableWizard');
274  // Setting cfgArr directly from the input:
275  $configuration = $this->TABLECFG['c'];
276  } else {
277  // Convert the input array to a string of configuration code:
278  $bodyText = $this->cfgArray2CfgString($this->TABLECFG['c']);
279  // Create cfgArr from the string based configuration - that way it is cleaned up
280  // and any incompatibilities will be removed!
281  $configuration = $this->cfgString2CfgArray($bodyText, $row[$this->colsFieldName]);
282  }
283  // If a save button has been pressed, then save the new field content:
284  if ($_POST['_savedok'] || $_POST['_saveandclosedok']) {
285  // Get DataHandler object:
287  $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
288  // Put content into the data array:
289  $data = [];
290  if ($this->P['flexFormPath']) {
291  // Current value of flexForm path:
292  $currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]);
294  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
295  $flexFormTools->setArrayValueByPath($this->P['flexFormPath'], $currentFlexFormData, $bodyText);
296  $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $currentFlexFormData;
297  } else {
298  $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $bodyText;
299  }
300  // Perform the update:
301  $dataHandler->start($data, []);
302  $dataHandler->process_datamap();
303  // If the save/close button was pressed, then redirect the screen:
304  if ($_POST['_saveandclosedok']) {
306  }
307  }
308  } else {
309  // If nothing has been submitted, load the $bodyText variable from the selected database row:
310  if ($this->xmlStorage) {
311  $configuration = GeneralUtility::xml2array($row[$this->P['field']]);
312  } else {
313  if ($this->P['flexFormPath']) {
314  // Current value of flexForm path:
315  $currentFlexFormData = GeneralUtility::xml2array($row[$this->P['field']]);
317  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
318  $configuration = $flexFormTools->getArrayValueByPath(
319  $this->P['flexFormPath'],
320  $currentFlexFormData
321  );
322  $configuration = $this->cfgString2CfgArray($configuration, 0);
323  } else {
324  // Regular line based table configuration:
325  $configuration = $this->cfgString2CfgArray($row[$this->P['field']], $row[$this->colsFieldName]);
326  }
327  }
328  $configuration = is_array($configuration) ? $configuration : [];
329  }
330  return $configuration;
331  }
332 
340  public function getTableHTML($configuration)
341  {
342  // Traverse the rows:
343  $tRows = [];
344  $k = 0;
345  $countLines = count($configuration);
346  foreach ($configuration as $cellArr) {
347  if (is_array($cellArr)) {
348  // Initialize:
349  $cells = [];
350  $a = 0;
351  // Traverse the columns:
352  foreach ($cellArr as $cellContent) {
353  if ($this->inputStyle) {
354  $cells[] = '<input class="form-control" type="text" name="TABLE[c][' . ($k + 1) * 2 . '][' . ($a + 1) * 2 . ']" value="' . htmlspecialchars($cellContent) . '" />';
355  } else {
356  $cellContent = preg_replace('/<br[ ]?[\\/]?>/i', LF, $cellContent);
357  $cells[] = '<textarea class="form-control" rows="6" name="TABLE[c][' . ($k + 1) * 2 . '][' . ($a + 1) * 2 . ']">' . htmlspecialchars($cellContent) . '</textarea>';
358  }
359  // Increment counter:
360  $a++;
361  }
362  // CTRL panel for a table row (move up/down/around):
363  $onClick = 'document.wizardForm.action+=' . GeneralUtility::quoteJSvalue('#ANC_' . (($k + 1) * 2 - 2)) . ';';
364  $onClick = ' onclick="' . htmlspecialchars($onClick) . '"';
365  $ctrl = '';
366  if ($k !== 0) {
367  $ctrl .= '<button class="btn btn-default" name="TABLE[row_up][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_up')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-up"></span></button>';
368  } else {
369  $ctrl .= '<button class="btn btn-default" name="TABLE[row_bottom][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_bottom')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-double-down"></span></button>';
370  }
371  if ($k + 1 !== $countLines) {
372  $ctrl .= '<button class="btn btn-default" name="TABLE[row_down][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_down')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-down"></span></button>';
373  } else {
374  $ctrl .= '<button class="btn btn-default" name="TABLE[row_top][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_top')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-angle-double-up"></span></button>';
375  }
376  $ctrl .= '<button class="btn btn-default" name="TABLE[row_remove][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_removeRow')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-trash"></span></button>';
377  $ctrl .= '<button class="btn btn-default" name="TABLE[row_add][' . ($k + 1) * 2 . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_addRow')) . '"' . $onClick . '><span class="t3-icon fa fa-fw fa-plus"></span></button>';
378  $tRows[] = '
379  <tr>
380  <td>
381  <a name="ANC_' . ($k + 1) * 2 . '"></a>
382  <span class="btn-group' . ($this->inputStyle ? '' : '-vertical') . '">' . $ctrl . '</span>
383  </td>
384  <td>' . implode('</td>
385  <td>', $cells) . '</td>
386  </tr>';
387  // Increment counter:
388  $k++;
389  }
390  }
391  // CTRL panel for a table column (move left/right/around/delete)
392  $cells = [];
393  $cells[] = '';
394  // Finding first row:
395  $firstRow = reset($configuration);
396  if (is_array($firstRow)) {
397  $cols = count($firstRow);
398  for ($a = 1; $a <= $cols; $a++) {
399  $b = $a * 2;
400  $ctrl = '';
401  if ($a !== 1) {
402  $ctrl .= '<button class="btn btn-default" name="TABLE[col_left][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_left')) . '"><span class="t3-icon fa fa-fw fa-angle-left"></span></button>';
403  } else {
404  $ctrl .= '<button class="btn btn-default" name="TABLE[col_end][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_end')) . '"><span class="t3-icon fa fa-fw fa-angle-double-right"></span></button>';
405  }
406  if ($a != $cols) {
407  $ctrl .= '<button class="btn btn-default" name="TABLE[col_right][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_right')) . '"><span class="t3-icon fa fa-fw fa-angle-right"></span></button>';
408  } else {
409  $ctrl .= '<button class="btn btn-default" name="TABLE[col_start][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_start')) . '"><span class="t3-icon fa fa-fw fa-angle-double-left"></span></button>';
410  }
411  $ctrl .= '<button class="btn btn-default" name="TABLE[col_remove][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_removeColumn')) . '"><span class="t3-icon fa fa-fw fa-trash"></span></button>';
412  $ctrl .= '<button class="btn btn-default" name="TABLE[col_add][' . $b . ']" title="' . htmlspecialchars($this->getLanguageService()->getLL('table_addColumn')) . '"><span class="t3-icon fa fa-fw fa-plus"></span></button>';
413  $cells[] = '<span class="btn-group">' . $ctrl . '</span>';
414  }
415  $tRows[] = '
416  <tfoot>
417  <tr>
418  <td>' . implode('</td>
419  <td>', $cells) . '</td>
420  </tr>
421  </tfoot>';
422  }
423  $content = '';
424  $addSubmitOnClick = 'onclick="document.getElementById(\'TableController\').submit();"';
425  // Implode all table rows into a string, wrapped in table tags.
426  $content .= '
427 
428  <!-- Table wizard -->
429  <div class="table-fit table-fit-inline-block">
430  <table id="typo3-tablewizard" class="table table-center">
431  ' . implode('', $tRows) . '
432  </table>
433  </div>';
434  // Input type checkbox:
435  $content .= '
436 
437  <!-- Input mode check box: -->
438  <div class="checkbox">
439  <input type="hidden" name="TABLE[textFields]" value="0" />
440  <label for="textFields">
441  <input type="checkbox" ' . $addSubmitOnClick . ' name="TABLE[textFields]" id="textFields" value="1"' . ($this->inputStyle ? ' checked="checked"' : '') . ' />
442  ' . $this->getLanguageService()->getLL('table_smallFields') . '
443  </label>
444  </div>';
445  return $content;
446  }
447 
454  public function changeFunc()
455  {
456  if ($this->TABLECFG['col_remove']) {
457  $kk = key($this->TABLECFG['col_remove']);
458  $cmd = 'col_remove';
459  } elseif ($this->TABLECFG['col_add']) {
460  $kk = key($this->TABLECFG['col_add']);
461  $cmd = 'col_add';
462  } elseif ($this->TABLECFG['col_start']) {
463  $kk = key($this->TABLECFG['col_start']);
464  $cmd = 'col_start';
465  } elseif ($this->TABLECFG['col_end']) {
466  $kk = key($this->TABLECFG['col_end']);
467  $cmd = 'col_end';
468  } elseif ($this->TABLECFG['col_left']) {
469  $kk = key($this->TABLECFG['col_left']);
470  $cmd = 'col_left';
471  } elseif ($this->TABLECFG['col_right']) {
472  $kk = key($this->TABLECFG['col_right']);
473  $cmd = 'col_right';
474  } elseif ($this->TABLECFG['row_remove']) {
475  $kk = key($this->TABLECFG['row_remove']);
476  $cmd = 'row_remove';
477  } elseif ($this->TABLECFG['row_add']) {
478  $kk = key($this->TABLECFG['row_add']);
479  $cmd = 'row_add';
480  } elseif ($this->TABLECFG['row_top']) {
481  $kk = key($this->TABLECFG['row_top']);
482  $cmd = 'row_top';
483  } elseif ($this->TABLECFG['row_bottom']) {
484  $kk = key($this->TABLECFG['row_bottom']);
485  $cmd = 'row_bottom';
486  } elseif ($this->TABLECFG['row_up']) {
487  $kk = key($this->TABLECFG['row_up']);
488  $cmd = 'row_up';
489  } elseif ($this->TABLECFG['row_down']) {
490  $kk = key($this->TABLECFG['row_down']);
491  $cmd = 'row_down';
492  } else {
493  $kk = '';
494  $cmd = '';
495  }
496  if ($cmd && MathUtility::canBeInterpretedAsInteger($kk)) {
497  if (strpos($cmd, 'row_') === 0) {
498  switch ($cmd) {
499  case 'row_remove':
500  unset($this->TABLECFG['c'][$kk]);
501  break;
502  case 'row_add':
503  for ($a = 1; $a <= $this->numNewRows; $a++) {
504  // Checking if set: The point is that any new row between existing rows
505  // will be TRUE after one row is added while if rows are added in the bottom
506  // of the table there will be no existing rows to stop the addition of new rows
507  // which means it will add up to $this->numNewRows rows then.
508  if (!isset($this->TABLECFG['c'][$kk + $a])) {
509  $this->TABLECFG['c'][$kk + $a] = [];
510  } else {
511  break;
512  }
513  }
514  break;
515  case 'row_top':
516  $this->TABLECFG['c'][1] = $this->TABLECFG['c'][$kk];
517  unset($this->TABLECFG['c'][$kk]);
518  break;
519  case 'row_bottom':
520  $this->TABLECFG['c'][10000000] = $this->TABLECFG['c'][$kk];
521  unset($this->TABLECFG['c'][$kk]);
522  break;
523  case 'row_up':
524  $this->TABLECFG['c'][$kk - 3] = $this->TABLECFG['c'][$kk];
525  unset($this->TABLECFG['c'][$kk]);
526  break;
527  case 'row_down':
528  $this->TABLECFG['c'][$kk + 3] = $this->TABLECFG['c'][$kk];
529  unset($this->TABLECFG['c'][$kk]);
530  break;
531  }
532  ksort($this->TABLECFG['c']);
533  }
534  if (strpos($cmd, 'col_') === 0) {
535  foreach ($this->TABLECFG['c'] as $cAK => $value) {
536  switch ($cmd) {
537  case 'col_remove':
538  unset($this->TABLECFG['c'][$cAK][$kk]);
539  break;
540  case 'col_add':
541  $this->TABLECFG['c'][$cAK][$kk + 1] = '';
542  break;
543  case 'col_start':
544  $this->TABLECFG['c'][$cAK][1] = $this->TABLECFG['c'][$cAK][$kk];
545  unset($this->TABLECFG['c'][$cAK][$kk]);
546  break;
547  case 'col_end':
548  $this->TABLECFG['c'][$cAK][1000000] = $this->TABLECFG['c'][$cAK][$kk];
549  unset($this->TABLECFG['c'][$cAK][$kk]);
550  break;
551  case 'col_left':
552  $this->TABLECFG['c'][$cAK][$kk - 3] = $this->TABLECFG['c'][$cAK][$kk];
553  unset($this->TABLECFG['c'][$cAK][$kk]);
554  break;
555  case 'col_right':
556  $this->TABLECFG['c'][$cAK][$kk + 3] = $this->TABLECFG['c'][$cAK][$kk];
557  unset($this->TABLECFG['c'][$cAK][$kk]);
558  break;
559  }
560  ksort($this->TABLECFG['c'][$cAK]);
561  }
562  }
563  }
564  // Convert line breaks to <br /> tags:
565  foreach ($this->TABLECFG['c'] as $a => $value) {
566  foreach ($this->TABLECFG['c'][$a] as $b => $value2) {
567  $this->TABLECFG['c'][$a][$b] = str_replace(
568  LF,
569  '<br />',
570  str_replace(CR, '', $this->TABLECFG['c'][$a][$b])
571  );
572  }
573  }
574  }
575 
583  public function cfgArray2CfgString($cfgArr)
584  {
585  $inLines = [];
586  // Traverse the elements of the table wizard and transform the settings into configuration code.
587  foreach ($cfgArr as $valueA) {
588  $thisLine = [];
589  foreach ($valueA as $valueB) {
590  $thisLine[] = $this->tableParsing_quote
591  . str_replace($this->tableParsing_delimiter, '', $valueB) . $this->tableParsing_quote;
592  }
593  $inLines[] = implode($this->tableParsing_delimiter, $thisLine);
594  }
595  // Finally, implode the lines into a string:
596  return implode(LF, $inLines);
597  }
598 
607  public function cfgString2CfgArray($configurationCode, $columns)
608  {
609  // Explode lines in the configuration code - each line is a table row.
610  $tableLines = explode(LF, $configurationCode);
611  // Setting number of columns
612  // auto...
613  if (!$columns && trim($tableLines[0])) {
614  $columns = count(explode($this->tableParsing_delimiter, $tableLines[0]));
615  }
616  $columns = $columns ?: 4;
617  // Traverse the number of table elements:
618  $configurationArray = [];
619  foreach ($tableLines as $key => $value) {
620  // Initialize:
621  $valueParts = explode($this->tableParsing_delimiter, $value);
622  // Traverse columns:
623  for ($a = 0; $a < $columns; $a++) {
624  if ($this->tableParsing_quote
625  && $valueParts[$a][0] === $this->tableParsing_quote
626  && substr($valueParts[$a], -1, 1) === $this->tableParsing_quote
627  ) {
628  $valueParts[$a] = substr(trim($valueParts[$a]), 1, -1);
629  }
630  $configurationArray[$key][$a] = $valueParts[$a];
631  }
632  }
633  return $configurationArray;
634  }
635 }
static array2xml(array $array, $NSprefix='', $level=0, $docTag='phparray', $spaceInd=0, array $options=[], array $stackData=[])
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
static makeInstance($className,... $constructorArguments)
static redirect($url, $httpStatus=self::HTTP_STATUS_303)
static xml2array($string, $NSprefix='', $reportDocTag=false)
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
mainAction(ServerRequestInterface $request, ResponseInterface $response)