2 namespace TYPO3\CMS\Backend\Form\Element
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Backend\Utility\BackendUtility
;
18 use TYPO3\CMS\Core\Utility\GeneralUtility
;
21 * TCEforms wizard for rendering an AJAX selector for records
23 * @author Andreas Wolf <andreas.wolf@ikt-werk.de>
24 * @author Benjamin Mack <benni@typo3.org>
26 class SuggestElement
{
29 * @var int Count the number of ajax selectors used
31 public $suggestCount = 0;
36 public $cssClass = 'typo3-TCEforms-suggest';
39 * @var \TYPO3\CMS\Backend\Form\FormEngine
44 * Initialize an instance of SuggestElement
46 * @param \TYPO3\CMS\Backend\Form\FormEngine $tceForms Reference to an TCEforms instance
49 public function init($tceForms) {
50 $this->TCEformsObj
= $tceForms;
54 * Renders an ajax-enabled text field. Also adds required JS
56 * @param string $fieldname The fieldname in the form
57 * @param string $table The table we render this selector for
58 * @param string $field The field we render this selector for
59 * @param array $row The row which is currently edited
60 * @param array $config The TSconfig of the field
61 * @return string The HTML code for the selector
63 public function renderSuggestSelector($fieldname, $table, $field, array $row, array $config) {
64 $this->suggestCount++
;
65 $containerCssClass = $this->cssClass
. ' ' . $this->cssClass
. '-position-right';
66 $suggestId = 'suggest-' . $table . '-' . $field . '-' . $row['uid'];
67 if ($GLOBALS['TCA'][$table]['columns'][$field]['config']['type'] === 'flex') {
68 $fieldPattern = 'data[' . $table . '][' . $row['uid'] . '][';
69 $flexformField = str_replace($fieldPattern, '', $fieldname);
70 $flexformField = substr($flexformField, 0, -1);
71 $field = str_replace(array(']['), '|', $flexformField);
74 <div class="' . $containerCssClass . '" id="' . $suggestId . '">
75 <input type="text" id="' . $fieldname . 'Suggest" value="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.findRecord') . '" class="' . $this->cssClass
. '-search" />
76 <div class="' . $this->cssClass
. '-indicator" style="display: none;" id="' . $fieldname . 'SuggestIndicator">
77 <img src="' . $GLOBALS['BACK_PATH'] . 'gfx/spinner.gif" alt="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:alttext.suggestSearching') . '" />
79 <div class="' . $this->cssClass
. '-choices" style="display: none;" id="' . $fieldname . 'SuggestChoices"></div>
82 // Get minimumCharacters from TCA
84 if (isset($config['fieldConf']['config']['wizards']['suggest']['default']['minimumCharacters'])) {
85 $minChars = (int)$config['fieldConf']['config']['wizards']['suggest']['default']['minimumCharacters'];
87 // Overwrite it with minimumCharacters from TSConfig (TCEFORM) if given
88 if (isset($config['fieldTSConfig']['suggest.']['default.']['minimumCharacters'])) {
89 $minChars = (int)$config['fieldTSConfig']['suggest.']['default.']['minimumCharacters'];
91 $minChars = $minChars > 0 ?
$minChars : 2;
93 // fetch the TCA field type to hand it over to the JS class
95 if (isset($config['fieldConf']['config']['type'])) {
96 $type = $config['fieldConf']['config']['type'];
99 // Replace "-" with ucwords for the JS object name
100 $jsObj = str_replace(' ', '', ucwords(str_replace(array('-', '.'), ' ', GeneralUtility
::strtolower($suggestId))));
101 $this->TCEformsObj
->additionalJS_post
[] = '
102 var ' . $jsObj . ' = new TCEForms.Suggest("' . $fieldname . '", "' . $table . '", "' . $field . '", "' . $row['uid'] . '", ' . $row['pid'] . ', ' . $minChars . ', "' . $type . '");
103 ' . $jsObj . '.defaultValue = "' . GeneralUtility
::slashJS($GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.findRecord')) . '";
109 * Search a data structure array recursively -- including within nested
110 * (repeating) elements -- for a particular field config.
112 * @param array $dataStructure The data structure
113 * @param string $fieldName The field name
116 protected function getNestedDsFieldConfig(array $dataStructure, $fieldName) {
117 $fieldConfig = array();
118 $elements = $dataStructure['ROOT']['el'] ?
$dataStructure['ROOT']['el'] : $dataStructure['el'];
119 if (is_array($elements)) {
120 foreach ($elements as $k => $ds) {
121 if ($k === $fieldName) {
122 $fieldConfig = $ds['TCEforms']['config'];
124 } elseif (isset($ds['el'][$fieldName]['TCEforms']['config'])) {
125 $fieldConfig = $ds['el'][$fieldName]['TCEforms']['config'];
128 $fieldConfig = $this->getNestedDsFieldConfig($ds, $fieldName);
136 * Ajax handler for the "suggest" feature in TCEforms.
138 * @param array $params The parameters from the AJAX call
139 * @param \TYPO3\CMS\Core\Http\AjaxRequestHandler $ajaxObj The AJAX object representing the AJAX call
142 public function processAjaxRequest($params, &$ajaxObj) {
143 // Get parameters from $_GET/$_POST
144 $search = GeneralUtility
::_GP('value');
145 $table = GeneralUtility
::_GP('table');
146 $field = GeneralUtility
::_GP('field');
147 $uid = GeneralUtility
::_GP('uid');
148 $pageId = GeneralUtility
::_GP('pid');
149 // If the $uid is numeric, we have an already existing element, so get the
150 // TSconfig of the page itself or the element container (for non-page elements)
151 // otherwise it's a new element, so use given id of parent page (i.e., don't modify it here)
153 if (is_numeric($uid)) {
154 $row = BackendUtility
::getRecord($table, $uid);
155 if ($table == 'pages') {
158 $pageId = $row['pid'];
161 $TSconfig = BackendUtility
::getPagesTSconfig($pageId);
162 $queryTables = array();
163 $foreign_table_where = '';
164 $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
165 $parts = explode('|', $field);
166 if (!empty($row) && $GLOBALS['TCA'][$table]['columns'][$parts[0]]['config']['type'] === 'flex') {
167 $flexfieldTCAConfig = $GLOBALS['TCA'][$table]['columns'][$parts[0]]['config'];
168 $flexformDSArray = BackendUtility
::getFlexFormDS($flexfieldTCAConfig, $row, $table, $parts[0]);
169 $flexformDSArray = GeneralUtility
::resolveAllSheetsInDS($flexformDSArray);
170 $flexformElement = $parts[count($parts) - 2];
172 foreach ($flexformDSArray as $sheet) {
173 foreach ($sheet as $dataStructure) {
174 $fieldConfig = $this->getNestedDsFieldConfig($dataStructure, $flexformElement);
175 if (count($fieldConfig) > 0) {
184 $field = str_replace('|', '][', $field);
186 $wizardConfig = $fieldConfig['wizards']['suggest'];
187 if (isset($fieldConfig['allowed'])) {
188 if ($fieldConfig['allowed'] === '*') {
189 foreach ($GLOBALS['TCA'] as $tableName => $tableConfig) {
190 // TODO: Refactor function to BackendUtility
191 if (empty($tableConfig['ctrl']['hideTable'])
192 && ($GLOBALS['BE_USER']->isAdmin()
193 ||
(empty($tableConfig['ctrl']['adminOnly'])
194 && (empty($tableConfig['ctrl']['rootLevel'])
195 ||
!empty($tableConfig['ctrl']['security']['ignoreRootLevelRestriction']))))
197 $queryTables[] = $tableName;
200 unset($tableName, $tableConfig);
202 $queryTables = GeneralUtility
::trimExplode(',', $fieldConfig['allowed']);
204 } elseif (isset($fieldConfig['foreign_table'])) {
205 $queryTables = array($fieldConfig['foreign_table']);
206 $foreign_table_where = $fieldConfig['foreign_table_where'];
207 // strip ORDER BY clause
208 $foreign_table_where = trim(preg_replace('/ORDER[[:space:]]+BY.*/i', '', $foreign_table_where));
210 $resultRows = array();
211 // fetch the records for each query table. A query table is a table from which records are allowed to
212 // be added to the TCEForm selector, originally fetched from the "allowed" config option in the TCA
213 foreach ($queryTables as $queryTable) {
214 // if the table does not exist, skip it
215 if (!is_array($GLOBALS['TCA'][$queryTable]) ||
!count($GLOBALS['TCA'][$queryTable])) {
218 $config = (array)$wizardConfig['default'];
219 if (is_array($wizardConfig[$queryTable])) {
220 \TYPO3\CMS\Core\Utility\ArrayUtility
::mergeRecursiveWithOverrule($config, $wizardConfig[$queryTable]);
222 // merge the configurations of different "levels" to get the working configuration for this table and
223 // field (i.e., go from the most general to the most special configuration)
224 if (is_array($TSconfig['TCEFORM.']['suggest.']['default.'])) {
225 \TYPO3\CMS\Core\Utility\ArrayUtility
::mergeRecursiveWithOverrule($config, $TSconfig['TCEFORM.']['suggest.']['default.']);
227 if (is_array($TSconfig['TCEFORM.']['suggest.'][$queryTable . '.'])) {
228 \TYPO3\CMS\Core\Utility\ArrayUtility
::mergeRecursiveWithOverrule($config, $TSconfig['TCEFORM.']['suggest.'][$queryTable . '.']);
230 // use $table instead of $queryTable here because we overlay a config
231 // for the input-field here, not for the queried table
232 if (is_array($TSconfig['TCEFORM.'][$table . '.'][$field . '.']['suggest.']['default.'])) {
233 \TYPO3\CMS\Core\Utility\ArrayUtility
::mergeRecursiveWithOverrule($config, $TSconfig['TCEFORM.'][$table . '.'][$field . '.']['suggest.']['default.']);
235 if (is_array($TSconfig['TCEFORM.'][$table . '.'][$field . '.']['suggest.'][$queryTable . '.'])) {
236 \TYPO3\CMS\Core\Utility\ArrayUtility
::mergeRecursiveWithOverrule($config, $TSconfig['TCEFORM.'][$table . '.'][$field . '.']['suggest.'][$queryTable . '.']);
239 if (!isset($config['addWhere']) && $foreign_table_where) {
240 $config['addWhere'] = $foreign_table_where;
242 if (isset($config['addWhere'])) {
243 $replacement = array(
244 '###THIS_UID###' => (int)$uid,
245 '###CURRENT_PID###' => (int)$pageId
247 if (isset($TSconfig['TCEFORM.'][$table . '.'][$field . '.'])) {
248 $fieldTSconfig = $TSconfig['TCEFORM.'][$table . '.'][$field . '.'];
249 if (isset($fieldTSconfig['PAGE_TSCONFIG_ID'])) {
250 $replacement['###PAGE_TSCONFIG_ID###'] = (int)$fieldTSconfig['PAGE_TSCONFIG_ID'];
252 if (isset($fieldTSconfig['PAGE_TSCONFIG_IDLIST'])) {
253 $replacement['###PAGE_TSCONFIG_IDLIST###'] = $GLOBALS['TYPO3_DB']->cleanIntList($fieldTSconfig['PAGE_TSCONFIG_IDLIST']);
255 if (isset($fieldTSconfig['PAGE_TSCONFIG_STR'])) {
256 $replacement['###PAGE_TSCONFIG_STR###'] = $GLOBALS['TYPO3_DB']->quoteStr($fieldTSconfig['PAGE_TSCONFIG_STR'], $fieldConfig['foreign_table']);
259 $config['addWhere'] = strtr(' ' . $config['addWhere'], $replacement);
261 // instantiate the class that should fetch the records for this $queryTable
262 $receiverClassName = $config['receiverClass'];
263 if (!class_exists($receiverClassName)) {
264 $receiverClassName = 'TYPO3\\CMS\\Backend\\Form\\Element\\SuggestDefaultReceiver';
266 $receiverObj = GeneralUtility
::makeInstance($receiverClassName, $queryTable, $config);
267 $params = array('value' => $search);
268 $rows = $receiverObj->queryTable($params);
272 $resultRows = GeneralUtility
::array_merge($resultRows, $rows);
275 $listItems = array();
276 if (count($resultRows) > 0) {
277 // traverse all found records and sort them
279 foreach ($resultRows as $key => $row) {
280 $rowsSort[$key] = $row['text'];
283 $rowsSort = array_keys($rowsSort);
284 // Limit the number of items in the result list
285 $maxItems = $config['maxItemsInResultList'] ?
: 10;
286 $maxItems = min(count($resultRows), $maxItems);
287 // put together the selector entry
288 for ($i = 0; $i < $maxItems; $i++
) {
289 $row = $resultRows[$rowsSort[$i]];
290 $rowId = $row['table'] . '-' . $row['uid'] . '-' . $table . '-' . $uid . '-' . $field;
291 $listItems[] = '<li' . ($row['class'] != '' ?
' class="' . $row['class'] . '"' : '') . ' id="' . $rowId . '"' . ($row['style'] != '' ?
' style="' . $row['style'] . '"' : '') . '>' . $row['sprite'] . $row['text'] . '</li>';
294 if (count($listItems) > 0) {
295 $list = implode('', $listItems);
297 $list = '<li class="suggest-noresults"><i>' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.noRecordFound') . '</i></li>';
299 $list = '<ul class="' . $this->cssClass
. '-resultlist">' . $list . '</ul>';
300 $ajaxObj->addContent(0, $list);