15b7f096e472a466fe47aa8a4b339b7f97cb22e5
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / InlineStackProcessor.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form;
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
17 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20
21 /**
22 * Handle inline stack.
23 *
24 * Code related to inline elements need to know their nesting level. This class takes
25 * care of the according handling and can return field prefixes to be used in DOM.
26 */
27 class InlineStackProcessor {
28
29 /**
30 * The structure/hierarchy where working in, e.g. cascading inline tables
31 *
32 * @var array
33 */
34 protected $inlineStructure = array();
35
36 /**
37 * One of two possible initialize methods setting a given structure.
38 *
39 * @param array $structure
40 */
41 public function initializeByGivenStructure(array $structure = array()) {
42 $this->inlineStructure = $structure;
43 }
44
45 /**
46 * Convert the DOM object-id of an inline container to an array.
47 * The object-id could look like 'data-parentPageId-tx_mmftest_company-1-employees'.
48 * This initializes $this->inlineStructure - used by AJAX entry points
49 * There are two keys:
50 * - 'stable': Containing full qualified identifiers (table, uid and field)
51 * - 'unstable': Containing partly filled data (e.g. only table and possibly field)
52 *
53 * @param string $domObjectId The DOM object-id
54 * @param bool $loadConfig Load the TCA configuration for that level (default: TRUE)
55 * @return void
56 */
57 public function initializeByParsingDomObjectIdString($domObjectId, $loadConfig = TRUE) {
58 $unstable = array();
59 $vector = array('table', 'uid', 'field');
60
61 // Substitute FlexForm addition and make parsing a bit easier
62 $domObjectId = str_replace('---', ':', $domObjectId);
63 // The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>)
64 $pattern = '/^data' . '-' . '(.+?)' . '-' . '(.+)$/';
65
66 if (preg_match($pattern, $domObjectId, $match)) {
67 $inlineFirstPid = $match[1];
68 $parts = explode('-', $match[2]);
69 $partsCnt = count($parts);
70 for ($i = 0; $i < $partsCnt; $i++) {
71 if ($i > 0 && $i % 3 == 0) {
72 // Load the TCA configuration of the table field and store it in the stack
73 if ($loadConfig) {
74 $unstable['config'] = $GLOBALS['TCA'][$unstable['table']]['columns'][$unstable['field']]['config'];
75 // Fetch TSconfig:
76 $TSconfig = FormEngineUtility::getTSconfigForTableRow($unstable['table'], array('uid' => $unstable['uid'], 'pid' => $inlineFirstPid), $unstable['field']);
77 // Override TCA field config by TSconfig:
78 if (!$TSconfig['disabled']) {
79 $unstable['config'] = FormEngineUtility::overrideFieldConf($unstable['config'], $TSconfig);
80 }
81 $unstable['localizationMode'] = BackendUtility::getInlineLocalizationMode($unstable['table'], $unstable['config']);
82 }
83
84 // Extract FlexForm from field part (if any)
85 if (strpos($unstable['field'], ':') !== FALSE) {
86 $fieldParts = GeneralUtility::trimExplode(':', $unstable['field']);
87 $unstable['field'] = array_shift($fieldParts);
88 // FlexForm parts start with data:
89 if (count($fieldParts) > 0 && $fieldParts[0] === 'data') {
90 $unstable['flexform'] = $fieldParts;
91 }
92 }
93
94 $this->inlineStructure['stable'][] = $unstable;
95 $unstable = array();
96 }
97 $unstable[$vector[$i % 3]] = $parts[$i];
98 }
99 if (count($unstable)) {
100 $this->inlineStructure['unstable'] = $unstable;
101 }
102 }
103 }
104
105 /**
106 * Injects configuration via AJAX calls.
107 * This is used by inline ajax calls that transfer configuration options back to the stack for initialization
108 * The configuration is validated using HMAC to avoid hijacking.
109 *
110 * @param string $contextString Given context string from ajax call
111 * @return void
112 * @todo: Review this construct - Why can't the ajax call fetch these data on its own and transfers it to client instead?
113 */
114 public function injectAjaxConfiguration($contextString = '') {
115 $level = $this->calculateStructureLevel(-1);
116 if (empty($contextString) || $level === FALSE) {
117 return;
118 }
119 $current = &$this->inlineStructure['stable'][$level];
120 $context = json_decode($contextString, TRUE);
121 if (GeneralUtility::hmac(serialize($context['config'])) !== $context['hmac']) {
122 return;
123 }
124 $current['config'] = $context['config'];
125 $current['localizationMode'] = BackendUtility::getInlineLocalizationMode(
126 $current['table'],
127 $current['config']
128 );
129 }
130
131 /**
132 * Get current structure stack
133 *
134 * @return array Current structure stack
135 */
136 public function getStructure() {
137 return $this->inlineStructure;
138 }
139
140 /**
141 * Add a stable structure to the stack
142 *
143 * @param array $structureItem
144 * @return void
145 */
146 public function pushStableStructureItem(array $structureItem = array()) {
147 $this->inlineStructure['stable'][] = $structureItem;
148 }
149
150 /**
151 * Prefix for inline form fields
152 *
153 * @param string $prependFormFieldNames General prefix of forms
154 * @return string
155 */
156 public function getCurrentStructureFormPrefix($prependFormFieldNames) {
157 $current = $this->getStructureLevel(-1);
158 $inlineFormName = '';
159 // If there are still more inline levels available
160 if ($current !== FALSE) {
161 $inlineFormName = $prependFormFieldNames . $this->getStructureItemName($current, 'Disposal_AttributeName');
162 }
163 return $inlineFormName;
164 }
165
166 /**
167 * DOM object-id for this inline level
168 *
169 * @param integer $inlineFirstPid Pid of top level inline element storage
170 * @return string
171 */
172 public function getCurrentStructureDomObjectIdPrefix($inlineFirstPid) {
173 $current = $this->getStructureLevel(-1);
174 $inlineDomObjectId = '';
175 // If there are still more inline levels available
176 if ($current !== FALSE) {
177 $inlineDomObjectId = 'data' . '-' . $inlineFirstPid . '-' . $this->getStructurePath();
178 }
179 return $inlineDomObjectId;
180 }
181
182 /**
183 * Get a level from the stack and return the data.
184 * If the $level value is negative, this function works top-down,
185 * if the $level value is positive, this function works bottom-up.
186 * Hint: If -1 is given, the "current" - most bottom "stable" item is returned
187 *
188 * @param int $level Which level to return
189 * @return array The item of the stack at the requested level
190 */
191 public function getStructureLevel($level) {
192 $level = $this->calculateStructureLevel($level);
193
194 if ($level !== FALSE) {
195 return $this->inlineStructure['stable'][$level];
196 } else {
197 return FALSE;
198 }
199 }
200
201 /**
202 * Get the "unstable" structure item from structure stack.
203 * This is typically initialized by initializeByParsingDomObjectIdString()
204 *
205 * @return array Unstable structure item
206 * @throws \RuntimeException
207 */
208 public function getUnstableStructure() {
209 if (!isset($this->inlineStructure['unstable'])) {
210 throw new \RuntimeException('No unstable inline structure found', 1428582655);
211 }
212 return $this->inlineStructure['unstable'];
213 }
214
215 /**
216 * Calculates structure level.
217 *
218 * @param int $level Which level to return
219 * @return bool|int
220 */
221 protected function calculateStructureLevel($level) {
222 $result = FALSE;
223 $inlineStructureCount = count($this->inlineStructure['stable']);
224 if ($level < 0) {
225 $level = $inlineStructureCount + $level;
226 }
227 if ($level >= 0 && $level < $inlineStructureCount) {
228 $result = $level;
229 }
230 return $result;
231 }
232
233 /**
234 * Get the identifiers of a given depth of level, from the top of the stack to the bottom.
235 * An identifier looks like "<table>-<uid>-<field>".
236 *
237 * @param int $structureDepth How much levels to output, beginning from the top of the stack
238 * @return string The path of identifiers
239 */
240 protected function getStructurePath($structureDepth = -1) {
241 $structureLevels = array();
242 $structureCount = $this->getStructureDepth();
243 if ($structureDepth < 0 || $structureDepth > $structureCount) {
244 $structureDepth = $structureCount;
245 }
246 for ($i = 1; $i <= $structureDepth; $i++) {
247 array_unshift($structureLevels, $this->getStructureItemName($this->getStructureLevel(-$i), 'Disposal_AttributeId'));
248 }
249 return implode('-', $structureLevels);
250 }
251
252 /**
253 * Get the depth of the stable structure stack.
254 * (count($this->inlineStructure['stable'])
255 *
256 * @return int The depth of the structure stack
257 */
258 public function getStructureDepth() {
259 return count($this->inlineStructure['stable']);
260 }
261
262 /**
263 * Create a name/id for usage in HTML output of a level of the structure stack to be used in form names.
264 *
265 * @param array $levelData Array of a level of the structure stack (containing the keys table, uid and field)
266 * @param string $disposal How the structure name is used (e.g. as <div id="..."> or <input name="..." />)
267 * @return string The name/id of that level, to be used for HTML output
268 */
269 protected function getStructureItemName($levelData, $disposal = 'Disposal_AttributeId') {
270 $name = NULL;
271
272 if (is_array($levelData)) {
273 $parts = array($levelData['table'], $levelData['uid']);
274
275 if (!empty($levelData['field'])) {
276 $parts[] = $levelData['field'];
277 }
278
279 // Use in name attributes:
280 if ($disposal === 'Disposal_AttributeName') {
281 if (!empty($levelData['field']) && !empty($levelData['flexform']) && $this->getStructureLevel(-1) === $levelData) {
282 $parts[] = implode('][', $levelData['flexform']);
283 }
284 $name = '[' . implode('][', $parts) . ']';
285 // Use in object id attributes:
286 } else {
287 $name = implode('-', $parts);
288
289 if (!empty($levelData['field']) && !empty($levelData['flexform'])) {
290 array_unshift($levelData['flexform'], $name);
291 $name = implode('---', $levelData['flexform']);
292 }
293 }
294 }
295
296 return $name;
297 }
298
299 }