TYPO3 CMS  TYPO3_7-6
SuggestWizard.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 
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([']['], '|', $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 = json_encode($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', true) . '"
86  data-fieldname="' . htmlspecialchars($fieldname) . '"
87  data-table="' . htmlspecialchars($table) . '"
88  data-field="' . htmlspecialchars($field) . '"
89  data-uid="' . htmlspecialchars($row['uid']) . '"
90  data-pid="' . (int)$row['pid'] . '"
91  data-fieldtype="' . htmlspecialchars($type) . '"
92  data-minchars="' . (int)$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 = [];
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 
136  public function searchAction(ServerRequestInterface $request, ResponseInterface $response)
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 = json_decode($newRecordRow, true);
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 = [];
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 = [
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 = ['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 (bool)$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  if (substr($row['uid'], 0, 3) === 'NEW') {
288  // We have to cleanup record information as they are coming from FormEngines DataProvider
289  $pointerFields = GeneralUtility::trimExplode(',', $flexfieldTCAConfig['ds_pointerField']);
290  foreach ($pointerFields as $pointerField) {
291  if (is_array($row[$pointerField])) {
292  $row[$pointerField] = $row[$pointerField][0];
293  }
294  }
295  }
296  $flexformDSArray = BackendUtility::getFlexFormDS($flexfieldTCAConfig, $row, $table, $parts[0]);
297  $flexformDSArray = GeneralUtility::resolveAllSheetsInDS($flexformDSArray);
298  $flexformElement = $parts[count($parts) - 2];
299  foreach ($flexformDSArray as $sheet) {
300  foreach ($sheet as $_ => $dataStructure) {
301  $fieldConfig = $this->getNestedDsFieldConfig($dataStructure, $flexformElement);
302  if (!empty($fieldConfig)) {
303  break 2;
304  }
305  }
306  }
307  // Flexform field name levels are separated with | instead of encapsulation in [];
308  // reverse this here to be compatible with regular field names.
309  $field = str_replace('|', '][', $field);
310  }
311  }
312 
324  protected function getConfigurationForTable($queryTable, array $wizardConfig, array $TSconfig, $table, $field)
325  {
326  $config = (array)$wizardConfig['default'];
327 
328  if (is_array($wizardConfig[$queryTable])) {
329  ArrayUtility::mergeRecursiveWithOverrule($config, $wizardConfig[$queryTable]);
330  }
331  $globalSuggestTsConfig = $TSconfig['TCEFORM.']['suggest.'];
332  $currentFieldSuggestTsConfig = $TSconfig['TCEFORM.'][$table . '.'][$field . '.']['suggest.'];
333 
334  // merge the configurations of different "levels" to get the working configuration for this table and
335  // field (i.e., go from the most general to the most special configuration)
336  if (is_array($globalSuggestTsConfig['default.'])) {
337  ArrayUtility::mergeRecursiveWithOverrule($config, $globalSuggestTsConfig['default.']);
338  }
339 
340  if (is_array($globalSuggestTsConfig[$queryTable . '.'])) {
341  ArrayUtility::mergeRecursiveWithOverrule($config, $globalSuggestTsConfig[$queryTable . '.']);
342  }
343 
344  // use $table instead of $queryTable here because we overlay a config
345  // for the input-field here, not for the queried table
346  if (is_array($currentFieldSuggestTsConfig['default.'])) {
347  ArrayUtility::mergeRecursiveWithOverrule($config, $currentFieldSuggestTsConfig['default.']);
348  }
349 
350  if (is_array($currentFieldSuggestTsConfig[$queryTable . '.'])) {
351  ArrayUtility::mergeRecursiveWithOverrule($config, $currentFieldSuggestTsConfig[$queryTable . '.']);
352  }
353 
354  return $config;
355  }
356 
365  protected function createListItemsFromResultRow(array $resultRows, $maxItems)
366  {
367  if (empty($resultRows)) {
368  return [];
369  }
370  $listItems = [];
371 
372  // traverse all found records and sort them
373  $rowsSort = [];
374  foreach ($resultRows as $key => $row) {
375  $rowsSort[$key] = $row['text'];
376  }
377  asort($rowsSort);
378  $rowsSort = array_keys($rowsSort);
379 
380  // put together the selector entries
381  for ($i = 0; $i < $maxItems; ++$i) {
382  $listItems[] = $resultRows[$rowsSort[$i]];
383  }
384  return $listItems;
385  }
386 
394  protected function getTablesToQueryFromFieldConfiguration(array $fieldConfig)
395  {
396  $queryTables = [];
397 
398  if (isset($fieldConfig['allowed'])) {
399  if ($fieldConfig['allowed'] !== '*') {
400  // list of allowed tables
401  $queryTables = GeneralUtility::trimExplode(',', $fieldConfig['allowed']);
402  } else {
403  // all tables are allowed, if the user can access them
404  foreach ($GLOBALS['TCA'] as $tableName => $tableConfig) {
405  if (!$this->isTableHidden($tableConfig) && $this->currentBackendUserMayAccessTable($tableConfig)) {
406  $queryTables[] = $tableName;
407  }
408  }
409  unset($tableName, $tableConfig);
410  }
411  } elseif (isset($fieldConfig['foreign_table'])) {
412  // use the foreign table
413  $queryTables = [$fieldConfig['foreign_table']];
414  }
415 
416  return $queryTables;
417  }
418 
427  protected function getWhereClause(array $fieldConfig)
428  {
429  if (!isset($fieldConfig['foreign_table'])) {
430  return '';
431  }
432 
433  // strip ORDER BY clause
434  return trim(preg_replace('/ORDER[[:space:]]+BY.*/i', '', $fieldConfig['foreign_table_where']));
435  }
436 
440  protected function getLanguageService()
441  {
442  return $GLOBALS['LANG'];
443  }
444 }
static getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
getNestedDsFieldConfig(array $dataStructure, $fieldName)
static resolveAllSheetsInDS(array $dataStructArray)
createListItemsFromResultRow(array $resultRows, $maxItems)
getConfigurationForTable($queryTable, array $wizardConfig, array $TSconfig, $table, $field)
static getFlexFormDS($conf, $row, $table, $fieldName='', $WSOL=true, $newRecordPidValue=0)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
getTablesToQueryFromFieldConfiguration(array $fieldConfig)
searchAction(ServerRequestInterface $request, ResponseInterface $response)
overrideFieldNameAndConfigurationForFlexform($table, &$field, array $row, &$fieldConfig)
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
$uid
Definition: server.php:38
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']