4a150b56a4d0e3ced075fb0e98ce0ea947d2fa0b
[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 * @return string
154 */
155 public function getCurrentStructureFormPrefix() {
156 $current = $this->getStructureLevel(-1);
157 $inlineFormName = '';
158 // If there are still more inline levels available
159 if ($current !== FALSE) {
160 $inlineFormName = 'data' . $this->getStructureItemName($current, 'Disposal_AttributeName');
161 }
162 return $inlineFormName;
163 }
164
165 /**
166 * DOM object-id for this inline level
167 *
168 * @param int $inlineFirstPid Pid of top level inline element storage
169 * @return string
170 */
171 public function getCurrentStructureDomObjectIdPrefix($inlineFirstPid) {
172 $current = $this->getStructureLevel(-1);
173 $inlineDomObjectId = '';
174 // If there are still more inline levels available
175 if ($current !== FALSE) {
176 $inlineDomObjectId = 'data' . '-' . $inlineFirstPid . '-' . $this->getStructurePath();
177 }
178 return $inlineDomObjectId;
179 }
180
181 /**
182 * Get a level from the stack and return the data.
183 * If the $level value is negative, this function works top-down,
184 * if the $level value is positive, this function works bottom-up.
185 * Hint: If -1 is given, the "current" - most bottom "stable" item is returned
186 *
187 * @param int $level Which level to return
188 * @return array The item of the stack at the requested level
189 */
190 public function getStructureLevel($level) {
191 $level = $this->calculateStructureLevel($level);
192
193 if ($level !== FALSE) {
194 return $this->inlineStructure['stable'][$level];
195 } else {
196 return FALSE;
197 }
198 }
199
200 /**
201 * Get the "unstable" structure item from structure stack.
202 * This is typically initialized by initializeByParsingDomObjectIdString()
203 *
204 * @return array Unstable structure item
205 * @throws \RuntimeException
206 */
207 public function getUnstableStructure() {
208 if (!isset($this->inlineStructure['unstable'])) {
209 throw new \RuntimeException('No unstable inline structure found', 1428582655);
210 }
211 return $this->inlineStructure['unstable'];
212 }
213
214 /**
215 * Calculates structure level.
216 *
217 * @param int $level Which level to return
218 * @return bool|int
219 */
220 protected function calculateStructureLevel($level) {
221 $result = FALSE;
222 $inlineStructureCount = count($this->inlineStructure['stable']);
223 if ($level < 0) {
224 $level = $inlineStructureCount + $level;
225 }
226 if ($level >= 0 && $level < $inlineStructureCount) {
227 $result = $level;
228 }
229 return $result;
230 }
231
232 /**
233 * Get the identifiers of a given depth of level, from the top of the stack to the bottom.
234 * An identifier looks like "<table>-<uid>-<field>".
235 *
236 * @param int $structureDepth How much levels to output, beginning from the top of the stack
237 * @return string The path of identifiers
238 */
239 protected function getStructurePath($structureDepth = -1) {
240 $structureLevels = array();
241 $structureCount = $this->getStructureDepth();
242 if ($structureDepth < 0 || $structureDepth > $structureCount) {
243 $structureDepth = $structureCount;
244 }
245 for ($i = 1; $i <= $structureDepth; $i++) {
246 array_unshift($structureLevels, $this->getStructureItemName($this->getStructureLevel(-$i), 'Disposal_AttributeId'));
247 }
248 return implode('-', $structureLevels);
249 }
250
251 /**
252 * Get the depth of the stable structure stack.
253 * (count($this->inlineStructure['stable'])
254 *
255 * @return int The depth of the structure stack
256 */
257 public function getStructureDepth() {
258 return count($this->inlineStructure['stable']);
259 }
260
261 /**
262 * Create a name/id for usage in HTML output of a level of the structure stack to be used in form names.
263 *
264 * @param array $levelData Array of a level of the structure stack (containing the keys table, uid and field)
265 * @param string $disposal How the structure name is used (e.g. as <div id="..."> or <input name="..." />)
266 * @return string The name/id of that level, to be used for HTML output
267 */
268 protected function getStructureItemName($levelData, $disposal = 'Disposal_AttributeId') {
269 $name = NULL;
270
271 if (is_array($levelData)) {
272 $parts = array($levelData['table'], $levelData['uid']);
273
274 if (!empty($levelData['field'])) {
275 $parts[] = $levelData['field'];
276 }
277
278 // Use in name attributes:
279 if ($disposal === 'Disposal_AttributeName') {
280 if (!empty($levelData['field']) && !empty($levelData['flexform']) && $this->getStructureLevel(-1) === $levelData) {
281 $parts[] = implode('][', $levelData['flexform']);
282 }
283 $name = '[' . implode('][', $parts) . ']';
284 // Use in object id attributes:
285 } else {
286 $name = implode('-', $parts);
287
288 if (!empty($levelData['field']) && !empty($levelData['flexform'])) {
289 array_unshift($levelData['flexform'], $name);
290 $name = implode('---', $levelData['flexform']);
291 }
292 }
293 }
294
295 return $name;
296 }
297
298 }