76258a6ca43473be5a86049f0f78d11bf4bd60c6
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / SuggestElement.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Element;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2007-2013 Andreas Wolf <andreas.wolf@ikt-werk.de>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the text file GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29
30 use TYPO3\CMS\Backend\Utility\BackendUtility;
31 use TYPO3\CMS\Core\Utility\GeneralUtility;
32
33 /**
34 * TCEforms wizard for rendering an AJAX selector for records
35 *
36 * @author Andreas Wolf <andreas.wolf@ikt-werk.de>
37 * @author Benjamin Mack <benni@typo3.org>
38 */
39 class SuggestElement {
40
41 // Count the number of ajax selectors used
42 public $suggestCount = 0;
43
44 public $cssClass = 'typo3-TCEforms-suggest';
45
46 /**
47 * @var \TYPO3\CMS\Backend\Form\FormEngine
48 */
49 public $TCEformsObj;
50
51 /**
52 * Initialize an instance of SuggestElement
53 *
54 * @param \TYPO3\CMS\Backend\Form\FormEngine $tceForms Reference to an TCEforms instance
55 * @return void
56 */
57 public function init(&$tceForms) {
58 $this->TCEformsObj = &$tceForms;
59 }
60
61 /**
62 * Renders an ajax-enabled text field. Also adds required JS
63 *
64 * @param string $fieldname The fieldname in the form
65 * @param string $table The table we render this selector for
66 * @param string $field The field we render this selector for
67 * @param array $row The row which is currently edited
68 * @param array $config The TSconfig of the field
69 * @return string The HTML code for the selector
70 */
71 public function renderSuggestSelector($fieldname, $table, $field, array $row, array $config) {
72 $this->suggestCount++;
73 $containerCssClass = $this->cssClass . ' ' . $this->cssClass . '-position-right';
74 $suggestId = 'suggest-' . $table . '-' . $field . '-' . $row['uid'];
75 if ($GLOBALS['TCA'][$table]['columns'][$field]['config']['type'] === 'flex') {
76 $fieldPattern = 'data[' . $table . '][' . $row['uid'] . '][';
77 $flexformField = str_replace($fieldPattern, '', $fieldname);
78 $flexformField = substr($flexformField, 0, -1);
79 $field = str_replace(array(']['), '|', $flexformField);
80 }
81 $selector = '
82 <div class="' . $containerCssClass . '" id="' . $suggestId . '">
83 <input type="text" id="' . $fieldname . 'Suggest" value="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.findRecord') . '" class="' . $this->cssClass . '-search" />
84 <div class="' . $this->cssClass . '-indicator" style="display: none;" id="' . $fieldname . 'SuggestIndicator">
85 <img src="' . $GLOBALS['BACK_PATH'] . 'gfx/spinner.gif" alt="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:alttext.suggestSearching') . '" />
86 </div>
87 <div class="' . $this->cssClass . '-choices" style="display: none;" id="' . $fieldname . 'SuggestChoices"></div>
88
89 </div>';
90 // Get minimumCharacters from TCA
91 if (isset($config['fieldConf']['config']['wizards']['suggest']['default']['minimumCharacters'])) {
92 $minChars = (int)$config['fieldConf']['config']['wizards']['suggest']['default']['minimumCharacters'];
93 }
94 // Overwrite it with minimumCharacters from TSConfig (TCEFORM) if given
95 if (isset($config['fieldTSConfig']['suggest.']['default.']['minimumCharacters'])) {
96 $minChars = (int)$config['fieldTSConfig']['suggest.']['default.']['minimumCharacters'];
97 }
98 $minChars = $minChars > 0 ? $minChars : 2;
99
100 // fetch the TCA field type to hand it over to the JS class
101 $type = '';
102 if (isset($config['fieldConf']['config']['type'])) {
103 $type = $config['fieldConf']['config']['type'];
104 }
105
106 // Replace "-" with ucwords for the JS object name
107 $jsObj = str_replace(' ', '', ucwords(str_replace('-', ' ', GeneralUtility::strtolower($suggestId))));
108 $this->TCEformsObj->additionalJS_post[] = '
109 var ' . $jsObj . ' = new TCEForms.Suggest("' . $fieldname . '", "' . $table . '", "' . $field . '", "' . $row['uid'] . '", ' . $row['pid'] . ', ' . $minChars . ', "' . $type . '");
110 ' . $jsObj . '.defaultValue = "' . GeneralUtility::slashJS($GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.findRecord')) . '";
111 ';
112 return $selector;
113 }
114
115 /**
116 * Search a data structure array recursively -- including within nested
117 * (repeating) elements -- for a particular field config.
118 *
119 * @param array $dataStructure The data structure
120 * @param string $fieldName The field name
121 * @return array
122 */
123 protected function getNestedDsFieldConfig(array $dataStructure, $fieldName) {
124 $fieldConfig = array();
125 $elements = $dataStructure['ROOT']['el'] ? $dataStructure['ROOT']['el'] : $dataStructure['el'];
126 if (is_array($elements)) {
127 foreach ($elements as $k => $ds) {
128 if ($k === $fieldName) {
129 $fieldConfig = $ds['TCEforms']['config'];
130 break;
131 } elseif (isset($ds['el'][$fieldName]['TCEforms']['config'])) {
132 $fieldConfig = $ds['el'][$fieldName]['TCEforms']['config'];
133 break;
134 } else {
135 $fieldConfig = $this->getNestedDsFieldConfig($ds, $fieldName);
136 }
137 }
138 }
139 return $fieldConfig;
140 }
141
142 /**
143 * Ajax handler for the "suggest" feature in TCEforms.
144 *
145 * @param array $params The parameters from the AJAX call
146 * @param \TYPO3\CMS\Core\Http\AjaxRequestHandler $ajaxObj The AJAX object representing the AJAX call
147 * @return void
148 */
149 public function processAjaxRequest($params, &$ajaxObj) {
150 // Get parameters from $_GET/$_POST
151 $search = GeneralUtility::_GP('value');
152 $table = GeneralUtility::_GP('table');
153 $field = GeneralUtility::_GP('field');
154 $uid = GeneralUtility::_GP('uid');
155 $pageId = GeneralUtility::_GP('pid');
156 // If the $uid is numeric, we have an already existing element, so get the
157 // TSconfig of the page itself or the element container (for non-page elements)
158 // otherwise it's a new element, so use given id of parent page (i.e., don't modify it here)
159 if (is_numeric($uid)) {
160 if ($table == 'pages') {
161 $pageId = $uid;
162 } else {
163 $row = BackendUtility::getRecord($table, $uid);
164 $pageId = $row['pid'];
165 }
166 }
167 $TSconfig = BackendUtility::getPagesTSconfig($pageId);
168 $queryTables = array();
169 $foreign_table_where = '';
170 $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
171 $parts = explode('|', $field);
172 if ($GLOBALS['TCA'][$table]['columns'][$parts[0]]['config']['type'] === 'flex') {
173 if (is_array($row) && count($row) > 0) {
174 $flexfieldTCAConfig = $GLOBALS['TCA'][$table]['columns'][$parts[0]]['config'];
175 $flexformDSArray = BackendUtility::getFlexFormDS($flexfieldTCAConfig, $row, $table);
176 $flexformDSArray = GeneralUtility::resolveAllSheetsInDS($flexformDSArray);
177 $flexformElement = $parts[count($parts) - 2];
178 $continue = TRUE;
179 foreach ($flexformDSArray as $sheet) {
180 foreach ($sheet as $_ => $dataStructure) {
181 $fieldConfig = $this->getNestedDsFieldConfig($dataStructure, $flexformElement);
182 if (count($fieldConfig) > 0) {
183 $continue = FALSE;
184 break;
185 }
186 }
187 if (!$continue) {
188 break;
189 }
190 }
191 $field = str_replace('|', '][', $field);
192 }
193 }
194 $wizardConfig = $fieldConfig['wizards']['suggest'];
195 if (isset($fieldConfig['allowed'])) {
196 if ($fieldConfig['allowed'] === '*') {
197 foreach ($GLOBALS['TCA'] as $tableName => $tableConfig) {
198 // TODO: Refactor function to BackendUtility
199 if (empty($tableConfig['ctrl']['hideTable'])
200 && ($GLOBALS['BE_USER']->isAdmin()
201 || (empty($tableConfig['ctrl']['adminOnly'])
202 && (empty($tableConfig['ctrl']['rootLevel'])
203 || !empty($tableConfig['ctrl']['security']['ignoreRootLevelRestriction']))))
204 ) {
205 $queryTables[] = $tableName;
206 }
207 }
208 unset($tableName, $tableConfig);
209 } else {
210 $queryTables = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $fieldConfig['allowed']);
211 }
212 } elseif (isset($fieldConfig['foreign_table'])) {
213 $queryTables = array($fieldConfig['foreign_table']);
214 $foreign_table_where = $fieldConfig['foreign_table_where'];
215 // strip ORDER BY clause
216 $foreign_table_where = trim(preg_replace('/ORDER[[:space:]]+BY.*/i', '', $foreign_table_where));
217 }
218 $resultRows = array();
219 // fetch the records for each query table. A query table is a table from which records are allowed to
220 // be added to the TCEForm selector, originally fetched from the "allowed" config option in the TCA
221 foreach ($queryTables as $queryTable) {
222 // if the table does not exist, skip it
223 if (!is_array($GLOBALS['TCA'][$queryTable]) || !count($GLOBALS['TCA'][$queryTable])) {
224 continue;
225 }
226 $config = (array) $wizardConfig['default'];
227 if (is_array($wizardConfig[$queryTable])) {
228 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($config, $wizardConfig[$queryTable]);
229 }
230 // merge the configurations of different "levels" to get the working configuration for this table and
231 // field (i.e., go from the most general to the most special configuration)
232 if (is_array($TSconfig['TCEFORM.']['suggest.']['default.'])) {
233 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($config, $TSconfig['TCEFORM.']['suggest.']['default.']);
234 }
235 if (is_array($TSconfig['TCEFORM.']['suggest.'][$queryTable . '.'])) {
236 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($config, $TSconfig['TCEFORM.']['suggest.'][$queryTable . '.']);
237 }
238 // use $table instead of $queryTable here because we overlay a config
239 // for the input-field here, not for the queried table
240 if (is_array($TSconfig['TCEFORM.'][$table . '.'][$field . '.']['suggest.']['default.'])) {
241 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($config, $TSconfig['TCEFORM.'][$table . '.'][$field . '.']['suggest.']['default.']);
242 }
243 if (is_array($TSconfig['TCEFORM.'][$table . '.'][$field . '.']['suggest.'][$queryTable . '.'])) {
244 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($config, $TSconfig['TCEFORM.'][$table . '.'][$field . '.']['suggest.'][$queryTable . '.']);
245 }
246 //process addWhere
247 if (!isset($config['addWhere']) && $foreign_table_where) {
248 $config['addWhere'] = $foreign_table_where;
249 }
250 if (isset($config['addWhere'])) {
251 $config['addWhere'] = strtr(' ' . $config['addWhere'], array(
252 '###THIS_UID###' => (int)$uid,
253 '###CURRENT_PID###' => (int)$pageId
254 ));
255 }
256 // instantiate the class that should fetch the records for this $queryTable
257 $receiverClassName = $config['receiverClass'];
258 if (!class_exists($receiverClassName)) {
259 $receiverClassName = 'TYPO3\\CMS\\Backend\\Form\\Element\\SuggestDefaultReceiver';
260 }
261 $receiverObj = GeneralUtility::makeInstance($receiverClassName, $queryTable, $config);
262 $params = array('value' => $search);
263 $rows = $receiverObj->queryTable($params);
264 if (empty($rows)) {
265 continue;
266 }
267 $resultRows = GeneralUtility::array_merge($resultRows, $rows);
268 unset($rows);
269 }
270 $listItems = array();
271 if (count($resultRows) > 0) {
272 // traverse all found records and sort them
273 $rowsSort = array();
274 foreach ($resultRows as $key => $row) {
275 $rowsSort[$key] = $row['text'];
276 }
277 asort($rowsSort);
278 $rowsSort = array_keys($rowsSort);
279 // Limit the number of items in the result list
280 $maxItems = $config['maxItemsInResultList'] ?: 10;
281 $maxItems = min(count($resultRows), $maxItems);
282 // put together the selector entry
283 for ($i = 0; $i < $maxItems; $i++) {
284 $row = $resultRows[$rowsSort[$i]];
285 $rowId = $row['table'] . '-' . $row['uid'] . '-' . $table . '-' . $uid . '-' . $field;
286 $listItems[] = '<li' . ($row['class'] != '' ? ' class="' . $row['class'] . '"' : '') . ' id="' . $rowId . '"' . ($row['style'] != '' ? ' style="' . $row['style'] . '"' : '') . '>' . $row['sprite'] . $row['text'] . '</li>';
287 }
288 }
289 if (count($listItems) > 0) {
290 $list = implode('', $listItems);
291 } else {
292 $list = '<li class="suggest-noresults"><i>' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.noRecordFound') . '</i></li>';
293 }
294 $list = '<ul class="' . $this->cssClass . '-resultlist">' . $list . '</ul>';
295 $ajaxObj->addContent(0, $list);
296 }
297
298 }