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