TYPO3  7.6
SuggestWizard.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Backend\Form\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 
26 
31 {
42  public function renderSuggestSelector($fieldname, $table, $field, array $row, array $config)
43  {
45  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
46  $languageService = $this->getLanguageService();
47  $isFlexFormField = $GLOBALS['TCA'][$table]['columns'][$field]['config']['type'] === 'flex';
48  if ($isFlexFormField) {
49  $fieldPattern = 'data[' . $table . '][' . $row['uid'] . '][';
50  $flexformField = str_replace($fieldPattern, '', $fieldname);
51  $flexformField = substr($flexformField, 0, -1);
52  $field = str_replace(array(']['), '|', $flexformField);
53  }
54 
55  // Get minimumCharacters from TCA
56  $minChars = 0;
57  if (isset($config['fieldConf']['config']['wizards']['suggest']['default']['minimumCharacters'])) {
58  $minChars = (int)$config['fieldConf']['config']['wizards']['suggest']['default']['minimumCharacters'];
59  }
60  // Overwrite it with minimumCharacters from TSConfig (TCEFORM) if given
61  if (isset($config['fieldTSConfig']['suggest.']['default.']['minimumCharacters'])) {
62  $minChars = (int)$config['fieldTSConfig']['suggest.']['default.']['minimumCharacters'];
63  }
64  $minChars = $minChars > 0 ? $minChars : 2;
65 
66  // fetch the TCA field type to hand it over to the JS class
67  $type = '';
68  if (isset($config['fieldConf']['config']['type'])) {
69  $type = $config['fieldConf']['config']['type'];
70  }
71 
72  $jsRow = '';
73  if ($isFlexFormField || !MathUtility::canBeInterpretedAsInteger($row['uid'])) {
74  // Ff we have a new record, we hand that row over to JS.
75  // This way we can properly retrieve the configuration of our wizard
76  // if it is shown in a flexform
77  $jsRow = serialize($row);
78  }
79 
80  $selector = '
81  <div class="autocomplete t3-form-suggest-container">
82  <div class="input-group">
83  <span class="input-group-addon">' . $iconFactory->getIcon('actions-search', Icon::SIZE_SMALL)->render() . '</span>
84  <input type="search" class="t3-form-suggest form-control"
85  placeholder="' . $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.findRecord') . '"
86  data-fieldname="' . $fieldname . '"
87  data-table="' . $table . '"
88  data-field="' . $field . '"
89  data-uid="' . $row['uid'] . '"
90  data-pid="' . $row['pid'] . '"
91  data-fieldtype="' . $type . '"
92  data-minchars="' . $minChars . '"
93  data-recorddata="' . htmlspecialchars($jsRow) . '"
94  />
95  </div>
96  </div>';
97 
98  return $selector;
99  }
100 
109  protected function getNestedDsFieldConfig(array $dataStructure, $fieldName)
110  {
111  $fieldConfig = array();
112  $elements = $dataStructure['ROOT']['el'] ? $dataStructure['ROOT']['el'] : $dataStructure['el'];
113  if (is_array($elements)) {
114  foreach ($elements as $k => $ds) {
115  if ($k === $fieldName) {
116  $fieldConfig = $ds['TCEforms']['config'];
117  break;
118  } elseif (isset($ds['el'][$fieldName]['TCEforms']['config'])) {
119  $fieldConfig = $ds['el'][$fieldName]['TCEforms']['config'];
120  break;
121  } else {
122  $fieldConfig = $this->getNestedDsFieldConfig($ds, $fieldName);
123  }
124  }
125  }
126  return $fieldConfig;
127  }
128 
137  {
138  $parsedBody = $request->getParsedBody();
139  $queryParams = $request->getQueryParams();
140 
141  // Get parameters from $_GET/$_POST
142  $search = isset($parsedBody['value']) ? $parsedBody['value'] : $queryParams['value'];
143  $table = isset($parsedBody['table']) ? $parsedBody['table'] : $queryParams['table'];
144  $field = isset($parsedBody['field']) ? $parsedBody['field'] : $queryParams['field'];
145  $uid = isset($parsedBody['uid']) ? $parsedBody['uid'] : $queryParams['uid'];
146  $pageId = (int)(isset($parsedBody['pid']) ? $parsedBody['pid'] : $queryParams['pid']);
147  $newRecordRow = isset($parsedBody['newRecordRow']) ? $parsedBody['newRecordRow'] : $queryParams['newRecordRow'];
148  // If the $uid is numeric, we have an already existing element, so get the
149  // TSconfig of the page itself or the element container (for non-page elements)
150  // otherwise it's a new element, so use given id of parent page (i.e., don't modify it here)
151  if (is_numeric($uid)) {
152  $row = BackendUtility::getRecord($table, $uid);
153  if ($table === 'pages') {
154  $pageId = $uid;
155  } else {
156  $pageId = $row['pid'];
157  }
158  } else {
159  $row = unserialize($newRecordRow);
160  }
161  $TSconfig = BackendUtility::getPagesTSconfig($pageId);
162  $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
163  $this->overrideFieldNameAndConfigurationForFlexform($table, $field, $row, $fieldConfig);
164 
165  $wizardConfig = $fieldConfig['wizards']['suggest'];
166 
167  $queryTables = $this->getTablesToQueryFromFieldConfiguration($fieldConfig);
168  $whereClause = $this->getWhereClause($fieldConfig);
169 
170  $resultRows = array();
171 
172  // fetch the records for each query table. A query table is a table from which records are allowed to
173  // be added to the TCEForm selector, originally fetched from the "allowed" config option in the TCA
174  foreach ($queryTables as $queryTable) {
175  // if the table does not exist, skip it
176  if (!is_array($GLOBALS['TCA'][$queryTable]) || empty($GLOBALS['TCA'][$queryTable])) {
177  continue;
178  }
179 
180  $config = $this->getConfigurationForTable($queryTable, $wizardConfig, $TSconfig, $table, $field);
181 
182  // process addWhere
183  if (!isset($config['addWhere']) && $whereClause) {
184  $config['addWhere'] = $whereClause;
185  }
186  if (isset($config['addWhere'])) {
187  $replacement = array(
188  '###THIS_UID###' => (int)$uid,
189  '###CURRENT_PID###' => (int)$pageId
190  );
191  if (isset($TSconfig['TCEFORM.'][$table . '.'][$field . '.'])) {
192  $fieldTSconfig = $TSconfig['TCEFORM.'][$table . '.'][$field . '.'];
193  if (isset($fieldTSconfig['PAGE_TSCONFIG_ID'])) {
194  $replacement['###PAGE_TSCONFIG_ID###'] = (int)$fieldTSconfig['PAGE_TSCONFIG_ID'];
195  }
196  if (isset($fieldTSconfig['PAGE_TSCONFIG_IDLIST'])) {
197  $replacement['###PAGE_TSCONFIG_IDLIST###'] = $GLOBALS['TYPO3_DB']->cleanIntList($fieldTSconfig['PAGE_TSCONFIG_IDLIST']);
198  }
199  if (isset($fieldTSconfig['PAGE_TSCONFIG_STR'])) {
200  $replacement['###PAGE_TSCONFIG_STR###'] = $GLOBALS['TYPO3_DB']->quoteStr($fieldTSconfig['PAGE_TSCONFIG_STR'], $fieldConfig['foreign_table']);
201  }
202  }
203  $config['addWhere'] = strtr(' ' . $config['addWhere'], $replacement);
204  }
205 
206  // instantiate the class that should fetch the records for this $queryTable
207  $receiverClassName = $config['receiverClass'];
208  if (!class_exists($receiverClassName)) {
209  $receiverClassName = SuggestWizardDefaultReceiver::class;
210  }
211  $receiverObj = GeneralUtility::makeInstance($receiverClassName, $queryTable, $config);
212  $params = array('value' => $search);
213  $rows = $receiverObj->queryTable($params);
214  if (empty($rows)) {
215  continue;
216  }
217  $resultRows = $rows + $resultRows;
218  unset($rows);
219  }
220 
221  // Limit the number of items in the result list
222  $maxItems = isset($config['maxItemsInResultList']) ? $config['maxItemsInResultList'] : 10;
223  $maxItems = min(count($resultRows), $maxItems);
224 
225  $listItems = $this->createListItemsFromResultRow($resultRows, $maxItems);
226 
227  $response->getBody()->write(json_encode($listItems));
228  return $response;
229  }
230 
237  protected function isTableHidden(array $tableConfig)
238  {
239  return !$tableConfig['ctrl']['hideTable'];
240  }
241 
249  protected function currentBackendUserMayAccessTable(array $tableConfig)
250  {
251  if ($GLOBALS['BE_USER']->isAdmin()) {
252  return true;
253  }
254 
255  // If the user is no admin, they may not access admin-only tables
256  if ($tableConfig['ctrl']['adminOnly']) {
257  return false;
258  }
259 
260  // allow access to root level pages if security restrictions should be bypassed
261  return !$tableConfig['ctrl']['rootLevel'] || $tableConfig['ctrl']['security']['ignoreRootLevelRestriction'];
262  }
263 
273  protected function overrideFieldNameAndConfigurationForFlexform($table, &$field, array $row, &$fieldConfig)
274  {
275  // check if field is a flexform reference
276  if (strpos($field, '|') === false) {
277  $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
278  } else {
279  $parts = explode('|', $field);
280 
281  if ($GLOBALS['TCA'][$table]['columns'][$parts[0]]['config']['type'] !== 'flex') {
282  return;
283  }
284 
285  $flexfieldTCAConfig = $GLOBALS['TCA'][$table]['columns'][$parts[0]]['config'];
286  // @todo: should be done via data preparation, resolveAllSheetsInDS() can be deprecated then
287  $flexformDSArray = BackendUtility::getFlexFormDS($flexfieldTCAConfig, $row, $table, $parts[0]);
288  $flexformDSArray = GeneralUtility::resolveAllSheetsInDS($flexformDSArray);
289  $flexformElement = $parts[count($parts) - 2];
290  $continue = true;
291  foreach ($flexformDSArray as $sheet) {
292  foreach ($sheet as $_ => $dataStructure) {
293  $fieldConfig = $this->getNestedDsFieldConfig($dataStructure, $flexformElement);
294  if (!empty($fieldConfig)) {
295  $continue = false;
296  break;
297  }
298  }
299  if (!$continue) {
300  break;
301  }
302  }
303  // Flexform field name levels are separated with | instead of encapsulation in [];
304  // reverse this here to be compatible with regular field names.
305  $field = str_replace('|', '][', $field);
306  }
307  }
308 
320  protected function getConfigurationForTable($queryTable, array $wizardConfig, array $TSconfig, $table, $field)
321  {
322  $config = (array)$wizardConfig['default'];
323 
324  if (is_array($wizardConfig[$queryTable])) {
325  ArrayUtility::mergeRecursiveWithOverrule($config, $wizardConfig[$queryTable]);
326  }
327  $globalSuggestTsConfig = $TSconfig['TCEFORM.']['suggest.'];
328  $currentFieldSuggestTsConfig = $TSconfig['TCEFORM.'][$table . '.'][$field . '.']['suggest.'];
329 
330  // merge the configurations of different "levels" to get the working configuration for this table and
331  // field (i.e., go from the most general to the most special configuration)
332  if (is_array($globalSuggestTsConfig['default.'])) {
333  ArrayUtility::mergeRecursiveWithOverrule($config, $globalSuggestTsConfig['default.']);
334  }
335 
336  if (is_array($globalSuggestTsConfig[$queryTable . '.'])) {
337  ArrayUtility::mergeRecursiveWithOverrule($config, $globalSuggestTsConfig[$queryTable . '.']);
338  }
339 
340  // use $table instead of $queryTable here because we overlay a config
341  // for the input-field here, not for the queried table
342  if (is_array($currentFieldSuggestTsConfig['default.'])) {
343  ArrayUtility::mergeRecursiveWithOverrule($config, $currentFieldSuggestTsConfig['default.']);
344  }
345 
346  if (is_array($currentFieldSuggestTsConfig[$queryTable . '.'])) {
347  ArrayUtility::mergeRecursiveWithOverrule($config, $currentFieldSuggestTsConfig[$queryTable . '.']);
348  }
349 
350  return $config;
351  }
352 
361  protected function createListItemsFromResultRow(array $resultRows, $maxItems)
362  {
363  if (empty($resultRows)) {
364  return array();
365  }
366  $listItems = array();
367 
368  // traverse all found records and sort them
369  $rowsSort = array();
370  foreach ($resultRows as $key => $row) {
371  $rowsSort[$key] = $row['text'];
372  }
373  asort($rowsSort);
374  $rowsSort = array_keys($rowsSort);
375 
376  // put together the selector entries
377  for ($i = 0; $i < $maxItems; ++$i) {
378  $listItems[] = $resultRows[$rowsSort[$i]];
379  }
380  return $listItems;
381  }
382 
390  protected function getTablesToQueryFromFieldConfiguration(array $fieldConfig)
391  {
392  $queryTables = array();
393 
394  if (isset($fieldConfig['allowed'])) {
395  if ($fieldConfig['allowed'] !== '*') {
396  // list of allowed tables
397  $queryTables = GeneralUtility::trimExplode(',', $fieldConfig['allowed']);
398  } else {
399  // all tables are allowed, if the user can access them
400  foreach ($GLOBALS['TCA'] as $tableName => $tableConfig) {
401  if (!$this->isTableHidden($tableConfig) && $this->currentBackendUserMayAccessTable($tableConfig)) {
402  $queryTables[] = $tableName;
403  }
404  }
405  unset($tableName, $tableConfig);
406  }
407  } elseif (isset($fieldConfig['foreign_table'])) {
408  // use the foreign table
409  $queryTables = array($fieldConfig['foreign_table']);
410  }
411 
412  return $queryTables;
413  }
414 
423  protected function getWhereClause(array $fieldConfig)
424  {
425  if (!isset($fieldConfig['foreign_table'])) {
426  return '';
427  }
428 
429  // strip ORDER BY clause
430  return trim(preg_replace('/ORDER[[:space:]]+BY.*/i', '', $fieldConfig['foreign_table_where']));
431  }
432 
436  protected function getLanguageService()
437  {
438  return $GLOBALS['LANG'];
439  }
440 }